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] del self.sessions[original_jid][thread_id]
new_jid = gajim.get_jid_without_resource(original_jid) + '/' + to_resource 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: if not new_jid in self.sessions:
self.sessions[new_jid] = {} self.sessions[new_jid] = {}
@ -1628,7 +1628,7 @@ returns the session that we last sent a message to.'''
return no_threadid_sessions[-1] return no_threadid_sessions[-1]
def make_new_session(self, jid, thread_id = None, type = 'chat'): 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: if not jid in self.sessions:
self.sessions[jid] = {} self.sessions[jid] = {}

View file

@ -1,4 +1,4 @@
import gajim from common import gajim
from common import xmpp from common import xmpp
from common import helpers from common import helpers
@ -23,10 +23,7 @@ class StanzaSession(object):
def __init__(self, conn, jid, thread_id, type): def __init__(self, conn, jid, thread_id, type):
self.conn = conn self.conn = conn
if isinstance(jid, str) or isinstance(jid, unicode): self.jid = jid
self.jid = xmpp.JID(jid)
else:
self.jid = jid
self.type = type self.type = type
@ -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='nonce', value=base64.b64encode(self.n_o)))
result.addChild(node=xmpp.DataField(name='dhkeys', value=base64.b64encode(self.encode_mpi(e)))) 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 secrets = gajim.interface.list_secrets(self.conn.name, self.jid.getStripped())
result.addChild(node=xmpp.DataField(name='rshashes', value=[])) 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())) form_a2 = ''.join(map(lambda el: xmpp.c14n.c14n(el), result.getChildren()))
@ -615,7 +619,18 @@ class EncryptedStanzaSession(StanzaSession):
raise exceptions.NegotiationError, 'calculated mac_a differs from received mac_a' raise exceptions.NegotiationError, 'calculated mac_a differs from received mac_a'
# 4.5.4 generating bob's final session keys # 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 = '' oss = ''
# check for a retained secret # check for a retained secret
@ -623,15 +638,15 @@ class EncryptedStanzaSession(StanzaSession):
if self.sas_algs == 'sas28x5': if self.sas_algs == 'sas28x5':
self.sas = self.sas_28x5(m_a, self.form_b) 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 # XXX I can skip generating ks_o here
self.kc_s, self.km_s, self.ks_s = self.generate_responder_keys(k) 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) self.kc_o, self.km_o, self.ks_o = self.generate_initiator_keys(k)
# 4.5.5 # 4.5.5
if self.srs: if srs:
srshash = self.hmac(self.srs, 'Shared Retained Secret') srshash = self.hmac(srs, 'Shared Retained Secret')
else: else:
srshash = self.random_bytes(32) srshash = self.random_bytes(32)
@ -655,11 +670,7 @@ class EncryptedStanzaSession(StanzaSession):
self.send(response) 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': if self.negotiated['logging'] == 'mustnot':
self.loggable = False self.loggable = False
@ -668,27 +679,28 @@ class EncryptedStanzaSession(StanzaSession):
self.enable_encryption = True self.enable_encryption = True
def final_steps_alice(self, form): 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 = '' 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 = '' 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 # 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_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(self.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']) m_b = base64.b64decode(form['mac'])
id_b = base64.b64decode(form['identity']) id_b = base64.b64decode(form['identity'])
@ -716,6 +728,19 @@ class EncryptedStanzaSession(StanzaSession):
self.status = 'active' self.status = 'active'
self.enable_encryption = True 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' # generate a random number between 'bottom' and 'top'
def srand(self, bottom, top): def srand(self, bottom, top):
# minimum number of bytes needed to represent that range # minimum number of bytes needed to represent that range
@ -773,12 +798,12 @@ class EncryptedStanzaSession(StanzaSession):
self.status = None self.status = None
def is_loggable(self): def is_loggable(self):
name = self.conn.name account = self.conn.name
no_log_for = gajim.config.get_per('accounts', name, 'no_log_for') no_log_for = gajim.config.get_per('accounts', account, 'no_log_for')
if not no_log_for: if not no_log_for:
no_log_for = '' no_log_for = ''
no_log_for = no_log_for.split() 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.zeroconf import connection_zeroconf
from common import dbus_support from common import dbus_support
import pickle
if os.name == 'posix': # dl module is Unix Only if os.name == 'posix': # dl module is Unix Only
try: # rename the process name to gajim try: # rename the process name to gajim
import dl import dl
@ -208,6 +210,7 @@ gajimpaths = common.configpaths.gajimpaths
pid_filename = gajimpaths['PID_FILE'] pid_filename = gajimpaths['PID_FILE']
config_filename = gajimpaths['CONFIG_FILE'] config_filename = gajimpaths['CONFIG_FILE']
secrets_filename = gajimpaths['SECRETS_FILE']
import traceback import traceback
import errno import errno
@ -1390,6 +1393,46 @@ class Interface:
if os.path.isfile(path_to_original_file): if os.path.isfile(path_to_original_file):
os.remove(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): def add_event(self, account, jid, type_, event_args):
'''add an event to the gajim.events var''' '''add an event to the gajim.events var'''
# We add it to the gajim.events queue # We add it to the gajim.events queue
@ -1727,8 +1770,6 @@ class Interface:
negotiated.update(ask_user) negotiated.update(ask_user)
session.accept_e2e_alice(form, negotiated) session.accept_e2e_alice(form, negotiated)
negotiation.show_sas_dialog(jid, session.sas)
def reject_nondefault_options(widget): def reject_nondefault_options(widget):
session.reject_negotiation() session.reject_negotiation()
dialog.destroy() dialog.destroy()
@ -1739,19 +1780,20 @@ class Interface:
on_response_no = reject_nondefault_options) on_response_no = reject_nondefault_options)
else: else:
session.accept_e2e_alice(form, negotiated) session.accept_e2e_alice(form, negotiated)
negotiation.show_sas_dialog(jid, session.sas)
return return
elif session.status == 'responded-e2e' and form.getType() == 'result': elif session.status == 'responded-e2e' and form.getType() == 'result':
session.check_identity = lambda: negotiation.show_sas_dialog(jid, session.sas)
try: try:
session.accept_e2e_bob(form) session.accept_e2e_bob(form)
except exceptions.NegotiationError, details: except exceptions.NegotiationError, details:
session.fail_bad_negotiation(details) session.fail_bad_negotiation(details)
else:
negotiation.show_sas_dialog(jid, session.sas)
return return
elif session.status == 'identified-alice' and form.getType() == 'result': elif session.status == 'identified-alice' and form.getType() == 'result':
session.check_identity = lambda: negotiation.show_sas_dialog(jid, session.sas)
try: try:
session.final_steps_alice(form) session.final_steps_alice(form)
except exceptions.NegotiationError, details: except exceptions.NegotiationError, details:
@ -2549,5 +2591,11 @@ if __name__ == '__main__':
check_paths.check_and_possibly_create_paths() 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() Interface()
gtk.main() gtk.main()