shared retained secrets

This commit is contained in:
Brendan Taylor 2007-07-17 08:08:27 +00:00
parent 0ae43eab4e
commit fdef1c3d72
3 changed files with 116 additions and 43 deletions

View file

@ -1609,7 +1609,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
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 @@ returns the session that we last sent a message to.'''
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] = {}

View file

@ -1,4 +1,4 @@
import gajim
from common import gajim
from common import xmpp
from common import helpers
@ -23,10 +23,7 @@ class StanzaSession(object):
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 @@ class StanzaSession(object):
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,8 +540,15 @@ class EncryptedStanzaSession(StanzaSession):
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()))
@ -615,23 +619,34 @@ class EncryptedStanzaSession(StanzaSession):
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 + self.srs + oss)
k = self.sha256(k + 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,11 +670,7 @@ class EncryptedStanzaSession(StanzaSession):
self.send(response)
# destroy all copies of srs
self.srs = self.hmac(k, 'New Retained Secret')
# destroy k
self.do_retained_secret(k, srs)
if self.negotiated['logging'] == 'mustnot':
self.loggable = False
@ -668,27 +679,28 @@ class EncryptedStanzaSession(StanzaSession):
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).
# 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.
srs = ''
secrets = gajim.interface.list_secrets(self.conn.name, self.jid.getStripped())
srshash = base64.b64decode(form['srshash'])
for secret in secrets:
if self.hmac(secret, 'Shared Retained Secret') == srshash:
srs = secret
break
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:
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.
self.do_retained_secret(k, srs)
# 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 @@ class EncryptedStanzaSession(StanzaSession):
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 @@ class EncryptedStanzaSession(StanzaSession):
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

View file

@ -109,6 +109,8 @@ from common import exceptions
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 @@ gajimpaths = common.configpaths.gajimpaths
pid_filename = gajimpaths['PID_FILE']
config_filename = gajimpaths['CONFIG_FILE']
secrets_filename = gajimpaths['SECRETS_FILE']
import traceback
import errno
@ -1390,6 +1393,46 @@ class Interface:
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 @@ class Interface:
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 @@ class Interface:
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 @@ class Interface:
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 @@ if __name__ == '__main__':
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()