From 593c23ebc85810bb2acaae4cecce52e89d14696d Mon Sep 17 00:00:00 2001 From: Stephan Erb Date: Sun, 11 Jan 2009 22:29:58 +0000 Subject: [PATCH] Cosmetic only... as usaual: stanza_session.py. TODO: better variable names --- src/common/dh.py | 11 ++- src/common/stanza_session.py | 185 +++++++++++++++++------------------ 2 files changed, 97 insertions(+), 99 deletions(-) diff --git a/src/common/dh.py b/src/common/dh.py index e9b30d32e..4b038ea93 100644 --- a/src/common/dh.py +++ b/src/common/dh.py @@ -19,12 +19,13 @@ ## along with Gajim. If not, see . ## -import string +''' +This module defines a number of constants; specifically, large primes suitable +for use with the Diffie-Hellman key exchange. -# This file defines a number of constants; specifically, large primes suitable for -# use with the Diffie-Hellman key exchange. -# -# These constants have been obtained from RFC2409 and RFC3526. +These constants have been obtained from RFC2409 and RFC3526. +''' +import string generators = [ None, # one to get the right offset 2, diff --git a/src/common/stanza_session.py b/src/common/stanza_session.py index d0b0f57fc..c048b4e07 100644 --- a/src/common/stanza_session.py +++ b/src/common/stanza_session.py @@ -2,7 +2,6 @@ ## src/common/stanza_session.py ## ## Copyright (C) 2007 Julien Pivotto -## Stephan Erb ## Copyright (C) 2007-2008 Yann Leboulanger ## Brendan Taylor ## Jean-Marie Traissard @@ -24,29 +23,36 @@ ## from common import gajim - from common import xmpp -from common import exceptions +from common.exceptions import DecryptionError, NegotiationError +import xmpp.c14n import itertools import random import string - import time - -import xmpp.c14n - import base64 import os +if gajim.HAVE_PYCRYPTO: + from Crypto.Cipher import AES + from Crypto.Hash import HMAC, SHA256 + from Crypto.PublicKey import RSA + from common import crypto + + from common import dh + import secrets + XmlDsig = 'http://www.w3.org/2000/09/xmldsig#' class StanzaSession(object): + ''' + ''' def __init__(self, conn, jid, thread_id, type_): + ''' + ''' self.conn = conn - self.jid = jid - self.type = type_ if thread_id: @@ -69,9 +75,11 @@ class StanzaSession(object): def is_loggable(self): return self.loggable and gajim.config.should_log(self.conn.name, self.jid) - # remove events associated with this session from the queue - # returns True if any events were removed (unlike gajim.events.remove_events) def remove_events(self, types): + ''' + Remove events associated with this session from the queue. + returns True if any events were removed (unlike events.py remove_events) + ''' any_removed = False for j in (self.jid, self.jid.getStripped()): @@ -124,9 +132,10 @@ class StanzaSession(object): self.cancelled_negotiation() def cancelled_negotiation(self): - '''A negotiation has been cancelled, so reset this session to its default - state.''' - + ''' + A negotiation has been cancelled, so reset this session to its default + state. + ''' if self.control: self.control.on_cancel_session_negotiation() @@ -156,61 +165,52 @@ class StanzaSession(object): # we could send an acknowledgement message to the remote client here self.status = None -if gajim.HAVE_PYCRYPTO: - from Crypto.Cipher import AES - from Crypto.Hash import HMAC, SHA256 - from Crypto.PublicKey import RSA - from common import crypto - - from common import dh - import secrets - -# 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 -# 3. 'responded-e2e': -# 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 -# 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. - -# the transition between these states is handled in gajim.py's -# handle_session_negotiation method. class EncryptedStanzaSession(StanzaSession): + ''' + An encrypted stanza negotiation has several states. They arerepresented 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 + 3. 'responded-e2e': + 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 + 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. + + The transition between these states is handled in gajim.py's + handle_session_negotiation method. + ''' def __init__(self, conn, jid, thread_id, type_='chat'): StanzaSession.__init__(self, conn, jid, thread_id, type_='chat') self.xes = {} self.es = {} - self.n = 128 - self.enable_encryption = False # _s denotes 'self' (ie. this client) self._kc_s = None - # _o denotes 'other' (ie. the client at the other end of the session) self._kc_o = None # has the remote contact's identity ever been verified? self.verified_identity = False - # keep the encrypter updated with my latest cipher key def set_kc_s(self, value): + ''' + keep the encrypter updated with my latest cipher key + ''' self._kc_s = value self.encrypter = self.cipher.new(self._kc_s, self.cipher.MODE_CTR, counter=self.encryptcounter) @@ -218,8 +218,10 @@ class EncryptedStanzaSession(StanzaSession): def get_kc_s(self): return self._kc_s - # keep the decrypter updated with the other party's latest cipher key def set_kc_o(self, value): + ''' + keep the decrypter updated with the other party's latest cipher key + ''' self._kc_o = value self.decrypter = self.cipher.new(self._kc_o, self.cipher.MODE_CTR, counter=self.decryptcounter) @@ -244,10 +246,10 @@ class EncryptedStanzaSession(StanzaSession): return crypto.encode_mpi(gajim.pubkey.sign(hash_, '')[0]) def encrypt_stanza(self, stanza): - encryptable = [x for x in stanza.getChildren() if x.getName() not in ('error', 'amp', - 'thread')] + encryptable = [x for x in stanza.getChildren() if x.getName() not in + ('error', 'amp', 'thread')] - # XXX can also encrypt contents of elements in stanzas @type = + # FIXME can also encrypt contents of elements in stanzas @type = # 'error' # (except for child elements) @@ -266,7 +268,7 @@ class EncryptedStanzaSession(StanzaSession): c.setNamespace('http://www.xmpp.org/extensions/xep-0200.html#ns') c.NT.data = base64.b64encode(m_final) - # XXX check for rekey request, handle elements + # FIXME 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 + \ @@ -313,7 +315,7 @@ class EncryptedStanzaSession(StanzaSession): return self.encrypter.encrypt(padded) def decrypt_stanza(self, stanza): - # delete the unencrypted explanation body, if it exists + ''' delete the unencrypted explanation body, if it exists ''' orig_body = stanza.getTag('body') if orig_body: stanza.delChild(orig_body) @@ -331,7 +333,7 @@ class EncryptedStanzaSession(StanzaSession): crypto.encode_mpi_with_padding(self.c_o)) if not calculated_mac == received_mac: - raise exceptions.DecryptionError, 'bad signature' + raise DecryptionError('bad signature') m_final = base64.b64decode(c.getTagData('data')) m_compressed = self.decrypt(m_final) @@ -340,7 +342,7 @@ class EncryptedStanzaSession(StanzaSession): try: parsed = xmpp.Node(node='' + plaintext + '') except Exception: - raise exceptions.DecryptionError, 'decrypted not parseable as XML' + raise DecryptionError('decrypted not parseable as XML') for child in parsed.getChildren(): stanza.addChild(node=child) @@ -358,7 +360,7 @@ class EncryptedStanzaSession(StanzaSession): def get_shared_secret(self, e, y, p): if (not 1 < e < (p - 1)): - raise exceptions.NegotiationError, 'invalid DH value' + raise NegotiationError('invalid DH value') return crypto.sha256(crypto.encode_mpi(crypto.powmod(e, y, p))) @@ -374,7 +376,8 @@ class EncryptedStanzaSession(StanzaSession): m_o_calculated = self.hmac(self.km_o, crypto.encode_mpi(self.c_o) + id_o) if m_o_calculated != m_o: - raise exceptions.NegotiationError, 'calculated m_%s differs from received m_%s' % (i_o, i_o) + raise NegotiationError('calculated m_%s differs from received m_%s' % + (i_o, i_o)) if i_o == 'a' and self.sas_algs == 'sas28x5': # we don't need to calculate this if there's a verified retained secret @@ -387,7 +390,7 @@ class EncryptedStanzaSession(StanzaSession): if self.negotiated['recv_pubkey'] == 'hash': # fingerprint = parsed.getTagData('fingerprint') - # XXX find stored pubkey or terminate session + # FIXME find stored pubkey or terminate session raise NotImplementedError() else: if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'): @@ -399,7 +402,7 @@ class EncryptedStanzaSession(StanzaSession): pubkey_o = xmpp.c14n.c14n(keyvalue) else: - # XXX DSA, etc. + # FIXME DSA, etc. raise NotImplementedError() enc_sig = parsed.getTag(name='SignatureValue', @@ -426,10 +429,11 @@ class EncryptedStanzaSession(StanzaSession): hash_ = crypto.sha256(mac_o_calculated) if not eir_pubkey.verify(hash_, signature): - raise exceptions.NegotiationError, 'public key signature verification failed!' + raise NegotiationError('public key signature verification failed!') elif mac_o_calculated != mac_o: - raise exceptions.NegotiationError, 'calculated mac_%s differs from received mac_%s' % (i_o, i_o) + raise NegotiationError('calculated mac_%s differs from received mac_%s' + % (i_o, i_o)) def make_identity(self, form, dh_i): if self.negotiated['send_pubkey']: @@ -476,7 +480,7 @@ class EncryptedStanzaSession(StanzaSession): self.sas = crypto.sas_28x5(m_s, self.form_o) if self.sigmai: - # XXX save retained secret? + # FIXME save retained secret? self.check_identity(tuple) return (xmpp.DataField(name='identity', value=base64.b64encode(id_s)), @@ -519,7 +523,7 @@ class EncryptedStanzaSession(StanzaSession): x.addChild(node=xmpp.DataField(name='init_pubkey', options=['none', 'key', 'hash'], typ='list-single')) - # XXX store key, use hash + # FIXME store key, use hash x.addChild(node=xmpp.DataField(name='resp_pubkey', options=['none', 'key'], typ='list-single')) @@ -555,8 +559,8 @@ class EncryptedStanzaSession(StanzaSession): self.send(request) - # 4.3 esession response (bob) def verify_options_bob(self, form): + ''' 4.3 esession response (bob) ''' negotiated = {'recv_pubkey': None, 'send_pubkey': None} not_acceptable = [] ask_user = {} @@ -618,14 +622,14 @@ class EncryptedStanzaSession(StanzaSession): if (XmlDsig + 'rsa-sha256') in options: negotiated['sign_algs'] = XmlDsig + 'rsa-sha256' else: - # XXX some things are handled elsewhere, some things are + # FIXME some things are handled elsewhere, some things are # not-implemented pass return (negotiated, not_acceptable, ask_user) - # 4.3 esession response (bob) def respond_e2e_bob(self, form, negotiated, not_acceptable): + ''' 4.3 esession response (bob) ''' response = xmpp.Message() feature = response.NT.feature feature.setNamespace(xmpp.NS_FEATURE) @@ -697,8 +701,8 @@ class EncryptedStanzaSession(StanzaSession): self.send(response) - # 'Alice Accepts' def verify_options_alice(self, form): + ''' 'Alice Accepts' ''' negotiated = {} ask_user = {} not_acceptable = [] @@ -725,8 +729,8 @@ class EncryptedStanzaSession(StanzaSession): return (negotiated, not_acceptable, ask_user) - # 'Alice Accepts', continued def accept_e2e_alice(self, form, negotiated): + ''' 'Alice Accepts', continued ''' self.encryptable_stanzas = ['message'] self.sas_algs = 'sas28x5' self.cipher = AES @@ -743,7 +747,6 @@ class EncryptedStanzaSession(StanzaSession): self.c_s = crypto.decode_mpi(base64.b64decode(form['counter'])) self.c_o = self.c_s ^ (2 ** (self.n - 1)) - self.n_o = base64.b64decode(form['my_nonce']) mod_p = int(form['modp']) @@ -752,7 +755,6 @@ class EncryptedStanzaSession(StanzaSession): e = self.es[mod_p] self.d = crypto.decode_mpi(base64.b64decode(form['dhkeys'])) - self.k = self.get_shared_secret(self.d, x, p) result.addChild(node=xmpp.DataField(name='FORM_TYPE', @@ -798,8 +800,8 @@ class EncryptedStanzaSession(StanzaSession): else: self.status = 'identified-alice' - # 4.5 esession accept (bob) def accept_e2e_bob(self, form): + ''' 4.5 esession accept (bob) ''' response = xmpp.Message() init = response.NT.init @@ -808,6 +810,7 @@ class EncryptedStanzaSession(StanzaSession): x = xmpp.DataForm(typ='result') for field in ('nonce', 'dhkeys', 'rshashes', 'identity', 'mac'): + # FIXME: will do nothing in real world... assert field in form.asDict(), "alice's form didn't have a %s field" \ % field @@ -816,18 +819,15 @@ class EncryptedStanzaSession(StanzaSession): p = dh.primes[self.modp] if crypto.sha256(crypto.encode_mpi(e)) != self.negotiated['He']: - raise exceptions.NegotiationError, 'SHA256(e) != He' + raise NegotiationError('SHA256(e) != He') k = self.get_shared_secret(e, self.y, p) - self.kc_o, self.km_o, self.ks_o = self.generate_initiator_keys(k) # 4.5.2 verifying alice's identity - self.verify_identity(form, e, False, 'a') # 4.5.4 generating bob's final session keys - srs = '' srses = secrets.secrets().retained_secrets(self.conn.name, @@ -904,7 +904,6 @@ class EncryptedStanzaSession(StanzaSession): self.kc_o, self.km_o, self.ks_o = self.generate_responder_keys(k) # 4.6.2 Verifying Bob's Identity - self.verify_identity(form, self.d, False, 'b') # Note: If Alice discovers an error then she SHOULD ignore any encrypted # content she received in the stanza. @@ -919,10 +918,11 @@ class EncryptedStanzaSession(StanzaSession): self.control.print_esession_details() def do_retained_secret(self, k, old_srs): - '''calculate the new retained secret. determine if the user needs to check - the remote party's identity. set up callbacks for when the identity has - been verified.''' - + ''' + Calculate the new retained secret. determine if the user needs to check + the remote party's identity. Set up callbacks for when the identity has + been verified. + ''' new_srs = self.hmac(k, 'New Retained Secret') self.srs = new_srs @@ -962,7 +962,7 @@ class EncryptedStanzaSession(StanzaSession): x = crypto.srand(2 ** (2 * self.n - 1), p - 1) - # XXX this may be a source of performance issues + # FIXME this may be a source of performance issues e = crypto.powmod(g, x, p) self.xes[modp] = x @@ -980,21 +980,19 @@ class EncryptedStanzaSession(StanzaSession): def terminate_e2e(self): self.terminate() - self.enable_encryption = False def acknowledge_termination(self): StanzaSession.acknowledge_termination(self) - self.enable_encryption = False - def fail_bad_negotiation(self, reason, fields = None): - '''sends an error and cancels everything. - -if fields is None, the remote party has given us a bad cryptographic value of some kind - -otherwise, list the fields we haven't implemented''' - + def fail_bad_negotiation(self, reason, fields=None): + ''' + Sends an error and cancels everything. + + If fields is None, the remote party has given us a bad cryptographic + value of some kind. Otherwise, list the fields we haven't implemented + ''' err = xmpp.Error(xmpp.Message(), xmpp.ERR_FEATURE_NOT_IMPLEMENTED) err.T.error.T.text.setData(reason) @@ -1020,7 +1018,6 @@ otherwise, list the fields we haven't implemented''' def cancelled_negotiation(self): StanzaSession.cancelled_negotiation(self) self.enable_encryption = False - self.km_o = '' # vim: se ts=3: