26
25
from common import gajim
28
26
from common import xmpp
29
from common import exceptions
27
from common.exceptions import DecryptionError, NegotiationError
36
from hashlib import sha256
38
from common import crypto
40
if gajim.HAVE_PYCRYPTO:
41
from Crypto.Cipher import AES
42
from Crypto.PublicKey import RSA
41
47
XmlDsig = 'http://www.w3.org/2000/09/xmldsig#'
43
49
class StanzaSession(object):
44
52
def __init__(self, conn, jid, thread_id, type_):
52
61
self.received_thread_id = True
68
77
def is_loggable(self):
69
78
return self.loggable and gajim.config.should_log(self.conn.name, self.jid)
71
# remove events associated with this session from the queue
72
# returns True if any events were removed (unlike gajim.events.remove_events)
82
if self.resource and not to.endswith(self.resource):
83
to += '/' + self.resource
73
86
def remove_events(self, types):
88
Remove events associated with this session from the queue.
89
returns True if any events were removed (unlike events.py remove_events)
74
91
any_removed = False
76
93
for j in (self.jid, self.jid.getStripped()):
91
108
return any_removed
93
110
def generate_thread_id(self):
94
return ''.join([random.choice(string.ascii_letters) for x in xrange(0,
111
return ''.join([f(string.ascii_letters) for f in itertools.repeat(
97
114
def send(self, msg):
98
115
if self.thread_id:
99
116
msg.NT.thread = self.thread_id
101
msg.setAttr('to', self.jid)
118
msg.setAttr('to', self.get_to())
102
119
self.conn.send_stanza(msg)
104
121
if isinstance(msg, xmpp.Message):
155
173
# we could send an acknowledgement message to the remote client here
156
174
self.status = None
158
if gajim.HAVE_PYCRYPTO:
159
from Crypto.Cipher import AES
160
from Crypto.Hash import HMAC, SHA256
161
from Crypto.PublicKey import RSA
162
from common import crypto
164
from common import dh
167
# an encrypted stanza negotiation has several states. i've represented them
168
# as the following values in the 'status'
169
# attribute of the session object:
173
# 2. 'requested-e2e':
174
# this client has initiated an esession negotiation and is waiting
176
# 3. 'responded-e2e':
177
# this client has responded to an esession negotiation request and
178
# is waiting for the initiator to identify itself and complete the
180
# 4. 'identified-alice':
181
# this client identified itself and is waiting for the responder to
182
# identify itself and complete the negotiation
184
# an encrypted session has been successfully negotiated. messages
185
# of any of the types listed in 'encryptable_stanzas' should be
186
# encrypted before they're sent.
188
# the transition between these states is handled in gajim.py's
189
# handle_session_negotiation method.
191
177
class EncryptedStanzaSession(StanzaSession):
179
An encrypted stanza negotiation has several states. They arerepresented as
180
the following values in the 'status' attribute of the session object:
185
this client has initiated an esession negotiation and is waiting
188
this client has responded to an esession negotiation request and
189
is waiting for the initiator to identify itself and complete the
191
4. 'identified-alice':
192
this client identified itself and is waiting for the responder to
193
identify itself and complete the negotiation
195
an encrypted session has been successfully negotiated. messages
196
of any of the types listed in 'encryptable_stanzas' should be
197
encrypted before they're sent.
199
The transition between these states is handled in gajim.py's
200
handle_session_negotiation method.
192
202
def __init__(self, conn, jid, thread_id, type_='chat'):
193
203
StanzaSession.__init__(self, conn, jid, thread_id, type_='chat')
200
208
self.enable_encryption = False
202
210
# _s denotes 'self' (ie. this client)
203
211
self._kc_s = None
205
212
# _o denotes 'other' (ie. the client at the other end of the session)
206
213
self._kc_o = None
208
215
# has the remote contact's identity ever been verified?
209
216
self.verified_identity = False
211
# keep the encrypter updated with my latest cipher key
218
def _get_contact(self):
219
c = gajim.contacts.get_contact(self.conn.name, self.jid, self.resource)
221
c = gajim.contacts.get_contact(self.conn.name, self.jid)
224
def _is_buggy_gajim(self):
225
c = self._get_contact()
226
if gajim.capscache.is_supported(c, xmpp.NS_ROSTERX):
212
230
def set_kc_s(self, value):
232
keep the encrypter updated with my latest cipher key
213
234
self._kc_s = value
214
235
self.encrypter = self.cipher.new(self._kc_s, self.cipher.MODE_CTR,
215
236
counter=self.encryptcounter)
240
263
def sign(self, string):
241
264
if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'):
242
hash = crypto.sha256(string)
243
return crypto.encode_mpi(gajim.pubkey.sign(hash, '')[0])
265
hash_ = crypto.sha256(string)
266
return crypto.encode_mpi(gajim.pubkey.sign(hash_, '')[0])
245
268
def encrypt_stanza(self, stanza):
246
encryptable = filter(lambda x: x.getName() not in ('error', 'amp',
247
'thread'), stanza.getChildren())
269
encryptable = [x for x in stanza.getChildren() if x.getName() not in
270
('error', 'amp', 'thread')]
249
# XXX can also encrypt contents of <error/> elements in stanzas @type =
272
# FIXME can also encrypt contents of <error/> elements in stanzas @type =
251
274
# (except for <defined-condition
252
275
# xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> child elements)
265
288
c.setNamespace('http://www.xmpp.org/extensions/xep-0200.html#ns')
266
289
c.NT.data = base64.b64encode(m_final)
268
# XXX check for rekey request, handle <key/> elements
291
# FIXME check for rekey request, handle <key/> elements
270
293
m_content = ''.join(map(str, c.getChildren()))
271
294
c.NT.mac = base64.b64encode(self.hmac(self.km_s, m_content + \
285
308
def is_xep_200_encrypted(self, msg):
286
msg.getTag('c', namespace=common.xmpp.NS_STANZA_CRYPTO)
309
msg.getTag('c', namespace=xmpp.NS_STANZA_CRYPTO)
288
311
def hmac(self, key, content):
289
return HMAC.new(key, content, self.hash_alg).digest()
312
return HMAC(key, content, self.hash_alg).digest()
291
314
def generate_initiator_keys(self, k):
292
315
return (self.hmac(k, 'Initiator Cipher Key'),
323
346
stanza.delChild(c)
325
348
# contents of <c>, minus <mac>, minus whitespace
326
macable = ''.join(map(str, filter(lambda x: x.getName() != 'mac',
349
macable = ''.join(str(x) for x in c.getChildren() if x.getName() != 'mac')
329
351
received_mac = base64.b64decode(c.getTagData('mac'))
330
352
calculated_mac = self.hmac(self.km_o, macable + \
331
353
crypto.encode_mpi_with_padding(self.c_o))
333
355
if not calculated_mac == received_mac:
334
raise exceptions.DecryptionError, 'bad signature'
356
raise DecryptionError('bad signature')
336
358
m_final = base64.b64decode(c.getTagData('data'))
337
359
m_compressed = self.decrypt(m_final)
360
382
def get_shared_secret(self, e, y, p):
361
383
if (not 1 < e < (p - 1)):
362
raise exceptions.NegotiationError, 'invalid DH value'
384
raise NegotiationError('invalid DH value')
364
386
return crypto.sha256(crypto.encode_mpi(crypto.powmod(e, y, p)))
366
388
def c7lize_mac_id(self, form):
367
389
kids = form.getChildren()
368
macable = filter(lambda x: x.getVar() not in ('mac', 'identity'), kids)
369
return ''.join(map(lambda el: xmpp.c14n.c14n(el), macable))
390
macable = [x for x in kids if x.getVar() not in ('mac', 'identity')]
391
return ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el in \
371
394
def verify_identity(self, form, dh_i, sigmai, i_o):
372
395
m_o = base64.b64decode(form['mac'])
387
411
parsed = xmpp.Node(node='<node>' + plaintext + '</node>')
389
413
if self.negotiated['recv_pubkey'] == 'hash':
390
fingerprint = parsed.getTagData('fingerprint')
392
# XXX find stored pubkey or terminate session
393
raise 'unimplemented'
414
# fingerprint = parsed.getTagData('fingerprint')
415
# FIXME find stored pubkey or terminate session
416
raise NotImplementedError()
395
418
if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'):
396
419
keyvalue = parsed.getTag(name='RSAKeyValue', namespace=XmlDsig)
398
n, e = map(lambda x: crypto.decode_mpi(base64.b64decode(
399
keyvalue.getTagData(x))), ('Modulus', 'Exponent'))
421
n, e = (crypto.decode_mpi(base64.b64decode(
422
keyvalue.getTagData(x))) for x in ('Modulus', 'Exponent'))
400
423
eir_pubkey = RSA.construct((n,long(e)))
402
pubkey_o = xmpp.c14n.c14n(keyvalue)
425
pubkey_o = xmpp.c14n.c14n(keyvalue, self._is_buggy_gajim())
405
raise 'unimplemented'
428
raise NotImplementedError()
407
430
enc_sig = parsed.getTag(name='SignatureValue',
408
431
namespace=XmlDsig).getData()
425
448
mac_o_calculated = self.hmac(self.ks_o, content)
427
450
if self.negotiated['recv_pubkey']:
428
hash = crypto.sha256(mac_o_calculated)
451
hash_ = crypto.sha256(mac_o_calculated)
430
if not eir_pubkey.verify(hash, signature):
431
raise exceptions.NegotiationError, 'public key signature verification failed!'
453
if not eir_pubkey.verify(hash_, signature):
454
raise NegotiationError('public key signature verification failed!')
433
456
elif mac_o_calculated != mac_o:
434
raise exceptions.NegotiationError, 'calculated mac_%s differs from received mac_%s' % (i_o, i_o)
457
raise NegotiationError('calculated mac_%s differs from received mac_%s'
436
460
def make_identity(self, form, dh_i):
437
461
if self.negotiated['send_pubkey']:
478
503
self.sas = crypto.sas_28x5(m_s, self.form_o)
481
# XXX save retained secret?
482
self.check_identity(lambda : ())
506
# FIXME save retained secret?
507
self.check_identity(tuple)
484
509
return (xmpp.DataField(name='identity', value=base64.b64encode(id_s)),
485
510
xmpp.DataField(name='mac', value=base64.b64encode(m_s)))
521
546
x.addChild(node=xmpp.DataField(name='init_pubkey', options=['none', 'key',
522
547
'hash'], typ='list-single'))
524
# XXX store key, use hash
549
# FIXME store key, use hash
525
550
x.addChild(node=xmpp.DataField(name='resp_pubkey', options=['none',
526
551
'key'], typ='list-single'))
546
571
x.addChild(node=xmpp.DataField(name='modp', typ='list-single',
547
options=map(lambda x: [ None, x ], modp_options)))
572
options=[[None, y] for y in modp_options]))
549
574
x.addChild(node=self.make_dhfield(modp_options, sigmai))
550
575
self.sigmai = sigmai
552
self.form_s = ''.join(map(lambda el: xmpp.c14n.c14n(el), x.getChildren()))
577
self.form_s = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el \
554
580
feature.addChild(node=x)
573
599
self.sas_algs = 'sas28x5'
574
600
self.cipher = AES
575
self.hash_alg = SHA256
601
self.hash_alg = sha256
576
602
self.compression = None
578
for name, field in map(lambda name: (name, form.getField(name)),
579
form.asDict().keys()):
580
options = map(lambda x: x[1], field.getOptions())
604
for name in form.asDict():
605
field = form.getField(name)
606
options = [x[1] for x in field.getOptions()]
581
607
values = field.getValues()
583
609
if not field.getType() in ('list-single', 'list-multi'):
620
646
if (XmlDsig + 'rsa-sha256') in options:
621
647
negotiated['sign_algs'] = XmlDsig + 'rsa-sha256'
623
# XXX some things are handled elsewhere, some things are
649
# FIXME some things are handled elsewhere, some things are
624
650
# not-implemented
627
653
return (negotiated, not_acceptable, ask_user)
629
# 4.3 esession response (bob)
630
655
def respond_e2e_bob(self, form, negotiated, not_acceptable):
656
''' 4.3 esession response (bob) '''
631
657
response = xmpp.Message()
632
658
feature = response.NT.feature
633
659
feature.setNamespace(xmpp.NS_FEATURE)
678
704
b64ed = base64.b64encode(to_add[name])
679
705
x.addChild(node=xmpp.DataField(name=name, value=b64ed))
681
self.form_o = ''.join(map(lambda el: xmpp.c14n.c14n(el),
683
self.form_s = ''.join(map(lambda el: xmpp.c14n.c14n(el), x.getChildren()))
707
self.form_o = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el \
708
in form.getChildren())
709
self.form_s = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el \
685
712
self.status = 'responded-e2e'
729
756
return (negotiated, not_acceptable, ask_user)
731
# 'Alice Accepts', continued
732
758
def accept_e2e_alice(self, form, negotiated):
759
''' 'Alice Accepts', continued '''
733
760
self.encryptable_stanzas = ['message']
734
761
self.sas_algs = 'sas28x5'
735
762
self.cipher = AES
736
self.hash_alg = SHA256
763
self.hash_alg = sha256
737
764
self.compression = None
739
766
self.negotiated = negotiated
773
798
srses = secrets.secrets().retained_secrets(self.conn.name,
774
799
self.jid.getStripped())
775
rshashes = [self.hmac(self.n_s, rs) for (rs,v) in srses]
800
rshashes = [self.hmac(self.n_s, rs[0]) for rs in srses]
778
803
# we've never spoken before, but we'll pretend we have
779
rshash_size = self.hash_alg.digest_size
804
rshash_size = self.hash_alg().digest_size
780
805
rshashes.append(crypto.random_bytes(rshash_size))
782
807
rshashes = [base64.b64encode(rshash) for rshash in rshashes]
820
846
p = dh.primes[self.modp]
822
848
if crypto.sha256(crypto.encode_mpi(e)) != self.negotiated['He']:
823
raise exceptions.NegotiationError, 'SHA256(e) != He'
849
raise NegotiationError('SHA256(e) != He')
825
851
k = self.get_shared_secret(e, self.y, p)
827
852
self.kc_o, self.km_o, self.ks_o = self.generate_initiator_keys(k)
829
854
# 4.5.2 verifying alice's identity
831
855
self.verify_identity(form, e, False, 'a')
833
857
# 4.5.4 generating bob's final session keys
837
860
srses = secrets.secrets().retained_secrets(self.conn.name,
924
948
self.control.print_esession_details()
926
950
def do_retained_secret(self, k, old_srs):
927
'''calculate the new retained secret. determine if the user needs to check
928
the remote party's identity. set up callbacks for when the identity has
952
Calculate the new retained secret. determine if the user needs to check
953
the remote party's identity. Set up callbacks for when the identity has
931
956
new_srs = self.hmac(k, 'New Retained Secret')
932
957
self.srs = new_srs
986
1011
def terminate_e2e(self):
987
1012
self.terminate()
989
1013
self.enable_encryption = False
991
1015
def acknowledge_termination(self):
992
1016
StanzaSession.acknowledge_termination(self)
994
1017
self.enable_encryption = False
996
def fail_bad_negotiation(self, reason, fields = None):
997
'''sends an error and cancels everything.
999
if fields is None, the remote party has given us a bad cryptographic value of some kind
1001
otherwise, list the fields we haven't implemented'''
1019
def fail_bad_negotiation(self, reason, fields=None):
1021
Sends an error and cancels everything.
1023
If fields is None, the remote party has given us a bad cryptographic
1024
value of some kind. Otherwise, list the fields we haven't implemented
1003
1026
err = xmpp.Error(xmpp.Message(), xmpp.ERR_FEATURE_NOT_IMPLEMENTED)
1004
1027
err.T.error.T.text.setData(reason)