From 8af883e8521892e2e8e9e1dcb590b7a69a527b98 Mon Sep 17 00:00:00 2001 From: Brendan Taylor Date: Thu, 12 Jul 2007 06:25:05 +0000 Subject: [PATCH] refactored and corrected identity testing, prompt user when a session is initiated by an unsubscribed jid --- src/common/exceptions.py | 10 ++++- src/common/stanza_session.py | 62 +++++++++++--------------- src/gajim.py | 86 ++++++++++++++++++++++-------------- 3 files changed, 88 insertions(+), 70 deletions(-) diff --git a/src/common/exceptions.py b/src/common/exceptions.py index bceef79a4..6aa5b5fa4 100644 --- a/src/common/exceptions.py +++ b/src/common/exceptions.py @@ -45,8 +45,16 @@ class SessionBusNotPresent(Exception): def __str__(self): return _('Session bus is not available.\nTry reading http://trac.gajim.org/wiki/GajimDBus') +class NegotiationError(Exception): + '''A session negotiation failed''' + pass + +class DecryptionError(Exception): + '''A message couldn't be decrypted into usable XML''' + pass + class GajimGeneralException(Exception): - '''This exception ir our general exception''' + '''This exception is our general exception''' def __init__(self, text=''): Exception.__init__(self) self.text = text diff --git a/src/common/stanza_session.py b/src/common/stanza_session.py index db90339a0..0c19305ca 100644 --- a/src/common/stanza_session.py +++ b/src/common/stanza_session.py @@ -2,6 +2,7 @@ import gajim from common import xmpp from common import helpers +from common import exceptions import random import string @@ -47,13 +48,14 @@ class StanzaSession(object): return "".join([random.choice(string.letters) for x in xrange(0,32)]) def send(self, msg): - if self.thread_id: + if self.thread_id and isinstance(msg, xmpp.Message): msg.setThread(self.thread_id) msg.setAttr('to', self.jid) self.conn.send_stanza(msg) - - self.last_send = time.time() + + if isinstance(msg, xmpp.Message): + self.last_send = time.time() def reject_negotiation(self, body = None): msg = xmpp.Message() @@ -291,7 +293,7 @@ class EncryptedStanzaSession(StanzaSession): try: parsed = xmpp.Node(node='' + plaintext + '') except: - raise DecryptionError + raise exceptions.DecryptionError for child in parsed.getChildren(): stanza.addChild(node=child) @@ -446,7 +448,7 @@ class EncryptedStanzaSession(StanzaSession): self.n_o = base64.b64decode(form['my_nonce']) dhhashes = form.getField('dhhashes').getValues() - self.He = dhhashes[group_order].encode("utf8") + self.He = base64.b64decode(dhhashes[group_order].encode("utf8")) bytes = int(self.n / 8) @@ -586,13 +588,8 @@ class EncryptedStanzaSession(StanzaSession): e = self.decode_mpi(base64.b64decode(form['dhkeys'])) p = dh.primes[self.modp] - if (not self.sha256(self.encode_mpi(e)) == self.He): or \ - (not e > 1 and e < (p - 1)): - err = xmpp.Error(response, xmpp.ERR_FEATURE_NOT_IMPLEMENTED) - err.T.error.T.text.setData("invalid DH value 'e'") - self.send(err) - self.status = None - return + if (self.sha256(self.encode_mpi(e)) != self.He) or (not 1 < e < (p - 1)): + raise exceptions.NegotiationError, "invalid DH value 'e'" k = self.sha256(self.encode_mpi(self.powmod(e, self.y, p))) @@ -605,11 +602,7 @@ class EncryptedStanzaSession(StanzaSession): m_a_calculated = self.hmac(self.km_o, self.encode_mpi(self.c_o) + id_a) if m_a_calculated != m_a: - err = xmpp.Error(response, xmpp.ERR_FEATURE_NOT_IMPLEMENTED) - err.T.error.T.text.setData('calculated m_a differs from received m_a') - self.send(err) - self.status = None - return + raise exceptions.NegotiationError, 'calculated m_a differs from received m_a' mac_a = self.decrypt(id_a) @@ -619,11 +612,7 @@ class EncryptedStanzaSession(StanzaSession): mac_a_calculated = self.hmac(self.ks_o, self.n_s + self.n_o + self.encode_mpi(e) + self.form_a + form_a2) if mac_a_calculated != mac_a: - err = xmpp.Error(response, xmpp.ERR_FEATURE_NOT_IMPLEMENTED) - err.T.error.T.text.setData('calculated mac_a differs from received mac_a') - self.send(err) - self.status = None - return + raise exceptions.NegotiationError, 'calculated mac_a differs from received mac_a' # 4.5.4 generating bob's final session keys self.srs = '' @@ -653,7 +642,8 @@ class EncryptedStanzaSession(StanzaSession): form_b2 = ''.join(map(lambda el: xmpp.c14n.c14n(el), x.getChildren())) old_c_s = self.c_s - mac_b = self.hmac(self.n_o + self.n_s + self.encode_mpi(self.d) + self.form_b + form_b2, self.ks_s) + + mac_b = self.hmac(self.ks_s, self.n_o + self.n_s + self.encode_mpi(self.d) + self.form_b + form_b2) id_b = self.encrypt(mac_b) m_b = self.hmac(self.km_s, self.encode_mpi(old_c_s) + id_b) @@ -703,27 +693,20 @@ class EncryptedStanzaSession(StanzaSession): m_b = base64.b64decode(form['mac']) id_b = base64.b64decode(form['identity']) - m_b_calculated = self.hmac(self.encode_mpi(self.c_o) + id_b, self.km_o) + m_b_calculated = self.hmac(self.km_o, self.encode_mpi(self.c_o) + id_b) if m_b_calculated != m_b: - err = xmpp.Error(response, xmpp.ERR_FEATURE_NOT_IMPLEMENTED) - err.T.error.T.text.setData('calculated m_b differs from received m_b') - self.send(err) - self.status = None - return + raise exceptions.NegotiationError, 'calculated m_b differs from received m_b' mac_b = self.decrypt(id_b) - form_b2 = ''.join(map(lambda el: xmpp.c14n.c14n(el), form.getChildren())) + macable_children = filter(lambda x: x.getVar() not in ('mac', 'identity'), form.getChildren()) + form_b2 = ''.join(map(lambda el: xmpp.c14n.c14n(el), macable_children)) - mac_b_calculated = self.hmac(self.n_s + self.n_o + self.encode_mpi(self.d) + self.form_b + form_b2, self.ks_o) + mac_b_calculated = self.hmac(self.ks_o, self.n_s + self.n_o + self.encode_mpi(self.d) + self.form_b + form_b2) if mac_b_calculated != mac_b: - err = xmpp.Error(response, xmpp.ERR_FEATURE_NOT_IMPLEMENTED) - err.T.error.T.text.setData('calculated mac_b differs from received mac_b') - self.send(err) - self.status = None - return + raise exceptions.NegotiationError, 'calculated mac_b differs from received mac_b' # Note: If Alice discovers an error then she SHOULD ignore any encrypted content she received in the stanza. @@ -782,6 +765,13 @@ class EncryptedStanzaSession(StanzaSession): self.enable_encryption = False + def fail_bad_negotiation(self, reason): + '''they've tried to feed us a bogus value, send an error and cancel everything.''' + err = xmpp.Error(xmpp.Message(), xmpp.ERR_FEATURE_NOT_IMPLEMENTED) + err.T.error.T.text.setData(reason) + self.send(err) + self.status = None + def is_loggable(self): name = self.conn.name no_log_for = gajim.config.get_per('accounts', name, 'no_log_for') diff --git a/src/gajim.py b/src/gajim.py index 9f0e8fe96..537436776 100755 --- a/src/gajim.py +++ b/src/gajim.py @@ -1669,38 +1669,50 @@ class Interface: # bob responds if form.getType() == 'form' and u'e2e' in \ map(lambda x: x[1], form.getField('security').getOptions()): - contact = gajim.contacts.get_contact(account, jid.getStripped(), jid.getResource()) + def continue_with_negotiation(*args): + if len(args): + self.dialog.destroy() - if gajim.SHOW_LIST[gajim.connections[account].connected] == 'invisible' or \ - contact.sub not in ('from', 'both'): + negotiated, not_acceptable, ask_user = session.verify_options_bob(form) + + if ask_user: + def accept_nondefault_options(widget): + self.dialog.destroy() + negotiated.update(ask_user) + session.respond_e2e_bob(form, negotiated, not_acceptable) + + def reject_nondefault_options(widget): + self.dialog.destroy() + for key in ask_user.keys(): + not_acceptable.append(key) + session.respond_e2e_bob(form, negotiated, not_acceptable) + + self.dialog = dialogs.YesNoDialog(_('Confirm these session options'), + _('''The remote client wants to negotiate an session with these features: + + %s + + Are these options acceptable?''') % (negotiation.describe_features(ask_user)), + on_response_yes = accept_nondefault_options, + on_response_no = reject_nondefault_options) + else: + session.respond_e2e_bob(form, negotiated, not_acceptable) + + def ignore_negotiation(widget): + self.dialog.destroy() return - - negotiated, not_acceptable, ask_user = session.verify_options_bob(form) - - if ask_user: - def accept_nondefault_options(widget): - negotiated.update(ask_user) - session.respond_e2e_bob(form, negotiated, not_acceptable) - - dialog.destroy() - - def reject_nondefault_options(widget): - for key in ask_user.keys(): - not_acceptable.append(key) - session.respond_e2e_bob(form, negotiated, not_acceptable) - - dialog.destroy() - - dialog = dialogs.YesNoDialog(_('Confirm these session options'), - _('''The remote client wants to negotiate an session with these features: - -%s - -Are these options acceptable?''') % (negotiation.describe_features(ask_user)), - on_response_yes = accept_nondefault_options, - on_response_no = reject_nondefault_options) + + contact = gajim.contacts.get_contact_with_highest_priority(account, str(jid)) + + if gajim.SHOW_LIST[gajim.connections[account].connected] == 'invisible' or not contact or\ + contact.sub not in ('from', 'both'): + self.dialog = dialogs.YesNoDialog(_('Start session?'), + _('''%s would like to start a session with you. Should I respond?''') % jid, + on_response_yes = continue_with_negotiation, + on_response_no = ignore_negotiation, + ) else: - session.respond_e2e_bob(form, negotiated, not_acceptable) + continue_with_negotiation() return @@ -1727,16 +1739,24 @@ Are these options acceptable?''') % (negotiation.describe_features(ask_user)), on_response_no = reject_nondefault_options) else: session.accept_e2e_alice(form, negotiated) - negotiation.show_sas_dialog(jid, session.sas) return elif session.status == 'responded-e2e' and form.getType() == 'result': - session.accept_e2e_bob(form) - negotiation.show_sas_dialog(jid, session.sas) + try: + session.accept_e2e_bob(form) + except exceptions.NegotiationError, details: + session.fail_bad_negotiation(details) + else: + negotiation.show_sas_dialog(jid, session.sas) + return elif session.status == 'identified-alice' and form.getType() == 'result': - session.final_steps_alice(form) + try: + session.final_steps_alice(form) + except exceptions.NegotiationError, details: + session.fail_bad_negotiation(details) + return if form.getField('terminate'):