diff --git a/README.html b/README.html
index 2621eef0b..7196e281b 100644
--- a/README.html
+++ b/README.html
@@ -29,6 +29,7 @@ Gajim is a GTK+ app that loves GNOME. You can do 'make' so you don't require gno
Optional Runtime Requirements
- PyOpenSSL (python-pyopenssl package in Debian) for secure SSL/TLS. Python's default SSL is insecure, so this package is highly recommended!
+- python-crypto to enable End to end encryption
- For zeroconf (bonjour), the "enable link-local messaging" checkbox, you need dbus-glib, python-avahi
- dnsutils (or whatever package provides the nslookup binary) for SRV support; if you don't know what that is, you don't need it
- gtkspell and aspell-LANG where lang is your locale eg. en, fr etc
diff --git a/src/chat_control.py b/src/chat_control.py
index 8c595744e..8d7e5a95b 100644
--- a/src/chat_control.py
+++ b/src/chat_control.py
@@ -1592,8 +1592,11 @@ class ChatControl(ChatControlBase):
toggle_gpg_menuitem.set_property('sensitive', is_sensitive)
# TODO: check that the remote client supports e2e
- isactive = int(self.session != None and self.session.enable_encryption)
- toggle_e2e_menuitem.set_active(isactive)
+ if not gajim.HAVE_PYCRYPTO:
+ toggle_e2e_menuitem.set_sensitive(False)
+ else:
+ isactive = int(self.session != None and self.session.enable_encryption)
+ toggle_e2e_menuitem.set_active(isactive)
# If we don't have resource, we can't do file transfer
# in transports, contact holds our info we need to disable it too
diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py
index 754c0a52e..476929bd6 100644
--- a/src/common/connection_handlers.py
+++ b/src/common/connection_handlers.py
@@ -1490,7 +1490,8 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
return
if msg.getTag('feature') and msg.getTag('feature').namespace == \
common.xmpp.NS_FEATURE:
- self._FeatureNegCB(con, msg, session)
+ if gajim.HAVE_PYCRYPTO:
+ self._FeatureNegCB(con, msg, session)
return
if msg.getTag('init') and msg.getTag('init').namespace == \
common.xmpp.NS_ESESSION_INIT:
diff --git a/src/common/gajim.py b/src/common/gajim.py
index 8610f3f7a..50c9656b1 100644
--- a/src/common/gajim.py
+++ b/src/common/gajim.py
@@ -136,6 +136,16 @@ priority_dict = {}
for status in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
priority_dict[status] = config.get('autopriority' + status)
+HAVE_PYCRYPTO = True
+try:
+ from Crypto.PublicKey.RSA import generate
+except ImportError:
+ HAVE_PYCRYPTO = False
+else:
+ # public key for XEP-0116
+ #FIXME os.urandom is not a cryptographic PRNG
+ pubkey = Crypto.PublicKey.RSA.generate(384, os.urandom)
+
def get_nick_from_jid(jid):
pos = jid.find('@')
return jid[:pos]
diff --git a/src/common/stanza_session.py b/src/common/stanza_session.py
index e1ffc53f9..0ca8c761f 100644
--- a/src/common/stanza_session.py
+++ b/src/common/stanza_session.py
@@ -14,10 +14,6 @@ import time
from common import dh
import xmpp.c14n
-from Crypto.Cipher import AES
-from Crypto.Hash import HMAC, SHA256
-from Crypto.PublicKey import RSA
-
import base64
XmlDsig = 'http://www.w3.org/2000/09/xmldsig#'
@@ -99,25 +95,31 @@ class StanzaSession(object):
# we could send an acknowledgement message here, but we won't.
self.status = None
-# an encrypted stanza negotiation has several states. i've represented them as the following values in the 'status'
+if gajim.HAVE_PYCRYPTO:
+ from Crypto.Cipher import AES
+ from Crypto.Hash import HMAC, SHA256
+ from Crypto.PublicKey import RSA
+
+# an encrypted stanza negotiation has several states. i've represented them
+# as the following values in the 'status'
# attribute of the session object:
# 1. None:
# default state
# 2. 'requested-e2e':
-# this client has initiated an esession negotiation and is waiting for
-# a response
+# this client has initiated an esession negotiation and is waiting
+# for a response
# 3. 'responded-e2e':
-# this client has responded to an esession negotiation request and is
-# waiting for the initiator to identify itself and complete the
+# this client has responded to an esession negotiation request and
+# is waiting for the initiator to identify itself and complete the
# negotiation
# 4. 'identified-alice':
-# this client identified itself and is waiting for the responder to
+# this client identified itself and is waiting for the responder to
# identify itself and complete the negotiation
# 5. 'active':
-# an encrypted session has been successfully negotiated. messages of
-# any of the types listed in 'encryptable_stanzas' should be encrypted
-# before they're sent.
+# an encrypted session has been successfully negotiated. messages
+# of any of the types listed in 'encryptable_stanzas' should be
+# encrypted before they're sent.
# the transition between these states is handled in gajim.py's
# handle_session_negotiation method.
@@ -144,7 +146,8 @@ class EncryptedStanzaSession(StanzaSession):
# keep the encrypter updated with my latest cipher key
def set_kc_s(self, value):
self._kc_s = value
- self.encrypter = self.cipher.new(self._kc_s, self.cipher.MODE_CTR, counter=self.encryptcounter)
+ self.encrypter = self.cipher.new(self._kc_s, self.cipher.MODE_CTR,
+ counter=self.encryptcounter)
def get_kc_s(self):
return self._kc_s
@@ -152,7 +155,8 @@ class EncryptedStanzaSession(StanzaSession):
# keep the decrypter updated with the other party's latest cipher key
def set_kc_o(self, value):
self._kc_o = value
- self.decrypter = self.cipher.new(self._kc_o, self.cipher.MODE_CTR, counter=self.decryptcounter)
+ self.decrypter = self.cipher.new(self._kc_o, self.cipher.MODE_CTR,
+ counter=self.decryptcounter)
def get_kc_o(self):
return self._kc_o
@@ -167,7 +171,8 @@ class EncryptedStanzaSession(StanzaSession):
else:
return chr(n)
- # convert a large integer to a big-endian bitstring, padded with \x00s to 16 bytes
+ # convert a large integer to a big-endian bitstring, padded with \x00s to
+ # 16 bytes
def encode_mpi_with_padding(self, n):
ret = self.encode_mpi(n)
@@ -195,13 +200,16 @@ class EncryptedStanzaSession(StanzaSession):
def sign(self, string):
if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'):
hash = self.sha256(string)
- return self.encode_mpi(gajim.interface.pubkey.sign(hash, '')[0])
+ return self.encode_mpi(gajim.pubkey.sign(hash, '')[0])
def encrypt_stanza(self, stanza):
- encryptable = filter(lambda x: x.getName() not in ('error', 'amp', 'thread'), stanza.getChildren())
+ encryptable = filter(lambda x: x.getName() not in ('error', 'amp',
+ 'thread'), stanza.getChildren())
- # XXX can also encrypt contents of elements in stanzas @type = 'error'
- # (except for child elements)
+ # XXX can also encrypt contents of elements in stanzas @type =
+ # 'error'
+ # (except for child elements)
old_en_counter = self.c_s
@@ -220,7 +228,8 @@ class EncryptedStanzaSession(StanzaSession):
# XXX check for rekey request, handle elements
m_content = ''.join(map(str, c.getChildren()))
- c.NT.mac = base64.b64encode(self.hmac(self.km_s, m_content + self.encode_mpi(old_en_counter)))
+ c.NT.mac = base64.b64encode(self.hmac(self.km_s, m_content + \
+ self.encode_mpi(old_en_counter)))
return stanza
@@ -278,15 +287,18 @@ class EncryptedStanzaSession(StanzaSession):
return self.random_bytes(8)
def decrypt_stanza(self, stanza):
- c = stanza.getTag(name='c', namespace='http://www.xmpp.org/extensions/xep-0200.html#ns')
+ c = stanza.getTag(name='c',
+ namespace='http://www.xmpp.org/extensions/xep-0200.html#ns')
stanza.delChild(c)
# contents of , minus , minus whitespace
- macable = ''.join(map(str, filter(lambda x: x.getName() != 'mac', c.getChildren())))
+ macable = ''.join(map(str, filter(lambda x: x.getName() != 'mac',
+ c.getChildren())))
received_mac = base64.b64decode(c.getTagData('mac'))
- calculated_mac = self.hmac(self.km_o, macable + self.encode_mpi_with_padding(self.c_o))
+ calculated_mac = self.hmac(self.km_o, macable + \
+ self.encode_mpi_with_padding(self.c_o))
if not calculated_mac == received_mac:
raise exceptions.DecryptionError, 'bad signature'
@@ -351,7 +363,8 @@ class EncryptedStanzaSession(StanzaSession):
if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'):
keyvalue = parsed.getTag(name='RSAKeyValue', namespace=XmlDsig)
- n, e = map(lambda x: self.decode_mpi(base64.b64decode(keyvalue.getTagData(x))), ('Modulus', 'Exponent'))
+ n, e = map(lambda x: self.decode_mpi(base64.b64decode(
+ keyvalue.getTagData(x))), ('Modulus', 'Exponent'))
eir_pubkey = RSA.construct((n,long(e)))
pubkey_o = xmpp.c14n.c14n(keyvalue)
@@ -359,7 +372,8 @@ class EncryptedStanzaSession(StanzaSession):
# XXX DSA, etc.
raise 'unimplemented'
- enc_sig = parsed.getTag(name='SignatureValue', namespace=XmlDsig).getData()
+ enc_sig = parsed.getTag(name='SignatureValue',
+ namespace=XmlDsig).getData()
signature = (self.decode_mpi(base64.b64decode(enc_sig)),)
else:
mac_o = self.decrypt(id_o)
@@ -390,7 +404,7 @@ class EncryptedStanzaSession(StanzaSession):
def make_identity(self, form, dh_i):
if self.negotiated['send_pubkey']:
if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'):
- fields = (gajim.interface.pubkey.n, gajim.interface.pubkey.e)
+ fields = (gajim.pubkey.n, gajim.pubkey.e)
cb_fields = map(lambda f: base64.b64encode(self.encode_mpi(f)), fields)
diff --git a/src/gajim.py b/src/gajim.py
index 887e9ed0e..7a0587718 100755
--- a/src/gajim.py
+++ b/src/gajim.py
@@ -118,7 +118,6 @@ from chat_control import ChatControlBase
from atom_window import AtomWindow
import negotiation
-import Crypto.PublicKey.RSA
from common import exceptions
from common.zeroconf import connection_zeroconf
@@ -2765,10 +2764,6 @@ class Interface:
gobject.timeout_add(2000, self.process_connections)
gobject.timeout_add(10000, self.read_sleepy)
- # public key for XEP-0116
- # XXX os.urandom is not a cryptographic PRNG
- self.pubkey = Crypto.PublicKey.RSA.generate(384, os.urandom)
-
if __name__ == '__main__':
def sigint_cb(num, stack):
sys.exit(5)