disable logs in encrypted sessions.
This commit is contained in:
parent
4bd805cf07
commit
6fe668d863
|
@ -1948,10 +1948,13 @@ class ChatControl(ChatControlBase):
|
||||||
|
|
||||||
def _on_toggle_e2e_menuitem_activate(self, widget):
|
def _on_toggle_e2e_menuitem_activate(self, widget):
|
||||||
if self.session.enable_encryption:
|
if self.session.enable_encryption:
|
||||||
self.session.enable_encryption = False
|
|
||||||
self.session.terminate_e2e()
|
self.session.terminate_e2e()
|
||||||
|
|
||||||
|
jid = str(self.session.jid)
|
||||||
|
|
||||||
|
gajim.connections[self.account].delete_session(jid, self.session.thread_id)
|
||||||
|
self.session = gajim.connections[self.account].make_new_session(jid)
|
||||||
else:
|
else:
|
||||||
self.session.enable_encryption = True
|
|
||||||
self.session.negotiate_e2e()
|
self.session.negotiate_e2e()
|
||||||
|
|
||||||
def got_connected(self):
|
def got_connected(self):
|
||||||
|
|
|
@ -921,10 +921,7 @@ class Connection(ConnectionHandlers):
|
||||||
|
|
||||||
self.connection.send(msg_iq)
|
self.connection.send(msg_iq)
|
||||||
|
|
||||||
no_log_for = gajim.config.get_per('accounts', self.name, 'no_log_for')\
|
if session.is_loggable():
|
||||||
.split()
|
|
||||||
ji = gajim.get_jid_without_resource(jid)
|
|
||||||
if self.name not in no_log_for and ji not in no_log_for:
|
|
||||||
log_msg = msg
|
log_msg = msg
|
||||||
if subject:
|
if subject:
|
||||||
log_msg = _('Subject: %s\n%s') % (subject, msg)
|
log_msg = _('Subject: %s\n%s') % (subject, msg)
|
||||||
|
|
|
@ -1461,11 +1461,6 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
||||||
tim = time.strptime(tim, '%Y%m%dT%H:%M:%S')
|
tim = time.strptime(tim, '%Y%m%dT%H:%M:%S')
|
||||||
tim = time.localtime(timegm(tim))
|
tim = time.localtime(timegm(tim))
|
||||||
jid = helpers.get_jid_from_iq(msg)
|
jid = helpers.get_jid_from_iq(msg)
|
||||||
no_log_for = gajim.config.get_per('accounts', self.name,
|
|
||||||
'no_log_for')
|
|
||||||
if not no_log_for:
|
|
||||||
no_log_for = ''
|
|
||||||
no_log_for = no_log_for.split()
|
|
||||||
encrypted = False
|
encrypted = False
|
||||||
chatstate = None
|
chatstate = None
|
||||||
encTag = msg.getTag('x', namespace = common.xmpp.NS_ENCRYPTED)
|
encTag = msg.getTag('x', namespace = common.xmpp.NS_ENCRYPTED)
|
||||||
|
@ -1525,7 +1520,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
||||||
if not error_msg:
|
if not error_msg:
|
||||||
error_msg = msgtxt
|
error_msg = msgtxt
|
||||||
msgtxt = None
|
msgtxt = None
|
||||||
if self.name not in no_log_for:
|
if session.is_loggable():
|
||||||
gajim.logger.write('error', frm, error_msg, tim = tim,
|
gajim.logger.write('error', frm, error_msg, tim = tim,
|
||||||
subject = subject)
|
subject = subject)
|
||||||
self.dispatch('MSGERROR', (frm, msg.getErrorCode(), error_msg, msgtxt,
|
self.dispatch('MSGERROR', (frm, msg.getErrorCode(), error_msg, msgtxt,
|
||||||
|
@ -1544,15 +1539,14 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
||||||
if not self.last_history_line.has_key(jid):
|
if not self.last_history_line.has_key(jid):
|
||||||
return
|
return
|
||||||
self.dispatch('GC_MSG', (frm, msgtxt, tim, has_timestamp, msghtml))
|
self.dispatch('GC_MSG', (frm, msgtxt, tim, has_timestamp, msghtml))
|
||||||
if self.name not in no_log_for and not int(float(time.mktime(tim)))\
|
if session.is_loggable() and not int(float(time.mktime(tim)))\
|
||||||
<= self.last_history_line[jid] and msgtxt:
|
<= self.last_history_line[jid] and msgtxt:
|
||||||
gajim.logger.write('gc_msg', frm, msgtxt, tim = tim)
|
gajim.logger.write('gc_msg', frm, msgtxt, tim = tim)
|
||||||
return
|
return
|
||||||
elif mtype == 'chat': # it's type 'chat'
|
elif mtype == 'chat': # it's type 'chat'
|
||||||
if not msg.getTag('body') and chatstate is None: #no <body>
|
if not msg.getTag('body') and chatstate is None: #no <body>
|
||||||
return
|
return
|
||||||
if msg.getTag('body') and self.name not in no_log_for and jid not in\
|
if msg.getTag('body') and session.is_loggable() and msgtxt:
|
||||||
no_log_for and msgtxt:
|
|
||||||
msg_id = gajim.logger.write('chat_msg_recv', frm, msgtxt, tim = tim,
|
msg_id = gajim.logger.write('chat_msg_recv', frm, msgtxt, tim = tim,
|
||||||
subject = subject)
|
subject = subject)
|
||||||
else: # it's single message
|
else: # it's single message
|
||||||
|
@ -1564,7 +1558,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
||||||
password = invite.getTagData('password')
|
password = invite.getTagData('password')
|
||||||
self.dispatch('GC_INVITATION',(frm, jid_from, reason, password))
|
self.dispatch('GC_INVITATION',(frm, jid_from, reason, password))
|
||||||
return
|
return
|
||||||
if self.name not in no_log_for and jid not in no_log_for and msgtxt:
|
if session.is_loggable()and msgtxt:
|
||||||
gajim.logger.write('single_msg_recv', frm, msgtxt, tim = tim,
|
gajim.logger.write('single_msg_recv', frm, msgtxt, tim = tim,
|
||||||
subject = subject)
|
subject = subject)
|
||||||
mtype = 'normal'
|
mtype = 'normal'
|
||||||
|
@ -1576,7 +1570,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
||||||
# END messageCB
|
# END messageCB
|
||||||
|
|
||||||
def get_session(self, jid, thread_id, type):
|
def get_session(self, jid, thread_id, type):
|
||||||
'''returns an existing session between this connection and 'jid' or starts a new one.'''
|
'''returns an existing session between this connection and 'jid', returns a new one if none exist.'''
|
||||||
session = self.find_session(jid, thread_id, type)
|
session = self.find_session(jid, thread_id, type)
|
||||||
|
|
||||||
if session:
|
if session:
|
||||||
|
@ -1588,6 +1582,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
||||||
if bare_jid != jid:
|
if bare_jid != jid:
|
||||||
session = self.find_session(bare_jid, thread_id, type)
|
session = self.find_session(bare_jid, thread_id, type)
|
||||||
if session:
|
if session:
|
||||||
|
print repr(bare_jid), repr(thread_id), repr(jid.split("/")[1])
|
||||||
self.move_session(bare_jid, thread_id, jid.split("/")[1])
|
self.move_session(bare_jid, thread_id, jid.split("/")[1])
|
||||||
return session
|
return session
|
||||||
|
|
||||||
|
@ -1609,6 +1604,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
||||||
del self.sessions[jid]
|
del self.sessions[jid]
|
||||||
|
|
||||||
def move_session(self, original_jid, thread_id, to_resource):
|
def move_session(self, original_jid, thread_id, to_resource):
|
||||||
|
'''moves a session to another resource.'''
|
||||||
session = self.sessions[original_jid][thread_id]
|
session = self.sessions[original_jid][thread_id]
|
||||||
|
|
||||||
del self.sessions[original_jid][thread_id]
|
del self.sessions[original_jid][thread_id]
|
||||||
|
@ -1622,13 +1618,15 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
||||||
self.sessions[new_jid][thread_id] = session
|
self.sessions[new_jid][thread_id] = session
|
||||||
|
|
||||||
def find_null_session(self, jid):
|
def find_null_session(self, jid):
|
||||||
'''returns the session between this connecting and 'jid' that we last sent a message in.
|
'''finds all of the sessions between us and jid that jid hasn't sent a thread_id in yet.
|
||||||
this is needed to handle clients that don't support threads; see XEP-0201.'''
|
|
||||||
all = self.sessions[jid].values()
|
|
||||||
null_sessions = filter(lambda s: not s.received_thread_id, all)
|
|
||||||
null_sessions.sort(key=lambda s: s.last_send)
|
|
||||||
|
|
||||||
return null_sessions[-1]
|
returns the session that we last sent a message to.'''
|
||||||
|
|
||||||
|
sessions_with_jid = self.sessions[jid].values()
|
||||||
|
no_threadid_sessions = filter(lambda s: not s.received_thread_id, sessions_with_jid)
|
||||||
|
no_threadid_sessions.sort(key=lambda s: s.last_send)
|
||||||
|
|
||||||
|
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, jid, thread_id, type)
|
||||||
|
|
|
@ -41,7 +41,7 @@ class StanzaSession(object):
|
||||||
|
|
||||||
self.last_send = 0
|
self.last_send = 0
|
||||||
self.status = None
|
self.status = None
|
||||||
self.features = {}
|
self.negotiated = {}
|
||||||
|
|
||||||
def generate_thread_id(self):
|
def generate_thread_id(self):
|
||||||
return "".join([random.choice(string.letters) for x in xrange(0,32)])
|
return "".join([random.choice(string.letters) for x in xrange(0,32)])
|
||||||
|
@ -55,6 +55,29 @@ class StanzaSession(object):
|
||||||
|
|
||||||
self.last_send = time.time()
|
self.last_send = time.time()
|
||||||
|
|
||||||
|
def reject_negotiation(self, body = None):
|
||||||
|
msg = xmpp.Message()
|
||||||
|
feature = msg.NT.feature
|
||||||
|
feature.setNamespace(xmpp.NS_FEATURE)
|
||||||
|
|
||||||
|
x = xmpp.DataForm(typ='submit')
|
||||||
|
x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn'))
|
||||||
|
x.addChild(node=xmpp.DataField(name='accept', value='0'))
|
||||||
|
|
||||||
|
feature.addChild(node=x)
|
||||||
|
|
||||||
|
if body:
|
||||||
|
msg.setBody(body)
|
||||||
|
|
||||||
|
self.send(msg)
|
||||||
|
|
||||||
|
self.cancelled_negotiation()
|
||||||
|
|
||||||
|
def cancelled_negotiation(self):
|
||||||
|
'''A negotiation has been cancelled, so reset this session to its default state.'''
|
||||||
|
self.status = None
|
||||||
|
self.negotiated = {}
|
||||||
|
|
||||||
def terminate(self):
|
def terminate(self):
|
||||||
msg = xmpp.Message()
|
msg = xmpp.Message()
|
||||||
feature = msg.NT.feature
|
feature = msg.NT.feature
|
||||||
|
@ -101,6 +124,8 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
def __init__(self, conn, jid, thread_id, type = 'chat'):
|
def __init__(self, conn, jid, thread_id, type = 'chat'):
|
||||||
StanzaSession.__init__(self, conn, jid, thread_id, type = 'chat')
|
StanzaSession.__init__(self, conn, jid, thread_id, type = 'chat')
|
||||||
|
|
||||||
|
self.loggable = True
|
||||||
|
|
||||||
self.xes = {}
|
self.xes = {}
|
||||||
self.es = {}
|
self.es = {}
|
||||||
|
|
||||||
|
@ -195,12 +220,24 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
def hmac(self, key, content):
|
def hmac(self, key, content):
|
||||||
return HMAC.new(key, content, self.hash_alg).digest()
|
return HMAC.new(key, content, self.hash_alg).digest()
|
||||||
|
|
||||||
# this should be more generic?
|
|
||||||
def sha256(self, string):
|
def sha256(self, string):
|
||||||
sh = SHA256.new()
|
sh = SHA256.new()
|
||||||
sh.update(string)
|
sh.update(string)
|
||||||
return sh.digest()
|
return sh.digest()
|
||||||
|
|
||||||
|
base28_chr = "acdefghikmopqruvwxy123456789"
|
||||||
|
|
||||||
|
def sas_28x5(self, m_a, form_b):
|
||||||
|
sha = self.sha256(m_a + form_b + 'Short Authentication String')
|
||||||
|
lsb24 = self.decode_mpi(sha[-3:])
|
||||||
|
return self.base28(lsb24)
|
||||||
|
|
||||||
|
def base28(self, n):
|
||||||
|
if n >= 28:
|
||||||
|
return self.base28(n / 28) + self.base28_chr[n % 28]
|
||||||
|
else:
|
||||||
|
return self.base28_chr[n]
|
||||||
|
|
||||||
def generate_initiator_keys(self, k):
|
def generate_initiator_keys(self, k):
|
||||||
return (self.hmac(k, 'Initiator Cipher Key'),
|
return (self.hmac(k, 'Initiator Cipher Key'),
|
||||||
self.hmac(k, 'Initiator MAC Key'),
|
self.hmac(k, 'Initiator MAC Key'),
|
||||||
|
@ -265,7 +302,15 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
def decrypt(self, ciphertext):
|
def decrypt(self, ciphertext):
|
||||||
return self.decrypter.decrypt(ciphertext)
|
return self.decrypter.decrypt(ciphertext)
|
||||||
|
|
||||||
|
def logging_preference(self):
|
||||||
|
if gajim.config.get('log_encrypted_sessions'):
|
||||||
|
return ["may", "mustnot"]
|
||||||
|
else:
|
||||||
|
return ["mustnot", "may"]
|
||||||
|
|
||||||
def negotiate_e2e(self):
|
def negotiate_e2e(self):
|
||||||
|
self.negotiated = {}
|
||||||
|
|
||||||
request = xmpp.Message()
|
request = xmpp.Message()
|
||||||
feature = request.NT.feature
|
feature = request.NT.feature
|
||||||
feature.setNamespace(xmpp.NS_FEATURE)
|
feature.setNamespace(xmpp.NS_FEATURE)
|
||||||
|
@ -276,8 +321,7 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
x.addChild(node=xmpp.DataField(name='accept', value='1', typ='boolean', required=True))
|
x.addChild(node=xmpp.DataField(name='accept', value='1', typ='boolean', required=True))
|
||||||
|
|
||||||
# this field is incorrectly called 'otr' in XEPs 0116 and 0217
|
# this field is incorrectly called 'otr' in XEPs 0116 and 0217
|
||||||
# unsupported options: 'mustnot'
|
x.addChild(node=xmpp.DataField(name='logging', typ='list-single', options=self.logging_preference(), required=True))
|
||||||
x.addChild(node=xmpp.DataField(name='logging', typ='list-single', options=['may'], required=True))
|
|
||||||
|
|
||||||
# unsupported options: 'disabled', 'enabled'
|
# unsupported options: 'disabled', 'enabled'
|
||||||
x.addChild(node=xmpp.DataField(name='disclosure', typ='list-single', options=['never'], required=True))
|
x.addChild(node=xmpp.DataField(name='disclosure', typ='list-single', options=['never'], required=True))
|
||||||
|
@ -317,12 +361,10 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
self.send(request)
|
self.send(request)
|
||||||
|
|
||||||
# 4.3 esession response (bob)
|
# 4.3 esession response (bob)
|
||||||
def respond_e2e_bob(self, request_form):
|
def verify_options_bob(self, form):
|
||||||
response = xmpp.Message()
|
negotiated = {}
|
||||||
feature = response.NT.feature
|
not_acceptable = []
|
||||||
feature.setNamespace(xmpp.NS_FEATURE)
|
ask_user = {}
|
||||||
|
|
||||||
x = xmpp.DataForm(typ='submit')
|
|
||||||
|
|
||||||
fixed = { 'disclosure': 'never',
|
fixed = { 'disclosure': 'never',
|
||||||
'security': 'e2e',
|
'security': 'e2e',
|
||||||
|
@ -333,21 +375,16 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
'init_pubkey': 'none',
|
'init_pubkey': 'none',
|
||||||
'resp_pubkey': 'none',
|
'resp_pubkey': 'none',
|
||||||
'ver': '1.0',
|
'ver': '1.0',
|
||||||
'sas_algs': 'sas28x5',
|
'sas_algs': 'sas28x5' }
|
||||||
'logging': 'may' }
|
|
||||||
|
|
||||||
not_acceptable = []
|
|
||||||
|
|
||||||
self.encryptable_stanzas = ['message']
|
self.encryptable_stanzas = ['message']
|
||||||
|
|
||||||
self.sas_algs = 'sas28x5'
|
self.sas_algs = 'sas28x5'
|
||||||
self.cipher = AES
|
self.cipher = AES
|
||||||
self.hash_alg = SHA256
|
self.hash_alg = SHA256
|
||||||
self.compression = None
|
self.compression = None
|
||||||
|
|
||||||
x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn'))
|
for name, field in map(lambda name: (name, form.getField(name)), form.asDict().keys()):
|
||||||
x.addChild(node=xmpp.DataField(name='accept', value='true'))
|
|
||||||
|
|
||||||
for name, field in map(lambda name: (name, request_form.getField(name)), request_form.asDict().keys()):
|
|
||||||
options = map(lambda x: x[1], field.getOptions())
|
options = map(lambda x: x[1], field.getOptions())
|
||||||
values = field.getValues()
|
values = field.getValues()
|
||||||
|
|
||||||
|
@ -356,28 +393,61 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
|
|
||||||
if name in fixed:
|
if name in fixed:
|
||||||
if fixed[name] in options:
|
if fixed[name] in options:
|
||||||
x.addChild(node=xmpp.DataField(name=name, value=fixed[name]))
|
negotiated[name] = fixed[name]
|
||||||
else:
|
else:
|
||||||
not_acceptable.append(name)
|
not_acceptable.append(name)
|
||||||
elif name == 'modp':
|
|
||||||
# the offset of the group we chose (need it to match up with the dhhash)
|
|
||||||
group_order = 0
|
|
||||||
self.modp = int(options[group_order])
|
|
||||||
x.addChild(node=xmpp.DataField(name='modp', value=self.modp))
|
|
||||||
|
|
||||||
g = dh.generators[self.modp]
|
|
||||||
p = dh.primes[self.modp]
|
|
||||||
elif name == 'rekey_freq':
|
elif name == 'rekey_freq':
|
||||||
preferred = int(options[0])
|
preferred = int(options[0])
|
||||||
x.addChild(node=xmpp.DataField(name='rekey_freq', value=preferred))
|
negotiated['rekey_freq'] = preferred
|
||||||
|
|
||||||
self.rekey_freq = preferred
|
self.rekey_freq = preferred
|
||||||
elif name == 'my_nonce':
|
elif name == 'logging':
|
||||||
self.n_o = base64.b64decode(field.getValue())
|
my_prefs = self.logging_preference()
|
||||||
|
|
||||||
# XXX do something with not_acceptable
|
if my_prefs[0] in options:
|
||||||
|
pref = my_prefs[0]
|
||||||
|
negotiated['logging'] = pref
|
||||||
|
else:
|
||||||
|
for pref in my_prefs:
|
||||||
|
if pref in options:
|
||||||
|
ask_user['logging'] = pref
|
||||||
|
break
|
||||||
|
|
||||||
self.He = request_form.getField('dhhashes').getValues()[group_order].encode("utf8")
|
if not 'logging' in ask_user:
|
||||||
|
not_acceptable.append(name)
|
||||||
|
else:
|
||||||
|
# some things are handled elsewhere, some things are not-implemented
|
||||||
|
pass
|
||||||
|
|
||||||
|
return (negotiated, not_acceptable, ask_user)
|
||||||
|
|
||||||
|
# 4.3 esession response (bob)
|
||||||
|
def respond_e2e_bob(self, form, negotiated, not_acceptable):
|
||||||
|
response = xmpp.Message()
|
||||||
|
feature = response.NT.feature
|
||||||
|
feature.setNamespace(xmpp.NS_FEATURE)
|
||||||
|
|
||||||
|
x = xmpp.DataForm(typ='submit')
|
||||||
|
|
||||||
|
x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn'))
|
||||||
|
x.addChild(node=xmpp.DataField(name='accept', value='true'))
|
||||||
|
|
||||||
|
for name in negotiated:
|
||||||
|
x.addChild(node=xmpp.DataField(name=name, value=negotiated[name]))
|
||||||
|
|
||||||
|
self.negotiated = negotiated
|
||||||
|
|
||||||
|
# the offset of the group we chose (need it to match up with the dhhash)
|
||||||
|
group_order = 0
|
||||||
|
self.modp = int(form.getField('modp').getOptions()[group_order][1])
|
||||||
|
x.addChild(node=xmpp.DataField(name='modp', value=self.modp))
|
||||||
|
|
||||||
|
g = dh.generators[self.modp]
|
||||||
|
p = dh.primes[self.modp]
|
||||||
|
|
||||||
|
self.n_o = base64.b64decode(form['my_nonce'])
|
||||||
|
|
||||||
|
dhhashes = form.getField('dhhashes').getValues()
|
||||||
|
self.He = dhhashes[group_order].encode("utf8")
|
||||||
|
|
||||||
bytes = int(self.n / 8)
|
bytes = int(self.n / 8)
|
||||||
|
|
||||||
|
@ -389,30 +459,61 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
self.y = self.srand(2 ** (2 * self.n - 1), p - 1)
|
self.y = self.srand(2 ** (2 * self.n - 1), p - 1)
|
||||||
self.d = self.powmod(g, self.y, p)
|
self.d = self.powmod(g, self.y, p)
|
||||||
|
|
||||||
to_add = { 'my_nonce': self.n_s, 'dhkeys': self.encode_mpi(self.d), 'counter': self.encode_mpi(self.c_o), 'nonce': self.n_o }
|
to_add = { 'my_nonce': self.n_s,
|
||||||
|
'dhkeys': self.encode_mpi(self.d),
|
||||||
|
'counter': self.encode_mpi(self.c_o),
|
||||||
|
'nonce': self.n_o }
|
||||||
|
|
||||||
for name in to_add:
|
for name in to_add:
|
||||||
b64ed = base64.b64encode(to_add[name])
|
b64ed = base64.b64encode(to_add[name])
|
||||||
x.addChild(node=xmpp.DataField(name=name, value=b64ed))
|
x.addChild(node=xmpp.DataField(name=name, value=b64ed))
|
||||||
|
|
||||||
self.form_a = ''.join(map(lambda el: xmpp.c14n.c14n(el), request_form.getChildren()))
|
self.form_a = ''.join(map(lambda el: xmpp.c14n.c14n(el), form.getChildren()))
|
||||||
self.form_b = ''.join(map(lambda el: xmpp.c14n.c14n(el), x.getChildren()))
|
self.form_b = ''.join(map(lambda el: xmpp.c14n.c14n(el), x.getChildren()))
|
||||||
|
|
||||||
self.status = 'responded-e2e'
|
self.status = 'responded-e2e'
|
||||||
|
|
||||||
feature.addChild(node=x)
|
feature.addChild(node=x)
|
||||||
|
|
||||||
|
if not_acceptable:
|
||||||
|
pass
|
||||||
|
# XXX
|
||||||
|
# <error code='406' type='modify'>
|
||||||
|
# <not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
|
||||||
|
# <feature xmlns='http://jabber.org/protocol/feature-neg'>
|
||||||
|
# <field var='security'/>
|
||||||
|
# </feature>
|
||||||
|
# </error>
|
||||||
|
|
||||||
self.send(response)
|
self.send(response)
|
||||||
|
|
||||||
# 'Alice Accepts'
|
# 'Alice Accepts'
|
||||||
def accept_e2e_alice(self, form):
|
def verify_options_alice(self, form):
|
||||||
# 1. Verify that the ESession options selected by Bob are acceptable
|
# 1. Verify that the ESession options selected by Bob are acceptable
|
||||||
|
|
||||||
|
negotiated = {}
|
||||||
|
ask_user = {}
|
||||||
|
not_acceptable = []
|
||||||
|
|
||||||
|
if not form['logging'] in self.logging_preference():
|
||||||
|
not_acceptable.append(form['logging'])
|
||||||
|
elif form['logging'] != self.logging_preference()[0]:
|
||||||
|
ask_user['logging'] = form['logging']
|
||||||
|
else:
|
||||||
|
negotiated['logging'] = self.logging_preference()[0]
|
||||||
|
|
||||||
|
return (negotiated, not_acceptable, ask_user)
|
||||||
|
|
||||||
|
# 'Alice Accepts', continued
|
||||||
|
def accept_e2e_alice(self, form, negotiated):
|
||||||
self.encryptable_stanzas = ['message']
|
self.encryptable_stanzas = ['message']
|
||||||
self.sas_algs = 'sas28x5'
|
self.sas_algs = 'sas28x5'
|
||||||
self.cipher = AES
|
self.cipher = AES
|
||||||
self.hash_alg = SHA256
|
self.hash_alg = SHA256
|
||||||
self.compression = None
|
self.compression = None
|
||||||
|
|
||||||
|
self.negotiated = negotiated
|
||||||
|
|
||||||
# 2. Return a <not-acceptable/> error to Bob unless: 1 < d < p - 1
|
# 2. Return a <not-acceptable/> error to Bob unless: 1 < d < p - 1
|
||||||
self.form_b = ''.join(map(lambda el: xmpp.c14n.c14n(el), form.getChildren()))
|
self.form_b = ''.join(map(lambda el: xmpp.c14n.c14n(el), form.getChildren()))
|
||||||
|
|
||||||
|
@ -457,6 +558,11 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
|
|
||||||
m_a = self.hmac(self.km_s, self.encode_mpi(old_c_s) + id_a)
|
m_a = self.hmac(self.km_s, self.encode_mpi(old_c_s) + id_a)
|
||||||
|
|
||||||
|
# check for a retained secret
|
||||||
|
# if none exists, prompt the user with the SAS
|
||||||
|
if self.sas_algs == 'sas28x5':
|
||||||
|
print "sas: %s" % self.sas_28x5(m_a, self.form_b)
|
||||||
|
|
||||||
result.addChild(node=xmpp.DataField(name='identity', value=base64.b64encode(id_a)))
|
result.addChild(node=xmpp.DataField(name='identity', value=base64.b64encode(id_a)))
|
||||||
result.addChild(node=xmpp.DataField(name='mac', value=base64.b64encode(m_a)))
|
result.addChild(node=xmpp.DataField(name='mac', value=base64.b64encode(m_a)))
|
||||||
|
|
||||||
|
@ -520,6 +626,11 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
self.srs = ''
|
self.srs = ''
|
||||||
oss = ''
|
oss = ''
|
||||||
|
|
||||||
|
# check for a retained secret
|
||||||
|
# if none exists, prompt the user with the SAS
|
||||||
|
if self.sas_algs == 'sas28x5':
|
||||||
|
print "sas: %s" % self.sas_28x5(m_a, self.form_b)
|
||||||
|
|
||||||
k = self.sha256(k + self.srs + oss)
|
k = self.sha256(k + self.srs + oss)
|
||||||
|
|
||||||
# XXX I can skip generating ks_o here
|
# XXX I can skip generating ks_o here
|
||||||
|
@ -556,6 +667,10 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
self.srs = self.hmac(k, 'New Retained Secret')
|
self.srs = self.hmac(k, 'New Retained Secret')
|
||||||
|
|
||||||
# destroy k
|
# destroy k
|
||||||
|
|
||||||
|
if self.negotiated['logging'] == 'mustnot':
|
||||||
|
self.loggable = False
|
||||||
|
|
||||||
self.status = 'active'
|
self.status = 'active'
|
||||||
self.enable_encryption = True
|
self.enable_encryption = True
|
||||||
|
|
||||||
|
@ -594,6 +709,9 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
|
|
||||||
# Note: If Alice discovers an error then she SHOULD ignore any encrypted content she received in the stanza.
|
# Note: If Alice discovers an error then she SHOULD ignore any encrypted content she received in the stanza.
|
||||||
# XXX check for MAC equality?
|
# XXX check for MAC equality?
|
||||||
|
|
||||||
|
if self.negotiated['logging'] == 'mustnot':
|
||||||
|
self.loggable = False
|
||||||
|
|
||||||
self.status = 'active'
|
self.status = 'active'
|
||||||
self.enable_encryption = True
|
self.enable_encryption = True
|
||||||
|
@ -647,3 +765,14 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
StanzaSession.acknowledge_termination(self)
|
StanzaSession.acknowledge_termination(self)
|
||||||
|
|
||||||
self.enable_encryption = False
|
self.enable_encryption = False
|
||||||
|
|
||||||
|
def is_loggable(self):
|
||||||
|
name = self.conn.name
|
||||||
|
no_log_for = gajim.config.get_per('accounts', name, '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
|
||||||
|
|
73
src/gajim.py
73
src/gajim.py
|
@ -1657,13 +1657,64 @@ class Interface:
|
||||||
|
|
||||||
def handle_session_negotiation(self, account, data):
|
def handle_session_negotiation(self, account, data):
|
||||||
jid, session, form = data
|
jid, session, form = data
|
||||||
|
|
||||||
# encrypted session states
|
if form.getField('accept') and not form['accept'] in ('1', 'true'):
|
||||||
if form.getType() == 'form' and u'e2e' in map(lambda x: x[1], form.getField('security').getOptions()):
|
dialogs.InformationDialog(_('Session negotiation cancelled.'),
|
||||||
session.respond_e2e_bob(form)
|
_('The client at %s cancelled the session negotiation.') % (jid))
|
||||||
|
session.cancelled_negotiation()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# encrypted session states. these are descriped in stanza_session.py
|
||||||
|
|
||||||
|
# bob responds
|
||||||
|
if form.getType() == 'form' and u'e2e' in map(lambda x: x[1], form.getField('security').getOptions()):
|
||||||
|
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) # XXX for some reason I can't concatenate using += here?
|
||||||
|
session.respond_e2e_bob(form, negotiated, not_acceptable)
|
||||||
|
|
||||||
|
dialog.destroy()
|
||||||
|
|
||||||
|
dialog = dialogs.ConfirmationDialog(_('confirm these negotiation options'),
|
||||||
|
_('are the following options acceptable? %s') % (ask_user),
|
||||||
|
on_response_ok = accept_nondefault_options,
|
||||||
|
on_response_cancel = reject_nondefault_options)
|
||||||
|
else:
|
||||||
|
session.respond_e2e_bob(form, negotiated, not_acceptable)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
# alice accepts
|
||||||
elif session.status == 'requested-e2e' and form.getType() == 'submit':
|
elif session.status == 'requested-e2e' and form.getType() == 'submit':
|
||||||
session.accept_e2e_alice(form)
|
negotiated, not_acceptable, ask_user = session.verify_options_alice(form)
|
||||||
|
|
||||||
|
if ask_user:
|
||||||
|
def accept_nondefault_options(widget):
|
||||||
|
negotiated.update(ask_user)
|
||||||
|
session.accept_e2e_alice(form, negotiated)
|
||||||
|
|
||||||
|
dialog.destroy()
|
||||||
|
|
||||||
|
def reject_nondefault_options(widget):
|
||||||
|
session.reject_negotiation()
|
||||||
|
dialog.destroy()
|
||||||
|
|
||||||
|
dialog = dialogs.ConfirmationDialog(_('confirm these negotiation options'),
|
||||||
|
_('are the following options acceptable? %s') % (ask_user),
|
||||||
|
on_response_ok = accept_nondefault_options,
|
||||||
|
on_response_cancel = reject_nondefault_options)
|
||||||
|
else:
|
||||||
|
session.accept_e2e_alice(form, negotiated)
|
||||||
|
|
||||||
return
|
return
|
||||||
elif session.status == 'responded-e2e' and form.getType() == 'result':
|
elif session.status == 'responded-e2e' and form.getType() == 'result':
|
||||||
session.accept_e2e_bob(form)
|
session.accept_e2e_bob(form)
|
||||||
|
@ -1671,6 +1722,18 @@ class Interface:
|
||||||
elif session.status == 'identified-alice' and form.getType() == 'result':
|
elif session.status == 'identified-alice' and form.getType() == 'result':
|
||||||
session.final_steps_alice(form)
|
session.final_steps_alice(form)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if form.getField('terminate'):
|
||||||
|
if form.getField('terminate').getValue() in ('1', 'true'):
|
||||||
|
session.acknowledge_termination()
|
||||||
|
gajim.connections[account].delete_session(str(jid), session.thread_id)
|
||||||
|
|
||||||
|
ctrl = gajim.interface.msg_win_mgr.get_control(str(jid), account)
|
||||||
|
|
||||||
|
if ctrl:
|
||||||
|
ctrl.session = gajim.connections[self.account].make_new_session(str(jid))
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
# non-esession negotiation. this isn't very useful, but i'm keeping it around
|
# non-esession negotiation. this isn't very useful, but i'm keeping it around
|
||||||
# to test my test suite.
|
# to test my test suite.
|
||||||
|
|
|
@ -115,6 +115,8 @@ class MessageControl:
|
||||||
return
|
return
|
||||||
if self.session:
|
if self.session:
|
||||||
print "starting a new session, forgetting about the old one!"
|
print "starting a new session, forgetting about the old one!"
|
||||||
|
gajim.connections[self.account].delete_session(self.contact.jid, self.session.thread_id)
|
||||||
|
|
||||||
self.session = session
|
self.session = session
|
||||||
|
|
||||||
def send_message(self, message, keyID = '', type = 'chat',
|
def send_message(self, message, keyID = '', type = 'chat',
|
||||||
|
|
Loading…
Reference in New Issue