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']))