~ubuntu-branches/ubuntu/lucid/gajim/lucid-security

« back to all changes in this revision

Viewing changes to src/common/stanza_session.py

  • Committer: Maia Kozheva
  • Date: 2009-11-25 08:32:36 UTC
  • mfrom: (1.1.16 upstream)
  • Revision ID: sikon@maia-desktop-20091125083236-hkxrujhn3amehuve
Merged new upstream release 0.13

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
## src/common/stanza_session.py
3
3
##
4
4
## Copyright (C) 2007 Julien Pivotto <roidelapluie AT gmail.com>
5
 
##                    Stephan Erb <steve-e AT h3c.de>
6
5
## Copyright (C) 2007-2008 Yann Leboulanger <asterix AT lagaule.org>
7
6
##                         Brendan Taylor <whateley AT gmail.com>
8
7
##                         Jean-Marie Traissard <jim AT lapin.org>
24
23
##
25
24
 
26
25
from common import gajim
27
 
 
28
26
from common import xmpp
29
 
from common import exceptions
 
27
from common.exceptions import DecryptionError, NegotiationError
 
28
import xmpp.c14n
30
29
 
 
30
import itertools
31
31
import random
32
32
import string
33
 
 
34
33
import time
35
 
 
36
 
import xmpp.c14n
37
 
 
38
34
import base64
39
35
import os
 
36
from hashlib import sha256
 
37
from hmac import HMAC
 
38
from common import crypto
 
39
 
 
40
if gajim.HAVE_PYCRYPTO:
 
41
        from Crypto.Cipher import AES
 
42
        from Crypto.PublicKey import RSA
 
43
 
 
44
        from common import dh
 
45
        import secrets
40
46
 
41
47
XmlDsig = 'http://www.w3.org/2000/09/xmldsig#'
42
48
 
43
49
class StanzaSession(object):
 
50
        '''
 
51
        '''
44
52
        def __init__(self, conn, jid, thread_id, type_):
 
53
                '''
 
54
                '''
45
55
                self.conn = conn
46
 
 
47
56
                self.jid = jid
48
 
 
49
57
                self.type = type_
 
58
                self.resource = None
50
59
 
51
60
                if thread_id:
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)
70
79
 
71
 
        # remove events associated with this session from the queue
72
 
        # returns True if any events were removed (unlike gajim.events.remove_events)
 
80
        def get_to(self):
 
81
                to = str(self.jid)
 
82
                if self.resource and not to.endswith(self.resource):
 
83
                        to += '/' + self.resource
 
84
                return to
 
85
 
73
86
        def remove_events(self, types):
 
87
                '''
 
88
                Remove events associated with this session from the queue.
 
89
                returns True if any events were removed (unlike events.py remove_events)
 
90
                '''
74
91
                any_removed = False
75
92
 
76
93
                for j in (self.jid, self.jid.getStripped()):
91
108
                return any_removed
92
109
 
93
110
        def generate_thread_id(self):
94
 
                return ''.join([random.choice(string.ascii_letters) for x in xrange(0,
95
 
                        32)])
 
111
                return ''.join([f(string.ascii_letters) for f in itertools.repeat(
 
112
                        random.choice, 32)])
96
113
 
97
114
        def send(self, msg):
98
115
                if self.thread_id:
99
116
                        msg.NT.thread = self.thread_id
100
117
 
101
 
                msg.setAttr('to', self.jid)
 
118
                msg.setAttr('to', self.get_to())
102
119
                self.conn.send_stanza(msg)
103
120
 
104
121
                if isinstance(msg, xmpp.Message):
123
140
                self.cancelled_negotiation()
124
141
 
125
142
        def cancelled_negotiation(self):
126
 
                '''A negotiation has been cancelled, so reset this session to its default
127
 
                state.'''
128
 
 
 
143
                '''
 
144
                A negotiation has been cancelled, so reset this session to its default
 
145
                state.
 
146
                '''
129
147
                if self.control:
130
148
                        self.control.on_cancel_session_negotiation()
131
149
 
155
173
                # we could send an acknowledgement message to the remote client here
156
174
                self.status = None
157
175
 
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
163
 
 
164
 
        from common import dh
165
 
        import secrets
166
 
 
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:
170
 
 
171
 
# 1. None:
172
 
#                               default state
173
 
# 2. 'requested-e2e':
174
 
#                               this client has initiated an esession negotiation and is waiting
175
 
#                               for a response
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
179
 
#                               negotiation
180
 
# 4. 'identified-alice':
181
 
#                               this client identified itself and is waiting for the responder to
182
 
#                               identify itself and complete the negotiation
183
 
# 5. 'active':
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.
187
 
 
188
 
# the transition between these states is handled in gajim.py's
189
 
#       handle_session_negotiation method.
190
176
 
191
177
class EncryptedStanzaSession(StanzaSession):
 
178
        '''
 
179
        An encrypted stanza negotiation has several states. They arerepresented as
 
180
        the following values in the 'status' attribute of the session object:
 
181
 
 
182
                1. None:
 
183
                                default state
 
184
                2. 'requested-e2e':
 
185
                                this client has initiated an esession negotiation and is waiting
 
186
                                for a response
 
187
                3. 'responded-e2e':
 
188
                                this client has responded to an esession negotiation request and
 
189
                                is waiting for the initiator to identify itself and complete the
 
190
                                negotiation
 
191
                4. 'identified-alice':
 
192
                                this client identified itself and is waiting for the responder to
 
193
                                identify itself and complete the negotiation
 
194
                5. 'active':
 
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.
 
198
 
 
199
        The transition between these states is handled in gajim.py's
 
200
        handle_session_negotiation method.
 
201
        '''
192
202
        def __init__(self, conn, jid, thread_id, type_='chat'):
193
203
                StanzaSession.__init__(self, conn, jid, thread_id, type_='chat')
194
204
 
195
205
                self.xes = {}
196
206
                self.es = {}
197
 
 
198
207
                self.n = 128
199
 
 
200
208
                self.enable_encryption = False
201
209
 
202
210
                # _s denotes 'self' (ie. this client)
203
211
                self._kc_s = None
204
 
 
205
212
                # _o denotes 'other' (ie. the client at the other end of the session)
206
213
                self._kc_o = None
207
214
 
208
215
                # has the remote contact's identity ever been verified?
209
216
                self.verified_identity = False
210
217
 
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)
 
220
                if not c:
 
221
                        c = gajim.contacts.get_contact(self.conn.name, self.jid)
 
222
                return c
 
223
 
 
224
        def _is_buggy_gajim(self):
 
225
                c = self._get_contact()
 
226
                if gajim.capscache.is_supported(c, xmpp.NS_ROSTERX):
 
227
                        return False
 
228
                return True
 
229
 
212
230
        def set_kc_s(self, value):
 
231
                '''
 
232
                keep the encrypter updated with my latest cipher key
 
233
                '''
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)
217
238
        def get_kc_s(self):
218
239
                return self._kc_s
219
240
 
220
 
        # keep the decrypter updated with the other party's latest cipher key
221
241
        def set_kc_o(self, value):
 
242
                '''
 
243
                keep the decrypter updated with the other party's latest cipher key
 
244
                '''
222
245
                self._kc_o = value
223
246
                self.decrypter = self.cipher.new(self._kc_o, self.cipher.MODE_CTR,
224
247
                        counter=self.decryptcounter)
239
262
 
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])
244
267
 
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')]
248
271
 
249
 
                # XXX can also encrypt contents of <error/> elements in stanzas @type =
 
272
                # FIXME can also encrypt contents of <error/> elements in stanzas @type =
250
273
                # 'error'
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)
267
290
 
268
 
                # XXX check for rekey request, handle <key/> elements
 
291
                # FIXME check for rekey request, handle <key/> elements
269
292
 
270
293
                m_content = ''.join(map(str, c.getChildren()))
271
294
                c.NT.mac = base64.b64encode(self.hmac(self.km_s, m_content + \
283
306
                return stanza
284
307
 
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)
287
310
 
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()
290
313
 
291
314
        def generate_initiator_keys(self, k):
292
315
                return (self.hmac(k, 'Initiator Cipher Key'),
312
335
                return self.encrypter.encrypt(padded)
313
336
 
314
337
        def decrypt_stanza(self, stanza):
315
 
                # delete the unencrypted explanation body, if it exists
 
338
                ''' delete the unencrypted explanation body, if it exists '''
316
339
                orig_body = stanza.getTag('body')
317
340
                if orig_body:
318
341
                        stanza.delChild(orig_body)
323
346
                stanza.delChild(c)
324
347
 
325
348
                # contents of <c>, minus <mac>, minus whitespace
326
 
                macable = ''.join(map(str, filter(lambda x: x.getName() != 'mac',
327
 
                        c.getChildren())))
 
349
                macable = ''.join(str(x) for x in c.getChildren() if x.getName() != 'mac')
328
350
 
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))
332
354
 
333
355
                if not calculated_mac == received_mac:
334
 
                        raise exceptions.DecryptionError, 'bad signature'
 
356
                        raise DecryptionError('bad signature')
335
357
 
336
358
                m_final = base64.b64decode(c.getTagData('data'))
337
359
                m_compressed = self.decrypt(m_final)
340
362
                try:
341
363
                        parsed = xmpp.Node(node='<node>' + plaintext + '</node>')
342
364
                except Exception:
343
 
                        raise exceptions.DecryptionError, 'decrypted <data/> not parseable as XML'
 
365
                        raise DecryptionError('decrypted <data/> not parseable as XML')
344
366
 
345
367
                for child in parsed.getChildren():
346
368
                        stanza.addChild(node=child)
359
381
 
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')
363
385
 
364
386
                return crypto.sha256(crypto.encode_mpi(crypto.powmod(e, y, p)))
365
387
 
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 \
 
392
                        macable)
370
393
 
371
394
        def verify_identity(self, form, dh_i, sigmai, i_o):
372
395
                m_o = base64.b64decode(form['mac'])
375
398
                m_o_calculated = self.hmac(self.km_o, crypto.encode_mpi(self.c_o) + id_o)
376
399
 
377
400
                if m_o_calculated != m_o:
378
 
                        raise exceptions.NegotiationError, 'calculated m_%s differs from received m_%s' % (i_o, i_o)
 
401
                        raise NegotiationError('calculated m_%s differs from received m_%s' %
 
402
                                (i_o, i_o))
379
403
 
380
404
                if i_o == 'a' and self.sas_algs == 'sas28x5':
381
405
                        # we don't need to calculate this if there's a verified retained secret
387
411
                        parsed = xmpp.Node(node='<node>' + plaintext + '</node>')
388
412
 
389
413
                        if self.negotiated['recv_pubkey'] == 'hash':
390
 
                                fingerprint = parsed.getTagData('fingerprint')
391
 
 
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()
394
417
                        else:
395
418
                                if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'):
396
419
                                        keyvalue = parsed.getTag(name='RSAKeyValue', namespace=XmlDsig)
397
420
 
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)))
401
424
 
402
 
                                        pubkey_o = xmpp.c14n.c14n(keyvalue)
 
425
                                        pubkey_o = xmpp.c14n.c14n(keyvalue, self._is_buggy_gajim())
403
426
                                else:
404
 
                                        # XXX DSA, etc.
405
 
                                        raise 'unimplemented'
 
427
                                        # FIXME DSA, etc.
 
428
                                        raise NotImplementedError()
406
429
 
407
430
                        enc_sig = parsed.getTag(name='SignatureValue',
408
431
                                namespace=XmlDsig).getData()
425
448
                mac_o_calculated = self.hmac(self.ks_o, content)
426
449
 
427
450
                if self.negotiated['recv_pubkey']:
428
 
                        hash = crypto.sha256(mac_o_calculated)
 
451
                        hash_ = crypto.sha256(mac_o_calculated)
429
452
 
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!')
432
455
 
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'
 
458
                                % (i_o, i_o))
435
459
 
436
460
        def make_identity(self, form, dh_i):
437
461
                if self.negotiated['send_pubkey']:
439
463
                                pubkey = secrets.secrets().my_pubkey(self.conn.name)
440
464
                                fields = (pubkey.n, pubkey.e)
441
465
 
442
 
                                cb_fields = map(lambda f: base64.b64encode(crypto.encode_mpi(f)),
443
 
                                        fields)
 
466
                                cb_fields = [base64.b64encode(crypto.encode_mpi(f)) for f in
 
467
                                        fields]
444
468
 
445
469
                                pubkey_s = '<RSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#"'
446
470
                                '><Modulus>%s</Modulus><Exponent>%s</Exponent></RSAKeyValue>' % \
448
472
                else:
449
473
                        pubkey_s = ''
450
474
 
451
 
                form_s2 = ''.join(map(lambda el: xmpp.c14n.c14n(el), form.getChildren()))
 
475
                form_s2 = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for el in \
 
476
                        form.getChildren())
452
477
 
453
478
                old_c_s = self.c_s
454
479
                content = self.n_o + self.n_s + crypto.encode_mpi(dh_i) + pubkey_s + \
478
503
                        self.sas = crypto.sas_28x5(m_s, self.form_o)
479
504
 
480
505
                        if self.sigmai:
481
 
                                # XXX save retained secret?
482
 
                                self.check_identity(lambda : ())
 
506
                                # FIXME save retained secret?
 
507
                                self.check_identity(tuple)
483
508
 
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'))
523
548
 
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'))
527
552
 
544
569
                        ',') ]
545
570
 
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]))
548
573
 
549
574
                x.addChild(node=self.make_dhfield(modp_options, sigmai))
550
575
                self.sigmai = sigmai
551
576
 
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 \
 
578
                        in x.getChildren())
553
579
 
554
580
                feature.addChild(node=x)
555
581
 
557
583
 
558
584
                self.send(request)
559
585
 
560
 
        # 4.3 esession response (bob)
561
586
        def verify_options_bob(self, form):
 
587
                ''' 4.3 esession response (bob) '''
562
588
                negotiated = {'recv_pubkey': None, 'send_pubkey': None}
563
589
                not_acceptable = []
564
590
                ask_user = {}
572
598
 
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
577
603
 
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()
582
608
 
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'
622
648
                        else:
623
 
                                # XXX some things are handled elsewhere, some things are
 
649
                                # FIXME some things are handled elsewhere, some things are
624
650
                                # not-implemented
625
651
                                pass
626
652
 
627
653
                return (negotiated, not_acceptable, ask_user)
628
654
 
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))
680
706
 
681
 
                self.form_o = ''.join(map(lambda el: xmpp.c14n.c14n(el),
682
 
                        form.getChildren()))
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 \
 
710
                        in x.getChildren())
684
711
 
685
712
                self.status = 'responded-e2e'
686
713
 
700
727
 
701
728
                self.send(response)
702
729
 
703
 
        # 'Alice Accepts'
704
730
        def verify_options_alice(self, form):
 
731
                ''' 'Alice Accepts' '''
705
732
                negotiated = {}
706
733
                ask_user = {}
707
734
                not_acceptable = []
728
755
 
729
756
                return (negotiated, not_acceptable, ask_user)
730
757
 
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
738
765
 
739
766
                self.negotiated = negotiated
746
773
 
747
774
                self.c_s = crypto.decode_mpi(base64.b64decode(form['counter']))
748
775
                self.c_o = self.c_s ^ (2 ** (self.n - 1))
749
 
 
750
776
                self.n_o = base64.b64decode(form['my_nonce'])
751
777
 
752
778
                mod_p = int(form['modp'])
755
781
                e = self.es[mod_p]
756
782
 
757
783
                self.d = crypto.decode_mpi(base64.b64decode(form['dhkeys']))
758
 
 
759
784
                self.k = self.get_shared_secret(self.d, x, p)
760
785
 
761
786
                result.addChild(node=xmpp.DataField(name='FORM_TYPE',
772
797
                else:
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]
776
801
 
777
802
                        if not rshashes:
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))
781
806
 
782
807
                        rshashes = [base64.b64encode(rshash) for rshash in rshashes]
784
809
                        result.addChild(node=xmpp.DataField(name='dhkeys',
785
810
                                value=base64.b64encode(crypto.encode_mpi(e))))
786
811
 
787
 
                        self.form_o = ''.join(map(lambda el: xmpp.c14n.c14n(el),
788
 
                                form.getChildren()))
 
812
                        self.form_o = ''.join(xmpp.c14n.c14n(el, self._is_buggy_gajim()) for \
 
813
                                el in form.getChildren())
789
814
 
790
815
                # MUST securely destroy K unless it will be used later to generate the
791
816
                # final shared secret
802
827
                else:
803
828
                        self.status = 'identified-alice'
804
829
 
805
 
        # 4.5 esession accept (bob)
