Author: bct
Date: 2007-07-17 10:08:27 +0200 (Tue, 17 Jul 2007)
New Revision: 8405
Modified:
branches/e2e_encryption/src/common/connection_handlers.py
branches/e2e_encryption/src/common/stanza_session.py
branches/e2e_encryption/src/gajim.py
Log:
shared retained secrets
Modified: branches/e2e_encryption/src/common/connection_handlers.py
===================================================================
--- branches/e2e_encryption/src/common/connection_handlers.py 2007-07-17 08:08:01 UTC (rev 8404)
+++ branches/e2e_encryption/src/common/connection_handlers.py 2007-07-17 08:08:27 UTC (rev 8405)
@@ -1609,7 +1609,7 @@
del self.sessions[original_jid][thread_id]
new_jid = gajim.get_jid_without_resource(original_jid) + '/' + to_resource
- session.jid = new_jid
+ session.jid = common.xmpp.JID(new_jid)
if not new_jid in self.sessions:
self.sessions[new_jid] = {}
@@ -1628,7 +1628,7 @@
return no_threadid_sessions[-1]
def make_new_session(self, jid, thread_id = None, type = 'chat'):
- sess = EncryptedStanzaSession(self, jid, thread_id, type)
+ sess = EncryptedStanzaSession(self, common.xmpp.JID(jid), thread_id, type)
if not jid in self.sessions:
self.sessions[jid] = {}
Modified: branches/e2e_encryption/src/common/stanza_session.py
===================================================================
--- branches/e2e_encryption/src/common/stanza_session.py 2007-07-17 08:08:01 UTC (rev 8404)
+++ branches/e2e_encryption/src/common/stanza_session.py 2007-07-17 08:08:27 UTC (rev 8405)
@@ -1,4 +1,4 @@
-import gajim
+from common import gajim
from common import xmpp
from common import helpers
@@ -23,10 +23,7 @@
def __init__(self, conn, jid, thread_id, type):
self.conn = conn
- if isinstance(jid, str) or isinstance(jid, unicode):
- self.jid = xmpp.JID(jid)
- else:
- self.jid = jid
+ self.jid = jid
self.type = type
@@ -43,7 +40,7 @@
self.last_send = 0
self.status = None
self.negotiated = {}
-
+
def generate_thread_id(self):
return "".join([random.choice(string.letters) for x in xrange(0,32)])
@@ -543,9 +540,16 @@
result.addChild(node=xmpp.DataField(name='nonce', value=base64.b64encode(self.n_o)))
result.addChild(node=xmpp.DataField(name='dhkeys', value=base64.b64encode(self.encode_mpi(e))))
- # TODO: store and return rshashes, or at least randomly generate some
- result.addChild(node=xmpp.DataField(name='rshashes', value=[]))
+ secrets = gajim.interface.list_secrets(self.conn.name, self.jid.getStripped())
+ rshashes = [self.hmac(self.n_s, rs) for rs in secrets]
+ # XXX add some random fake rshashes here
+
+ rshashes.sort()
+
+ rshashes = [base64.b64encode(rshash) for rshash in rshashes]
+ result.addChild(node=xmpp.DataField(name='rshashes', value=rshashes))
+
form_a2 = ''.join(map(lambda el: xmpp.c14n.c14n(el), result.getChildren()))
self.kc_s, self.km_s, self.ks_s = self.generate_initiator_keys(self.k)
@@ -615,23 +619,34 @@
raise exceptions.NegotiationError, 'calculated mac_a differs from received mac_a'
# 4.5.4 generating bob's final session keys
- self.srs = ''
+
+ srs = ''
+
+ secrets = gajim.interface.list_secrets(self.conn.name, self.jid.getStripped())
+ rshashes = [base64.b64decode(rshash) for rshash in form.getField('rshashes').getValues()]
+
+ for secret in secrets:
+ if self.hmac(self.n_o, secret) in rshashes:
+ srs = secret
+ break
+
+ # other shared secret, we haven't got one.
oss = ''
# check for a retained secret
# if none exists, prompt the user with the SAS
if self.sas_algs == 'sas28x5':
self.sas = self.sas_28x5(m_a, self.form_b)
+
+ k = self.sha256(k + srs + oss)
- k = self.sha256(k + self.srs + oss)
-
# XXX I can skip generating ks_o here
self.kc_s, self.km_s, self.ks_s = self.generate_responder_keys(k)
self.kc_o, self.km_o, self.ks_o = self.generate_initiator_keys(k)
# 4.5.5
- if self.srs:
- srshash = self.hmac(self.srs, 'Shared Retained Secret')
+ if srs:
+ srshash = self.hmac(srs, 'Shared Retained Secret')
else:
srshash = self.random_bytes(32)
@@ -655,12 +670,8 @@
self.send(response)
- # destroy all copies of srs
+ self.do_retained_secret(k, srs)
- self.srs = self.hmac(k, 'New Retained Secret')
-
- # destroy k
-
if self.negotiated['logging'] == 'mustnot':
self.loggable = False
@@ -668,27 +679,28 @@
self.enable_encryption = True
def final_steps_alice(self, form):
- # Alice MUST identify the shared retained secret (SRS) by selecting from her client's list of the secrets it retained from sessions with Bob's clients (the most recent secret for each of the clients he has used to negotiate ESessions with Alice's client).
+ srs = ''
+ secrets = gajim.interface.list_secrets(self.conn.name, self.jid.getStripped())
- # Alice does this by using each secret in the list in turn as the key to calculate the HMAC (with SHA256) of the string "Shared Retained Secret", and comparing the calculated value with the value in the 'srshash' field she received from Bob (see Sending Bob's Identity). Once she finds a match, and has confirmed that the secret has not expired (because it is older than an implementation-defined period of time), then she has found the SRS.
+ srshash = base64.b64decode(form['srshash'])
+ for secret in secrets:
+ if self.hmac(secret, 'Shared Retained Secret') == srshash:
+ srs = secret
+ break
- srs = ''
oss = ''
- self.k = self.sha256(self.k + srs + oss)
+ k = self.sha256(self.k + srs + oss)
+ del self.k
- # Alice MUST destroy all her copies of the old retained secret (SRS) she was keeping for Bob's client, and calculate a new retained secret for this session:
+ self.do_retained_secret(k, srs)
- srs = self.hmac('New Retained Secret', self.k)
-
- # Alice MUST securely store the new value along with the retained secrets her client shares with Bob's other clients.
-
# don't need to calculate ks_s here
- self.kc_s, self.km_s, self.ks_s = self.generate_initiator_keys(self.k)
- self.kc_o, self.km_o, self.ks_o = self.generate_responder_keys(self.k)
+ self.kc_s, self.km_s, self.ks_s = self.generate_initiator_keys(k)
+ self.kc_o, self.km_o, self.ks_o = self.generate_responder_keys(k)
-#4.6.2 Verifying Bob's Identity
+ # 4.6.2 Verifying Bob's Identity
m_b = base64.b64decode(form['mac'])
id_b = base64.b64decode(form['identity'])
@@ -716,6 +728,19 @@
self.status = 'active'
self.enable_encryption = True
+ # calculate and store the new retained secret
+ # prompt the user to check the remote party's identity (if necessary)
+ def do_retained_secret(self, k, srs):
+ new_srs = self.hmac(k, 'New Retained Secret')
+ account = self.conn.name
+ bjid = self.jid.getStripped()
+
+ if srs:
+ gajim.interface.replace_secret(account, bjid, srs, new_srs)
+ else:
+ self.check_identity()
+ gajim.interface.save_new_secret(account, bjid, new_srs)
+
# generate a random number between 'bottom' and 'top'
def srand(self, bottom, top):
# minimum number of bytes needed to represent that range
@@ -773,12 +798,12 @@
self.status = None
def is_loggable(self):
- name = self.conn.name
- no_log_for = gajim.config.get_per('accounts', name, 'no_log_for')
+ account = self.conn.name
+ no_log_for = gajim.config.get_per('accounts', account, 'no_log_for')
if not no_log_for:
no_log_for = ''
no_log_for = no_log_for.split()
- return self.loggable and name not in no_log_for and self.jid not in no_log_for
+ return self.loggable and account not in no_log_for and self.jid not in no_log_for
Modified: branches/e2e_encryption/src/gajim.py
===================================================================
--- branches/e2e_encryption/src/gajim.py 2007-07-17 08:08:01 UTC (rev 8404)
+++ branches/e2e_encryption/src/gajim.py 2007-07-17 08:08:27 UTC (rev 8405)
@@ -109,6 +109,8 @@
from common.zeroconf import connection_zeroconf
from common import dbus_support
+import pickle
+
if os.name == 'posix': # dl module is Unix Only
try: # rename the process name to gajim
import dl
@@ -208,6 +210,7 @@
pid_filename = gajimpaths['PID_FILE']
config_filename = gajimpaths['CONFIG_FILE']
+secrets_filename = gajimpaths['SECRETS_FILE']
import traceback
import errno
@@ -1390,6 +1393,46 @@
if os.path.isfile(path_to_original_file):
os.remove(path_to_original_file)
+ def list_secrets(self, account, jid):
+ f = open(secrets_filename)
+
+ try:
+ s = pickle.load(f)[account][jid]
+ except KeyError:
+ s = []
+
+ f.close()
+ return s
+
+ def save_new_secret(self, account, jid, secret):
+ f = open(secrets_filename, 'r')
+ secrets = pickle.load(f)
+ f.close()
+
+ if not account in secrets:
+ secrets[account] = {}
+
+ if not jid in secrets[account]:
+ secrets[account][jid] = []
+
+ secrets[account][jid].append(secret)
+
+ f = open(secrets_filename, 'w')
+ pickle.dump(secrets, f)
+ f.close()
+
+ def replace_secret(self, account, jid, old_secret, new_secret):
+ f = open(secrets_filename, 'r')
+ secrets = pickle.load(f)
+ f.close()
+
+ this_secrets = secrets[account][jid]
+ this_secrets[this_secrets.index(old_secret)] = new_secret
+
+ f = open(secrets_filename, 'w')
+ pickle.dump(secrets, f)
+ f.close()
+
def add_event(self, account, jid, type_, event_args):
'''add an event to the gajim.events var'''
# We add it to the gajim.events queue
@@ -1657,7 +1700,7 @@
def handle_session_negotiation(self, account, data):
jid, session, form = data
-
+
if form.getField('accept') and not form['accept'] in ('1', 'true'):
dialogs.InformationDialog(_('Session negotiation cancelled'),
_('The client at %s cancelled the session negotiation.') % (jid))
@@ -1727,8 +1770,6 @@
negotiated.update(ask_user)
session.accept_e2e_alice(form, negotiated)
- negotiation.show_sas_dialog(jid, session.sas)
-
def reject_nondefault_options(widget):
session.reject_negotiation()
dialog.destroy()
@@ -1739,19 +1780,20 @@
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.check_identity = lambda: 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.check_identity = lambda: negotiation.show_sas_dialog(jid, session.sas)
+
try:
session.final_steps_alice(form)
except exceptions.NegotiationError, details:
@@ -2549,5 +2591,11 @@
check_paths.check_and_possibly_create_paths()
+ # create secrets file (unless it exists)
+ if not os.path.exists(secrets_filename):
+ f = open(secrets_filename, 'w')
+ pickle.dump({}, f)
+ f.close()
+
Interface()
gtk.main()