Mercurial > libervia-backend
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, |