Mercurial > libervia-backend
comparison sat/plugins/plugin_xep_0384.py @ 2738:eb58f26ed236
plugin XEP-0384: update to last python-omemo + trust management:
- Plugin has been updated to use last version of python-omemo (10.0.3).
- A temporary method remove all storage data if they are found, this method must be removed before 0.7 release (only people using dev version should have old omemo data in there storage).
- Trust management is not implemented, using new encryptionTrustUIGet method (an UI is also displayed when trust handling is needed before sending a message).
- omemo.DefaultOTPKPolicy is now used, instead of previous test policy of always deleting.
OMEMO e2e encryption is now functional for one2one conversations, including fingerprint management.
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 02 Jan 2019 18:50:28 +0100 |
parents | 0bef44f8e8ca |
children | e6716d90c2fe |
comparison
equal
deleted
inserted
replaced
2737:5c2ed8a5ae22 | 2738:eb58f26ed236 |
---|---|
15 # GNU Affero General Public License for more details. | 15 # GNU Affero General Public License for more details. |
16 | 16 |
17 # You should have received a copy of the GNU Affero General Public License | 17 # You should have received a copy of the GNU Affero General Public License |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. |
19 | 19 |
20 from sat.core.i18n import _ | 20 from sat.core.i18n import _, D_ |
21 from sat.core.constants import Const as C | 21 from sat.core.constants import Const as C |
22 from sat.core.log import getLogger | 22 from sat.core.log import getLogger |
23 from sat.core import exceptions | 23 from sat.core import exceptions |
24 from omemo import exceptions as omemo_excpt | |
24 from twisted.internet import defer | 25 from twisted.internet import defer |
25 from twisted.words.xish import domish | 26 from twisted.words.xish import domish |
26 from twisted.words.protocols.jabber import jid | 27 from twisted.words.protocols.jabber import jid |
27 from twisted.words.protocols.jabber import error | 28 from twisted.words.protocols.jabber import error |
28 from sat.memory import persistent | 29 from sat.memory import persistent |
29 from functools import partial | 30 from functools import partial |
31 from sat.tools import xml_tools | |
30 import logging | 32 import logging |
31 import random | 33 import random |
32 import base64 | 34 import base64 |
33 try: | 35 try: |
34 import omemo | 36 import omemo |
35 from omemo.extendedpublicbundle import ExtendedPublicBundle | 37 from omemo.extendedpublicbundle import ExtendedPublicBundle |
36 from omemo import wireformat | 38 from omemo_backend_signal import BACKEND as omemo_backend |
37 except ImportError: | 39 # from omemo import wireformat |
40 except ImportError as e: | |
38 raise exceptions.MissingModule( | 41 raise exceptions.MissingModule( |
39 u'Missing module omemo, please download/install it. You can use ' | 42 u'Missing module omemo, please download/install it. You can use ' |
40 u'"pip install omemo"' | 43 u'"pip install omemo"' |
41 ) | 44 ) |
42 | 45 |
51 C.PI_MAIN: u"OMEMO", | 54 C.PI_MAIN: u"OMEMO", |
52 C.PI_HANDLER: u"no", | 55 C.PI_HANDLER: u"no", |
53 C.PI_DESCRIPTION: _(u"""Implementation of OMEMO"""), | 56 C.PI_DESCRIPTION: _(u"""Implementation of OMEMO"""), |
54 } | 57 } |
55 | 58 |
59 OMEMO_MIN_VER = (0, 10, 3) | |
56 NS_OMEMO = "eu.siacs.conversations.axolotl" | 60 NS_OMEMO = "eu.siacs.conversations.axolotl" |
57 NS_OMEMO_DEVICES = NS_OMEMO + ".devicelist" | 61 NS_OMEMO_DEVICES = NS_OMEMO + ".devicelist" |
58 NS_OMEMO_BUNDLE = NS_OMEMO + ".bundles:{device_id}" | 62 NS_OMEMO_BUNDLE = NS_OMEMO + ".bundles:{device_id}" |
59 KEY_STATE = "STATE" | 63 KEY_STATE = "STATE" |
60 KEY_DEVICE_ID = "DEVICE_ID" | 64 KEY_DEVICE_ID = "DEVICE_ID" |
61 KEY_SESSION = "SESSION" | 65 KEY_SESSION = "SESSION" |
66 KEY_TRUST = "TRUST" | |
62 KEY_ACTIVE_DEVICES = "DEVICES" | 67 KEY_ACTIVE_DEVICES = "DEVICES" |
63 KEY_INACTIVE_DEVICES = "DEVICES" | 68 KEY_INACTIVE_DEVICES = "INACTIVE_DEVICES" |
64 | 69 KEY_ALL_JIDS = "ALL_JIDS" |
65 | 70 |
66 # we want to manage log emitted by omemo module ourseves | 71 |
72 # we want to manage log emitted by omemo module ourselves | |
67 | 73 |
68 class SatHandler(logging.Handler): | 74 class SatHandler(logging.Handler): |
69 | 75 |
70 def emit(self, record): | 76 def emit(self, record): |
71 log.log(record.levelname, record.getMessage()) | 77 log.log(record.levelname, record.getMessage()) |
79 | 85 |
80 SatHandler.install() | 86 SatHandler.install() |
81 | 87 |
82 | 88 |
83 def b64enc(data): | 89 def b64enc(data): |
84 return base64.b64encode(bytes(bytearray(data))).decode("ASCII") | 90 return base64.b64encode(bytes(bytearray(data))).decode("US-ASCII") |
91 | |
92 | |
93 def promise2Deferred(promise_): | |
94 """Create a Deferred and fire it when promise is resolved | |
95 | |
96 @param promise_(promise.Promise): promise to convert | |
97 @return (defer.Deferred): deferred instance linked to the promise | |
98 """ | |
99 d = defer.Deferred() | |
100 promise_.then(d.callback, d.errback) | |
101 return d | |
85 | 102 |
86 | 103 |
87 class OmemoStorage(omemo.Storage): | 104 class OmemoStorage(omemo.Storage): |
88 | 105 |
89 def __init__(self, client, device_id, persistent_dict): | 106 def __init__(self, client, device_id, all_jids, persistent_dict): |
90 """ | 107 """ |
91 @param persistent_dict(persistent.LazyPersistentBinaryDict): object which will | 108 @param persistent_dict(persistent.LazyPersistentBinaryDict): object which will |
92 store data in SàT database | 109 store data in SàT database |
93 """ | 110 """ |
94 self.own_bare_jid_s = client.jid.userhost() | 111 self.own_bare_jid_s = client.jid.userhost() |
95 self.device_id = device_id | 112 self.device_id = device_id |
113 self.all_jids = all_jids | |
96 self.data = persistent_dict | 114 self.data = persistent_dict |
97 | 115 |
98 @property | 116 @property |
99 def is_async(self): | 117 def is_async(self): |
100 return True | 118 return True |
108 Deferred is called | 126 Deferred is called |
109 """ | 127 """ |
110 deferred.addCallback(partial(callback, True)) | 128 deferred.addCallback(partial(callback, True)) |
111 deferred.addErrback(partial(callback, False)) | 129 deferred.addErrback(partial(callback, False)) |
112 | 130 |
131 def _checkJid(self, bare_jid): | |
132 """Check if jid is know, and store it if not | |
133 | |
134 @param bare_jid(unicode): bare jid to check | |
135 @return (D): Deferred fired when jid is stored | |
136 """ | |
137 if bare_jid in self.all_jids: | |
138 return defer.succeed(None) | |
139 else: | |
140 self.all_jids.add(bare_jid) | |
141 d = self.data.force(KEY_ALL_JIDS, self.all_jids) | |
142 return d | |
143 | |
113 def loadOwnData(self, callback): | 144 def loadOwnData(self, callback): |
114 callback(True, {'own_bare_jid': self.own_bare_jid_s, | 145 callback(True, {'own_bare_jid': self.own_bare_jid_s, |
115 'own_device_id': self.device_id}) | 146 'own_device_id': self.device_id}) |
116 | 147 |
117 def storeOwnData(self, callback, own_bare_jid, own_device_id): | 148 def storeOwnData(self, callback, own_bare_jid, own_device_id): |
135 def storeSession(self, callback, bare_jid, device_id, session): | 166 def storeSession(self, callback, bare_jid, device_id, session): |
136 key = u'\n'.join([KEY_SESSION, bare_jid, unicode(device_id)]) | 167 key = u'\n'.join([KEY_SESSION, bare_jid, unicode(device_id)]) |
137 d = self.data.force(key, session) | 168 d = self.data.force(key, session) |
138 self.setCb(d, callback) | 169 self.setCb(d, callback) |
139 | 170 |
171 def deleteSession(self, callback, bare_jid, device_id): | |
172 key = u'\n'.join([KEY_SESSION, bare_jid, unicode(device_id)]) | |
173 d = self.data.remove(key) | |
174 self.setCb(d, callback) | |
175 | |
140 def loadActiveDevices(self, callback, bare_jid): | 176 def loadActiveDevices(self, callback, bare_jid): |
141 key = u'\n'.join([KEY_ACTIVE_DEVICES, bare_jid]) | 177 key = u'\n'.join([KEY_ACTIVE_DEVICES, bare_jid]) |
142 d = self.data.get(key, {}) | 178 d = self.data.get(key, {}) |
143 self.setCb(d, callback) | 179 if callback is not None: |
180 self.setCb(d, callback) | |
181 return d | |
144 | 182 |
145 def loadInactiveDevices(self, callback, bare_jid): | 183 def loadInactiveDevices(self, callback, bare_jid): |
146 key = u'\n'.join([KEY_INACTIVE_DEVICES, bare_jid]) | 184 key = u'\n'.join([KEY_INACTIVE_DEVICES, bare_jid]) |
147 d = self.data.get(key, {}) | 185 d = self.data.get(key, {}) |
148 self.setCb(d, callback) | 186 if callback is not None: |
187 self.setCb(d, callback) | |
188 return d | |
149 | 189 |
150 def storeActiveDevices(self, callback, bare_jid, devices): | 190 def storeActiveDevices(self, callback, bare_jid, devices): |
151 key = u'\n'.join([KEY_ACTIVE_DEVICES, bare_jid]) | 191 key = u'\n'.join([KEY_ACTIVE_DEVICES, bare_jid]) |
152 d = self.data.force(key, devices) | 192 d = self._checkJid(bare_jid) |
193 d.addCallback(lambda _: self.data.force(key, devices)) | |
153 self.setCb(d, callback) | 194 self.setCb(d, callback) |
154 | 195 |
155 def storeInactiveDevices(self, callback, bare_jid, devices): | 196 def storeInactiveDevices(self, callback, bare_jid, devices): |
156 key = u'\n'.join([KEY_INACTIVE_DEVICES, bare_jid]) | 197 key = u'\n'.join([KEY_INACTIVE_DEVICES, bare_jid]) |
157 d = self.data.force(key, devices) | 198 d = self._checkJid(bare_jid) |
199 d.addCallback(lambda _: self.data.force(key, devices)) | |
158 self.setCb(d, callback) | 200 self.setCb(d, callback) |
159 | 201 |
160 def isTrusted(self, callback, bare_jid, device): | 202 def storeTrust(self, callback, bare_jid, device_id, trust): |
161 trusted = True | 203 key = u'\n'.join([KEY_TRUST, bare_jid, unicode(device_id)]) |
162 callback(True, trusted) | 204 d = self.data.force(key, trust) |
163 | 205 self.setCb(d, callback) |
164 | 206 |
165 class SatOTPKPolicy(omemo.OTPKPolicy): | 207 def loadTrust(self, callback, bare_jid, device_id): |
166 | 208 key = u'\n'.join([KEY_TRUST, bare_jid, unicode(device_id)]) |
167 @staticmethod | 209 d = self.data.get(key) |
168 def decideOTPK(preKeyMessages): | 210 if callback is not None: |
169 # always delete | 211 self.setCb(d, callback) |
170 return False | 212 return d |
213 | |
214 def listJIDs(self, callback): | |
215 d = defer.succeed(self.all_jids) | |
216 if callback is not None: | |
217 self.setCb(d, callback) | |
218 return d | |
219 | |
220 def _deleteJID_logResults(self, results): | |
221 failed = [success for success, __ in results if not success] | |
222 if failed: | |
223 log.warning( | |
224 u"delete JID failed for {failed_count} on {total_count} operations" | |
225 .format(failed_count=len(failed), total_count=len(results))) | |
226 else: | |
227 log.info( | |
228 u"Delete JID operation succeed ({total_count} operations)." | |
229 .format(total_count=len(results))) | |
230 | |
231 def _deleteJID_gotDevices(self, results, bare_jid): | |
232 assert len(results) == 2 | |
233 active_success, active_devices = results[0] | |
234 inactive_success, inactive_devices = results[0] | |
235 d_list = [] | |
236 for success, devices in results: | |
237 if not success: | |
238 log.warning("Can't retrieve devices for {bare_jid}: {reason}" | |
239 .format(bare_jid=bare_jid, reason=active_devices)) | |
240 else: | |
241 for device_id in devices: | |
242 for key in (KEY_SESSION, KEY_TRUST): | |
243 k = u'\n'.join([key, bare_jid, unicode(device_id)]) | |
244 d_list.append(self.data.remove(k)) | |
245 | |
246 d_list.append(self.data.remove(KEY_ACTIVE_DEVICES, bare_jid)) | |
247 d_list.append(self.data.remove(KEY_INACTIVE_DEVICES, bare_jid)) | |
248 d_list.append(lambda __: self.all_jids.discard(bare_jid)) | |
249 # FIXME: there is a risk of race condition here, | |
250 # if self.all_jids is modified between discard and force) | |
251 d_list.append(lambda __: self.data.force(KEY_ALL_JIDS, self.all_jids)) | |
252 d = defer.DeferredList(d_list) | |
253 d.addCallback(self._deleteJID_logResults) | |
254 return d | |
255 | |
256 def deleteJID(self, callback, bare_jid): | |
257 """Retrieve all (in)actives of bare_jid, and delete all related keys""" | |
258 d_list = [] | |
259 | |
260 key = u'\n'.join([KEY_ACTIVE_DEVICES, bare_jid]) | |
261 d_list.append(self.data.get(key, [])) | |
262 | |
263 key = u'\n'.join([KEY_INACTIVE_DEVICES, bare_jid]) | |
264 d_inactive = self.data.get(key, {}) | |
265 # inactive devices are returned as a dict mapping from devices_id to timestamp | |
266 # but we only need devices ids | |
267 d_inactive.addCallback(lambda devices: [k for k, __ in devices]) | |
268 | |
269 d_list.append(d_inactive) | |
270 d = defer.DeferredList(d_list) | |
271 d.addCallback(self._deleteJID_gotDevices, bare_jid) | |
272 if callback is not None: | |
273 self.setCb(d, callback) | |
274 return d | |
275 | |
276 | |
277 class SatOTPKPolicy(omemo.DefaultOTPKPolicy): | |
278 pass | |
171 | 279 |
172 | 280 |
173 class OmemoSession(object): | 281 class OmemoSession(object): |
174 """Wrapper to use omemo.OmemoSession with Deferred""" | 282 """Wrapper to use omemo.OmemoSession with Deferred""" |
175 | 283 |
176 def __init__(self, session): | 284 def __init__(self, session): |
177 self._session = session | 285 self._session = session |
178 | 286 |
179 @property | 287 @property |
180 def state(self): | 288 def republish_bundle(self): |
181 return self._session.state | 289 return self._session.republish_bundle |
182 | 290 |
183 @staticmethod | 291 @property |
184 def promise2Deferred(promise_): | 292 def public_bundle(self): |
185 """Create a Deferred and fire it when promise is resolved | 293 return self._session.public_bundle |
186 | |
187 @param promise_(promise.Promise): promise to convert | |
188 @return (defer.Deferred): deferred instance linked to the promise | |
189 """ | |
190 d = defer.Deferred() | |
191 promise_.then(d.callback, d.errback) | |
192 return d | |
193 | 294 |
194 @classmethod | 295 @classmethod |
195 def create(cls, client, storage, my_device_id = None): | 296 def create(cls, client, storage, my_device_id = None): |
196 omemo_session_p = client._xep_0384_session = omemo.SessionManager.create( | 297 omemo_session_p = client._xep_0384_session = omemo.SessionManager.create( |
197 storage, | 298 storage, |
198 SatOTPKPolicy, | 299 SatOTPKPolicy, |
300 omemo_backend, | |
199 client.jid.userhost(), | 301 client.jid.userhost(), |
200 my_device_id) | 302 my_device_id) |
201 d = cls.promise2Deferred(omemo_session_p) | 303 d = promise2Deferred(omemo_session_p) |
202 d.addCallback(lambda session: cls(session)) | 304 d.addCallback(lambda session: cls(session)) |
203 return d | 305 return d |
204 | 306 |
205 def newDeviceList(self, devices, jid): | 307 def newDeviceList(self, jid, devices): |
206 jid = jid.userhost() | 308 jid = jid.userhost() |
207 new_device_p = self._session.newDeviceList(devices, jid) | 309 new_device_p = self._session.newDeviceList(jid, devices) |
208 return self.promise2Deferred(new_device_p) | 310 return promise2Deferred(new_device_p) |
209 | 311 |
210 def getDevices(self, bare_jid=None): | 312 def getDevices(self, bare_jid=None): |
211 get_devices_p = self._session.getDevices(bare_jid=bare_jid) | 313 get_devices_p = self._session.getDevices(bare_jid=bare_jid) |
212 return self.promise2Deferred(get_devices_p) | 314 return promise2Deferred(get_devices_p) |
213 | 315 |
214 def buildSession(self, bare_jid, device, bundle): | 316 def buildSession(self, bare_jid, device, bundle): |
215 bare_jid = bare_jid.userhost() | 317 bare_jid = bare_jid.userhost() |
216 build_session_p = self._session.buildSession(bare_jid, device, bundle) | 318 build_session_p = self._session.buildSession(bare_jid, device, bundle) |
217 return self.promise2Deferred(build_session_p) | 319 return promise2Deferred(build_session_p) |
218 | 320 |
219 def encryptMessage(self, bare_jids, message, bundles=None, devices=None, | 321 def encryptMessage(self, bare_jids, message, bundles=None, expect_problems=None): |
220 always_trust = False): | |
221 """Encrypt a message | 322 """Encrypt a message |
222 | 323 |
223 @param bare_jids(iterable[jid.JID]): destinees of the message | 324 @param bare_jids(iterable[jid.JID]): destinees of the message |
224 @param message(unicode): message to encode | 325 @param message(unicode): message to encode |
225 @param bundles(dict[jid.JID, dict[int, ExtendedPublicBundle]): | 326 @param bundles(dict[jid.JID, dict[int, ExtendedPublicBundle]): |
226 entities => devices => bundles map | 327 entities => devices => bundles map |
227 @param devices(iterable[int], None): devices to encode for | |
228 @param always_trust(bool): TODO | |
229 @return D(dict): encryption data | 328 @return D(dict): encryption data |
230 """ | 329 """ |
231 if isinstance(bare_jids, jid.JID): | 330 if isinstance(bare_jids, jid.JID): |
232 bare_jids = bare_jids.userhost() | 331 bare_jids = bare_jids.userhost() |
233 else: | 332 else: |
236 bundles = {e.userhost(): v for e, v in bundles.iteritems()} | 335 bundles = {e.userhost(): v for e, v in bundles.iteritems()} |
237 encrypt_mess_p = self._session.encryptMessage( | 336 encrypt_mess_p = self._session.encryptMessage( |
238 bare_jids=bare_jids, | 337 bare_jids=bare_jids, |
239 plaintext=message.encode('utf-8'), | 338 plaintext=message.encode('utf-8'), |
240 bundles=bundles, | 339 bundles=bundles, |
241 devices=devices, | 340 expect_problems=expect_problems) |
242 always_trust=always_trust) | 341 return promise2Deferred(encrypt_mess_p) |
243 return self.promise2Deferred(encrypt_mess_p) | |
244 | 342 |
245 def decryptMessage(self, bare_jid, device, iv, message, is_pre_key_message, | 343 def decryptMessage(self, bare_jid, device, iv, message, is_pre_key_message, |
246 payload=None, from_storage=False): | 344 ciphertext, additional_information=None, allow_untrusted=False): |
247 bare_jid = bare_jid.userhost() | 345 bare_jid = bare_jid.userhost() |
248 decrypt_mess_p = self._session.decryptMessage( | 346 decrypt_mess_p = self._session.decryptMessage( |
249 bare_jid=bare_jid, | 347 bare_jid=bare_jid, |
250 device=device, | 348 device=device, |
251 iv=iv, | 349 iv=iv, |
252 message=message, | 350 message=message, |
253 is_pre_key_message=is_pre_key_message, | 351 is_pre_key_message=is_pre_key_message, |
254 payload=payload, | 352 ciphertext=ciphertext, |
255 from_storage=from_storage) | 353 additional_information=additional_information, |
256 return self.promise2Deferred(decrypt_mess_p) | 354 allow_untrusted=allow_untrusted |
355 ) | |
356 return promise2Deferred(decrypt_mess_p) | |
357 | |
358 def trust(self, bare_jid, device, key): | |
359 bare_jid = bare_jid.userhost() | |
360 trust_p = self._session.trust( | |
361 bare_jid=bare_jid, | |
362 device=device, | |
363 key=key) | |
364 return promise2Deferred(trust_p) | |
365 | |
366 def distrust(self, bare_jid, device, key): | |
367 bare_jid = bare_jid.userhost() | |
368 distrust_p = self._session.distrust( | |
369 bare_jid=bare_jid, | |
370 device=device, | |
371 key=key) | |
372 return promise2Deferred(distrust_p) | |
373 | |
374 def getTrustForJID(self, bare_jid): | |
375 bare_jid = bare_jid.userhost() | |
376 get_trust_p = self._session.getTrustForJID(bare_jid=bare_jid) | |
377 return promise2Deferred(get_trust_p) | |
257 | 378 |
258 | 379 |
259 class OMEMO(object): | 380 class OMEMO(object): |
381 | |
260 def __init__(self, host): | 382 def __init__(self, host): |
261 log.info(_(u"OMEMO plugin initialization")) | 383 log.info(_(u"OMEMO plugin initialization (omemo module v{version})").format( |
384 version=omemo.__version__)) | |
385 version = tuple(map(int, omemo.__version__.split(u'.')[:3])) | |
386 if version < OMEMO_MIN_VER: | |
387 log.warning(_( | |
388 u"Your version of omemo module is too old: {v[0]}.{v[1]}.{v[2]} is " | |
389 u"minimum required), please update.").format(v=OMEMO_MIN_VER)) | |
390 raise exceptions.CancelError("module is too old") | |
262 self.host = host | 391 self.host = host |
263 self._p_hints = host.plugins[u"XEP-0334"] | 392 self._p_hints = host.plugins[u"XEP-0334"] |
264 self._p_carbons = host.plugins[u"XEP-0280"] | 393 self._p_carbons = host.plugins[u"XEP-0280"] |
265 self._p = host.plugins[u"XEP-0060"] | 394 self._p = host.plugins[u"XEP-0060"] |
266 host.trigger.add("MessageReceived", self._messageReceivedTrigger, priority=100050) | 395 host.trigger.add("MessageReceived", self._messageReceivedTrigger, priority=100050) |
268 self.host.registerEncryptionPlugin(self, u"OMEMO", NS_OMEMO, 100) | 397 self.host.registerEncryptionPlugin(self, u"OMEMO", NS_OMEMO, 100) |
269 pep = host.plugins['XEP-0163'] | 398 pep = host.plugins['XEP-0163'] |
270 pep.addPEPEvent("OMEMO_DEVICES", NS_OMEMO_DEVICES, self.onNewDevices) | 399 pep.addPEPEvent("OMEMO_DEVICES", NS_OMEMO_DEVICES, self.onNewDevices) |
271 | 400 |
272 @defer.inlineCallbacks | 401 @defer.inlineCallbacks |
402 def trustUICb(self, xmlui_data, trust_data, expect_problems=None, | |
403 profile=C.PROF_KEY_NONE): | |
404 if C.bool(xmlui_data.get('cancelled', 'false')): | |
405 defer.returnValue({}) | |
406 client = self.host.getClient(profile) | |
407 session = client._xep_0384_session | |
408 answer = xml_tools.XMLUIResult2DataFormResult(xmlui_data) | |
409 for key, value in answer.iteritems(): | |
410 if key.startswith(u'trust_'): | |
411 trust_id = key[6:] | |
412 else: | |
413 continue | |
414 data = trust_data[trust_id] | |
415 trust = C.bool(value) | |
416 if trust: | |
417 yield session.trust(data[u"jid"], | |
418 data[u"device"], | |
419 data[u"ik"]) | |
420 else: | |
421 yield session.distrust(data[u"jid"], | |
422 data[u"device"], | |
423 data[u"ik"]) | |
424 if expect_problems is not None: | |
425 expect_problems.setdefault(data.bare_jid, set()).add(data.device) | |
426 defer.returnValue({}) | |
427 | |
428 | |
429 | |
430 @defer.inlineCallbacks | |
431 def getTrustUI(self, client, entity_jid=None, trust_data=None, submit_id=None): | |
432 """Generate a XMLUI to manage trust | |
433 | |
434 @param entity_jid(None, jid.JID): jid of entity to manage | |
435 None to use trust_data | |
436 @param trust_data(None, dict): devices data: | |
437 None to use entity_jid | |
438 else a dict mapping from trust ids (unicode) to devices data, | |
439 where a device data must have the following keys: | |
440 - jid(jid.JID): bare jid of the device owner | |
441 - device(int): device id | |
442 - ik(bytes): identity key | |
443 and may have the following key: | |
444 - trusted(bool): True if device is trusted | |
445 @param submit_id(None, unicode): submit_id to use | |
446 if None set UI callback to trustUICb | |
447 @return D(xmlui): trust management form | |
448 """ | |
449 # we need entity_jid xor trust_data | |
450 assert entity_jid and not trust_data or not entity_jid and trust_data | |
451 if entity_jid.resource: | |
452 raise ValueError(u"A bare jid is expected") | |
453 | |
454 session = client._xep_0384_session | |
455 | |
456 if trust_data is None: | |
457 trust_data = {} | |
458 trust_session_data = yield session.getTrustForJID(entity_jid) | |
459 bare_jid_s = entity_jid.userhost() | |
460 for device_id, trust_info in trust_session_data['active'].iteritems(): | |
461 ik = trust_info["key"] | |
462 trust_id = unicode(hash((bare_jid_s, device_id, ik))) | |
463 trust_data[trust_id] = { | |
464 u"jid": entity_jid, | |
465 u"device": device_id, | |
466 u"ik": ik, | |
467 u"trusted": trust_info[u"trusted"], | |
468 } | |
469 | |
470 if submit_id is None: | |
471 submit_id = self.host.registerCallback(partial(self.trustUICb, | |
472 trust_data=trust_data), | |
473 with_data=True, | |
474 one_shot=True) | |
475 xmlui = xml_tools.XMLUI( | |
476 panel_type = C.XMLUI_FORM, | |
477 title = D_(u"OMEMO trust management"), | |
478 submit_id = submit_id | |
479 ) | |
480 xmlui.addText(D_( | |
481 u"This is OMEMO trusting system. You'll see below the devices of your " | |
482 u"contacts, and a checkbox to trust them or not. A trusted device " | |
483 u"can read your messages in plain text, so be sure to only validate " | |
484 u"devices that you are sure are belonging to your contact. It's better " | |
485 u"to do this when you are next to your contact and her/his device, so " | |
486 u"you can check the \"fingerprint\" (the number next to the device) " | |
487 u"yourself. Do *not* validate a device if the fingerprint is wrong!")) | |
488 | |
489 xmlui.changeContainer("label") | |
490 xmlui.addLabel(D_(u"This device ID")) | |
491 xmlui.addText(unicode(client._xep_0384_device_id)) | |
492 xmlui.addLabel(D_(u"This device fingerprint")) | |
493 ik_hex = session.public_bundle.ik.encode('hex').upper() | |
494 fp_human = u' '.join([ik_hex[i:i+8] for i in range(0, len(ik_hex), 8)]) | |
495 xmlui.addText(fp_human) | |
496 xmlui.addEmpty() | |
497 xmlui.addEmpty() | |
498 | |
499 | |
500 for trust_id, data in trust_data.iteritems(): | |
501 xmlui.addLabel(D_(u"Contact")) | |
502 xmlui.addJid(data[u'jid']) | |
503 xmlui.addLabel(D_(u"Device ID")) | |
504 xmlui.addText(unicode(data[u'device'])) | |
505 xmlui.addLabel(D_(u"Fingerprint")) | |
506 ik_hex = data[u'ik'].encode('hex').upper() | |
507 fp_human = u' '.join([ik_hex[i:i+8] for i in range(0, len(ik_hex), 8)]) | |
508 xmlui.addText(fp_human) | |
509 xmlui.addLabel(D_(u"Trust this device?")) | |
510 xmlui.addBool(u"trust_{}".format(trust_id), | |
511 value=C.boolConst(data.get(u'trusted', False))) | |
512 | |
513 xmlui.addEmpty() | |
514 xmlui.addEmpty() | |
515 | |
516 defer.returnValue(xmlui) | |
517 | |
518 @defer.inlineCallbacks | |
519 def _purgeOldData(self, client, persistent_dict): | |
520 # FIXME: temporary method to deal with data change in omemo module | |
521 # We remove the old data, which is acceptable as | |
522 # no release of SàT (beside alpha versions) has been done | |
523 # since this data has been used. | |
524 # /!\ this method must be removed before 0.7 release /!\ | |
525 log.warning(u"FIXME: Using temporary purgeOldData code, to be removed before 0.7 release.") | |
526 | |
527 state = yield persistent_dict.get(KEY_STATE) | |
528 if state and "version" not in state: | |
529 log.info(u"Old data found, purging it") | |
530 self.host.memory.storage.delPrivateNamespace("XEP-0384", binary=True, | |
531 profile=client.profile) | |
532 | |
533 @defer.inlineCallbacks | |
273 def profileConnected(self, client): | 534 def profileConnected(self, client): |
535 client._xep_0384_ready = defer.Deferred() | |
274 # we first need to get devices ids (including our own) | 536 # we first need to get devices ids (including our own) |
275 persistent_dict = persistent.LazyPersistentBinaryDict("XEP-0384", client.profile) | 537 persistent_dict = persistent.LazyPersistentBinaryDict("XEP-0384", client.profile) |
538 yield self._purgeOldData(client, persistent_dict) | |
276 # all known devices of profile | 539 # all known devices of profile |
277 devices = yield self.getDevices(client) | 540 devices = yield self.getDevices(client) |
278 # and our own device id | 541 # and our own device id |
279 device_id = yield persistent_dict.get(KEY_DEVICE_ID) | 542 device_id = yield persistent_dict.get(KEY_DEVICE_ID) |
280 if device_id is None: | 543 if device_id is None: |
544 log.info(_(u"We have no identity for this device yet, let's generate one")) | |
281 # we have a new device, we create device_id | 545 # we have a new device, we create device_id |
282 device_id = random.randint(1, 2**31-1) | 546 device_id = random.randint(1, 2**31-1) |
283 # we check that it's really unique | 547 # we check that it's really unique |
284 while device_id in devices: | 548 while device_id in devices: |
285 device_id = random.randint(1, 2**31-1) | 549 device_id = random.randint(1, 2**31-1) |
288 | 552 |
289 if device_id not in devices: | 553 if device_id not in devices: |
290 devices.add(device_id) | 554 devices.add(device_id) |
291 yield self.setDevices(client, devices) | 555 yield self.setDevices(client, devices) |
292 | 556 |
293 omemo_storage = OmemoStorage(client, device_id, persistent_dict) | 557 all_jids = yield persistent_dict.get(KEY_ALL_JIDS, set()) |
558 | |
559 omemo_storage = OmemoStorage(client, device_id, all_jids, persistent_dict) | |
294 omemo_session = yield OmemoSession.create(client, omemo_storage, device_id) | 560 omemo_session = yield OmemoSession.create(client, omemo_storage, device_id) |
295 client._xep_0384_cache = {} | 561 client._xep_0384_cache = {} |
296 client._xep_0384_session = omemo_session | 562 client._xep_0384_session = omemo_session |
297 client._xep_0384_device_id = device_id | 563 client._xep_0384_device_id = device_id |
298 yield omemo_session.newDeviceList(devices, client.jid) | 564 yield omemo_session.newDeviceList(client.jid, devices) |
299 if omemo_session.state.changed: | 565 if omemo_session.republish_bundle: |
300 log.info(_(u"Saving public bundle for this device ({device_id})").format( | 566 log.info(_(u"Saving public bundle for this device ({device_id})").format( |
301 device_id=device_id)) | 567 device_id=device_id)) |
302 bundle = omemo_session.state.getPublicBundle() | 568 yield self.setBundle(client, omemo_session.public_bundle, device_id) |
303 yield self.setBundle(client, bundle, device_id) | 569 client._xep_0384_ready.callback(None) |
570 del client._xep_0384_ready | |
304 | 571 |
305 ## XMPP PEP nodes manipulation | 572 ## XMPP PEP nodes manipulation |
306 | 573 |
307 # devices | 574 # devices |
308 | 575 |
374 def getBundles(self, client, entity_jid, devices_ids): | 641 def getBundles(self, client, entity_jid, devices_ids): |
375 """Retrieve public bundles of an entity devices | 642 """Retrieve public bundles of an entity devices |
376 | 643 |
377 @param entity_jid(jid.JID): bare jid of entity | 644 @param entity_jid(jid.JID): bare jid of entity |
378 @param devices_id(iterable[int]): ids of the devices bundles to retrieve | 645 @param devices_id(iterable[int]): ids of the devices bundles to retrieve |
379 @return (dict[int, ExtendedPublicBundle]): bundles collection | 646 @return (tuple(dict[int, ExtendedPublicBundle], list(int))): |
380 key is device_id | 647 - bundles collection: |
381 value is parsed bundle | 648 * key is device_id |
649 * value is parsed bundle | |
650 - set of bundles not found | |
382 """ | 651 """ |
383 assert not entity_jid.resource | 652 assert not entity_jid.resource |
384 bundles = {} | 653 bundles = {} |
654 missing = set() | |
385 for device_id in devices_ids: | 655 for device_id in devices_ids: |
386 node = NS_OMEMO_BUNDLE.format(device_id=device_id) | 656 node = NS_OMEMO_BUNDLE.format(device_id=device_id) |
387 try: | 657 try: |
388 items, metadata = yield self._p.getItems(client, entity_jid, node) | 658 items, metadata = yield self._p.getItems(client, entity_jid, node) |
389 except Exception as e: | 659 except error.StanzaError as e: |
390 log.warning(_(u"Can't get bundle for device {device_id}: {reason}") | 660 if e.condition == u"item-not-found": |
391 .format(device_id=device_id, reason=e)) | 661 log.warning(_(u"Bundle missing for device {device_id}") |
392 continue | 662 .format(device_id=device_id)) |
663 missing.add(device_id) | |
664 continue | |
665 else: | |
666 log.warning(_(u"Can't get bundle for device {device_id}: {reason}") | |
667 .format(device_id=device_id, reason=e)) | |
668 continue | |
393 if not items: | 669 if not items: |
394 log.warning(_(u"no item found in node {node}, can't get public bundle " | 670 log.warning(_(u"no item found in node {node}, can't get public bundle " |
395 u"for device {device_id}").format(node=node, | 671 u"for device {device_id}").format(node=node, |
396 device_id=device_id)) | 672 device_id=device_id)) |
397 continue | 673 continue |
417 try: | 693 try: |
418 spkPublic = base64.b64decode(unicode(signedPreKeyPublic_elt)) | 694 spkPublic = base64.b64decode(unicode(signedPreKeyPublic_elt)) |
419 spkSignature = base64.b64decode( | 695 spkSignature = base64.b64decode( |
420 unicode(signedPreKeySignature_elt)) | 696 unicode(signedPreKeySignature_elt)) |
421 | 697 |
422 identityKey = base64.b64decode(unicode(identityKey_elt)) | 698 ik = base64.b64decode(unicode(identityKey_elt)) |
423 spk = { | 699 spk = { |
424 "key": wireformat.decodePublicKey(spkPublic), | 700 "key": spkPublic, |
425 "id": int(signedPreKeyPublic_elt['signedPreKeyId']) | 701 "id": int(signedPreKeyPublic_elt['signedPreKeyId']) |
426 } | 702 } |
427 ik = wireformat.decodePublicKey(identityKey) | |
428 otpks = [] | 703 otpks = [] |
429 for preKeyPublic_elt in prekeys_elt.elements(NS_OMEMO, 'preKeyPublic'): | 704 for preKeyPublic_elt in prekeys_elt.elements(NS_OMEMO, 'preKeyPublic'): |
430 preKeyPublic = base64.b64decode(unicode(preKeyPublic_elt)) | 705 preKeyPublic = base64.b64decode(unicode(preKeyPublic_elt)) |
431 otpk = { | 706 otpk = { |
432 "key": wireformat.decodePublicKey(preKeyPublic), | 707 "key": preKeyPublic, |
433 "id": int(preKeyPublic_elt['preKeyId']) | 708 "id": int(preKeyPublic_elt['preKeyId']) |
434 } | 709 } |
435 otpks.append(otpk) | 710 otpks.append(otpk) |
436 | 711 |
437 except Exception as e: | 712 except Exception as e: |
438 log.warning(_(u"error while decoding key for device {device_id}: {msg}") | 713 log.warning(_(u"error while decoding key for device {device_id}: {msg}") |
439 .format(device_id=device_id, msg=e)) | 714 .format(device_id=device_id, msg=e)) |
440 continue | 715 continue |
441 | 716 |
442 bundles[device_id] = ExtendedPublicBundle(ik, spk, spkSignature, otpks) | 717 bundles[device_id] = ExtendedPublicBundle.parse(omemo_backend, ik, spk, |
443 | 718 spkSignature, otpks) |
444 defer.returnValue(bundles) | 719 |
720 defer.returnValue((bundles, missing)) | |
445 | 721 |
446 def setBundleEb(self, failure_): | 722 def setBundleEb(self, failure_): |
447 log.warning(_(u"Can't set bundle: {reason}").format(reason=failure_)) | 723 log.warning(_(u"Can't set bundle: {reason}").format(reason=failure_)) |
448 | 724 |
449 def setBundle(self, client, bundle, device_id): | 725 def setBundle(self, client, bundle, device_id): |
450 """Set public bundle for this device. | 726 """Set public bundle for this device. |
451 | 727 |
452 @param bundle(ExtendedPublicBundle): bundle to publish | 728 @param bundle(ExtendedPublicBundle): bundle to publish |
453 """ | 729 """ |
454 log.debug(_(u"updating bundle for {device_id}").format(device_id=device_id)) | 730 log.debug(_(u"updating bundle for {device_id}").format(device_id=device_id)) |
731 bundle = bundle.serialize(omemo_backend) | |
455 bundle_elt = domish.Element((NS_OMEMO, 'bundle')) | 732 bundle_elt = domish.Element((NS_OMEMO, 'bundle')) |
456 signedPreKeyPublic_elt = bundle_elt.addElement( | 733 signedPreKeyPublic_elt = bundle_elt.addElement( |
457 "signedPreKeyPublic", | 734 "signedPreKeyPublic", |
458 content=b64enc(wireformat.encodePublicKey(bundle.spk['key']))) | 735 content=b64enc(bundle["spk"]['key'])) |
459 signedPreKeyPublic_elt['signedPreKeyId'] = unicode(bundle.spk['id']) | 736 signedPreKeyPublic_elt['signedPreKeyId'] = unicode(bundle["spk"]['id']) |
460 | 737 |
461 bundle_elt.addElement( | 738 bundle_elt.addElement( |
462 "signedPreKeySignature", | 739 "signedPreKeySignature", |
463 content=b64enc(bundle.spk_signature)) | 740 content=b64enc(bundle["spk_signature"])) |
464 | 741 |
465 bundle_elt.addElement( | 742 bundle_elt.addElement( |
466 "identityKey", | 743 "identityKey", |
467 content=b64enc(wireformat.encodePublicKey(bundle.ik))) | 744 content=b64enc(bundle["ik"])) |
468 | 745 |
469 prekeys_elt = bundle_elt.addElement('prekeys') | 746 prekeys_elt = bundle_elt.addElement('prekeys') |
470 for otpk in bundle.otpks: | 747 for otpk in bundle["otpks"]: |
471 preKeyPublic_elt = prekeys_elt.addElement( | 748 preKeyPublic_elt = prekeys_elt.addElement( |
472 'preKeyPublic', | 749 'preKeyPublic', |
473 content=b64enc(wireformat.encodePublicKey(otpk["key"]))) | 750 content=b64enc(otpk["key"])) |
474 preKeyPublic_elt['preKeyId'] = unicode(otpk['id']) | 751 preKeyPublic_elt['preKeyId'] = unicode(otpk['id']) |
475 | 752 |
476 node = NS_OMEMO_BUNDLE.format(device_id=device_id) | 753 node = NS_OMEMO_BUNDLE.format(device_id=device_id) |
477 d = self._p.sendItem(client, None, node, bundle_elt, item_id=self._p.ID_SINGLETON) | 754 d = self._p.sendItem(client, None, node, bundle_elt, item_id=self._p.ID_SINGLETON) |
478 d.addErrback(self.setBundleEb) | 755 d.addErrback(self.setBundleEb) |
486 cache = client._xep_0384_cache | 763 cache = client._xep_0384_cache |
487 omemo_session = client._xep_0384_session | 764 omemo_session = client._xep_0384_session |
488 entity = itemsEvent.sender | 765 entity = itemsEvent.sender |
489 entity_cache = cache.setdefault(entity, {}) | 766 entity_cache = cache.setdefault(entity, {}) |
490 devices = self.parseDevices(itemsEvent.items) | 767 devices = self.parseDevices(itemsEvent.items) |
491 omemo_session.newDeviceList(devices, entity) | 768 omemo_session.newDeviceList(entity, devices) |
492 missing_devices = devices.difference(entity_cache.keys()) | 769 missing_devices = devices.difference(entity_cache.keys()) |
493 if missing_devices: | 770 if missing_devices: |
494 missing_bundles = yield self.getBundles(client, entity, missing_devices) | 771 bundles, bundles_not_found = yield self.getBundles( |
495 entity_cache.update(missing_bundles) | 772 client, entity, missing_devices) |
773 entity_cache.update(bundles) | |
774 if bundles_not_found and entity == client.jid.userhostJID(): | |
775 # we have devices announced in our own public list | |
776 # with missing bundles | |
777 own_device = client._xep_0384_device_id | |
778 if own_device in bundles_not_found: | |
779 log.warning(_(u"Our own device has no attached bundle, fixing it")) | |
780 bundles_not_found.remove(own_device) | |
781 yield self.setBundle(client, omemo_session.public_bundle, own_device) | |
782 | |
783 if bundles_not_found: | |
784 # some announced devices have no bundle, we update our public | |
785 # list to remove missing devices. | |
786 log.warning(_( | |
787 u"Some devices have missing bundles, cleaning out public " | |
788 u"devices list")) | |
789 existing_devices = devices - bundles_not_found | |
790 yield self.setDevices(client, existing_devices) | |
496 | 791 |
497 ## triggers | 792 ## triggers |
793 | |
794 @defer.inlineCallbacks | |
795 def handleProblems(self, client, bundles, problems): | |
796 """Try to solve problem found by EncryptMessage | |
797 | |
798 @param bundles(dict): bundles data as used in EncryptMessage | |
799 already filled with known bundles, missing bundles | |
800 need to be added to it | |
801 @param problems(list): exceptions raised by EncryptMessage | |
802 @return (dict): expect_problems arguments, used in EncryptMessage | |
803 this dict will list devices where problems can be ignored | |
804 (those devices won't receive the encrypted data) | |
805 """ | |
806 # FIXME: not all problems are handled yet | |
807 untrusted = {} | |
808 expect_problems = {} | |
809 for problem in problems: | |
810 if isinstance(problem, omemo_excpt.UntrustedException): | |
811 untrusted[unicode(hash(problem))] = problem | |
812 elif isinstance(problem, omemo_excpt.NoEligibleDevicesException): | |
813 pass | |
814 | |
815 if untrusted: | |
816 trust_data = {} | |
817 for device_id, data in untrusted.iteritems(): | |
818 trust_data[u'jid'] = jid.JID(data.bare_jid) | |
819 trust_data[u'device'] = data.device | |
820 trust_data[u'ik'] = data.ik | |
821 | |
822 xmlui = yield self.getTrustUI(client, trust_data, submit_id=u"") | |
823 | |
824 answer = yield xml_tools.deferXMLUI( | |
825 self.host, | |
826 xmlui, | |
827 action_extra={ | |
828 u"meta_encryption_trust": NS_OMEMO, | |
829 }, | |
830 profile=client.profile) | |
831 yield self.trustUICb(answer, trust_data, expect_problems, client.profile) | |
832 | |
833 defer.returnValue(expect_problems) | |
498 | 834 |
499 @defer.inlineCallbacks | 835 @defer.inlineCallbacks |
500 def encryptMessage(self, client, entity_bare_jid, message): | 836 def encryptMessage(self, client, entity_bare_jid, message): |
501 omemo_session = client._xep_0384_session | 837 omemo_session = client._xep_0384_session |
838 cache = client._xep_0384_cache | |
502 try: | 839 try: |
503 bundles = client._xep_0384_cache[entity_bare_jid] | 840 bundles = {entity_bare_jid: cache[entity_bare_jid]} |
504 except KeyError: | 841 except KeyError: |
505 raise exceptions.NotFound(_(u"No OMEMO encryption information found for this" | 842 # No devices know for this entity, let try to find them. |
506 u"contact ({entity})".format( | 843 # This can happen if the entity is not in our roster, or doesn't handle OMEMO |
507 entity=entity_bare_jid.full()))) | 844 # or if we haven't received the devices from PEP yet. |
508 encrypted = yield omemo_session.encryptMessage( | 845 try: |
509 entity_bare_jid, | 846 devices = yield self.getDevices(client, entity_bare_jid) |
510 message, | 847 bundles, __ = yield self.getBundles(client, entity_bare_jid, devices) |
511 {entity_bare_jid: bundles}) | 848 except Exception as e: |
849 raise exceptions.NotFound( | |
850 _(u"Can retrieve bundles for {entity}: {reason}" .format( | |
851 entity=entity_bare_jid.full(), reason=e))) | |
852 else: | |
853 cache[entity_bare_jid] = bundles | |
854 bundles = {entity_bare_jid: bundles} | |
855 | |
856 own_jid = client.jid.userhostJID() | |
857 if entity_bare_jid != own_jid: | |
858 # message will be copied to our devices, so we need to add our own bundles | |
859 bundles[own_jid] = cache[own_jid] | |
860 | |
861 try: | |
862 # first try may fail, in case of e.g. trust issue or missing bundle | |
863 encrypted = yield omemo_session.encryptMessage( | |
864 entity_bare_jid, | |
865 message, | |
866 bundles) | |
867 except omemo_excpt.EncryptionProblemsException as e: | |
868 # we know the problem to solve, we can try to fix them | |
869 expect_problems = yield self.handleProblems(client, bundles, e.problems) | |
870 # and try an encryption again. | |
871 try: | |
872 encrypted = yield omemo_session.encryptMessage( | |
873 entity_bare_jid, | |
874 message, | |
875 bundles, | |
876 expect_problems = expect_problems) | |
877 except omemo_excpt.EncryptionProblemsException as e: | |
878 log.warning( | |
879 _(u"Can't encrypt message for {entity}: {reason}".format( | |
880 entity=entity_bare_jid.full(), reason=e))) | |
881 raise e | |
882 | |
512 defer.returnValue(encrypted) | 883 defer.returnValue(encrypted) |
513 | 884 |
514 @defer.inlineCallbacks | 885 @defer.inlineCallbacks |
515 def _messageReceivedTrigger(self, client, message_elt, post_treat): | 886 def _messageReceivedTrigger(self, client, message_elt, post_treat): |
516 if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT: | 887 if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT: |
521 # no OMEMO message here | 892 # no OMEMO message here |
522 defer.returnValue(True) | 893 defer.returnValue(True) |
523 | 894 |
524 # we have an encrypted message let's decrypt it | 895 # we have an encrypted message let's decrypt it |
525 from_jid = jid.JID(message_elt['from']) | 896 from_jid = jid.JID(message_elt['from']) |
526 omemo_session = client._xep_0384_session | 897 try: |
898 omemo_session = client._xep_0384_session | |
899 except AttributeError: | |
900 # on startup, message can ve received before session actually exists | |
901 # so we need to synchronise here | |
902 yield client._xep_0384_ready | |
903 omemo_session = client._xep_0384_session | |
904 | |
527 device_id = client._xep_0384_device_id | 905 device_id = client._xep_0384_device_id |
528 try: | 906 try: |
529 header_elt = next(encrypted_elt.elements(NS_OMEMO, u'header')) | 907 header_elt = next(encrypted_elt.elements(NS_OMEMO, u'header')) |
530 iv_elt = next(header_elt.elements(NS_OMEMO, u'iv')) | 908 iv_elt = next(header_elt.elements(NS_OMEMO, u'iv')) |
531 except StopIteration: | 909 except StopIteration: |
541 defer.returnValue(False) | 919 defer.returnValue(False) |
542 try: | 920 try: |
543 key_elt = next((e for e in header_elt.elements(NS_OMEMO, u'key') | 921 key_elt = next((e for e in header_elt.elements(NS_OMEMO, u'key') |
544 if int(e[u'rid']) == device_id)) | 922 if int(e[u'rid']) == device_id)) |
545 except StopIteration: | 923 except StopIteration: |
546 log.warning(_(u"This OMEMO encrypted stanza has not been encrypted" | 924 log.warning(_(u"This OMEMO encrypted stanza has not been encrypted " |
547 u"for our device ({device_id}): {xml}").format( | 925 u"for our device (device_id: {device_id}, fingerprint: " |
548 device_id=device_id, xml=encrypted_elt.toXml())) | 926 u"{fingerprint}): {xml}").format( |
927 device_id=device_id, | |
928 fingerprint=omemo_session.public_bundle.ik.encode('hex'), | |
929 xml=encrypted_elt.toXml())) | |
549 defer.returnValue(False) | 930 defer.returnValue(False) |
550 except ValueError as e: | 931 except ValueError as e: |
551 log.warning(_(u"Invalid recipient ID: {msg}".format(msg=e))) | 932 log.warning(_(u"Invalid recipient ID: {msg}".format(msg=e))) |
552 defer.returnValue(False) | 933 defer.returnValue(False) |
553 is_pre_key = C.bool(key_elt.getAttribute('prekey', 'false')) | 934 is_pre_key = C.bool(key_elt.getAttribute('prekey', 'false')) |
554 payload_elt = next(encrypted_elt.elements(NS_OMEMO, u'payload'), None) | 935 payload_elt = next(encrypted_elt.elements(NS_OMEMO, u'payload'), None) |
555 | 936 additional_information = { |
937 "from_storage": bool(message_elt.delay) | |
938 } | |
939 | |
940 kwargs = { | |
941 "bare_jid": from_jid.userhostJID(), | |
942 "device": s_device_id, | |
943 "iv": base64.b64decode(bytes(iv_elt)), | |
944 "message": base64.b64decode(bytes(key_elt)), | |
945 "is_pre_key_message": is_pre_key, | |
946 "ciphertext": base64.b64decode(bytes(payload_elt)) | |
947 if payload_elt is not None else None, | |
948 "additional_information": additional_information, | |
949 } | |
556 try: | 950 try: |
557 cipher, plaintext = yield omemo_session.decryptMessage( | 951 try: |
558 bare_jid=from_jid.userhostJID(), | 952 plaintext = yield omemo_session.decryptMessage(**kwargs) |
559 device=s_device_id, | 953 except omemo_excpt.UntrustedException: |
560 iv=base64.b64decode(bytes(iv_elt)), | 954 post_treat.addCallback(client.encryption.markAsUntrusted) |
561 message=base64.b64decode(bytes(key_elt)), | 955 kwargs['allow_untrusted'] = True |
562 is_pre_key_message=is_pre_key, | 956 plaintext = yield omemo_session.decryptMessage(**kwargs) |
563 payload=base64.b64decode(bytes(payload_elt)) | |
564 if payload_elt is not None else None, | |
565 from_storage=False | |
566 ) | |
567 except Exception as e: | 957 except Exception as e: |
568 log.error(_(u"Can't decrypt message: {reason}\n{xml}").format( | 958 log.warning(_(u"Can't decrypt message: {reason}\n{xml}").format( |
569 reason=e, xml=message_elt.toXml())) | 959 reason=e, xml=message_elt.toXml())) |
570 defer.returnValue(False) | 960 defer.returnValue(False) |
571 if omemo_session.state.changed: | 961 if omemo_session.republish_bundle: |
572 bundle = omemo_session.state.getPublicBundle() | |
573 # we don't wait for the Deferred (i.e. no yield) on purpose | 962 # we don't wait for the Deferred (i.e. no yield) on purpose |
574 # there is no need to block the whole message workflow while | 963 # there is no need to block the whole message workflow while |
575 # updating the bundle | 964 # updating the bundle |
576 self.setBundle(client, bundle, device_id) | 965 self.setBundle(client, omemo_session.public_bundle, device_id) |
577 | 966 |
578 message_elt.children.remove(encrypted_elt) | 967 message_elt.children.remove(encrypted_elt) |
579 if plaintext: | 968 if plaintext: |
580 message_elt.addElement("body", content=plaintext.decode('utf-8')) | 969 message_elt.addElement("body", content=plaintext.decode('utf-8')) |
581 post_treat.addCallback(client.encryption.markAsEncrypted) | 970 post_treat.addCallback(client.encryption.markAsEncrypted) |
610 encrypted_elt = message_elt.addElement((NS_OMEMO, 'encrypted')) | 999 encrypted_elt = message_elt.addElement((NS_OMEMO, 'encrypted')) |
611 header_elt = encrypted_elt.addElement('header') | 1000 header_elt = encrypted_elt.addElement('header') |
612 header_elt['sid'] = unicode(encryption_data['sid']) | 1001 header_elt['sid'] = unicode(encryption_data['sid']) |
613 bare_jid_s = to_jid.userhost() | 1002 bare_jid_s = to_jid.userhost() |
614 | 1003 |
615 for message in (m for m in encryption_data['messages'] | 1004 for rid, data in encryption_data['keys'][bare_jid_s].iteritems(): |
616 if m['bare_jid'] == bare_jid_s): | |
617 key_elt = header_elt.addElement( | 1005 key_elt = header_elt.addElement( |
618 'key', | 1006 'key', |
619 content=b64enc(message['message'])) | 1007 content=b64enc(data['data'])) |
620 key_elt['rid'] = unicode(message['rid']) | 1008 key_elt['rid'] = unicode(rid) |
621 if message['pre_key']: | 1009 if data['pre_key']: |
622 key_elt['prekey'] = 'true' | 1010 key_elt['prekey'] = 'true' |
623 | 1011 |
624 header_elt.addElement( | 1012 header_elt.addElement( |
625 'iv', | 1013 'iv', |
626 content=b64enc(encryption_data['iv'])) | 1014 content=b64enc(encryption_data['iv'])) |