Mercurial > libervia-backend
comparison sat/plugins/plugin_xep_0384.py @ 2648:0f76813afc57
plugin XEP-0384: OMEMO implementation first draft:
this is the initial implementation of OMEMO encryption using python omemo module.
/!\ This implementation is not yet working /!\
author | Goffi <goffi@goffi.org> |
---|---|
date | Sun, 29 Jul 2018 19:24:21 +0200 |
parents | |
children | e7bfbded652a |
comparison
equal
deleted
inserted
replaced
2647:1bf7e89fded0 | 2648:0f76813afc57 |
---|---|
1 #!/usr/bin/env python2 | |
2 # -*- coding: utf-8 -*- | |
3 | |
4 # SAT plugin for OMEMO encryption | |
5 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org) | |
6 | |
7 # This program is free software: you can redistribute it and/or modify | |
8 # it under the terms of the GNU Affero General Public License as published by | |
9 # the Free Software Foundation, either version 3 of the License, or | |
10 # (at your option) any later version. | |
11 | |
12 # This program is distributed in the hope that it will be useful, | |
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 # GNU Affero General Public License for more details. | |
16 | |
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/>. | |
19 | |
20 from sat.core.i18n import _ | |
21 from sat.core.constants import Const as C | |
22 from sat.core.log import getLogger | |
23 from sat.core import exceptions | |
24 from twisted.internet import defer | |
25 from twisted.words.xish import domish | |
26 from twisted.words.protocols.jabber import jid | |
27 from twisted.words.protocols.jabber import error | |
28 from sat.memory import persistent | |
29 from functools import partial | |
30 import random | |
31 import base64 | |
32 try: | |
33 import omemo | |
34 from omemo.extendedpublicbundle import ExtendedPublicBundle | |
35 from omemo import wireformat | |
36 except ImportError: | |
37 raise exceptions.MissingModule( | |
38 u'Missing module omemo, please download/install it. You can use ' | |
39 u'"pip install omemo"' | |
40 ) | |
41 | |
42 log = getLogger(__name__) | |
43 | |
44 | |
45 PLUGIN_INFO = { | |
46 C.PI_NAME: u"OMEMO", | |
47 C.PI_IMPORT_NAME: u"OMEMO", | |
48 C.PI_TYPE: u"SEC", | |
49 C.PI_PROTOCOLS: [u"XEP-0384"], | |
50 C.PI_DEPENDENCIES: [u"XEP-0280", u"XEP-0334", u"XEP-0060"], | |
51 C.PI_MAIN: u"OMEMO", | |
52 C.PI_HANDLER: u"no", | |
53 C.PI_DESCRIPTION: _(u"""Implementation of OMEMO"""), | |
54 } | |
55 | |
56 NS_OMEMO = "eu.siacs.conversations.axolotl" | |
57 NS_OMEMO_DEVICES = NS_OMEMO + ".devicelist" | |
58 NS_OMEMO_BUNDLE = NS_OMEMO + ".bundles:{device_id}" | |
59 KEY_STATE = "STATE" | |
60 KEY_DEVICE_ID = "DEVICE_ID" | |
61 KEY_SESSION = "SESSION" | |
62 KEY_ACTIVE_DEVICES = "DEVICES" | |
63 KEY_INACTIVE_DEVICES = "DEVICES" | |
64 | |
65 | |
66 def b64enc(data): | |
67 return base64.b64encode(bytes(bytearray(data))).decode("ASCII") | |
68 | |
69 | |
70 class OmemoStorage(omemo.Storage): | |
71 | |
72 def __init__(self, persistent_dict): | |
73 """ | |
74 @param persistent_dict(persistent.LazyPersistentBinaryDict): object which will | |
75 store data in SàT database | |
76 """ | |
77 self.data = persistent_dict | |
78 | |
79 @property | |
80 def is_async(self): | |
81 return True | |
82 | |
83 def setCb(self, deferred, callback): | |
84 """Associate Deferred and callback | |
85 | |
86 callback of omemo.Storage expect a boolean with success state then result | |
87 Deferred on the other hand use 2 methods for callback and errback | |
88 This method use partial to call callback with boolean then result when | |
89 Deferred is called | |
90 """ | |
91 deferred.addCallback(partial(callback, True)) | |
92 deferred.addErrback(partial(callback, False)) | |
93 | |
94 def loadState(self, callback): | |
95 d = self.data.get(KEY_STATE) | |
96 self.setCb(d, callback) | |
97 | |
98 def storeState(self, callback, state, device_id): | |
99 d = self.data.force(KEY_STATE, {'state': state, 'device_id': device_id}) | |
100 self.setCb(d, callback) | |
101 | |
102 def loadSession(self, callback, jid, device_id): | |
103 key = u'\n'.join([KEY_SESSION, jid, unicode(device_id)]) | |
104 d = self.data.get(key) | |
105 self.setCb(d, callback) | |
106 | |
107 def storeSession(self, callback, jid, device_id, session): | |
108 key = u'\n'.join([KEY_SESSION, jid, unicode(device_id)]) | |
109 d = self.data.force(key, session) | |
110 self.setCb(d, callback) | |
111 | |
112 def loadActiveDevices(self, callback, jid): | |
113 key = u'\n'.join([KEY_ACTIVE_DEVICES, jid]) | |
114 d = self.data.get(key, {}) | |
115 self.setCb(d, callback) | |
116 | |
117 def loadInactiveDevices(self, callback, jid): | |
118 key = u'\n'.join([KEY_INACTIVE_DEVICES, jid]) | |
119 d = self.data.get(key, {}) | |
120 self.setCb(d, callback) | |
121 | |
122 def storeActiveDevices(self, callback, jid, devices): | |
123 key = u'\n'.join([KEY_ACTIVE_DEVICES, jid]) | |
124 d = self.data.force(key, devices) | |
125 self.setCb(d, callback) | |
126 | |
127 def storeInactiveDevices(self, callback, jid, devices): | |
128 key = u'\n'.join([KEY_INACTIVE_DEVICES, jid]) | |
129 d = self.data.force(key, devices) | |
130 self.setCb(d, callback) | |
131 | |
132 def isTrusted(self, callback, jid, device): | |
133 trusted = True | |
134 callback(True, trusted) | |
135 | |
136 | |
137 class SatOTPKPolicy(omemo.OTPKPolicy): | |
138 | |
139 @staticmethod | |
140 def decideOTPK(preKeyMessages): | |
141 # Always just delete the OTPK. | |
142 # This is the behaviour described in the original X3DH specification. | |
143 return True | |
144 | |
145 | |
146 class OmemoSession(object): | |
147 """Wrapper to use omemo.OmemoSession with Deferred""" | |
148 | |
149 def __init__(self, session): | |
150 self._session = session | |
151 | |
152 @property | |
153 def state(self): | |
154 return self._session.state | |
155 | |
156 @staticmethod | |
157 def promise2Deferred(promise_): | |
158 """Create a Deferred and fire it when promise is resolved | |
159 | |
160 @param promise_(promise.Promise): promise to convert | |
161 @return (defer.Deferred): deferred instance linked to the promise | |
162 """ | |
163 d = defer.Deferred() | |
164 promise_.then(d.callback, d.errback) | |
165 return d | |
166 | |
167 @classmethod | |
168 def create(cls, client, omemo_storage, device_id): | |
169 omemo_session_p = client._xep_0384_session = omemo.SessionManager.create( | |
170 client.jid.userhost(), omemo_storage, SatOTPKPolicy, my_device_id=device_id) | |
171 d = cls.promise2Deferred(omemo_session_p) | |
172 d.addCallback(lambda session: cls(session)) | |
173 return d | |
174 | |
175 def newDeviceList(self, devices, jid=None): | |
176 if jid is not None: | |
177 jid = jid.userhost() | |
178 new_device_p = self._session.newDeviceList(devices, jid) | |
179 return self.promise2Deferred(new_device_p) | |
180 | |
181 def getDevices(self, bare_jid=None): | |
182 get_devices_p = self._session.getDevices(bare_jid=bare_jid) | |
183 return self.promise2Deferred(get_devices_p) | |
184 | |
185 def buildSession(self, bare_jid, device, bundle): | |
186 bare_jid = bare_jid.userhost() | |
187 build_session_p = self._session.buildSession(bare_jid, device, bundle) | |
188 return self.promise2Deferred(build_session_p) | |
189 | |
190 def encryptMessage(self, bare_jids, message, bundles=None, devices=None, | |
191 always_trust = False): | |
192 """Encrypt a message | |
193 | |
194 @param bare_jids(iterable[jid.JID]): destinees of the message | |
195 @param message(unicode): message to encode | |
196 @param bundles(dict[jid.JID, dict[int, ExtendedPublicBundle]): | |
197 entities => devices => bundles map | |
198 @param devices(iterable[int], None): devices to encode for | |
199 @param always_trust(bool): TODO | |
200 @return D(dict): encryption data | |
201 """ | |
202 if isinstance(bare_jids, jid.JID): | |
203 bare_jids = bare_jids.userhost() | |
204 else: | |
205 bare_jids = [e.userhost() for e in bare_jids] | |
206 if bundles is not None: | |
207 bundles = {e.userhost(): v for e, v in bundles.iteritems()} | |
208 encrypt_mess_p = self._session.encryptMessage( | |
209 bare_jids=bare_jids, | |
210 plaintext=message.encode('utf-8'), | |
211 bundles=bundles, | |
212 devices=devices, | |
213 always_trust=always_trust) | |
214 return self.promise2Deferred(encrypt_mess_p) | |
215 | |
216 def decryptMessage(self, bare_jid, device, iv, message, is_pre_key_message, | |
217 payload=None, from_storage=False): | |
218 bare_jid = bare_jid.userhost() | |
219 decrypt_mess_p = self._session.decryptMessage( | |
220 bare_jid=bare_jid, | |
221 device=device, | |
222 iv=iv, | |
223 message=message, | |
224 is_pre_key_message=is_pre_key_message, | |
225 payload=payload, | |
226 from_storage=from_storage) | |
227 return self.promise2Deferred(decrypt_mess_p) | |
228 | |
229 | |
230 class OMEMO(object): | |
231 def __init__(self, host): | |
232 log.info(_(u"OMEMO plugin initialization")) | |
233 self.host = host | |
234 self._p_hints = host.plugins[u"XEP-0334"] | |
235 self._p_carbons = host.plugins[u"XEP-0280"] | |
236 self._p = host.plugins[u"XEP-0060"] | |
237 host.trigger.add("MessageReceived", self._messageReceivedTrigger, priority=100050) | |
238 host.trigger.add("sendMessageData", self._sendMessageDataTrigger) | |
239 self.host.registerEncryptionPlugin(self, "OMEMO", NS_OMEMO, 100) | |
240 | |
241 @defer.inlineCallbacks | |
242 def profileConnected(self, client): | |
243 # we first need to get devices ids (including our own) | |
244 persistent_dict = persistent.LazyPersistentBinaryDict("XEP-0384", client.profile) | |
245 # all known devices of profile | |
246 devices = yield self.getDevices(client) | |
247 # and our own device id | |
248 device_id = yield persistent_dict.get(KEY_DEVICE_ID) | |
249 if device_id is None: | |
250 # we have a new device, we create device_id | |
251 device_id = random.randint(1, 2**31-1) | |
252 # we check that it's really unique | |
253 while device_id in devices: | |
254 device_id = random.randint(1, 2**31-1) | |
255 # and we save it | |
256 persistent_dict[KEY_DEVICE_ID] = device_id | |
257 | |
258 if device_id not in devices: | |
259 devices.add(device_id) | |
260 yield self.setDevices(client, devices) | |
261 | |
262 omemo_storage = OmemoStorage(persistent_dict) | |
263 omemo_session = yield OmemoSession.create(client, omemo_storage, device_id) | |
264 client._xep_0384_session = omemo_session | |
265 client._xep_0384_device_id = device_id | |
266 yield omemo_session.newDeviceList(devices) | |
267 if omemo_session.state.changed: | |
268 log.info(_(u"Saving public bundle for this device ({device_id})").format( | |
269 device_id=device_id)) | |
270 bundle = omemo_session.state.getPublicBundle() | |
271 yield self.setBundle(client, bundle, device_id) | |
272 | |
273 ## XMPP PEP nodes manipulation | |
274 | |
275 # devices | |
276 | |
277 @defer.inlineCallbacks | |
278 def getDevices(self, client, entity_jid=None): | |
279 """Retrieve list of registered OMEMO devices | |
280 | |
281 @param entity_jid(jid.JID, None): get devices from this entity | |
282 None to get our own devices | |
283 @return (set(int)): list of devices | |
284 """ | |
285 if entity_jid is not None: | |
286 assert not entity_jid.resource | |
287 devices = set() | |
288 try: | |
289 items, metadata = yield self._p.getItems(client, entity_jid, NS_OMEMO_DEVICES) | |
290 except error.StanzaError as e: | |
291 if e.condition == 'item-not-found': | |
292 log.info(_(u"there is no node to handle OMEMO devices")) | |
293 defer.returnValue(devices) | |
294 | |
295 if len(items) > 1: | |
296 log.warning(_(u"OMEMO devices list is stored in more that one items, " | |
297 u"this is not expected")) | |
298 if items: | |
299 try: | |
300 list_elt = next(items[0].elements(NS_OMEMO, 'list')) | |
301 except StopIteration: | |
302 log.warning(_(u"no list element found in OMEMO devices list")) | |
303 return | |
304 for device_elt in list_elt.elements(NS_OMEMO, 'device'): | |
305 try: | |
306 device_id = int(device_elt['id']) | |
307 except KeyError: | |
308 log.warning(_(u'device element is missing "id" attribute: {elt}') | |
309 .format(elt=device_elt.toXml())) | |
310 except ValueError: | |
311 log.warning(_(u'invalid device id: {device_id}').format( | |
312 device_id=device_elt['id'])) | |
313 else: | |
314 devices.add(device_id) | |
315 defer.returnValue(devices) | |
316 | |
317 def setDevicesEb(self, failure_): | |
318 log.warning(_(u"Can't set devices: {reason}").format(reason=failure_)) | |
319 | |
320 def setDevices(self, client, devices): | |
321 list_elt = domish.Element((NS_OMEMO, 'list')) | |
322 for device in devices: | |
323 device_elt = list_elt.addElement('device') | |
324 device_elt['id'] = unicode(device) | |
325 d = self._p.sendItem( | |
326 client, None, NS_OMEMO_DEVICES, list_elt, item_id=self._p.ID_SINGLETON) | |
327 d.addErrback(self.setDevicesEb) | |
328 return d | |
329 | |
330 # bundles | |
331 | |
332 @defer.inlineCallbacks | |
333 def getBundles(self, client, entity_jid, devices_ids): | |
334 """Retrieve public bundles of an entity devices | |
335 | |
336 @param entity_jid(jid.JID): bare jid of entity | |
337 @param devices_id(iterable[int]): ids of the devices bundles to retrieve | |
338 @return (dict[int, ExtendedPublicBundle]): bundles collection | |
339 key is device_id | |
340 value is parsed bundle | |
341 """ | |
342 assert not entity_jid.resource | |
343 bundles = {} | |
344 for device_id in devices_ids: | |
345 node = NS_OMEMO_BUNDLE.format(device_id=device_id) | |
346 try: | |
347 items, metadata = yield self._p.getItems(client, entity_jid, node) | |
348 except Exception as e: | |
349 log.warning(_(u"Can't get bundle for device {device_id}: {reason}") | |
350 .format(device_id=device_id, reason=e)) | |
351 continue | |
352 if not items: | |
353 log.warning(_(u"no item found in node {node}, can't get public bundle " | |
354 u"for device {device_id}").format(node=node, | |
355 device_id=device_id)) | |
356 continue | |
357 if len(items) > 1: | |
358 log.warning(_(u"more than one item found in {node}," | |
359 u"this is not expected").format(node=node)) | |
360 item = items[0] | |
361 try: | |
362 bundle_elt = next(item.elements(NS_OMEMO, 'bundle')) | |
363 signedPreKeyPublic_elt = next(bundle_elt.elements( | |
364 NS_OMEMO, 'signedPreKeyPublic')) | |
365 signedPreKeySignature_elt = next(bundle_elt.elements( | |
366 NS_OMEMO, 'signedPreKeySignature')) | |
367 identityKey_elt = next(bundle_elt.elements( | |
368 NS_OMEMO, 'identityKey')) | |
369 prekeys_elt = next(bundle_elt.elements( | |
370 NS_OMEMO, 'prekeys')) | |
371 except StopIteration: | |
372 log.warning(_(u"invalid bundle for device {device_id}, ignoring").format( | |
373 device_id=device_id)) | |
374 continue | |
375 | |
376 try: | |
377 spkPublic = base64.b64decode(unicode(signedPreKeyPublic_elt)) | |
378 spkSignature = base64.b64decode( | |
379 unicode(signedPreKeySignature_elt)) | |
380 | |
381 identityKey = base64.b64decode(unicode(identityKey_elt)) | |
382 spk = { | |
383 "key": wireformat.decodePublicKey(spkPublic), | |
384 "id": int(signedPreKeyPublic_elt['signedPreKeyId']) | |
385 } | |
386 ik = wireformat.decodePublicKey(identityKey) | |
387 otpks = [] | |
388 for preKeyPublic_elt in prekeys_elt.elements(NS_OMEMO, 'preKeyPublic'): | |
389 preKeyPublic = base64.b64decode(unicode(preKeyPublic_elt)) | |
390 otpk = { | |
391 "key": wireformat.decodePublicKey(preKeyPublic), | |
392 "id": int(preKeyPublic_elt['preKeyId']) | |
393 } | |
394 otpks.append(otpk) | |
395 | |
396 except Exception as e: | |
397 log.warning(_(u"error while decoding key for device {devide_id}: {msg}") | |
398 .format(device_id=device_id, msg=e)) | |
399 continue | |
400 | |
401 bundles[device_id] = ExtendedPublicBundle(ik, spk, spkSignature, otpks) | |
402 | |
403 defer.returnValue(bundles) | |
404 | |
405 def setBundleEb(self, failure_): | |
406 log.warning(_(u"Can't set bundle: {reason}").format(reason=failure_)) | |
407 | |
408 def setBundle(self, client, bundle, device_id): | |
409 """Set public bundle for this device. | |
410 | |
411 @param bundle(ExtendedPublicBundle): bundle to publish | |
412 """ | |
413 log.debug(_(u"updating bundle for {device_id}").format(device_id=device_id)) | |
414 bundle_elt = domish.Element((NS_OMEMO, 'bundle')) | |
415 signedPreKeyPublic_elt = bundle_elt.addElement( | |
416 "signedPreKeyPublic", | |
417 content=b64enc(wireformat.encodePublicKey(bundle.spk['key']))) | |
418 signedPreKeyPublic_elt['signedPreKeyId'] = unicode(bundle.spk['id']) | |
419 | |
420 bundle_elt.addElement( | |
421 "signedPreKeySignature", | |
422 content=b64enc(bundle.spk_signature)) | |
423 | |
424 bundle_elt.addElement( | |
425 "identityKey", | |
426 content=b64enc(wireformat.encodePublicKey(bundle.ik))) | |
427 | |
428 prekeys_elt = bundle_elt.addElement('prekeys') | |
429 for otpk in bundle.otpks: | |
430 preKeyPublic_elt = prekeys_elt.addElement( | |
431 'preKeyPublic', | |
432 content=b64enc(wireformat.encodePublicKey(otpk["key"]))) | |
433 preKeyPublic_elt['preKeyId'] = unicode(otpk['id']) | |
434 | |
435 node = NS_OMEMO_BUNDLE.format(device_id=device_id) | |
436 d = self._p.sendItem(client, None, node, bundle_elt, item_id=self._p.ID_SINGLETON) | |
437 d.addErrback(self.setBundleEb) | |
438 return d | |
439 | |
440 ## triggers | |
441 | |
442 @defer.inlineCallbacks | |
443 def encryptMessage(self, client, entity_bare_jid, message): | |
444 omemo_session = client._xep_0384_session | |
445 devices = yield self.getDevices(client, entity_bare_jid) | |
446 omemo_session.newDeviceList(devices, entity_bare_jid) | |
447 bundles = yield self.getBundles(client, entity_bare_jid, devices) | |
448 encrypted = yield omemo_session.encryptMessage( | |
449 entity_bare_jid, | |
450 message, | |
451 {entity_bare_jid: bundles}) | |
452 defer.returnValue(encrypted) | |
453 | |
454 @defer.inlineCallbacks | |
455 def _messageReceivedTrigger(self, client, message_elt, post_treat): | |
456 if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT: | |
457 defer.returnValue(True) | |
458 try: | |
459 encrypted_elt = next(message_elt.elements(NS_OMEMO, u"encrypted")) | |
460 except StopIteration: | |
461 # no OMEMO message here | |
462 defer.returnValue(True) | |
463 | |
464 # we have an encrypted message let's decrypt it | |
465 # from_jid = jid.JID(message_elt['from']) | |
466 omemo_session = client._xep_0384_session | |
467 device_id = client._xep_0384_device_id | |
468 try: | |
469 header_elt = next(encrypted_elt.elements(NS_OMEMO, u'header')) | |
470 iv_elt = next(header_elt.elements(NS_OMEMO, u'iv')) | |
471 except StopIteration: | |
472 log.warning(_(u"Invalid OMEMO encrypted stanza, ignoring: {xml}") | |
473 .format(xml=message_elt.toXml())) | |
474 defer.returnValue(False) | |
475 try: | |
476 key_elt = next((e for e in header_elt.elements(NS_OMEMO, u'key') | |
477 if int(e[u'rid']) == device_id)) | |
478 except StopIteration: | |
479 log.warning(_(u"This OMEMO encrypted stanza has not been encrypted" | |
480 u"for our device ({device_id}): {xml}").format( | |
481 device_id=device_id, xml=encrypted_elt.toXml())) | |
482 defer.returnValue(False) | |
483 except ValueError as e: | |
484 log.warning(_(u"Invalid recipient ID: {msg}".format(msg=e))) | |
485 defer.returnValue(False) | |
486 is_pre_key = C.bool(key_elt.getAttribute('prekey', 'false')) | |
487 payload_elt = next(encrypted_elt.elements(NS_OMEMO, u'payload'), None) | |
488 | |
489 try: | |
490 cipher, plaintext = yield omemo_session.decryptMessage( | |
491 bare_jid=client.jid.userhostJID(), | |
492 device=device_id, | |
493 iv=base64.b64decode(bytes(iv_elt)), | |
494 message=base64.b64decode(bytes(key_elt)), | |
495 is_pre_key_message=is_pre_key, | |
496 payload=base64.b64decode(bytes(payload_elt)) | |
497 if payload_elt is not None else None, | |
498 from_storage=False | |
499 ) | |
500 except Exception as e: | |
501 log.error(_(u"Can't decrypt message: {reason}\n{xml}").format( | |
502 reason=e, xml=message_elt.toXml())) | |
503 defer.returnValue(False) | |
504 if omemo_session.state.changed: | |
505 bundle = omemo_session.state.getPublicBundle() | |
506 # we don't wait for the Deferred (i.e. no yield) on purpose | |
507 # there is no need to block the whole message workflow while | |
508 # updating the bundle | |
509 self.setBundle(client, bundle, device_id) | |
510 | |
511 message_elt.children.remove(encrypted_elt) | |
512 if plaintext: | |
513 message_elt.addElement("body", content=plaintext.decode('utf-8')) | |
514 defer.returnValue(True) | |
515 | |
516 @defer.inlineCallbacks | |
517 def _sendMessageDataTrigger(self, client, mess_data): | |
518 encryption = mess_data.get(C.MESS_KEY_ENCRYPTION) | |
519 if encryption is None or encryption['plugin'].namespace != NS_OMEMO: | |
520 return | |
521 message_elt = mess_data["xml"] | |
522 to_jid = mess_data["to"].userhostJID() | |
523 log.debug(u"encrypting message") | |
524 body = None | |
525 for child in list(message_elt.children): | |
526 if child.name == "body": | |
527 # we remove all unencrypted body, | |
528 # and will only encrypt the first one | |
529 if body is None: | |
530 body = child | |
531 message_elt.children.remove(child) | |
532 elif child.name == "html": | |
533 # we don't want any XHTML-IM element | |
534 message_elt.children.remove(child) | |
535 | |
536 if body is None: | |
537 log.warning(u"No message found") | |
538 return | |
539 | |
540 encryption_data = yield self.encryptMessage(client, to_jid, unicode(body)) | |
541 | |
542 encrypted_elt = message_elt.addElement((NS_OMEMO, 'encrypted')) | |
543 header_elt = encrypted_elt.addElement('header') | |
544 header_elt['sid'] = unicode(encryption_data['sid']) | |
545 bare_jid_s = to_jid.userhost() | |
546 | |
547 for message in (m for m in encryption_data['messages'] | |
548 if m['bare_jid'] == bare_jid_s): | |
549 key_elt = header_elt.addElement( | |
550 'key', | |
551 content=b64enc(message['message'])) | |
552 key_elt['rid'] = unicode(message['rid']) | |
553 if message['pre_key']: | |
554 key_elt['prekey'] = 'true' | |
555 | |
556 header_elt.addElement( | |
557 'iv', | |
558 content=b64enc(encryption_data['iv'])) | |
559 try: | |
560 encrypted_elt.addElement( | |
561 'payload', | |
562 content=b64enc(encryption_data['payload'])) | |
563 except KeyError: | |
564 pass |