comparison sat/plugins/plugin_xep_0384.py @ 3237:b0c57c9a4bd8

plugin XEP-0384: OMEMO trust policy: OMEMO trust policy can now be specified. For now there are 2 policies: - `manual`: each new device fingerprint must be explicitly trusted or not before the device can be used, and the message sent - `BTBV` (Blind Trust Before Verification): each new device fingerprint is automically trusted, until user manually trust or not a device, in which case the behaviour becomes the same as for `manual` for the entity. When using the Trust UI, user can put the entity back to blind trust if they wish. A message is send as feedback to user when a new device is/must be trusted, trying to explain clearly what's happening to the user. Devices which have been automically trusted are marked, so user can know which ones may cause security issue.
author Goffi <goffi@goffi.org>
date Fri, 27 Mar 2020 10:02:14 +0100
parents 9477f3197981
children d85b68e44297
comparison
equal deleted inserted replaced
3236:9477f3197981 3237:b0c57c9a4bd8
14 # GNU Affero General Public License for more details. 14 # GNU Affero General Public License for more details.
15 15
16 # You should have received a copy of the GNU Affero General Public License 16 # You should have received a copy of the GNU Affero General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. 17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 18
19 import logging
20 import random
21 import base64
22 from functools import partial
23 from xml.sax.saxutils import quoteattr
19 from sat.core.i18n import _, D_ 24 from sat.core.i18n import _, D_
20 from sat.core.constants import Const as C 25 from sat.core.constants import Const as C
21 from sat.core.log import getLogger 26 from sat.core.log import getLogger
22 from sat.core import exceptions 27 from sat.core import exceptions
23 from twisted.internet import defer, reactor 28 from twisted.internet import defer, reactor
24 from twisted.words.xish import domish 29 from twisted.words.xish import domish
25 from twisted.words.protocols.jabber import jid 30 from twisted.words.protocols.jabber import jid
26 from twisted.words.protocols.jabber import error as jabber_error 31 from twisted.words.protocols.jabber import error as jabber_error
27 from sat.memory import persistent 32 from sat.memory import persistent
28 from functools import partial
29 from sat.tools import xml_tools 33 from sat.tools import xml_tools
30 import logging
31 import random
32 import base64
33 try: 34 try:
34 import omemo 35 import omemo
35 from omemo import exceptions as omemo_excpt 36 from omemo import exceptions as omemo_excpt
36 from omemo.extendedpublicbundle import ExtendedPublicBundle 37 from omemo.extendedpublicbundle import ExtendedPublicBundle
37 except ImportError: 38 except ImportError:
67 NS_OMEMO_BUNDLE = NS_OMEMO + ".bundles:{device_id}" 68 NS_OMEMO_BUNDLE = NS_OMEMO + ".bundles:{device_id}"
68 KEY_STATE = "STATE" 69 KEY_STATE = "STATE"
69 KEY_DEVICE_ID = "DEVICE_ID" 70 KEY_DEVICE_ID = "DEVICE_ID"
70 KEY_SESSION = "SESSION" 71 KEY_SESSION = "SESSION"
71 KEY_TRUST = "TRUST" 72 KEY_TRUST = "TRUST"
73 # devices which have been automatically trusted by policy like BTBV
74 KEY_AUTO_TRUST = "AUTO_TRUST"
75 # list of peer bare jids where trust UI has been used at least once
76 # this is useful to activate manual trust with BTBV policy
77 KEY_MANUAL_TRUST = "MANUAL_TRUST"
72 KEY_ACTIVE_DEVICES = "DEVICES" 78 KEY_ACTIVE_DEVICES = "DEVICES"
73 KEY_INACTIVE_DEVICES = "INACTIVE_DEVICES" 79 KEY_INACTIVE_DEVICES = "INACTIVE_DEVICES"
74 KEY_ALL_JIDS = "ALL_JIDS" 80 KEY_ALL_JIDS = "ALL_JIDS"
75 # time before plaintext cache for MUC is expired 81 # time before plaintext cache for MUC is expired
76 # expressed in seconds, reset on each new MUC message 82 # expressed in seconds, reset on each new MUC message
77 MUC_CACHE_TTL = 60 * 5 83 MUC_CACHE_TTL = 60 * 5
78 84
85 PARAM_CATEGORY = "Security"
86 PARAM_NAME = "omemo_policy"
87
79 88
80 # we want to manage log emitted by omemo module ourselves 89 # we want to manage log emitted by omemo module ourselves
81 90
82 class SatHandler(logging.Handler): 91 class SatHandler(logging.Handler):
83 92
109 return d 118 return d
110 119
111 120
112 class OmemoStorage(omemo.Storage): 121 class OmemoStorage(omemo.Storage):
113 122
114 def __init__(self, client, device_id, all_jids, persistent_dict): 123 def __init__(self, client, device_id, all_jids):
115 """
116 @param persistent_dict(persistent.LazyPersistentBinaryDict): object which will
117 store data in SàT database
118 """
119 self.own_bare_jid_s = client.jid.userhost() 124 self.own_bare_jid_s = client.jid.userhost()
120 self.device_id = device_id 125 self.device_id = device_id
121 self.all_jids = all_jids 126 self.all_jids = all_jids
122 self.data = persistent_dict 127 self.data = client._xep_0384_data
123 128
124 @property 129 @property
125 def is_async(self): 130 def is_async(self):
126 return True 131 return True
127 132
260 d = defer.DeferredList(d_list) 265 d = defer.DeferredList(d_list)
261 d.addCallback(self._deleteJID_logResults) 266 d.addCallback(self._deleteJID_logResults)
262 return d 267 return d
263 268
264 def deleteJID(self, callback, bare_jid): 269 def deleteJID(self, callback, bare_jid):
265 """Retrieve all (in)actives of bare_jid, and delete all related keys""" 270 """Retrieve all (in)actives devices of bare_jid, and delete all related keys"""
266 d_list = [] 271 d_list = []
267 272
268 key = '\n'.join([KEY_ACTIVE_DEVICES, bare_jid]) 273 key = '\n'.join([KEY_ACTIVE_DEVICES, bare_jid])
269 d_list.append(self.data.get(key, [])) 274 d_list.append(self.data.get(key, []))
270 275
390 return promise2Deferred(get_trust_p) 395 return promise2Deferred(get_trust_p)
391 396
392 397
393 class OMEMO: 398 class OMEMO:
394 399
400 params = """
401 <params>
402 <individual>
403 <category name="{category_name}" label="{category_label}">
404 <param name="{param_name}" label={param_label} type="list" security="3">
405 <option value="manual" label={opt_manual_lbl} />
406 <option value="btbv" label={opt_btbv_lbl} selected="true" />
407 </param>
408 </category>
409 </individual>
410 </params>
411 """.format(
412 category_name=PARAM_CATEGORY,
413 category_label=D_("Security"),
414 param_name=PARAM_NAME,
415 param_label=quoteattr(D_("OMEMO default trust policy")),
416 opt_manual_lbl=quoteattr(D_("Manual trust (more secure)")),
417 opt_btbv_lbl=quoteattr(
418 D_("Blind Trust Before Verification (more user friendly)")),
419 )
420
395 def __init__(self, host): 421 def __init__(self, host):
396 log.info(_("OMEMO plugin initialization (omemo module v{version})").format( 422 log.info(_("OMEMO plugin initialization (omemo module v{version})").format(
397 version=omemo.__version__)) 423 version=omemo.__version__))
398 version = tuple(map(int, omemo.__version__.split('.')[:3])) 424 version = tuple(map(int, omemo.__version__.split('.')[:3]))
399 if version < OMEMO_MIN_VER: 425 if version < OMEMO_MIN_VER:
400 log.warning(_( 426 log.warning(_(
401 "Your version of omemo module is too old: {v[0]}.{v[1]}.{v[2]} is " 427 "Your version of omemo module is too old: {v[0]}.{v[1]}.{v[2]} is "
402 "minimum required, please update.").format(v=OMEMO_MIN_VER)) 428 "minimum required, please update.").format(v=OMEMO_MIN_VER))
403 raise exceptions.CancelError("module is too old") 429 raise exceptions.CancelError("module is too old")
404 self.host = host 430 self.host = host
431 host.memory.updateParams(self.params)
405 self._p_hints = host.plugins["XEP-0334"] 432 self._p_hints = host.plugins["XEP-0334"]
406 self._p_carbons = host.plugins["XEP-0280"] 433 self._p_carbons = host.plugins["XEP-0280"]
407 self._p = host.plugins["XEP-0060"] 434 self._p = host.plugins["XEP-0060"]
408 self._m = host.plugins.get("XEP-0045") 435 self._m = host.plugins.get("XEP-0045")
409 self._sid = host.plugins.get("XEP-0359") 436 self._sid = host.plugins.get("XEP-0359")
448 475
449 feedback = _("OMEMO session has been reset") 476 feedback = _("OMEMO session has been reset")
450 self.text_cmds.feedBack(client, feedback, mess_data) 477 self.text_cmds.feedBack(client, feedback, mess_data)
451 return False 478 return False
452 479
453 @defer.inlineCallbacks 480 async def trustUICb(
454 def trustUICb(self, xmlui_data, trust_data, expect_problems=None, 481 self, xmlui_data, trust_data, expect_problems=None, profile=C.PROF_KEY_NONE):
455 profile=C.PROF_KEY_NONE):
456 if C.bool(xmlui_data.get('cancelled', 'false')): 482 if C.bool(xmlui_data.get('cancelled', 'false')):
457 defer.returnValue({}) 483 return {}
458 client = self.host.getClient(profile) 484 client = self.host.getClient(profile)
459 session = client._xep_0384_session 485 session = client._xep_0384_session
486 stored_data = client._xep_0384_data
487 manual_trust = await stored_data.get(KEY_MANUAL_TRUST, set())
488 auto_trusted_cache = {}
460 answer = xml_tools.XMLUIResult2DataFormResult(xmlui_data) 489 answer = xml_tools.XMLUIResult2DataFormResult(xmlui_data)
490 blind_trust = C.bool(answer.get('blind_trust', C.BOOL_FALSE))
461 for key, value in answer.items(): 491 for key, value in answer.items():
462 if key.startswith('trust_'): 492 if key.startswith('trust_'):
463 trust_id = key[6:] 493 trust_id = key[6:]
464 else: 494 else:
465 continue 495 continue
466 data = trust_data[trust_id] 496 data = trust_data[trust_id]
497 if blind_trust:
498 # user request to restore blind trust for this entity
499 # so if the entity is present in manual trust, we remove it
500 if data["jid"].full() in manual_trust:
501 manual_trust.remove(data["jid"].full())
502 await stored_data.aset(KEY_MANUAL_TRUST, manual_trust)
503 elif data["jid"].full() not in manual_trust:
504 # validating this trust UI implies that we activate manual mode for
505 # this entity (used for BTBV policy)
506 manual_trust.add(data["jid"].full())
507 await stored_data.aset(KEY_MANUAL_TRUST, manual_trust)
467 trust = C.bool(value) 508 trust = C.bool(value)
468 yield session.setTrust( 509
510 if not trust:
511 # if device is not trusted, we check if it must be removed from auto
512 # trusted devices list
513 bare_jid_s = data['jid'].userhost()
514 key = f"{KEY_AUTO_TRUST}\n{bare_jid_s}"
515 if bare_jid_s not in auto_trusted_cache:
516 auto_trusted_cache[bare_jid_s] = await stored_data.get(
517 key, default=set())
518 auto_trusted = auto_trusted_cache[bare_jid_s]
519 if data['device'] in auto_trusted:
520 # as we don't trust this device anymore, we can remove it from the
521 # list of automatically trusted devices
522 auto_trusted.remove(data['device'])
523 await stored_data.aset(key, auto_trusted)
524 log.info(D_(
525 "device {device} from {peer_jid} is not an auto-trusted device "
526 "anymore").format(device=data['device'], peer_jid=bare_jid_s))
527
528 await session.setTrust(
469 data["jid"], 529 data["jid"],
470 data["device"], 530 data["device"],
471 data["ik"], 531 data["ik"],
472 trusted=trust, 532 trusted=trust,
473 ) 533 )
474 if not trust and expect_problems is not None: 534 if not trust and expect_problems is not None:
475 expect_problems.setdefault(data['jid'].userhost(), set()).add( 535 expect_problems.setdefault(data['jid'].userhost(), set()).add(
476 data['device'] 536 data['device']
477 ) 537 )
478 defer.returnValue({}) 538 return {}
479 539
480 @defer.inlineCallbacks 540 async def getTrustUI(self, client, entity_jid=None, trust_data=None, submit_id=None):
481 def getTrustUI(self, client, entity_jid=None, trust_data=None, submit_id=None):
482 """Generate a XMLUI to manage trust 541 """Generate a XMLUI to manage trust
483 542
484 @param entity_jid(None, jid.JID): jid of entity to manage 543 @param entity_jid(None, jid.JID): jid of entity to manage
485 None to use trust_data 544 None to use trust_data
486 @param trust_data(None, dict): devices data: 545 @param trust_data(None, dict): devices data:
500 assert entity_jid and not trust_data or not entity_jid and trust_data 559 assert entity_jid and not trust_data or not entity_jid and trust_data
501 if entity_jid and entity_jid.resource: 560 if entity_jid and entity_jid.resource:
502 raise ValueError("A bare jid is expected") 561 raise ValueError("A bare jid is expected")
503 562
504 session = client._xep_0384_session 563 session = client._xep_0384_session
564 stored_data = client._xep_0384_data
505 565
506 if trust_data is None: 566 if trust_data is None:
507 cache = client._xep_0384_cache.setdefault(entity_jid, {}) 567 cache = client._xep_0384_cache.setdefault(entity_jid, {})
508 trust_data = {} 568 trust_data = {}
509 if self._m is not None and self._m.isJoinedRoom(client, entity_jid): 569 if self._m is not None and self._m.isJoinedRoom(client, entity_jid):
510 trust_jids = self.getJIDsForRoom(client, entity_jid) 570 trust_jids = self.getJIDsForRoom(client, entity_jid)
511 else: 571 else:
512 trust_jids = [entity_jid] 572 trust_jids = [entity_jid]
513 for trust_jid in trust_jids: 573 for trust_jid in trust_jids:
514 trust_session_data = yield session.getTrustForJID(trust_jid) 574 trust_session_data = await session.getTrustForJID(trust_jid)
515 bare_jid_s = trust_jid.userhost() 575 bare_jid_s = trust_jid.userhost()
516 for device_id, trust_info in trust_session_data['active'].items(): 576 for device_id, trust_info in trust_session_data['active'].items():
517 if trust_info is None: 577 if trust_info is None:
518 # device has never been (un)trusted, we have to retrieve its 578 # device has never been (un)trusted, we have to retrieve its
519 # fingerprint (i.e. identity key or "ik") through public bundle 579 # fingerprint (i.e. identity key or "ik") through public bundle
520 if device_id not in cache: 580 if device_id not in cache:
521 bundles, missing = yield self.getBundles(client, 581 bundles, missing = await self.getBundles(client,
522 trust_jid, 582 trust_jid,
523 [device_id]) 583 [device_id])
524 if device_id not in bundles: 584 if device_id not in bundles:
525 log.warning(_( 585 log.warning(_(
526 "Can't find bundle for device {device_id} of user " 586 "Can't find bundle for device {device_id} of user "
543 "ik": ik, 603 "ik": ik,
544 "trusted": trust_info["trusted"], 604 "trusted": trust_info["trusted"],
545 } 605 }
546 606
547 if submit_id is None: 607 if submit_id is None:
548 submit_id = self.host.registerCallback(partial(self.trustUICb, 608 submit_id = self.host.registerCallback(
549 trust_data=trust_data), 609 lambda data, profile: defer.ensureDeferred(
550 with_data=True, 610 self.trustUICb(data, trust_data=trust_data, profile=profile)),
551 one_shot=True) 611 with_data=True,
612 one_shot=True)
552 xmlui = xml_tools.XMLUI( 613 xmlui = xml_tools.XMLUI(
553 panel_type = C.XMLUI_FORM, 614 panel_type = C.XMLUI_FORM,
554 title = D_("OMEMO trust management"), 615 title = D_("OMEMO trust management"),
555 submit_id = submit_id 616 submit_id = submit_id
556 ) 617 )
571 fp_human = ' '.join([ik_hex[i:i+8] for i in range(0, len(ik_hex), 8)]) 632 fp_human = ' '.join([ik_hex[i:i+8] for i in range(0, len(ik_hex), 8)])
572 xmlui.addText(fp_human) 633 xmlui.addText(fp_human)
573 xmlui.addEmpty() 634 xmlui.addEmpty()
574 xmlui.addEmpty() 635 xmlui.addEmpty()
575 636
637 if entity_jid is not None:
638 omemo_policy = self.host.memory.getParamA(
639 PARAM_NAME, PARAM_CATEGORY, profile_key=client.profile
640 )
641 if omemo_policy == 'btbv':
642 xmlui.addLabel(D_("Automatically trust new devices?"))
643 # blind trust is always disabled when UI is requested
644 # as submitting UI is a verification which should disable it.
645 xmlui.addBool("blind_trust", value=C.BOOL_FALSE)
646 xmlui.addEmpty()
647 xmlui.addEmpty()
648
649 auto_trust_cache = {}
576 650
577 for trust_id, data in trust_data.items(): 651 for trust_id, data in trust_data.items():
652 bare_jid_s = data['jid'].userhost()
653 if bare_jid_s not in auto_trust_cache:
654 key = f"{KEY_AUTO_TRUST}\n{bare_jid_s}"
655 auto_trust_cache[bare_jid_s] = await stored_data.get(key, set())
578 xmlui.addLabel(D_("Contact")) 656 xmlui.addLabel(D_("Contact"))
579 xmlui.addJid(data['jid']) 657 xmlui.addJid(data['jid'])
580 xmlui.addLabel(D_("Device ID")) 658 xmlui.addLabel(D_("Device ID"))
581 xmlui.addText(str(data['device'])) 659 xmlui.addText(str(data['device']))
582 xmlui.addLabel(D_("Fingerprint")) 660 xmlui.addLabel(D_("Fingerprint"))
584 fp_human = ' '.join([ik_hex[i:i+8] for i in range(0, len(ik_hex), 8)]) 662 fp_human = ' '.join([ik_hex[i:i+8] for i in range(0, len(ik_hex), 8)])
585 xmlui.addText(fp_human) 663 xmlui.addText(fp_human)
586 xmlui.addLabel(D_("Trust this device?")) 664 xmlui.addLabel(D_("Trust this device?"))
587 xmlui.addBool("trust_{}".format(trust_id), 665 xmlui.addBool("trust_{}".format(trust_id),
588 value=C.boolConst(data.get('trusted', False))) 666 value=C.boolConst(data.get('trusted', False)))
667 if data['device'] in auto_trust_cache[bare_jid_s]:
668 xmlui.addEmpty()
669 xmlui.addLabel(D_("(automatically trusted)"))
670
589 671
590 xmlui.addEmpty() 672 xmlui.addEmpty()
591 xmlui.addEmpty() 673 xmlui.addEmpty()
592 674
593 defer.returnValue(xmlui) 675 return xmlui
594 676
595 @defer.inlineCallbacks 677 @defer.inlineCallbacks
596 def profileConnected(self, client): 678 def profileConnected(self, client):
597 if self._m is not None: 679 if self._m is not None:
598 # we keep plain text message for MUC messages we send 680 # we keep plain text message for MUC messages we send
605 # FIXME: is _xep_0384_ready needed? can we use profileConnecting? 687 # FIXME: is _xep_0384_ready needed? can we use profileConnecting?
606 # Workflow should be checked 688 # Workflow should be checked
607 client._xep_0384_ready = defer.Deferred() 689 client._xep_0384_ready = defer.Deferred()
608 # we first need to get devices ids (including our own) 690 # we first need to get devices ids (including our own)
609 persistent_dict = persistent.LazyPersistentBinaryDict("XEP-0384", client.profile) 691 persistent_dict = persistent.LazyPersistentBinaryDict("XEP-0384", client.profile)
692 client._xep_0384_data = persistent_dict
610 # all known devices of profile 693 # all known devices of profile
611 devices = yield self.getDevices(client) 694 devices = yield self.getDevices(client)
612 # and our own device id 695 # and our own device id
613 device_id = yield persistent_dict.get(KEY_DEVICE_ID) 696 device_id = yield persistent_dict.get(KEY_DEVICE_ID)
614 if device_id is None: 697 if device_id is None:
628 devices.add(device_id) 711 devices.add(device_id)
629 yield defer.ensureDeferred(self.setDevices(client, devices)) 712 yield defer.ensureDeferred(self.setDevices(client, devices))
630 713
631 all_jids = yield persistent_dict.get(KEY_ALL_JIDS, set()) 714 all_jids = yield persistent_dict.get(KEY_ALL_JIDS, set())
632 715
633 omemo_storage = OmemoStorage(client, device_id, all_jids, persistent_dict) 716 omemo_storage = OmemoStorage(client, device_id, all_jids)
634 omemo_session = yield OmemoSession.create(client, omemo_storage, device_id) 717 omemo_session = yield OmemoSession.create(client, omemo_storage, device_id)
635 client._xep_0384_cache = {} 718 client._xep_0384_cache = {}
636 client._xep_0384_session = omemo_session 719 client._xep_0384_session = omemo_session
637 client._xep_0384_device_id = device_id 720 client._xep_0384_device_id = device_id
638 yield omemo_session.newDeviceList(client.jid, devices) 721 yield omemo_session.newDeviceList(client.jid, devices)
860 devices.add(own_device) 943 devices.add(own_device)
861 await self.setDevices(client, devices) 944 await self.setDevices(client, devices)
862 945
863 ## triggers 946 ## triggers
864 947
865 @defer.inlineCallbacks 948 async def policyBTBV(self, client, feedback_jid, expect_problems, undecided):
866 def handleProblems(self, client, feedback_jid, bundles, expect_problems, problems): 949 session = client._xep_0384_session
950 stored_data = client._xep_0384_data
951 for pb in undecided.values():
952 peer_jid = jid.JID(pb.bare_jid)
953 device = pb.device
954 ik = pb.ik
955 key = f"{KEY_AUTO_TRUST}\n{pb.bare_jid}"
956 auto_trusted = await stored_data.get(key, default=set())
957 auto_trusted.add(device)
958 await stored_data.aset(key, auto_trusted)
959 await session.setTrust(peer_jid, device, ik, True)
960
961 user_msg = D_(
962 "Not all destination devices are trusted, unknown devices will be blind "
963 "trusted due to the OMEMO Blind Trust Before Verification policy. If you "
964 "want a more secure workflow, please activate \"manual\" OMEMO policy in "
965 "settings' \"Security\" tab.\nFollowing fingerprint have been automatically "
966 "trusted:\n{devices}"
967 ).format(
968 devices = ', '.join(
969 f"- {pb.device} ({pb.bare_jid}): {pb.ik.hex().upper()}"
970 for pb in undecided.values()
971 )
972 )
973 client.feedback(feedback_jid, user_msg)
974
975 async def policyManual(self, client, feedback_jid, expect_problems, undecided):
976 trust_data = {}
977 for trust_id, data in undecided.items():
978 trust_data[trust_id] = {
979 'jid': jid.JID(data.bare_jid),
980 'device': data.device,
981 'ik': data.ik}
982
983 user_msg = D_("Not all destination devices are trusted, we can't encrypt "
984 "message in such a situation. Please indicate if you trust "
985 "those devices or not in the trust manager before we can "
986 "send this message")
987 client.feedback(feedback_jid, user_msg)
988 xmlui = await self.getTrustUI(client, trust_data=trust_data, submit_id="")
989
990 answer = await xml_tools.deferXMLUI(
991 self.host,
992 xmlui,
993 action_extra={
994 "meta_encryption_trust": NS_OMEMO,
995 },
996 profile=client.profile)
997 await self.trustUICb(answer, trust_data, expect_problems, client.profile)
998
999 async def handleProblems(
1000 self, client, feedback_jid, bundles, expect_problems, problems):
867 """Try to solve problems found by EncryptMessage 1001 """Try to solve problems found by EncryptMessage
868 1002
869 @param feedback_jid(jid.JID): bare jid where the feedback message must be sent 1003 @param feedback_jid(jid.JID): bare jid where the feedback message must be sent
870 @param bundles(dict): bundles data as used in EncryptMessage 1004 @param bundles(dict): bundles data as used in EncryptMessage
871 already filled with known bundles, missing bundles 1005 already filled with known bundles, missing bundles
908 entity_cache = cache.setdefault(pb_entity, {}) 1042 entity_cache = cache.setdefault(pb_entity, {})
909 entity_bundles = bundles.setdefault(pb_entity, {}) 1043 entity_bundles = bundles.setdefault(pb_entity, {})
910 if problem.device in entity_cache: 1044 if problem.device in entity_cache:
911 entity_bundles[problem.device] = entity_cache[problem.device] 1045 entity_bundles[problem.device] = entity_cache[problem.device]
912 else: 1046 else:
913 found_bundles, missing = yield self.getBundles( 1047 found_bundles, missing = await self.getBundles(
914 client, pb_entity, [problem.device]) 1048 client, pb_entity, [problem.device])
915 entity_cache.update(bundles) 1049 entity_cache.update(bundles)
916 entity_bundles.update(found_bundles) 1050 entity_bundles.update(found_bundles)
917 if problem.device in missing: 1051 if problem.device in missing:
918 missing_bundles.setdefault(pb_entity, set()).add( 1052 missing_bundles.setdefault(pb_entity, set()).add(
940 "his/her device(s) (bundle on device {devices}), the message won't " 1074 "his/her device(s) (bundle on device {devices}), the message won't "
941 "be readable on this/those device.").format( 1075 "be readable on this/those device.").format(
942 peer=peer_jid.full(), devices=", ".join(devices_s))) 1076 peer=peer_jid.full(), devices=", ".join(devices_s)))
943 1077
944 if undecided: 1078 if undecided:
945 trust_data = {} 1079 omemo_policy = self.host.memory.getParamA(
946 for trust_id, data in undecided.items(): 1080 PARAM_NAME, PARAM_CATEGORY, profile_key=client.profile
947 trust_data[trust_id] = { 1081 )
948 'jid': jid.JID(data.bare_jid), 1082 if omemo_policy == 'btbv':
949 'device': data.device, 1083 # we first separate entities which have been trusted manually
950 'ik': data.ik} 1084 manual_trust = await client._xep_0384_data.get(KEY_MANUAL_TRUST)
951 1085 if manual_trust:
952 user_msg = D_("Not all destination devices are trusted, we can't encrypt " 1086 manual_undecided = {}
953 "message in such a situation. Please indicate if you trust " 1087 for hash_, pb in undecided.items():
954 "those devices or not in the trust manager before we can " 1088 if pb.bare_jid in manual_trust:
955 "send this message") 1089 manual_undecided[hash_] = pb
956 client.feedback(feedback_jid, user_msg) 1090 for hash_ in manual_undecided:
957 xmlui = yield self.getTrustUI(client, trust_data=trust_data, submit_id="") 1091 del undecided[hash_]
958 1092 else:
959 answer = yield xml_tools.deferXMLUI( 1093 manual_undecided = None
960 self.host, 1094
961 xmlui, 1095 if undecided:
962 action_extra={ 1096 # we do the automatic trust here
963 "meta_encryption_trust": NS_OMEMO, 1097 await self.policyBTBV(
964 }, 1098 client, feedback_jid, expect_problems, undecided)
965 profile=client.profile) 1099 if manual_undecided:
966 yield self.trustUICb(answer, trust_data, expect_problems, client.profile) 1100 # here user has to manually trust new devices from entities already
967 1101 # verified
968 @defer.inlineCallbacks 1102 await self.policyManual(
969 def encryptMessage(self, client, entity_bare_jids, message, feedback_jid=None): 1103 client, feedback_jid, expect_problems, manual_undecided)
1104 elif omemo_policy == 'manual':
1105 await self.policyManual(
1106 client, feedback_jid, expect_problems, undecided)
1107 else:
1108 raise exceptions.InternalError(f"Unexpected OMEMO policy: {omemo_policy}")
1109
1110 async def encryptMessage(self, client, entity_bare_jids, message, feedback_jid=None):
970 if feedback_jid is None: 1111 if feedback_jid is None:
971 if len(entity_bare_jids) != 1: 1112 if len(entity_bare_jids) != 1:
972 log.error( 1113 log.error(
973 "feedback_jid must be provided when message is encrypted for more " 1114 "feedback_jid must be provided when message is encrypted for more "
974 "than one entities") 1115 "than one entities")
983 msg = _("Too many iterations in encryption loop") 1124 msg = _("Too many iterations in encryption loop")
984 log.error(msg) 1125 log.error(msg)
985 raise exceptions.InternalError(msg) 1126 raise exceptions.InternalError(msg)
986 # encryptMessage may fail, in case of e.g. trust issue or missing bundle 1127 # encryptMessage may fail, in case of e.g. trust issue or missing bundle
987 try: 1128 try:
988 encrypted = yield omemo_session.encryptMessage( 1129 encrypted = await omemo_session.encryptMessage(
989 entity_bare_jids, 1130 entity_bare_jids,
990 message, 1131 message,
991 bundles, 1132 bundles,
992 expect_problems = expect_problems) 1133 expect_problems = expect_problems)
993 except omemo_excpt.EncryptionProblemsException as e: 1134 except omemo_excpt.EncryptionProblemsException as e:
994 # we know the problem to solve, we can try to fix them 1135 # we know the problem to solve, we can try to fix them
995 yield self.handleProblems( 1136 await self.handleProblems(
996 client, 1137 client,
997 feedback_jid=feedback_jid, 1138 feedback_jid=feedback_jid,
998 bundles=bundles, 1139 bundles=bundles,
999 expect_problems=expect_problems, 1140 expect_problems=expect_problems,
1000 problems=e.problems) 1141 problems=e.problems)
1238 timer.reset(MUC_CACHE_TTL) 1379 timer.reset(MUC_CACHE_TTL)
1239 # we use origin-id when possible, to identifiy the message in a stable way 1380 # we use origin-id when possible, to identifiy the message in a stable way
1240 if self._sid is not None: 1381 if self._sid is not None:
1241 self._sid.addOriginId(message_elt, mess_data['uid']) 1382 self._sid.addOriginId(message_elt, mess_data['uid'])
1242 1383
1243 encryption_data = yield self.encryptMessage( 1384 encryption_data = yield defer.ensureDeferred(self.encryptMessage(
1244 client, to_jids, body, feedback_jid=feedback_jid) 1385 client, to_jids, body, feedback_jid=feedback_jid))
1245 1386
1246 encrypted_elt = message_elt.addElement((NS_OMEMO, 'encrypted')) 1387 encrypted_elt = message_elt.addElement((NS_OMEMO, 'encrypted'))
1247 header_elt = encrypted_elt.addElement('header') 1388 header_elt = encrypted_elt.addElement('header')
1248 header_elt['sid'] = str(encryption_data['sid']) 1389 header_elt['sid'] = str(encryption_data['sid'])
1249 1390