Cosmetic only... as usaual: stanza_session.py. TODO: better variable names
This commit is contained in:
parent
d23aa630ba
commit
593c23ebc8
|
@ -19,12 +19,13 @@
|
|||
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
import string
|
||||
'''
|
||||
This module defines a number of constants; specifically, large primes suitable
|
||||
for use with the Diffie-Hellman key exchange.
|
||||
|
||||
# This file defines a number of constants; specifically, large primes suitable for
|
||||
# use with the Diffie-Hellman key exchange.
|
||||
#
|
||||
# These constants have been obtained from RFC2409 and RFC3526.
|
||||
These constants have been obtained from RFC2409 and RFC3526.
|
||||
'''
|
||||
import string
|
||||
|
||||
generators = [ None, # one to get the right offset
|
||||
2,
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
## src/common/stanza_session.py
|
||||
##
|
||||
## Copyright (C) 2007 Julien Pivotto <roidelapluie AT gmail.com>
|
||||
## Stephan Erb <steve-e AT h3c.de>
|
||||
## Copyright (C) 2007-2008 Yann Leboulanger <asterix AT lagaule.org>
|
||||
## Brendan Taylor <whateley AT gmail.com>
|
||||
## Jean-Marie Traissard <jim AT lapin.org>
|
||||
|
@ -24,29 +23,36 @@
|
|||
##
|
||||
|
||||
from common import gajim
|
||||
|
||||
from common import xmpp
|
||||
from common import exceptions
|
||||
from common.exceptions import DecryptionError, NegotiationError
|
||||
import xmpp.c14n
|
||||
|
||||
import itertools
|
||||
import random
|
||||
import string
|
||||
|
||||
import time
|
||||
|
||||
import xmpp.c14n
|
||||
|
||||
import base64
|
||||
import os
|
||||
|
||||
if gajim.HAVE_PYCRYPTO:
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Hash import HMAC, SHA256
|
||||
from Crypto.PublicKey import RSA
|
||||
from common import crypto
|
||||
|
||||
from common import dh
|
||||
import secrets
|
||||
|
||||
XmlDsig = 'http://www.w3.org/2000/09/xmldsig#'
|
||||
|
||||
class StanzaSession(object):
|
||||
'''
|
||||
'''
|
||||
def __init__(self, conn, jid, thread_id, type_):
|
||||
'''
|
||||
'''
|
||||
self.conn = conn
|
||||
|
||||
self.jid = jid
|
||||
|
||||
self.type = type_
|
||||
|
||||
if thread_id:
|
||||
|
@ -69,9 +75,11 @@ class StanzaSession(object):
|
|||
def is_loggable(self):
|
||||
return self.loggable and gajim.config.should_log(self.conn.name, self.jid)
|
||||
|
||||
# remove events associated with this session from the queue
|
||||
# returns True if any events were removed (unlike gajim.events.remove_events)
|
||||
def remove_events(self, types):
|
||||
'''
|
||||
Remove events associated with this session from the queue.
|
||||
returns True if any events were removed (unlike events.py remove_events)
|
||||
'''
|
||||
any_removed = False
|
||||
|
||||
for j in (self.jid, self.jid.getStripped()):
|
||||
|
@ -124,9 +132,10 @@ class StanzaSession(object):
|
|||
self.cancelled_negotiation()
|
||||
|
||||
def cancelled_negotiation(self):
|
||||
'''A negotiation has been cancelled, so reset this session to its default
|
||||
state.'''
|
||||
|
||||
'''
|
||||
A negotiation has been cancelled, so reset this session to its default
|
||||
state.
|
||||
'''
|
||||
if self.control:
|
||||
self.control.on_cancel_session_negotiation()
|
||||
|
||||
|
@ -156,61 +165,52 @@ class StanzaSession(object):
|
|||
# we could send an acknowledgement message to the remote client here
|
||||
self.status = None
|
||||
|
||||
if gajim.HAVE_PYCRYPTO:
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Hash import HMAC, SHA256
|
||||
from Crypto.PublicKey import RSA
|
||||
from common import crypto
|
||||
|
||||
from common import dh
|
||||
import secrets
|
||||
|
||||
# an encrypted stanza negotiation has several states. i've represented them
|
||||
# as the following values in the 'status'
|
||||
# attribute of the session object:
|
||||
|
||||
# 1. None:
|
||||
# default state
|
||||
# 2. 'requested-e2e':
|
||||
# this client has initiated an esession negotiation and is waiting
|
||||
# for a response
|
||||
# 3. 'responded-e2e':
|
||||
# this client has responded to an esession negotiation request and
|
||||
# is waiting for the initiator to identify itself and complete the
|
||||
# negotiation
|
||||
# 4. 'identified-alice':
|
||||
# this client identified itself and is waiting for the responder to
|
||||
# identify itself and complete the negotiation
|
||||
# 5. 'active':
|
||||
# an encrypted session has been successfully negotiated. messages
|
||||
# of any of the types listed in 'encryptable_stanzas' should be
|
||||
# encrypted before they're sent.
|
||||
|
||||
# the transition between these states is handled in gajim.py's
|
||||
# handle_session_negotiation method.
|
||||
|
||||
class EncryptedStanzaSession(StanzaSession):
|
||||
'''
|
||||
An encrypted stanza negotiation has several states. They arerepresented as
|
||||
the following values in the 'status' attribute of the session object:
|
||||
|
||||
1. None:
|
||||
default state
|
||||
2. 'requested-e2e':
|
||||
this client has initiated an esession negotiation and is waiting
|
||||
for a response
|
||||
3. 'responded-e2e':
|
||||
this client has responded to an esession negotiation request and
|
||||
is waiting for the initiator to identify itself and complete the
|
||||
negotiation
|
||||
4. 'identified-alice':
|
||||
this client identified itself and is waiting for the responder to
|
||||
identify itself and complete the negotiation
|
||||
5. 'active':
|
||||
an encrypted session has been successfully negotiated. messages
|
||||
of any of the types listed in 'encryptable_stanzas' should be
|
||||
encrypted before they're sent.
|
||||
|
||||
The transition between these states is handled in gajim.py's
|
||||
handle_session_negotiation method.
|
||||
'''
|
||||
def __init__(self, conn, jid, thread_id, type_='chat'):
|
||||
StanzaSession.__init__(self, conn, jid, thread_id, type_='chat')
|
||||
|
||||
self.xes = {}
|
||||
self.es = {}
|
||||
|
||||
self.n = 128
|
||||
|
||||
self.enable_encryption = False
|
||||
|
||||
# _s denotes 'self' (ie. this client)
|
||||
self._kc_s = None
|
||||
|
||||
# _o denotes 'other' (ie. the client at the other end of the session)
|
||||
self._kc_o = None
|
||||
|
||||
# has the remote contact's identity ever been verified?
|
||||
self.verified_identity = False
|
||||
|
||||
# keep the encrypter updated with my latest cipher key
|
||||
def set_kc_s(self, value):
|
||||
'''
|
||||
keep the encrypter updated with my latest cipher key
|
||||
'''
|
||||
self._kc_s = value
|
||||
self.encrypter = self.cipher.new(self._kc_s, self.cipher.MODE_CTR,
|
||||
counter=self.encryptcounter)
|
||||
|
@ -218,8 +218,10 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
def get_kc_s(self):
|
||||
return self._kc_s
|
||||
|
||||
# keep the decrypter updated with the other party's latest cipher key
|
||||
def set_kc_o(self, value):
|
||||
'''
|
||||
keep the decrypter updated with the other party's latest cipher key
|
||||
'''
|
||||
self._kc_o = value
|
||||
self.decrypter = self.cipher.new(self._kc_o, self.cipher.MODE_CTR,
|
||||
counter=self.decryptcounter)
|
||||
|
@ -244,10 +246,10 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
return crypto.encode_mpi(gajim.pubkey.sign(hash_, '')[0])
|
||||
|
||||
def encrypt_stanza(self, stanza):
|
||||
encryptable = [x for x in stanza.getChildren() if x.getName() not in ('error', 'amp',
|
||||
'thread')]
|
||||
encryptable = [x for x in stanza.getChildren() if x.getName() not in
|
||||
('error', 'amp', 'thread')]
|
||||
|
||||
# XXX can also encrypt contents of <error/> elements in stanzas @type =
|
||||
# FIXME can also encrypt contents of <error/> elements in stanzas @type =
|
||||
# 'error'
|
||||
# (except for <defined-condition
|
||||
# xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> child elements)
|
||||
|
@ -266,7 +268,7 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
c.setNamespace('http://www.xmpp.org/extensions/xep-0200.html#ns')
|
||||
c.NT.data = base64.b64encode(m_final)
|
||||
|
||||
# XXX check for rekey request, handle <key/> elements
|
||||
# FIXME check for rekey request, handle <key/> elements
|
||||
|
||||
m_content = ''.join(map(str, c.getChildren()))
|
||||
c.NT.mac = base64.b64encode(self.hmac(self.km_s, m_content + \
|
||||
|
@ -313,7 +315,7 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
return self.encrypter.encrypt(padded)
|
||||
|
||||
def decrypt_stanza(self, stanza):
|
||||
# delete the unencrypted explanation body, if it exists
|
||||
''' delete the unencrypted explanation body, if it exists '''
|
||||
orig_body = stanza.getTag('body')
|
||||
if orig_body:
|
||||
stanza.delChild(orig_body)
|
||||
|
@ -331,7 +333,7 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
crypto.encode_mpi_with_padding(self.c_o))
|
||||
|
||||
if not calculated_mac == received_mac:
|
||||
raise exceptions.DecryptionError, 'bad signature'
|
||||
raise DecryptionError('bad signature')
|
||||
|
||||
m_final = base64.b64decode(c.getTagData('data'))
|
||||
m_compressed = self.decrypt(m_final)
|
||||
|
@ -340,7 +342,7 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
try:
|
||||
parsed = xmpp.Node(node='<node>' + plaintext + '</node>')
|
||||
except Exception:
|
||||
raise exceptions.DecryptionError, 'decrypted <data/> not parseable as XML'
|
||||
raise DecryptionError('decrypted <data/> not parseable as XML')
|
||||
|
||||
for child in parsed.getChildren():
|
||||
stanza.addChild(node=child)
|
||||
|
@ -358,7 +360,7 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
|
||||
def get_shared_secret(self, e, y, p):
|
||||
if (not 1 < e < (p - 1)):
|
||||
raise exceptions.NegotiationError, 'invalid DH value'
|
||||
raise NegotiationError('invalid DH value')
|
||||
|
||||
return crypto.sha256(crypto.encode_mpi(crypto.powmod(e, y, p)))
|
||||
|
||||
|
@ -374,7 +376,8 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
m_o_calculated = self.hmac(self.km_o, crypto.encode_mpi(self.c_o) + id_o)
|
||||
|
||||
if m_o_calculated != m_o:
|
||||
raise exceptions.NegotiationError, 'calculated m_%s differs from received m_%s' % (i_o, i_o)
|
||||
raise NegotiationError('calculated m_%s differs from received m_%s' %
|
||||
(i_o, i_o))
|
||||
|
||||
if i_o == 'a' and self.sas_algs == 'sas28x5':
|
||||
# we don't need to calculate this if there's a verified retained secret
|
||||
|
@ -387,7 +390,7 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
|
||||
if self.negotiated['recv_pubkey'] == 'hash':
|
||||
# fingerprint = parsed.getTagData('fingerprint')
|
||||
# XXX find stored pubkey or terminate session
|
||||
# FIXME find stored pubkey or terminate session
|
||||
raise NotImplementedError()
|
||||
else:
|
||||
if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'):
|
||||
|
@ -399,7 +402,7 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
|
||||
pubkey_o = xmpp.c14n.c14n(keyvalue)
|
||||
else:
|
||||
# XXX DSA, etc.
|
||||
# FIXME DSA, etc.
|
||||
raise NotImplementedError()
|
||||
|
||||
enc_sig = parsed.getTag(name='SignatureValue',
|
||||
|
@ -426,10 +429,11 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
hash_ = crypto.sha256(mac_o_calculated)
|
||||
|
||||
if not eir_pubkey.verify(hash_, signature):
|
||||
raise exceptions.NegotiationError, 'public key signature verification failed!'
|
||||
raise NegotiationError('public key signature verification failed!')
|
||||
|
||||
elif mac_o_calculated != mac_o:
|
||||
raise exceptions.NegotiationError, 'calculated mac_%s differs from received mac_%s' % (i_o, i_o)
|
||||
raise NegotiationError('calculated mac_%s differs from received mac_%s'
|
||||
% (i_o, i_o))
|
||||
|
||||
def make_identity(self, form, dh_i):
|
||||
if self.negotiated['send_pubkey']:
|
||||
|
@ -476,7 +480,7 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
self.sas = crypto.sas_28x5(m_s, self.form_o)
|
||||
|
||||
if self.sigmai:
|
||||
# XXX save retained secret?
|
||||
# FIXME save retained secret?
|
||||
self.check_identity(tuple)
|
||||
|
||||
return (xmpp.DataField(name='identity', value=base64.b64encode(id_s)),
|
||||
|
@ -519,7 +523,7 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
x.addChild(node=xmpp.DataField(name='init_pubkey', options=['none', 'key',
|
||||
'hash'], typ='list-single'))
|
||||
|
||||
# XXX store key, use hash
|
||||
# FIXME store key, use hash
|
||||
x.addChild(node=xmpp.DataField(name='resp_pubkey', options=['none',
|
||||
'key'], typ='list-single'))
|
||||
|
||||
|
@ -555,8 +559,8 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
|
||||
self.send(request)
|
||||
|
||||
# 4.3 esession response (bob)
|
||||
def verify_options_bob(self, form):
|
||||
''' 4.3 esession response (bob) '''
|
||||
negotiated = {'recv_pubkey': None, 'send_pubkey': None}
|
||||
not_acceptable = []
|
||||
ask_user = {}
|
||||
|
@ -618,14 +622,14 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
if (XmlDsig + 'rsa-sha256') in options:
|
||||
negotiated['sign_algs'] = XmlDsig + 'rsa-sha256'
|
||||
else:
|
||||
# XXX some things are handled elsewhere, some things are
|
||||
# FIXME 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):
|
||||
''' 4.3 esession response (bob) '''
|
||||
response = xmpp.Message()
|
||||
feature = response.NT.feature
|
||||
feature.setNamespace(xmpp.NS_FEATURE)
|
||||
|
@ -697,8 +701,8 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
|
||||
self.send(response)
|
||||
|
||||
# 'Alice Accepts'
|
||||
def verify_options_alice(self, form):
|
||||
''' 'Alice Accepts' '''
|
||||
negotiated = {}
|
||||
ask_user = {}
|
||||
not_acceptable = []
|
||||
|
@ -725,8 +729,8 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
|
||||
return (negotiated, not_acceptable, ask_user)
|
||||
|
||||
# 'Alice Accepts', continued
|
||||
def accept_e2e_alice(self, form, negotiated):
|
||||
''' 'Alice Accepts', continued '''
|
||||
self.encryptable_stanzas = ['message']
|
||||
self.sas_algs = 'sas28x5'
|
||||
self.cipher = AES
|
||||
|
@ -743,7 +747,6 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
|
||||
self.c_s = crypto.decode_mpi(base64.b64decode(form['counter']))
|
||||
self.c_o = self.c_s ^ (2 ** (self.n - 1))
|
||||
|
||||
self.n_o = base64.b64decode(form['my_nonce'])
|
||||
|
||||
mod_p = int(form['modp'])
|
||||
|
@ -752,7 +755,6 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
e = self.es[mod_p]
|
||||
|
||||
self.d = crypto.decode_mpi(base64.b64decode(form['dhkeys']))
|
||||
|
||||
self.k = self.get_shared_secret(self.d, x, p)
|
||||
|
||||
result.addChild(node=xmpp.DataField(name='FORM_TYPE',
|
||||
|
@ -798,8 +800,8 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
else:
|
||||
self.status = 'identified-alice'
|
||||
|
||||
# 4.5 esession accept (bob)
|
||||
def accept_e2e_bob(self, form):
|
||||
''' 4.5 esession accept (bob) '''
|
||||
response = xmpp.Message()
|
||||
|
||||
init = response.NT.init
|
||||
|
@ -808,6 +810,7 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
x = xmpp.DataForm(typ='result')
|
||||
|
||||
for field in ('nonce', 'dhkeys', 'rshashes', 'identity', 'mac'):
|
||||
# FIXME: will do nothing in real world...
|
||||
assert field in form.asDict(), "alice's form didn't have a %s field" \
|
||||
% field
|
||||
|
||||
|
@ -816,18 +819,15 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
p = dh.primes[self.modp]
|
||||
|
||||
if crypto.sha256(crypto.encode_mpi(e)) != self.negotiated['He']:
|
||||
raise exceptions.NegotiationError, 'SHA256(e) != He'
|
||||
raise NegotiationError('SHA256(e) != He')
|
||||
|
||||
k = self.get_shared_secret(e, self.y, p)
|
||||
|
||||
self.kc_o, self.km_o, self.ks_o = self.generate_initiator_keys(k)
|
||||
|
||||
# 4.5.2 verifying alice's identity
|
||||
|
||||
self.verify_identity(form, e, False, 'a')
|
||||
|
||||
# 4.5.4 generating bob's final session keys
|
||||
|
||||
srs = ''
|
||||
|
||||
srses = secrets.secrets().retained_secrets(self.conn.name,
|
||||
|
@ -904,7 +904,6 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
self.kc_o, self.km_o, self.ks_o = self.generate_responder_keys(k)
|
||||
|
||||
# 4.6.2 Verifying Bob's Identity
|
||||
|
||||
self.verify_identity(form, self.d, False, 'b')
|
||||
# Note: If Alice discovers an error then she SHOULD ignore any encrypted
|
||||
# content she received in the stanza.
|
||||
|
@ -919,10 +918,11 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
self.control.print_esession_details()
|
||||
|
||||
def do_retained_secret(self, k, old_srs):
|
||||
'''calculate the new retained secret. determine if the user needs to check
|
||||
the remote party's identity. set up callbacks for when the identity has
|
||||
been verified.'''
|
||||
|
||||
'''
|
||||
Calculate the new retained secret. determine if the user needs to check
|
||||
the remote party's identity. Set up callbacks for when the identity has
|
||||
been verified.
|
||||
'''
|
||||
new_srs = self.hmac(k, 'New Retained Secret')
|
||||
self.srs = new_srs
|
||||
|
||||
|
@ -962,7 +962,7 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
|
||||
x = crypto.srand(2 ** (2 * self.n - 1), p - 1)
|
||||
|
||||
# XXX this may be a source of performance issues
|
||||
# FIXME this may be a source of performance issues
|
||||
e = crypto.powmod(g, x, p)
|
||||
|
||||
self.xes[modp] = x
|
||||
|
@ -980,21 +980,19 @@ class EncryptedStanzaSession(StanzaSession):
|
|||
|
||||
def terminate_e2e(self):
|
||||
self.terminate()
|
||||
|
||||
self.enable_encryption = False
|
||||
|
||||
def acknowledge_termination(self):
|
||||
StanzaSession.acknowledge_termination(self)
|
||||
|
||||
self.enable_encryption = False
|
||||
|
||||
def fail_bad_negotiation(self, reason, fields = None):
|
||||
'''sends an error and cancels everything.
|
||||
|
||||
if fields is None, the remote party has given us a bad cryptographic value of some kind
|
||||
|
||||
otherwise, list the fields we haven't implemented'''
|
||||
|
||||
def fail_bad_negotiation(self, reason, fields=None):
|
||||
'''
|
||||
Sends an error and cancels everything.
|
||||
|
||||
If fields is None, the remote party has given us a bad cryptographic
|
||||
value of some kind. Otherwise, list the fields we haven't implemented
|
||||
'''
|
||||
err = xmpp.Error(xmpp.Message(), xmpp.ERR_FEATURE_NOT_IMPLEMENTED)
|
||||
err.T.error.T.text.setData(reason)
|
||||
|
||||
|
@ -1020,7 +1018,6 @@ otherwise, list the fields we haven't implemented'''
|
|||
def cancelled_negotiation(self):
|
||||
StanzaSession.cancelled_negotiation(self)
|
||||
self.enable_encryption = False
|
||||
|
||||
self.km_o = ''
|
||||
|
||||
# vim: se ts=3:
|
||||
|
|
Loading…
Reference in New Issue