806
830
        def accept_e2e_bob(self, form):
 
831
                ''' 4.5 esession accept (bob) '''
807
832
                response = xmpp.Message()
808
833
 
809
834
                init = response.NT.init
812
837
                x = xmpp.DataForm(typ='result')
813
838
 
814
839
                for field in ('nonce', 'dhkeys', 'rshashes', 'identity', 'mac'):
 
840
                        # FIXME: will do nothing in real world...
815
841
                        assert field in form.asDict(), "alice's form didn't have a %s field" \
816
842
                                % field
817
843
 
820
846
                p = dh.primes[self.modp]
821
847
 
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')
824
850
 
825
851
                k = self.get_shared_secret(e, self.y, p)
826
 
 
827
852
                self.kc_o, self.km_o, self.ks_o = self.generate_initiator_keys(k)
828
853
 
829
854
                # 4.5.2 verifying alice's identity
830
 
 
831
855
                self.verify_identity(form, e, False, 'a')
832
856
 
833
857
                # 4.5.4 generating bob's final session keys
834
 
 
835
858
                srs = ''
836
859
 
837
860
                srses = secrets.secrets().retained_secrets(self.conn.name,
839
862
                rshashes = [base64.b64decode(rshash) for rshash in form.getField(
840
863
                        'rshashes').getValues()]
841
864
 
842
 
                for (secret, verified) in srses:
 
865
                for s in srses:
 
866
                        secret = s[0]
843
867
                        if self.hmac(self.n_o, secret) in rshashes:
844
868
                                srs = secret
845
869
                                break
893
917
                except IndexError:
894
918
                        return
895
919
 
896
 
                for (secret, verified) in srses:
 
920
                for s in srses:
 
921
                        secret = s[0]
897
922
                        if self.hmac(secret, 'Shared Retained Secret') == srshash:
898
923
                                srs = secret
899
924
                                break
909
934
                self.kc_o, self.km_o, self.ks_o = self.generate_responder_keys(k)
910
935
 
911
936
                # 4.6.2 Verifying Bob's Identity
912
 
 
913
937
                self.verify_identity(form, self.d, False, 'b')
914
938
                # Note: If Alice discovers an error then she SHOULD ignore any encrypted
915
939
                # content she received in the stanza.
924
948
                        self.control.print_esession_details()
925
949
 
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
929
 
                been verified.'''
930
 
 
 
951
                '''
 
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
 
954
                been verified.
 
955
                '''
931
956
                new_srs = self.hmac(k, 'New Retained Secret')
932
957
                self.srs = new_srs
933
958
 
967
992
 
968
993
                        x = crypto.srand(2 ** (2 * self.n - 1), p - 1)
969
994
 
970
 
                        # XXX this may be a source of performance issues
 
995
                        # FIXME this may be a source of performance issues
971
996
                        e = crypto.powmod(g, x, p)
972
997
 
973
998
                        self.xes[modp] = x
985
1010
 
986
1011
        def terminate_e2e(self):
987
1012
                self.terminate()
988
 
 
989
1013
                self.enable_encryption = False
990
1014
 
991
1015
        def acknowledge_termination(self):
992
1016
                StanzaSession.acknowledge_termination(self)
993
 
 
994
1017
                self.enable_encryption = False
995
1018
 
996
 
        def fail_bad_negotiation(self, reason, fields = None):
997
 
                '''sends an error and cancels everything.
998
 
 
999
 
if fields is None, the remote party has given us a bad cryptographic value of some kind
1000
 
 
1001
 
otherwise, list the fields we haven't implemented'''
1002
 
 
 
1019
        def fail_bad_negotiation(self, reason, fields=None):
 
1020
                '''
 
1021
                Sends an error and cancels everything.
 
1022
 
 
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
 
1025
                '''
1003
1026
                err = xmpp.Error(xmpp.Message(), xmpp.ERR_FEATURE_NOT_IMPLEMENTED)
1004
1027
                err.T.error.T.text.setData(reason)
1005
1028
 
1025
1048
        def cancelled_negotiation(self):
1026
1049
                StanzaSession.cancelled_negotiation(self)
1027
1050
                self.enable_encryption = False
1028
 
 
1029
1051
                self.km_o = ''
1030
1052
 
1031
1053
# vim: se ts=3: