shared retained secrets
This commit is contained in:
parent
0ae43eab4e
commit
fdef1c3d72
3 changed files with 116 additions and 43 deletions
|
@ -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] = {}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -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,7 +619,18 @@ 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
|
||||
|
@ -623,15 +638,15 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
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
|
||||
|
|
58
src/gajim.py
58
src/gajim.py
|
@ -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
|
||||
|
@ -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()
|
||||
|
|
Loading…
Add table
Reference in a new issue