esession bugfixes
This commit is contained in:
parent
9985b784d5
commit
0230c91e4c
4 changed files with 111 additions and 31 deletions
|
@ -971,6 +971,9 @@ class ChatControl(ChatControlBase):
|
||||||
|
|
||||||
self.session = session
|
self.session = session
|
||||||
|
|
||||||
|
# does this window have an existing, active esession?
|
||||||
|
self.esessioned = False
|
||||||
|
|
||||||
self.status_tooltip = gtk.Tooltips()
|
self.status_tooltip = gtk.Tooltips()
|
||||||
self.update_ui()
|
self.update_ui()
|
||||||
# restore previous conversation
|
# restore previous conversation
|
||||||
|
@ -1236,13 +1239,13 @@ class ChatControl(ChatControlBase):
|
||||||
|
|
||||||
contact = self.contact
|
contact = self.contact
|
||||||
|
|
||||||
|
encrypted = bool(self.session) and self.session.enable_encryption
|
||||||
|
|
||||||
keyID = ''
|
keyID = ''
|
||||||
encrypted = False
|
|
||||||
if self.xml.get_widget('gpg_togglebutton').get_active():
|
if self.xml.get_widget('gpg_togglebutton').get_active():
|
||||||
keyID = contact.keyID
|
keyID = contact.keyID
|
||||||
encrypted = True
|
encrypted = True
|
||||||
|
|
||||||
|
|
||||||
chatstates_on = gajim.config.get('outgoing_chat_state_notifications') != \
|
chatstates_on = gajim.config.get('outgoing_chat_state_notifications') != \
|
||||||
'disabled'
|
'disabled'
|
||||||
composing_jep = contact.composing_jep
|
composing_jep = contact.composing_jep
|
||||||
|
@ -1352,18 +1355,46 @@ class ChatControl(ChatControlBase):
|
||||||
kind = 'info'
|
kind = 'info'
|
||||||
name = ''
|
name = ''
|
||||||
else:
|
else:
|
||||||
ec = gajim.encrypted_chats[self.account]
|
# ESessions
|
||||||
if encrypted and jid not in ec:
|
if self.session.enable_encryption:
|
||||||
msg = _('Encryption enabled')
|
if not self.esessioned:
|
||||||
|
msg = _('Encryption enabled')
|
||||||
|
ChatControlBase.print_conversation_line(self, msg,
|
||||||
|
'status', '', tim)
|
||||||
|
|
||||||
|
if self.session.loggable:
|
||||||
|
msg = _('Session WILL be logged')
|
||||||
|
else:
|
||||||
|
msg = _('Session WILL NOT be logged')
|
||||||
|
|
||||||
|
ChatControlBase.print_conversation_line(self, msg,
|
||||||
|
'status', '', tim)
|
||||||
|
|
||||||
|
self.esessioned = True
|
||||||
|
elif not encrypted:
|
||||||
|
msg = _('The following message was NOT encrypted')
|
||||||
|
ChatControlBase.print_conversation_line(self, msg,
|
||||||
|
'status', '', tim)
|
||||||
|
elif self.esessioned:
|
||||||
|
msg = _('Encryption disabled')
|
||||||
ChatControlBase.print_conversation_line(self, msg,
|
ChatControlBase.print_conversation_line(self, msg,
|
||||||
'status', '', tim)
|
'status', '', tim)
|
||||||
ec.append(jid)
|
self.esessioned = False
|
||||||
elif not encrypted and jid in ec:
|
else:
|
||||||
msg = _('Encryption disabled')
|
# GPG encryption
|
||||||
ChatControlBase.print_conversation_line(self, msg,
|
ec = gajim.encrypted_chats[self.account]
|
||||||
'status', '', tim)
|
if encrypted and jid not in ec:
|
||||||
ec.remove(jid)
|
msg = _('Encryption enabled')
|
||||||
self.xml.get_widget('gpg_togglebutton').set_active(encrypted)
|
ChatControlBase.print_conversation_line(self, msg,
|
||||||
|
'status', '', tim)
|
||||||
|
ec.append(jid)
|
||||||
|
elif not encrypted and jid in ec:
|
||||||
|
msg = _('Encryption disabled')
|
||||||
|
ChatControlBase.print_conversation_line(self, msg,
|
||||||
|
'status', '', tim)
|
||||||
|
ec.remove(jid)
|
||||||
|
self.xml.get_widget('gpg_togglebutton').set_active(encrypted)
|
||||||
|
|
||||||
if not frm:
|
if not frm:
|
||||||
kind = 'incoming'
|
kind = 'incoming'
|
||||||
name = contact.get_shown_name()
|
name = contact.get_shown_name()
|
||||||
|
@ -1949,9 +1980,15 @@ class ChatControl(ChatControlBase):
|
||||||
if self.session and self.session.enable_encryption:
|
if self.session and self.session.enable_encryption:
|
||||||
self.session.terminate_e2e()
|
self.session.terminate_e2e()
|
||||||
|
|
||||||
|
msg = _('Encryption disabled')
|
||||||
|
ChatControlBase.print_conversation_line(self, msg,
|
||||||
|
'status', '', None)
|
||||||
|
self.esessioned = False
|
||||||
|
|
||||||
jid = str(self.session.jid)
|
jid = str(self.session.jid)
|
||||||
|
|
||||||
gajim.connections[self.account].delete_session(jid, self.session.thread_id)
|
gajim.connections[self.account].delete_session(jid, self.session.thread_id)
|
||||||
|
|
||||||
self.session = gajim.connections[self.account].make_new_session(jid)
|
self.session = gajim.connections[self.account].make_new_session(jid)
|
||||||
else:
|
else:
|
||||||
if not self.session:
|
if not self.session:
|
||||||
|
|
|
@ -1451,18 +1451,22 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
||||||
self._InitE2ECB(con, msg, session)
|
self._InitE2ECB(con, msg, session)
|
||||||
|
|
||||||
encrypted = False
|
encrypted = False
|
||||||
|
tim = msg.getTimestamp()
|
||||||
|
tim = time.strptime(tim, '%Y%m%dT%H:%M:%S')
|
||||||
|
tim = time.localtime(timegm(tim))
|
||||||
|
|
||||||
e2eTag = msg.getTag('c', namespace = common.xmpp.NS_STANZA_CRYPTO)
|
e2e_tag = msg.getTag('c', namespace = common.xmpp.NS_STANZA_CRYPTO)
|
||||||
if e2eTag:
|
if e2e_tag:
|
||||||
encrypted = True
|
encrypted = True
|
||||||
msg = session.decrypt_stanza(msg)
|
|
||||||
|
try:
|
||||||
|
msg = session.decrypt_stanza(msg)
|
||||||
|
except:
|
||||||
|
self.dispatch('FAILED_DECRYPT', (frm, tim))
|
||||||
|
|
||||||
msgtxt = msg.getBody()
|
msgtxt = msg.getBody()
|
||||||
msghtml = msg.getXHTML()
|
msghtml = msg.getXHTML()
|
||||||
subject = msg.getSubject() # if not there, it's None
|
subject = msg.getSubject() # if not there, it's None
|
||||||
tim = msg.getTimestamp()
|
|
||||||
tim = time.strptime(tim, '%Y%m%dT%H:%M:%S')
|
|
||||||
tim = time.localtime(timegm(tim))
|
|
||||||
jid = helpers.get_jid_from_iq(msg)
|
jid = helpers.get_jid_from_iq(msg)
|
||||||
chatstate = None
|
chatstate = None
|
||||||
encTag = msg.getTag('x', namespace = common.xmpp.NS_ENCRYPTED)
|
encTag = msg.getTag('x', namespace = common.xmpp.NS_ENCRYPTED)
|
||||||
|
@ -1573,6 +1577,8 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
||||||
|
|
||||||
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', returns a new one if none exist.'''
|
'''returns an existing session between this connection and 'jid', returns a new one if none exist.'''
|
||||||
|
print repr(self.sessions)
|
||||||
|
|
||||||
session = self.find_session(jid, thread_id, type)
|
session = self.find_session(jid, thread_id, type)
|
||||||
|
|
||||||
if session:
|
if session:
|
||||||
|
|
|
@ -289,7 +289,7 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
calculated_mac = self.hmac(self.km_o, macable + self.encode_mpi_with_padding(self.c_o))
|
calculated_mac = self.hmac(self.km_o, macable + self.encode_mpi_with_padding(self.c_o))
|
||||||
|
|
||||||
if not calculated_mac == received_mac:
|
if not calculated_mac == received_mac:
|
||||||
raise 'bad signature (%s != %s)' % (repr(received_mac), repr(calculated_mac))
|
raise exceptions.DecryptionError, 'bad signature'
|
||||||
|
|
||||||
m_final = base64.b64decode(c.getTagData('data'))
|
m_final = base64.b64decode(c.getTagData('data'))
|
||||||
m_compressed = self.decrypt(m_final)
|
m_compressed = self.decrypt(m_final)
|
||||||
|
@ -298,7 +298,7 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
try:
|
try:
|
||||||
parsed = xmpp.Node(node='<node>' + plaintext + '</node>')
|
parsed = xmpp.Node(node='<node>' + plaintext + '</node>')
|
||||||
except:
|
except:
|
||||||
raise exceptions.DecryptionError
|
raise exceptions.DecryptionError, 'decrypted <data/> not parseable as XML'
|
||||||
|
|
||||||
for child in parsed.getChildren():
|
for child in parsed.getChildren():
|
||||||
stanza.addChild(node=child)
|
stanza.addChild(node=child)
|
||||||
|
@ -377,12 +377,12 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
content += self.form_o + form_o2
|
content += self.form_o + form_o2
|
||||||
|
|
||||||
mac_o_calculated = self.hmac(self.ks_o, content)
|
mac_o_calculated = self.hmac(self.ks_o, content)
|
||||||
|
|
||||||
if self.negotiated['recv_pubkey']:
|
if self.negotiated['recv_pubkey']:
|
||||||
hash = self.sha256(mac_o_calculated)
|
hash = self.sha256(mac_o_calculated)
|
||||||
|
|
||||||
if not eir_pubkey.verify(hash, signature):
|
if not eir_pubkey.verify(hash, signature):
|
||||||
raise exceptions.NegotiationError, 'public key signature verification failed!'
|
raise exceptions.NegotiationError, 'public key signature verification failed!'
|
||||||
|
|
||||||
elif mac_o_calculated != mac_o:
|
elif mac_o_calculated != mac_o:
|
||||||
raise exceptions.NegotiationError, 'calculated mac_%s differs from received mac_%s' % (i_o, i_o)
|
raise exceptions.NegotiationError, 'calculated mac_%s differs from received mac_%s' % (i_o, i_o)
|
||||||
|
@ -589,7 +589,7 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
self.n_o = base64.b64decode(form['my_nonce'])
|
self.n_o = base64.b64decode(form['my_nonce'])
|
||||||
|
|
||||||
dhhashes = form.getField('dhhashes').getValues()
|
dhhashes = form.getField('dhhashes').getValues()
|
||||||
self.He = base64.b64decode(dhhashes[group_order].encode("utf8"))
|
self.negotiated['He'] = base64.b64decode(dhhashes[group_order].encode("utf8"))
|
||||||
|
|
||||||
bytes = int(self.n / 8)
|
bytes = int(self.n / 8)
|
||||||
|
|
||||||
|
@ -741,7 +741,8 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
e = self.decode_mpi(base64.b64decode(form['dhkeys']))
|
e = self.decode_mpi(base64.b64decode(form['dhkeys']))
|
||||||
p = dh.primes[self.modp]
|
p = dh.primes[self.modp]
|
||||||
|
|
||||||
# XXX return <feature-not-implemented> if hash(e) != He
|
if self.sha256(self.encode_mpi(e)) != self.negotiated['He']:
|
||||||
|
raise exceptions.NegotiationError, 'SHA256(e) != He'
|
||||||
|
|
||||||
k = self.get_shared_secret(e, self.y, p)
|
k = self.get_shared_secret(e, self.y, p)
|
||||||
|
|
||||||
|
@ -903,10 +904,17 @@ class EncryptedStanzaSession(StanzaSession):
|
||||||
|
|
||||||
def fail_bad_negotiation(self, reason):
|
def fail_bad_negotiation(self, reason):
|
||||||
'''they've tried to feed us a bogus value, send an error and cancel everything.'''
|
'''they've tried to feed us a bogus value, send an error and cancel everything.'''
|
||||||
|
|
||||||
err = xmpp.Error(xmpp.Message(), xmpp.ERR_FEATURE_NOT_IMPLEMENTED)
|
err = xmpp.Error(xmpp.Message(), xmpp.ERR_FEATURE_NOT_IMPLEMENTED)
|
||||||
err.T.error.T.text.setData(reason)
|
err.T.error.T.text.setData(reason)
|
||||||
self.send(err)
|
self.send(err)
|
||||||
|
|
||||||
self.status = None
|
self.status = None
|
||||||
|
self.enable_encryption = False
|
||||||
|
|
||||||
|
# this prevents the MAC check on decryption from succeeding,
|
||||||
|
# preventing falsified messages from going through.
|
||||||
|
self.km_o = ''
|
||||||
|
|
||||||
def is_loggable(self):
|
def is_loggable(self):
|
||||||
account = self.conn.name
|
account = self.conn.name
|
||||||
|
|
43
src/gajim.py
43
src/gajim.py
|
@ -1701,6 +1701,15 @@ class Interface:
|
||||||
atom_entry, = data
|
atom_entry, = data
|
||||||
AtomWindow.newAtomEntry(atom_entry)
|
AtomWindow.newAtomEntry(atom_entry)
|
||||||
|
|
||||||
|
def handle_event_failed_decrypt(self, account, data):
|
||||||
|
jid, tim = data
|
||||||
|
|
||||||
|
ctrl = self.msg_win_mgr.get_control(jid, account)
|
||||||
|
if ctrl:
|
||||||
|
ctrl.print_conversation_line('Unable to decrypt message from %s\nIt may have been tampered with.' % (jid), 'status', '', tim)
|
||||||
|
else:
|
||||||
|
print 'failed decrypt, unable to find a control to notify you in.'
|
||||||
|
|
||||||
def handle_session_negotiation(self, account, data):
|
def handle_session_negotiation(self, account, data):
|
||||||
jid, session, form = data
|
jid, session, form = data
|
||||||
|
|
||||||
|
@ -1713,12 +1722,25 @@ class Interface:
|
||||||
# encrypted session states. these are described in stanza_session.py
|
# encrypted session states. these are described in stanza_session.py
|
||||||
|
|
||||||
# bob responds
|
# bob responds
|
||||||
if form.getType() == 'form' and u'e2e' in \
|
if form.getType() == 'form' and 'security' in form.asDict():
|
||||||
map(lambda x: x[1], form.getField('security').getOptions()):
|
|
||||||
def continue_with_negotiation(*args):
|
def continue_with_negotiation(*args):
|
||||||
if len(args):
|
if len(args):
|
||||||
self.dialog.destroy()
|
self.dialog.destroy()
|
||||||
|
|
||||||
|
# we don't support 3-message negotiation as the responder
|
||||||
|
if 'dhkeys' in form.asDict():
|
||||||
|
err = xmpp.Error(xmpp.Message(), xmpp.ERR_FEATURE_NOT_IMPLEMENTED)
|
||||||
|
|
||||||
|
feature = xmpp.Node(xmpp.NS_FEATURE + ' feature')
|
||||||
|
field = xmpp.Node('field')
|
||||||
|
field['var'] = 'dhkeys'
|
||||||
|
|
||||||
|
feature.addChild(node=field)
|
||||||
|
err.addChild(node=feature)
|
||||||
|
|
||||||
|
session.send(err)
|
||||||
|
return
|
||||||
|
|
||||||
negotiated, not_acceptable, ask_user = session.verify_options_bob(form)
|
negotiated, not_acceptable, ask_user = session.verify_options_bob(form)
|
||||||
|
|
||||||
if ask_user:
|
if ask_user:
|
||||||
|
@ -1776,8 +1798,11 @@ class Interface:
|
||||||
dialog.destroy()
|
dialog.destroy()
|
||||||
|
|
||||||
negotiated.update(ask_user)
|
negotiated.update(ask_user)
|
||||||
|
|
||||||
session.accept_e2e_alice(form, negotiated)
|
try:
|
||||||
|
session.accept_e2e_alice(form, negotiated)
|
||||||
|
except exceptions.NegotiationError, details:
|
||||||
|
session.fail_bad_negotiation(details)
|
||||||
|
|
||||||
def reject_nondefault_options(widget):
|
def reject_nondefault_options(widget):
|
||||||
session.reject_negotiation()
|
session.reject_negotiation()
|
||||||
|
@ -1788,7 +1813,10 @@ class Interface:
|
||||||
on_response_yes = accept_nondefault_options,
|
on_response_yes = accept_nondefault_options,
|
||||||
on_response_no = reject_nondefault_options)
|
on_response_no = reject_nondefault_options)
|
||||||
else:
|
else:
|
||||||
session.accept_e2e_alice(form, negotiated)
|
try:
|
||||||
|
session.accept_e2e_alice(form, negotiated)
|
||||||
|
except exceptions.NegotiationError, details:
|
||||||
|
session.fail_bad_negotiation(details)
|
||||||
|
|
||||||
return
|
return
|
||||||
elif session.status == 'responded-e2e' and form.getType() == 'result':
|
elif session.status == 'responded-e2e' and form.getType() == 'result':
|
||||||
|
@ -1802,7 +1830,7 @@ class Interface:
|
||||||
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)
|
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:
|
||||||
|
@ -1819,7 +1847,7 @@ class Interface:
|
||||||
ctrl = gajim.interface.msg_win_mgr.get_control(str(jid), account)
|
ctrl = gajim.interface.msg_win_mgr.get_control(str(jid), account)
|
||||||
|
|
||||||
if ctrl:
|
if ctrl:
|
||||||
ctrl.session = gajim.connections[self.account].make_new_session(str(jid))
|
ctrl.session = gajim.connections[account].make_new_session(str(jid))
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -2266,6 +2294,7 @@ class Interface:
|
||||||
'SIGNED_IN': self.handle_event_signed_in,
|
'SIGNED_IN': self.handle_event_signed_in,
|
||||||
'METACONTACTS': self.handle_event_metacontacts,
|
'METACONTACTS': self.handle_event_metacontacts,
|
||||||
'ATOM_ENTRY': self.handle_atom_entry,
|
'ATOM_ENTRY': self.handle_atom_entry,
|
||||||
|
'FAILED_DECRYPT': self.handle_event_failed_decrypt,
|
||||||
'PRIVACY_LISTS_RECEIVED': self.handle_event_privacy_lists_received,
|
'PRIVACY_LISTS_RECEIVED': self.handle_event_privacy_lists_received,
|
||||||
'PRIVACY_LIST_RECEIVED': self.handle_event_privacy_list_received,
|
'PRIVACY_LIST_RECEIVED': self.handle_event_privacy_list_received,
|
||||||
'PRIVACY_LISTS_ACTIVE_DEFAULT': \
|
'PRIVACY_LISTS_ACTIVE_DEFAULT': \
|
||||||
|
|
Loading…
Add table
Reference in a new issue