comparison sat/plugins/plugin_xep_0384.py @ 2654:e7bfbded652a

plugin XEP-0384, install: adapted plugin to omemo module changes + added omemo module to dependencies: - omemo module logs are now integrated in SàT - OmemoStorage adapated to changes - now really delete OTPK each time, behaviour may be modified in future - fixed bad arguments used during decryption - adapted to other changes OMEMO is now working \o/ It still needs some adjustements though: bundles/devices are for the moment requested on each message encryption, and fingerprints management is not implemented yet.
author Goffi <goffi@goffi.org>
date Sat, 11 Aug 2018 18:24:55 +0200
parents 0f76813afc57
children 0bef44f8e8ca
comparison
equal deleted inserted replaced
2653:7213caa5c5d0 2654:e7bfbded652a
25 from twisted.words.xish import domish 25 from twisted.words.xish import domish
26 from twisted.words.protocols.jabber import jid 26 from twisted.words.protocols.jabber import jid
27 from twisted.words.protocols.jabber import error 27 from twisted.words.protocols.jabber import error
28 from sat.memory import persistent 28 from sat.memory import persistent
29 from functools import partial 29 from functools import partial
30 import logging
30 import random 31 import random
31 import base64 32 import base64
32 try: 33 try:
33 import omemo 34 import omemo
34 from omemo.extendedpublicbundle import ExtendedPublicBundle 35 from omemo.extendedpublicbundle import ExtendedPublicBundle
39 u'"pip install omemo"' 40 u'"pip install omemo"'
40 ) 41 )
41 42
42 log = getLogger(__name__) 43 log = getLogger(__name__)
43 44
44
45 PLUGIN_INFO = { 45 PLUGIN_INFO = {
46 C.PI_NAME: u"OMEMO", 46 C.PI_NAME: u"OMEMO",
47 C.PI_IMPORT_NAME: u"OMEMO", 47 C.PI_IMPORT_NAME: u"OMEMO",
48 C.PI_TYPE: u"SEC", 48 C.PI_TYPE: u"SEC",
49 C.PI_PROTOCOLS: [u"XEP-0384"], 49 C.PI_PROTOCOLS: [u"XEP-0384"],
61 KEY_SESSION = "SESSION" 61 KEY_SESSION = "SESSION"
62 KEY_ACTIVE_DEVICES = "DEVICES" 62 KEY_ACTIVE_DEVICES = "DEVICES"
63 KEY_INACTIVE_DEVICES = "DEVICES" 63 KEY_INACTIVE_DEVICES = "DEVICES"
64 64
65 65
66 # we want to manage log emitted by omemo module ourseves
67
68 class SatHandler(logging.Handler):
69
70 def emit(self, record):
71 log.log(record.levelname, record.getMessage())
72
73 @staticmethod
74 def install():
75 omemo_sm_logger = logging.getLogger("omemo.SessionManager")
76 omemo_sm_logger.propagate = False
77 omemo_sm_logger.addHandler(SatHandler())
78
79
80 SatHandler.install()
81
82
66 def b64enc(data): 83 def b64enc(data):
67 return base64.b64encode(bytes(bytearray(data))).decode("ASCII") 84 return base64.b64encode(bytes(bytearray(data))).decode("ASCII")
68 85
69 86
70 class OmemoStorage(omemo.Storage): 87 class OmemoStorage(omemo.Storage):
71 88
72 def __init__(self, persistent_dict): 89 def __init__(self, client, device_id, persistent_dict):
73 """ 90 """
74 @param persistent_dict(persistent.LazyPersistentBinaryDict): object which will 91 @param persistent_dict(persistent.LazyPersistentBinaryDict): object which will
75 store data in SàT database 92 store data in SàT database
76 """ 93 """
94 self.own_bare_jid_s = client.jid.userhost()
95 self.device_id = device_id
77 self.data = persistent_dict 96 self.data = persistent_dict
78 97
79 @property 98 @property
80 def is_async(self): 99 def is_async(self):
81 return True 100 return True
89 Deferred is called 108 Deferred is called
90 """ 109 """
91 deferred.addCallback(partial(callback, True)) 110 deferred.addCallback(partial(callback, True))
92 deferred.addErrback(partial(callback, False)) 111 deferred.addErrback(partial(callback, False))
93 112
113 def loadOwnData(self, callback):
114 callback(True, {'own_bare_jid': self.own_bare_jid_s,
115 'own_device_id': self.device_id})
116
117 def storeOwnData(self, callback, own_bare_jid, own_device_id):
118 if own_bare_jid != self.own_bare_jid_s or own_device_id != self.device_id:
119 raise exceptions.InternalError('bare jid or device id inconsistency!')
120 callback(True, None)
121
94 def loadState(self, callback): 122 def loadState(self, callback):
95 d = self.data.get(KEY_STATE) 123 d = self.data.get(KEY_STATE)
96 self.setCb(d, callback) 124 self.setCb(d, callback)
97 125
98 def storeState(self, callback, state, device_id): 126 def storeState(self, callback, state):
99 d = self.data.force(KEY_STATE, {'state': state, 'device_id': device_id}) 127 d = self.data.force(KEY_STATE, state)
100 self.setCb(d, callback) 128 self.setCb(d, callback)
101 129
102 def loadSession(self, callback, jid, device_id): 130 def loadSession(self, callback, bare_jid, device_id):
103 key = u'\n'.join([KEY_SESSION, jid, unicode(device_id)]) 131 key = u'\n'.join([KEY_SESSION, bare_jid, unicode(device_id)])
104 d = self.data.get(key) 132 d = self.data.get(key)
105 self.setCb(d, callback) 133 self.setCb(d, callback)
106 134
107 def storeSession(self, callback, jid, device_id, session): 135 def storeSession(self, callback, bare_jid, device_id, session):
108 key = u'\n'.join([KEY_SESSION, jid, unicode(device_id)]) 136 key = u'\n'.join([KEY_SESSION, bare_jid, unicode(device_id)])
109 d = self.data.force(key, session) 137 d = self.data.force(key, session)
110 self.setCb(d, callback) 138 self.setCb(d, callback)
111 139
112 def loadActiveDevices(self, callback, jid): 140 def loadActiveDevices(self, callback, bare_jid):
113 key = u'\n'.join([KEY_ACTIVE_DEVICES, jid]) 141 key = u'\n'.join([KEY_ACTIVE_DEVICES, bare_jid])
114 d = self.data.get(key, {}) 142 d = self.data.get(key, {})
115 self.setCb(d, callback) 143 self.setCb(d, callback)
116 144
117 def loadInactiveDevices(self, callback, jid): 145 def loadInactiveDevices(self, callback, bare_jid):
118 key = u'\n'.join([KEY_INACTIVE_DEVICES, jid]) 146 key = u'\n'.join([KEY_INACTIVE_DEVICES, bare_jid])
119 d = self.data.get(key, {}) 147 d = self.data.get(key, {})
120 self.setCb(d, callback) 148 self.setCb(d, callback)
121 149
122 def storeActiveDevices(self, callback, jid, devices): 150 def storeActiveDevices(self, callback, bare_jid, devices):
123 key = u'\n'.join([KEY_ACTIVE_DEVICES, jid]) 151 key = u'\n'.join([KEY_ACTIVE_DEVICES, bare_jid])
124 d = self.data.force(key, devices) 152 d = self.data.force(key, devices)
125 self.setCb(d, callback) 153 self.setCb(d, callback)
126 154
127 def storeInactiveDevices(self, callback, jid, devices): 155 def storeInactiveDevices(self, callback, bare_jid, devices):
128 key = u'\n'.join([KEY_INACTIVE_DEVICES, jid]) 156 key = u'\n'.join([KEY_INACTIVE_DEVICES, bare_jid])
129 d = self.data.force(key, devices) 157 d = self.data.force(key, devices)
130 self.setCb(d, callback) 158 self.setCb(d, callback)
131 159
132 def isTrusted(self, callback, jid, device): 160 def isTrusted(self, callback, bare_jid, device):
133 trusted = True 161 trusted = True
134 callback(True, trusted) 162 callback(True, trusted)
135 163
136 164
137 class SatOTPKPolicy(omemo.OTPKPolicy): 165 class SatOTPKPolicy(omemo.OTPKPolicy):
138 166
139 @staticmethod 167 @staticmethod
140 def decideOTPK(preKeyMessages): 168 def decideOTPK(preKeyMessages):
141 # Always just delete the OTPK. 169 # always delete
142 # This is the behaviour described in the original X3DH specification. 170 return False
143 return True
144 171
145 172
146 class OmemoSession(object): 173 class OmemoSession(object):
147 """Wrapper to use omemo.OmemoSession with Deferred""" 174 """Wrapper to use omemo.OmemoSession with Deferred"""
148 175
163 d = defer.Deferred() 190 d = defer.Deferred()
164 promise_.then(d.callback, d.errback) 191 promise_.then(d.callback, d.errback)
165 return d 192 return d
166 193
167 @classmethod 194 @classmethod
168 def create(cls, client, omemo_storage, device_id): 195 def create(cls, client, storage, my_device_id = None):
169 omemo_session_p = client._xep_0384_session = omemo.SessionManager.create( 196 omemo_session_p = client._xep_0384_session = omemo.SessionManager.create(
170 client.jid.userhost(), omemo_storage, SatOTPKPolicy, my_device_id=device_id) 197 storage,
198 SatOTPKPolicy,
199 client.jid.userhost(),
200 my_device_id)
171 d = cls.promise2Deferred(omemo_session_p) 201 d = cls.promise2Deferred(omemo_session_p)
172 d.addCallback(lambda session: cls(session)) 202 d.addCallback(lambda session: cls(session))
173 return d 203 return d
174 204
175 def newDeviceList(self, devices, jid=None): 205 def newDeviceList(self, devices, jid):
176 if jid is not None: 206 jid = jid.userhost()
177 jid = jid.userhost()
178 new_device_p = self._session.newDeviceList(devices, jid) 207 new_device_p = self._session.newDeviceList(devices, jid)
179 return self.promise2Deferred(new_device_p) 208 return self.promise2Deferred(new_device_p)
180 209
181 def getDevices(self, bare_jid=None): 210 def getDevices(self, bare_jid=None):
182 get_devices_p = self._session.getDevices(bare_jid=bare_jid) 211 get_devices_p = self._session.getDevices(bare_jid=bare_jid)
234 self._p_hints = host.plugins[u"XEP-0334"] 263 self._p_hints = host.plugins[u"XEP-0334"]
235 self._p_carbons = host.plugins[u"XEP-0280"] 264 self._p_carbons = host.plugins[u"XEP-0280"]
236 self._p = host.plugins[u"XEP-0060"] 265 self._p = host.plugins[u"XEP-0060"]
237 host.trigger.add("MessageReceived", self._messageReceivedTrigger, priority=100050) 266 host.trigger.add("MessageReceived", self._messageReceivedTrigger, priority=100050)
238 host.trigger.add("sendMessageData", self._sendMessageDataTrigger) 267 host.trigger.add("sendMessageData", self._sendMessageDataTrigger)
239 self.host.registerEncryptionPlugin(self, "OMEMO", NS_OMEMO, 100) 268 self.host.registerEncryptionPlugin(self, u"OMEMO", NS_OMEMO, 100)
240 269
241 @defer.inlineCallbacks 270 @defer.inlineCallbacks
242 def profileConnected(self, client): 271 def profileConnected(self, client):
243 # we first need to get devices ids (including our own) 272 # we first need to get devices ids (including our own)
244 persistent_dict = persistent.LazyPersistentBinaryDict("XEP-0384", client.profile) 273 persistent_dict = persistent.LazyPersistentBinaryDict("XEP-0384", client.profile)
257 286
258 if device_id not in devices: 287 if device_id not in devices:
259 devices.add(device_id) 288 devices.add(device_id)
260 yield self.setDevices(client, devices) 289 yield self.setDevices(client, devices)
261 290
262 omemo_storage = OmemoStorage(persistent_dict) 291 omemo_storage = OmemoStorage(client, device_id, persistent_dict)
263 omemo_session = yield OmemoSession.create(client, omemo_storage, device_id) 292 omemo_session = yield OmemoSession.create(client, omemo_storage, device_id)
264 client._xep_0384_session = omemo_session 293 client._xep_0384_session = omemo_session
265 client._xep_0384_device_id = device_id 294 client._xep_0384_device_id = device_id
266 yield omemo_session.newDeviceList(devices) 295 yield omemo_session.newDeviceList(devices, client.jid)
267 if omemo_session.state.changed: 296 if omemo_session.state.changed:
268 log.info(_(u"Saving public bundle for this device ({device_id})").format( 297 log.info(_(u"Saving public bundle for this device ({device_id})").format(
269 device_id=device_id)) 298 device_id=device_id))
270 bundle = omemo_session.state.getPublicBundle() 299 bundle = omemo_session.state.getPublicBundle()
271 yield self.setBundle(client, bundle, device_id) 300 yield self.setBundle(client, bundle, device_id)
353 log.warning(_(u"no item found in node {node}, can't get public bundle " 382 log.warning(_(u"no item found in node {node}, can't get public bundle "
354 u"for device {device_id}").format(node=node, 383 u"for device {device_id}").format(node=node,
355 device_id=device_id)) 384 device_id=device_id))
356 continue 385 continue
357 if len(items) > 1: 386 if len(items) > 1:
358 log.warning(_(u"more than one item found in {node}," 387 log.warning(_(u"more than one item found in {node}, "
359 u"this is not expected").format(node=node)) 388 u"this is not expected").format(node=node))
360 item = items[0] 389 item = items[0]
361 try: 390 try:
362 bundle_elt = next(item.elements(NS_OMEMO, 'bundle')) 391 bundle_elt = next(item.elements(NS_OMEMO, 'bundle'))
363 signedPreKeyPublic_elt = next(bundle_elt.elements( 392 signedPreKeyPublic_elt = next(bundle_elt.elements(
392 "id": int(preKeyPublic_elt['preKeyId']) 421 "id": int(preKeyPublic_elt['preKeyId'])
393 } 422 }
394 otpks.append(otpk) 423 otpks.append(otpk)
395 424
396 except Exception as e: 425 except Exception as e:
397 log.warning(_(u"error while decoding key for device {devide_id}: {msg}") 426 log.warning(_(u"error while decoding key for device {device_id}: {msg}")
398 .format(device_id=device_id, msg=e)) 427 .format(device_id=device_id, msg=e))
399 continue 428 continue
400 429
401 bundles[device_id] = ExtendedPublicBundle(ik, spk, spkSignature, otpks) 430 bundles[device_id] = ExtendedPublicBundle(ik, spk, spkSignature, otpks)
402 431
460 except StopIteration: 489 except StopIteration:
461 # no OMEMO message here 490 # no OMEMO message here
462 defer.returnValue(True) 491 defer.returnValue(True)
463 492
464 # we have an encrypted message let's decrypt it 493 # we have an encrypted message let's decrypt it
465 # from_jid = jid.JID(message_elt['from']) 494 from_jid = jid.JID(message_elt['from'])
466 omemo_session = client._xep_0384_session 495 omemo_session = client._xep_0384_session
467 device_id = client._xep_0384_device_id 496 device_id = client._xep_0384_device_id
468 try: 497 try:
469 header_elt = next(encrypted_elt.elements(NS_OMEMO, u'header')) 498 header_elt = next(encrypted_elt.elements(NS_OMEMO, u'header'))
470 iv_elt = next(header_elt.elements(NS_OMEMO, u'iv')) 499 iv_elt = next(header_elt.elements(NS_OMEMO, u'iv'))
471 except StopIteration: 500 except StopIteration:
472 log.warning(_(u"Invalid OMEMO encrypted stanza, ignoring: {xml}") 501 log.warning(_(u"Invalid OMEMO encrypted stanza, ignoring: {xml}")
502 .format(xml=message_elt.toXml()))
503 defer.returnValue(False)
504 try:
505 s_device_id = header_elt['sid']
506 except KeyError:
507 log.warning(_(u"Invalid OMEMO encrypted stanza, missing sender device ID, "
508 u"ignoring: {xml}")
473 .format(xml=message_elt.toXml())) 509 .format(xml=message_elt.toXml()))
474 defer.returnValue(False) 510 defer.returnValue(False)
475 try: 511 try:
476 key_elt = next((e for e in header_elt.elements(NS_OMEMO, u'key') 512 key_elt = next((e for e in header_elt.elements(NS_OMEMO, u'key')
477 if int(e[u'rid']) == device_id)) 513 if int(e[u'rid']) == device_id))
486 is_pre_key = C.bool(key_elt.getAttribute('prekey', 'false')) 522 is_pre_key = C.bool(key_elt.getAttribute('prekey', 'false'))
487 payload_elt = next(encrypted_elt.elements(NS_OMEMO, u'payload'), None) 523 payload_elt = next(encrypted_elt.elements(NS_OMEMO, u'payload'), None)
488 524
489 try: 525 try:
490 cipher, plaintext = yield omemo_session.decryptMessage( 526 cipher, plaintext = yield omemo_session.decryptMessage(
491 bare_jid=client.jid.userhostJID(), 527 bare_jid=from_jid.userhostJID(),
492 device=device_id, 528 device=s_device_id,
493 iv=base64.b64decode(bytes(iv_elt)), 529 iv=base64.b64decode(bytes(iv_elt)),
494 message=base64.b64decode(bytes(key_elt)), 530 message=base64.b64decode(bytes(key_elt)),
495 is_pre_key_message=is_pre_key, 531 is_pre_key_message=is_pre_key,
496 payload=base64.b64decode(bytes(payload_elt)) 532 payload=base64.b64decode(bytes(payload_elt))
497 if payload_elt is not None else None, 533 if payload_elt is not None else None,