make python-crypto an optional dependency

This commit is contained in:
Yann Leboulanger 2007-08-27 13:36:24 +00:00
parent 03dbf641de
commit 62edcc71de
6 changed files with 59 additions and 35 deletions

View File

@ -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>

View File

@ -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

View File

@ -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:

View File

@ -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]

View File

@ -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)

View File

@ -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)