make python-crypto an optional dependency
This commit is contained in:
parent
03dbf641de
commit
62edcc71de
|
@ -29,6 +29,7 @@ Gajim is a GTK+ app that loves GNOME. You can do 'make' so you don't require gno
|
||||||
<h2>Optional Runtime Requirements</h2>
|
<h2>Optional Runtime Requirements</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="http://pyopenssl.sourceforge.net/">PyOpenSSL</a> (python-pyopenssl package in Debian) for <em>secure</em> SSL/TLS. Python's default SSL is insecure, so this package is highly recommended!</li>
|
<li><a href="http://pyopenssl.sourceforge.net/">PyOpenSSL</a> (python-pyopenssl package in Debian) for <em>secure</em> SSL/TLS. Python's default SSL is insecure, so this package is highly recommended!</li>
|
||||||
|
<li>python-crypto to enable End to end encryption</li>
|
||||||
<li>For zeroconf (bonjour), the "enable link-local messaging" checkbox, you need dbus-glib, python-avahi</li>
|
<li>For zeroconf (bonjour), the "enable link-local messaging" checkbox, you need dbus-glib, python-avahi</li>
|
||||||
<li>dnsutils (or whatever package provides the nslookup binary) for SRV support; if you don't know what that is, you don't need it</li>
|
<li>dnsutils (or whatever package provides the nslookup binary) for SRV support; if you don't know what that is, you don't need it</li>
|
||||||
<li>gtkspell and aspell-LANG where lang is your locale eg. en, fr etc</li>
|
<li>gtkspell and aspell-LANG where lang is your locale eg. en, fr etc</li>
|
||||||
|
|
|
@ -1592,8 +1592,11 @@ class ChatControl(ChatControlBase):
|
||||||
toggle_gpg_menuitem.set_property('sensitive', is_sensitive)
|
toggle_gpg_menuitem.set_property('sensitive', is_sensitive)
|
||||||
|
|
||||||
# TODO: check that the remote client supports e2e
|
# TODO: check that the remote client supports e2e
|
||||||
isactive = int(self.session != None and self.session.enable_encryption)
|
if not gajim.HAVE_PYCRYPTO:
|
||||||
toggle_e2e_menuitem.set_active(isactive)
|
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
|
# If we don't have resource, we can't do file transfer
|
||||||
# in transports, contact holds our info we need to disable it too
|
# in transports, contact holds our info we need to disable it too
|
||||||
|
|
|
@ -1490,7 +1490,8 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
||||||
return
|
return
|
||||||
if msg.getTag('feature') and msg.getTag('feature').namespace == \
|
if msg.getTag('feature') and msg.getTag('feature').namespace == \
|
||||||
common.xmpp.NS_FEATURE:
|
common.xmpp.NS_FEATURE:
|
||||||
self._FeatureNegCB(con, msg, session)
|
if gajim.HAVE_PYCRYPTO:
|
||||||
|
self._FeatureNegCB(con, msg, session)
|
||||||
return
|
return
|
||||||
if msg.getTag('init') and msg.getTag('init').namespace == \
|
if msg.getTag('init') and msg.getTag('init').namespace == \
|
||||||
common.xmpp.NS_ESESSION_INIT:
|
common.xmpp.NS_ESESSION_INIT:
|
||||||
|
|
|
@ -136,6 +136,16 @@ priority_dict = {}
|
||||||
for status in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
|
for status in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
|
||||||
priority_dict[status] = config.get('autopriority' + status)
|
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):
|
def get_nick_from_jid(jid):
|
||||||
pos = jid.find('@')
|
pos = jid.find('@')
|
||||||
return jid[:pos]
|
return jid[:pos]
|
||||||
|
|
|
@ -14,10 +14,6 @@ import time
|
||||||
from common import dh
|
from common import dh
|
||||||
import xmpp.c14n
|
import xmpp.c14n
|
||||||
|
|
||||||
from Crypto.Cipher import AES
|
|
||||||
from Crypto.Hash import HMAC, SHA256
|
|
||||||
from Crypto.PublicKey import RSA
|
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
XmlDsig = 'http://www.w3.org/2000/09/xmldsig#'
|
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.
|
# we could send an acknowledgement message here, but we won't.
|
||||||
self.status = None
|
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:
|
# attribute of the session object:
|
||||||
|
|
||||||
# 1. None:
|
# 1. None:
|
||||||
# default state
|
# default state
|
||||||
# 2. 'requested-e2e':
|
# 2. 'requested-e2e':
|
||||||
# this client has initiated an esession negotiation and is waiting for
|
# this client has initiated an esession negotiation and is waiting
|
||||||
# a response
|
# for a response
|
||||||
# 3. 'responded-e2e':
|
# 3. 'responded-e2e':
|
||||||
# this client has responded to an esession negotiation request and is
|
# this client has responded to an esession negotiation request and
|
||||||
# waiting for the initiator to identify itself and complete the
|
# is waiting for the initiator to identify itself and complete the
|
||||||
# negotiation
|
# negotiation
|
||||||
# 4. 'identified-alice':
|
# 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
|
# identify itself and complete the negotiation
|
||||||
# 5. 'active':
|
# 5. 'active':
|
||||||
# an encrypted session has been successfully negotiated. messages of
|
# an encrypted session has been successfully negotiated. messages
|
||||||
# any of the types listed in 'encryptable_stanzas' should be encrypted
|
# of any of the types listed in 'encryptable_stanzas' should be
|
||||||
# before they're sent.
|
# encrypted before they're sent.
|
||||||
|
|
||||||
# the transition between these states is handled in gajim.py's
|
# the transition between these states is handled in gajim.py's
|
||||||
# handle_session_negotiation method.
|
# handle_session_negotiation method.
|
||||||
|
@ -144,7 +146,8 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
# keep the encrypter updated with my latest cipher key
|
# keep the encrypter updated with my latest cipher key
|
||||||
def set_kc_s(self, value):
|
def set_kc_s(self, value):
|
||||||
self._kc_s = 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):
|
def get_kc_s(self):
|
||||||
return self._kc_s
|
return self._kc_s
|
||||||
|
@ -152,7 +155,8 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
# keep the decrypter updated with the other party's latest cipher key
|
# keep the decrypter updated with the other party's latest cipher key
|
||||||
def set_kc_o(self, value):
|
def set_kc_o(self, value):
|
||||||
self._kc_o = 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):
|
def get_kc_o(self):
|
||||||
return self._kc_o
|
return self._kc_o
|
||||||
|
@ -167,7 +171,8 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
else:
|
else:
|
||||||
return chr(n)
|
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):
|
def encode_mpi_with_padding(self, n):
|
||||||
ret = self.encode_mpi(n)
|
ret = self.encode_mpi(n)
|
||||||
|
|
||||||
|
@ -195,13 +200,16 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
def sign(self, string):
|
def sign(self, string):
|
||||||
if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'):
|
if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'):
|
||||||
hash = self.sha256(string)
|
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):
|
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 <error/> elements in stanzas @type = 'error'
|
# XXX can also encrypt contents of <error/> elements in stanzas @type =
|
||||||
# (except for <defined-condition xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> child elements)
|
# 'error'
|
||||||
|
# (except for <defined-condition
|
||||||
|
# xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> child elements)
|
||||||
|
|
||||||
old_en_counter = self.c_s
|
old_en_counter = self.c_s
|
||||||
|
|
||||||
|
@ -220,7 +228,8 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
# XXX check for rekey request, handle <key/> elements
|
# XXX check for rekey request, handle <key/> elements
|
||||||
|
|
||||||
m_content = ''.join(map(str, c.getChildren()))
|
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
|
return stanza
|
||||||
|
|
||||||
|
@ -278,15 +287,18 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
return self.random_bytes(8)
|
return self.random_bytes(8)
|
||||||
|
|
||||||
def decrypt_stanza(self, stanza):
|
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)
|
stanza.delChild(c)
|
||||||
|
|
||||||
# contents of <c>, minus <mac>, minus whitespace
|
# contents of <c>, minus <mac>, 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'))
|
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:
|
if not calculated_mac == received_mac:
|
||||||
raise exceptions.DecryptionError, 'bad signature'
|
raise exceptions.DecryptionError, 'bad signature'
|
||||||
|
@ -351,7 +363,8 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'):
|
if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'):
|
||||||
keyvalue = parsed.getTag(name='RSAKeyValue', namespace=XmlDsig)
|
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)))
|
eir_pubkey = RSA.construct((n,long(e)))
|
||||||
|
|
||||||
pubkey_o = xmpp.c14n.c14n(keyvalue)
|
pubkey_o = xmpp.c14n.c14n(keyvalue)
|
||||||
|
@ -359,7 +372,8 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
# XXX DSA, etc.
|
# XXX DSA, etc.
|
||||||
raise 'unimplemented'
|
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)),)
|
signature = (self.decode_mpi(base64.b64decode(enc_sig)),)
|
||||||
else:
|
else:
|
||||||
mac_o = self.decrypt(id_o)
|
mac_o = self.decrypt(id_o)
|
||||||
|
@ -390,7 +404,7 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
def make_identity(self, form, dh_i):
|
def make_identity(self, form, dh_i):
|
||||||
if self.negotiated['send_pubkey']:
|
if self.negotiated['send_pubkey']:
|
||||||
if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'):
|
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)
|
cb_fields = map(lambda f: base64.b64encode(self.encode_mpi(f)), fields)
|
||||||
|
|
||||||
|
|
|
@ -118,7 +118,6 @@ from chat_control import ChatControlBase
|
||||||
from atom_window import AtomWindow
|
from atom_window import AtomWindow
|
||||||
|
|
||||||
import negotiation
|
import negotiation
|
||||||
import Crypto.PublicKey.RSA
|
|
||||||
|
|
||||||
from common import exceptions
|
from common import exceptions
|
||||||
from common.zeroconf import connection_zeroconf
|
from common.zeroconf import connection_zeroconf
|
||||||
|
@ -2765,10 +2764,6 @@ class Interface:
|
||||||
gobject.timeout_add(2000, self.process_connections)
|
gobject.timeout_add(2000, self.process_connections)
|
||||||
gobject.timeout_add(10000, self.read_sleepy)
|
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__':
|
if __name__ == '__main__':
|
||||||
def sigint_cb(num, stack):
|
def sigint_cb(num, stack):
|
||||||
sys.exit(5)
|
sys.exit(5)
|
||||||
|
|
Loading…
Reference in New Issue