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