Mercurial > libervia-backend
comparison sat/plugins/plugin_xep_0384.py @ 3104:118d91c932a7
plugin XEP-0384: OMEMO for MUC implementation:
- encryption is now allowed for group chats
- when an encryption is requested for a MUC, real jids or all occupants are used to
encrypt the message
- a cache for plain text message sent to MUC is used, because for security reason we can't
encrypt message for our own device with OMEMO (that would prevent ratchet and break the
prefect forward secrecy). Thus, message sent in MUC are cached for 5 min, and the
decrypted version is used when found. We don't send immediately the plain text message
to frontends and history because we want to keep the same MUC behaviour as for plain
text, and receiving a message means that it was received and sent back by MUC service
- <origin-id> is used to identify messages sent by our device
- a feedback_jid is now use to use correct entity for feedback message in case of problem:
with a room we have to send feedback message to the room and not the the emitter
- encryptMessage now only accepts list in the renamed "entity_bare_jids" argument
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 30 Dec 2019 20:59:46 +0100 |
parents | 518208085dfb |
children | 9d0df638c8b4 |
comparison
equal
deleted
inserted
replaced
3103:f0f48b4deb35 | 3104:118d91c932a7 |
---|---|
18 | 18 |
19 from sat.core.i18n import _, D_ | 19 from sat.core.i18n import _, D_ |
20 from sat.core.constants import Const as C | 20 from sat.core.constants import Const as C |
21 from sat.core.log import getLogger | 21 from sat.core.log import getLogger |
22 from sat.core import exceptions | 22 from sat.core import exceptions |
23 from twisted.internet import defer | 23 from twisted.internet import defer, reactor |
24 from twisted.words.xish import domish | 24 from twisted.words.xish import domish |
25 from twisted.words.protocols.jabber import jid | 25 from twisted.words.protocols.jabber import jid |
26 from twisted.words.protocols.jabber import error | 26 from twisted.words.protocols.jabber import error |
27 from sat.memory import persistent | 27 from sat.memory import persistent |
28 from functools import partial | 28 from functools import partial |
53 C.PI_NAME: "OMEMO", | 53 C.PI_NAME: "OMEMO", |
54 C.PI_IMPORT_NAME: "XEP-0384", | 54 C.PI_IMPORT_NAME: "XEP-0384", |
55 C.PI_TYPE: "SEC", | 55 C.PI_TYPE: "SEC", |
56 C.PI_PROTOCOLS: ["XEP-0384"], | 56 C.PI_PROTOCOLS: ["XEP-0384"], |
57 C.PI_DEPENDENCIES: ["XEP-0163", "XEP-0280", "XEP-0334", "XEP-0060"], | 57 C.PI_DEPENDENCIES: ["XEP-0163", "XEP-0280", "XEP-0334", "XEP-0060"], |
58 C.PI_RECOMMENDATIONS: ["XEP-0045", "XEP-0359"], | |
58 C.PI_MAIN: "OMEMO", | 59 C.PI_MAIN: "OMEMO", |
59 C.PI_HANDLER: "no", | 60 C.PI_HANDLER: "no", |
60 C.PI_DESCRIPTION: _("""Implementation of OMEMO"""), | 61 C.PI_DESCRIPTION: _("""Implementation of OMEMO"""), |
61 } | 62 } |
62 | 63 |
69 KEY_SESSION = "SESSION" | 70 KEY_SESSION = "SESSION" |
70 KEY_TRUST = "TRUST" | 71 KEY_TRUST = "TRUST" |
71 KEY_ACTIVE_DEVICES = "DEVICES" | 72 KEY_ACTIVE_DEVICES = "DEVICES" |
72 KEY_INACTIVE_DEVICES = "INACTIVE_DEVICES" | 73 KEY_INACTIVE_DEVICES = "INACTIVE_DEVICES" |
73 KEY_ALL_JIDS = "ALL_JIDS" | 74 KEY_ALL_JIDS = "ALL_JIDS" |
75 # time before plaintext cache for MUC is expired | |
76 # expressed in seconds, reset on each new MUC message | |
77 MUC_CACHE_TTL = 60 * 5 | |
74 | 78 |
75 | 79 |
76 # we want to manage log emitted by omemo module ourselves | 80 # we want to manage log emitted by omemo module ourselves |
77 | 81 |
78 class SatHandler(logging.Handler): | 82 class SatHandler(logging.Handler): |
329 @param message(unicode): message to encode | 333 @param message(unicode): message to encode |
330 @param bundles(dict[jid.JID, dict[int, ExtendedPublicBundle]): | 334 @param bundles(dict[jid.JID, dict[int, ExtendedPublicBundle]): |
331 entities => devices => bundles map | 335 entities => devices => bundles map |
332 @return D(dict): encryption data | 336 @return D(dict): encryption data |
333 """ | 337 """ |
334 if isinstance(bare_jids, jid.JID): | 338 bare_jids = [e.userhost() for e in bare_jids] |
335 bare_jids = bare_jids.userhost() | |
336 else: | |
337 bare_jids = [e.userhost() for e in bare_jids] | |
338 if bundles is not None: | 339 if bundles is not None: |
339 bundles = {e.userhost(): v for e, v in bundles.items()} | 340 bundles = {e.userhost(): v for e, v in bundles.items()} |
340 encrypt_mess_p = self._session.encryptMessage( | 341 encrypt_mess_p = self._session.encryptMessage( |
341 bare_jids=bare_jids, | 342 bare_jids=bare_jids, |
342 plaintext=message.encode(), | 343 plaintext=message.encode(), |
388 raise exceptions.CancelError("module is too old") | 389 raise exceptions.CancelError("module is too old") |
389 self.host = host | 390 self.host = host |
390 self._p_hints = host.plugins["XEP-0334"] | 391 self._p_hints = host.plugins["XEP-0334"] |
391 self._p_carbons = host.plugins["XEP-0280"] | 392 self._p_carbons = host.plugins["XEP-0280"] |
392 self._p = host.plugins["XEP-0060"] | 393 self._p = host.plugins["XEP-0060"] |
394 self._m = host.plugins.get("XEP-0045") | |
395 self._sid = host.plugins.get("XEP-0359") | |
393 host.trigger.add("MessageReceived", self._messageReceivedTrigger, priority=100050) | 396 host.trigger.add("MessageReceived", self._messageReceivedTrigger, priority=100050) |
394 host.trigger.add("sendMessageData", self._sendMessageDataTrigger) | 397 host.trigger.add("sendMessageData", self._sendMessageDataTrigger) |
395 self.host.registerEncryptionPlugin(self, "OMEMO", NS_OMEMO, 100) | 398 self.host.registerEncryptionPlugin(self, "OMEMO", NS_OMEMO, 100) |
396 pep = host.plugins['XEP-0163'] | 399 pep = host.plugins['XEP-0163'] |
397 pep.addPEPEvent("OMEMO_DEVICES", NS_OMEMO_DEVICES, self.onNewDevices) | 400 pep.addPEPEvent("OMEMO_DEVICES", NS_OMEMO_DEVICES, self.onNewDevices) |
450 session = client._xep_0384_session | 453 session = client._xep_0384_session |
451 | 454 |
452 if trust_data is None: | 455 if trust_data is None: |
453 cache = client._xep_0384_cache.setdefault(entity_jid, {}) | 456 cache = client._xep_0384_cache.setdefault(entity_jid, {}) |
454 trust_data = {} | 457 trust_data = {} |
455 trust_session_data = yield session.getTrustForJID(entity_jid) | 458 if self._m is not None and self._m.isJoinedRoom(client, entity_jid): |
456 bare_jid_s = entity_jid.userhost() | 459 trust_jids = self.getJIDsForRoom(client, entity_jid) |
457 for device_id, trust_info in trust_session_data['active'].items(): | 460 else: |
458 if trust_info is None: | 461 trust_jids = [entity_jid] |
459 # device has never been (un)trusted, we have to retrieve its | 462 for trust_jid in trust_jids: |
460 # fingerprint (i.e. identity key or "ik") through public bundle | 463 trust_session_data = yield session.getTrustForJID(trust_jid) |
461 if device_id not in cache: | 464 bare_jid_s = trust_jid.userhost() |
462 bundles, missing = yield self.getBundles(client, | 465 for device_id, trust_info in trust_session_data['active'].items(): |
463 entity_jid, | 466 if trust_info is None: |
464 [device_id]) | 467 # device has never been (un)trusted, we have to retrieve its |
465 if device_id not in bundles: | 468 # fingerprint (i.e. identity key or "ik") through public bundle |
466 log.warning(_( | 469 if device_id not in cache: |
467 "Can't find bundle for device {device_id} of user " | 470 bundles, missing = yield self.getBundles(client, |
468 "{bare_jid}, ignoring").format(device_id=device_id, | 471 trust_jid, |
469 bare_jid=bare_jid_s)) | 472 [device_id]) |
470 continue | 473 if device_id not in bundles: |
471 cache[device_id] = bundles[device_id] | 474 log.warning(_( |
472 # TODO: replace False below by None when undecided | 475 "Can't find bundle for device {device_id} of user " |
473 # trusts are handled | 476 "{bare_jid}, ignoring").format(device_id=device_id, |
474 trust_info = { | 477 bare_jid=bare_jid_s)) |
475 "key": cache[device_id].ik, | 478 continue |
476 "trusted": False | 479 cache[device_id] = bundles[device_id] |
477 } | 480 # TODO: replace False below by None when undecided |
478 | 481 # trusts are handled |
479 ik = trust_info["key"] | 482 trust_info = { |
480 trust_id = str(hash((bare_jid_s, device_id, ik))) | 483 "key": cache[device_id].ik, |
481 trust_data[trust_id] = { | 484 "trusted": False |
482 "jid": entity_jid, | 485 } |
483 "device": device_id, | 486 |
484 "ik": ik, | 487 ik = trust_info["key"] |
485 "trusted": trust_info["trusted"], | 488 trust_id = str(hash((bare_jid_s, device_id, ik))) |
486 } | 489 trust_data[trust_id] = { |
490 "jid": trust_jid, | |
491 "device": device_id, | |
492 "ik": ik, | |
493 "trusted": trust_info["trusted"], | |
494 } | |
487 | 495 |
488 if submit_id is None: | 496 if submit_id is None: |
489 submit_id = self.host.registerCallback(partial(self.trustUICb, | 497 submit_id = self.host.registerCallback(partial(self.trustUICb, |
490 trust_data=trust_data), | 498 trust_data=trust_data), |
491 with_data=True, | 499 with_data=True, |
533 | 541 |
534 defer.returnValue(xmlui) | 542 defer.returnValue(xmlui) |
535 | 543 |
536 @defer.inlineCallbacks | 544 @defer.inlineCallbacks |
537 def profileConnected(self, client): | 545 def profileConnected(self, client): |
546 if self._m is not None: | |
547 # we keep plain text message for MUC messages we send | |
548 # as we can't encrypt for our own device | |
549 client._xep_0384_muc_cache = {} | |
550 # and we keep them only for some time, in case something goes wrong | |
551 # with the MUC | |
552 client._xep_0384_muc_cache_timer = None | |
553 | |
538 # FIXME: is _xep_0384_ready needed? can we use profileConnecting? | 554 # FIXME: is _xep_0384_ready needed? can we use profileConnecting? |
539 # Workflow should be checked | 555 # Workflow should be checked |
540 client._xep_0384_ready = defer.Deferred() | 556 client._xep_0384_ready = defer.Deferred() |
541 # we first need to get devices ids (including our own) | 557 # we first need to get devices ids (including our own) |
542 persistent_dict = persistent.LazyPersistentBinaryDict("XEP-0384", client.profile) | 558 persistent_dict = persistent.LazyPersistentBinaryDict("XEP-0384", client.profile) |
783 yield self.setDevices(client, devices) | 799 yield self.setDevices(client, devices) |
784 | 800 |
785 ## triggers | 801 ## triggers |
786 | 802 |
787 @defer.inlineCallbacks | 803 @defer.inlineCallbacks |
788 def handleProblems(self, client, entity, bundles, expect_problems, problems): | 804 def handleProblems(self, client, feedback_jid, bundles, expect_problems, problems): |
789 """Try to solve problems found by EncryptMessage | 805 """Try to solve problems found by EncryptMessage |
790 | 806 |
791 @param entity(jid.JID): bare jid of the destinee | 807 @param feedback_jid(jid.JID): bare jid where the feedback message must be sent |
792 @param bundles(dict): bundles data as used in EncryptMessage | 808 @param bundles(dict): bundles data as used in EncryptMessage |
793 already filled with known bundles, missing bundles | 809 already filled with known bundles, missing bundles |
794 need to be added to it | 810 need to be added to it |
795 This dict is updated | 811 This dict is updated |
796 @param problems(list): exceptions raised by EncryptMessage | 812 @param problems(list): exceptions raised by EncryptMessage |
855 log.warning( | 871 log.warning( |
856 _("Can't retrieve bundle for device(s) {devices} of entity {peer}, " | 872 _("Can't retrieve bundle for device(s) {devices} of entity {peer}, " |
857 "the message will not be readable on this/those device(s)").format( | 873 "the message will not be readable on this/those device(s)").format( |
858 devices=", ".join(devices_s), peer=peer_jid.full())) | 874 devices=", ".join(devices_s), peer=peer_jid.full())) |
859 client.feedback( | 875 client.feedback( |
860 entity, | 876 feedback_jid, |
861 D_("You're destinee {peer} has missing encryption data on some of " | 877 D_("You're destinee {peer} has missing encryption data on some of " |
862 "his/her device(s) (bundle on device {devices}), the message won't " | 878 "his/her device(s) (bundle on device {devices}), the message won't " |
863 "be readable on this/those device.").format( | 879 "be readable on this/those device.").format( |
864 peer=peer_jid.full(), devices=", ".join(devices_s))) | 880 peer=peer_jid.full(), devices=", ".join(devices_s))) |
865 | 881 |
873 | 889 |
874 user_msg = D_("Not all destination devices are trusted, we can't encrypt " | 890 user_msg = D_("Not all destination devices are trusted, we can't encrypt " |
875 "message in such a situation. Please indicate if you trust " | 891 "message in such a situation. Please indicate if you trust " |
876 "those devices or not in the trust manager before we can " | 892 "those devices or not in the trust manager before we can " |
877 "send this message") | 893 "send this message") |
878 client.feedback(entity, user_msg) | 894 client.feedback(feedback_jid, user_msg) |
879 xmlui = yield self.getTrustUI(client, trust_data=trust_data, submit_id="") | 895 xmlui = yield self.getTrustUI(client, trust_data=trust_data, submit_id="") |
880 | 896 |
881 answer = yield xml_tools.deferXMLUI( | 897 answer = yield xml_tools.deferXMLUI( |
882 self.host, | 898 self.host, |
883 xmlui, | 899 xmlui, |
886 }, | 902 }, |
887 profile=client.profile) | 903 profile=client.profile) |
888 yield self.trustUICb(answer, trust_data, expect_problems, client.profile) | 904 yield self.trustUICb(answer, trust_data, expect_problems, client.profile) |
889 | 905 |
890 @defer.inlineCallbacks | 906 @defer.inlineCallbacks |
891 def encryptMessage(self, client, entity_bare_jid, message): | 907 def encryptMessage(self, client, entity_bare_jids, message, feedback_jid=None): |
908 if feedback_jid is None: | |
909 if len(entity_bare_jids) != 1: | |
910 log.error( | |
911 "feedback_jid must be provided when message is encrypted for more " | |
912 "than one entities") | |
913 feedback_jid = entity_bare_jids[0] | |
892 omemo_session = client._xep_0384_session | 914 omemo_session = client._xep_0384_session |
893 expect_problems = {} | 915 expect_problems = {} |
894 bundles = {} | 916 bundles = {} |
895 loop_idx = 0 | 917 loop_idx = 0 |
896 try: | 918 try: |
900 log.error(msg) | 922 log.error(msg) |
901 raise exceptions.InternalError(msg) | 923 raise exceptions.InternalError(msg) |
902 # encryptMessage may fail, in case of e.g. trust issue or missing bundle | 924 # encryptMessage may fail, in case of e.g. trust issue or missing bundle |
903 try: | 925 try: |
904 encrypted = yield omemo_session.encryptMessage( | 926 encrypted = yield omemo_session.encryptMessage( |
905 entity_bare_jid, | 927 entity_bare_jids, |
906 message, | 928 message, |
907 bundles, | 929 bundles, |
908 expect_problems = expect_problems) | 930 expect_problems = expect_problems) |
909 except omemo_excpt.EncryptionProblemsException as e: | 931 except omemo_excpt.EncryptionProblemsException as e: |
910 # we know the problem to solve, we can try to fix them | 932 # we know the problem to solve, we can try to fix them |
911 yield self.handleProblems( | 933 yield self.handleProblems( |
912 client, | 934 client, |
913 entity=entity_bare_jid, | 935 feedback_jid=feedback_jid, |
914 bundles=bundles, | 936 bundles=bundles, |
915 expect_problems=expect_problems, | 937 expect_problems=expect_problems, |
916 problems=e.problems) | 938 problems=e.problems) |
917 loop_idx += 1 | 939 loop_idx += 1 |
918 else: | 940 else: |
919 break | 941 break |
920 except Exception as e: | 942 except Exception as e: |
921 msg = _("Can't encrypt message for {entity}: {reason}".format( | 943 msg = _("Can't encrypt message for {entities}: {reason}".format( |
922 entity=entity_bare_jid.full(), reason=e)) | 944 entities=', '.join(e.full() for e in entity_bare_jids), reason=e)) |
923 log.warning(msg) | 945 log.warning(msg) |
924 extra = {C.MESS_EXTRA_INFO: C.EXTRA_INFO_ENCR_ERR} | 946 extra = {C.MESS_EXTRA_INFO: C.EXTRA_INFO_ENCR_ERR} |
925 client.feedback(entity_bare_jid, msg, extra) | 947 client.feedback(feedback_jid, msg, extra) |
926 raise e | 948 raise e |
927 | 949 |
928 defer.returnValue(encrypted) | 950 defer.returnValue(encrypted) |
929 | 951 |
930 @defer.inlineCallbacks | 952 @defer.inlineCallbacks |
931 def _messageReceivedTrigger(self, client, message_elt, post_treat): | 953 def _messageReceivedTrigger(self, client, message_elt, post_treat): |
932 if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT: | |
933 defer.returnValue(True) | |
934 try: | 954 try: |
935 encrypted_elt = next(message_elt.elements(NS_OMEMO, "encrypted")) | 955 encrypted_elt = next(message_elt.elements(NS_OMEMO, "encrypted")) |
936 except StopIteration: | 956 except StopIteration: |
937 # no OMEMO message here | 957 # no OMEMO message here |
938 defer.returnValue(True) | 958 defer.returnValue(True) |
939 | 959 |
940 # we have an encrypted message let's decrypt it | 960 # we have an encrypted message let's decrypt it |
961 | |
941 from_jid = jid.JID(message_elt['from']) | 962 from_jid = jid.JID(message_elt['from']) |
942 if from_jid.userhostJID() == client.jid.userhostJID(): | 963 |
943 feedback_jid = jid.JID(message_elt['to']) | 964 if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT: |
965 # with group chat, we must get the real jid for decryption | |
966 # and use the room as feedback_jid | |
967 | |
968 if self._m is None: | |
969 # plugin XEP-0045 (MUC) is not available | |
970 defer.returnValue(True) | |
971 | |
972 room_jid = from_jid.userhostJID() | |
973 feedback_jid = room_jid | |
974 if self._sid is not None: | |
975 mess_id = self._sid.getOriginId(message_elt) | |
976 else: | |
977 mess_id = None | |
978 | |
979 if mess_id is None: | |
980 mess_id = message_elt.getAttribute('id') | |
981 cache_key = (room_jid, mess_id) | |
982 | |
983 try: | |
984 room = self._m.getRoom(client, room_jid) | |
985 except exceptions.NotFound: | |
986 log.warning( | |
987 f"Received an OMEMO encrypted msg from a room {room_jid} which has " | |
988 f"not been joined, ignoring") | |
989 defer.returnValue(True) | |
990 | |
991 user = room.getUser(from_jid.resource) | |
992 if user is None: | |
993 log.warning(f"Can't find user {user} in room {room_jid}, ignoring") | |
994 defer.returnValue(True) | |
995 if not user.entity: | |
996 log.warning( | |
997 f"Real entity of user {user} in room {room_jid} can't be established," | |
998 f" OMEMO encrypted message can't be decrypted") | |
999 defer.returnValue(True) | |
1000 | |
1001 # now we have real jid of the entity, we use it instead of from_jid | |
1002 from_jid = user.entity.userhostJID() | |
1003 | |
944 else: | 1004 else: |
945 feedback_jid = from_jid | 1005 # we have a one2one message, we can user "from" and "to" normally |
946 try: | 1006 |
947 omemo_session = client._xep_0384_session | 1007 if from_jid.userhostJID() == client.jid.userhostJID(): |
948 except AttributeError: | 1008 feedback_jid = jid.JID(message_elt['to']) |
949 # on startup, message can ve received before session actually exists | 1009 else: |
950 # so we need to synchronise here | 1010 feedback_jid = from_jid |
951 yield client._xep_0384_ready | 1011 |
952 omemo_session = client._xep_0384_session | 1012 |
953 | 1013 if (message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT |
954 device_id = client._xep_0384_device_id | 1014 and mess_id is not None |
955 try: | 1015 and cache_key in client._xep_0384_muc_cache): |
956 header_elt = next(encrypted_elt.elements(NS_OMEMO, 'header')) | 1016 plaintext = client._xep_0384_muc_cache.pop(cache_key) |
957 iv_elt = next(header_elt.elements(NS_OMEMO, 'iv')) | 1017 if not client._xep_0384_muc_cache: |
958 except StopIteration: | 1018 client._xep_0384_muc_cache_timer.cancel() |
959 log.warning(_("Invalid OMEMO encrypted stanza, ignoring: {xml}") | 1019 client._xep_0384_muc_cache_timer = None |
960 .format(xml=message_elt.toXml())) | 1020 else: |
961 defer.returnValue(False) | |
962 try: | |
963 s_device_id = header_elt['sid'] | |
964 except KeyError: | |
965 log.warning(_("Invalid OMEMO encrypted stanza, missing sender device ID, " | |
966 "ignoring: {xml}") | |
967 .format(xml=message_elt.toXml())) | |
968 defer.returnValue(False) | |
969 try: | |
970 key_elt = next((e for e in header_elt.elements(NS_OMEMO, 'key') | |
971 if int(e['rid']) == device_id)) | |
972 except StopIteration: | |
973 log.warning(_("This OMEMO encrypted stanza has not been encrypted " | |
974 "for our device (device_id: {device_id}, fingerprint: " | |
975 "{fingerprint}): {xml}").format( | |
976 device_id=device_id, | |
977 fingerprint=omemo_session.public_bundle.ik.hex().upper(), | |
978 xml=encrypted_elt.toXml())) | |
979 user_msg = (D_("An OMEMO message from {sender} has not been encrypted for " | |
980 "our device, we can't decrypt it").format( | |
981 sender=from_jid.full())) | |
982 extra = {C.MESS_EXTRA_INFO: C.EXTRA_INFO_DECR_ERR} | |
983 client.feedback(feedback_jid, user_msg, extra) | |
984 defer.returnValue(False) | |
985 except ValueError as e: | |
986 log.warning(_("Invalid recipient ID: {msg}".format(msg=e))) | |
987 defer.returnValue(False) | |
988 is_pre_key = C.bool(key_elt.getAttribute('prekey', 'false')) | |
989 payload_elt = next(encrypted_elt.elements(NS_OMEMO, 'payload'), None) | |
990 additional_information = { | |
991 "from_storage": bool(message_elt.delay) | |
992 } | |
993 | |
994 kwargs = { | |
995 "bare_jid": from_jid.userhostJID(), | |
996 "device": s_device_id, | |
997 "iv": base64.b64decode(bytes(iv_elt)), | |
998 "message": base64.b64decode(bytes(key_elt)), | |
999 "is_pre_key_message": is_pre_key, | |
1000 "ciphertext": base64.b64decode(bytes(payload_elt)) | |
1001 if payload_elt is not None else None, | |
1002 "additional_information": additional_information, | |
1003 } | |
1004 try: | |
1005 try: | 1021 try: |
1006 plaintext = yield omemo_session.decryptMessage(**kwargs) | 1022 omemo_session = client._xep_0384_session |
1007 except omemo_excpt.TrustException: | 1023 except AttributeError: |
1008 post_treat.addCallback(client.encryption.markAsUntrusted) | 1024 # on startup, message can ve received before session actually exists |
1009 kwargs['allow_untrusted'] = True | 1025 # so we need to synchronise here |
1010 plaintext = yield omemo_session.decryptMessage(**kwargs) | 1026 yield client._xep_0384_ready |
1011 else: | 1027 omemo_session = client._xep_0384_session |
1012 post_treat.addCallback(client.encryption.markAsTrusted) | 1028 |
1013 plaintext = plaintext.decode() | 1029 device_id = client._xep_0384_device_id |
1014 except Exception as e: | 1030 try: |
1015 log.warning(_("Can't decrypt message: {reason}\n{xml}").format( | 1031 header_elt = next(encrypted_elt.elements(NS_OMEMO, 'header')) |
1016 reason=e, xml=message_elt.toXml())) | 1032 iv_elt = next(header_elt.elements(NS_OMEMO, 'iv')) |
1017 user_msg = (D_("An OMEMO message from {sender} can't be decrypted: {reason}") | 1033 except StopIteration: |
1018 .format(sender=from_jid.full(), reason=e)) | 1034 log.warning(_("Invalid OMEMO encrypted stanza, ignoring: {xml}") |
1019 extra = {C.MESS_EXTRA_INFO: C.EXTRA_INFO_DECR_ERR} | 1035 .format(xml=message_elt.toXml())) |
1020 client.feedback(feedback_jid, user_msg, extra) | 1036 defer.returnValue(False) |
1021 defer.returnValue(False) | 1037 try: |
1022 finally: | 1038 s_device_id = header_elt['sid'] |
1023 if omemo_session.republish_bundle: | 1039 except KeyError: |
1024 # we don't wait for the Deferred (i.e. no yield) on purpose | 1040 log.warning(_("Invalid OMEMO encrypted stanza, missing sender device ID, " |
1025 # there is no need to block the whole message workflow while | 1041 "ignoring: {xml}") |
1026 # updating the bundle | 1042 .format(xml=message_elt.toXml())) |
1027 self.setBundle(client, omemo_session.public_bundle, device_id) | 1043 defer.returnValue(False) |
1044 try: | |
1045 key_elt = next((e for e in header_elt.elements(NS_OMEMO, 'key') | |
1046 if int(e['rid']) == device_id)) | |
1047 except StopIteration: | |
1048 log.warning(_("This OMEMO encrypted stanza has not been encrypted " | |
1049 "for our device (device_id: {device_id}, fingerprint: " | |
1050 "{fingerprint}): {xml}").format( | |
1051 device_id=device_id, | |
1052 fingerprint=omemo_session.public_bundle.ik.hex().upper(), | |
1053 xml=encrypted_elt.toXml())) | |
1054 user_msg = (D_("An OMEMO message from {sender} has not been encrypted for " | |
1055 "our device, we can't decrypt it").format( | |
1056 sender=from_jid.full())) | |
1057 extra = {C.MESS_EXTRA_INFO: C.EXTRA_INFO_DECR_ERR} | |
1058 client.feedback(feedback_jid, user_msg, extra) | |
1059 defer.returnValue(False) | |
1060 except ValueError as e: | |
1061 log.warning(_("Invalid recipient ID: {msg}".format(msg=e))) | |
1062 defer.returnValue(False) | |
1063 is_pre_key = C.bool(key_elt.getAttribute('prekey', 'false')) | |
1064 payload_elt = next(encrypted_elt.elements(NS_OMEMO, 'payload'), None) | |
1065 additional_information = { | |
1066 "from_storage": bool(message_elt.delay) | |
1067 } | |
1068 | |
1069 kwargs = { | |
1070 "bare_jid": from_jid.userhostJID(), | |
1071 "device": s_device_id, | |
1072 "iv": base64.b64decode(bytes(iv_elt)), | |
1073 "message": base64.b64decode(bytes(key_elt)), | |
1074 "is_pre_key_message": is_pre_key, | |
1075 "ciphertext": base64.b64decode(bytes(payload_elt)) | |
1076 if payload_elt is not None else None, | |
1077 "additional_information": additional_information, | |
1078 } | |
1079 | |
1080 try: | |
1081 try: | |
1082 plaintext = yield omemo_session.decryptMessage(**kwargs) | |
1083 except omemo_excpt.TrustException: | |
1084 post_treat.addCallback(client.encryption.markAsUntrusted) | |
1085 kwargs['allow_untrusted'] = True | |
1086 plaintext = yield omemo_session.decryptMessage(**kwargs) | |
1087 else: | |
1088 post_treat.addCallback(client.encryption.markAsTrusted) | |
1089 plaintext = plaintext.decode() | |
1090 except Exception as e: | |
1091 log.warning(_("Can't decrypt message: {reason}\n{xml}").format( | |
1092 reason=e, xml=message_elt.toXml())) | |
1093 user_msg = (D_( | |
1094 "An OMEMO message from {sender} can't be decrypted: {reason}") | |
1095 .format(sender=from_jid.full(), reason=e)) | |
1096 extra = {C.MESS_EXTRA_INFO: C.EXTRA_INFO_DECR_ERR} | |
1097 client.feedback(feedback_jid, user_msg, extra) | |
1098 defer.returnValue(False) | |
1099 finally: | |
1100 if omemo_session.republish_bundle: | |
1101 # we don't wait for the Deferred (i.e. no yield) on purpose | |
1102 # there is no need to block the whole message workflow while | |
1103 # updating the bundle | |
1104 self.setBundle(client, omemo_session.public_bundle, device_id) | |
1028 | 1105 |
1029 message_elt.children.remove(encrypted_elt) | 1106 message_elt.children.remove(encrypted_elt) |
1030 if plaintext: | 1107 if plaintext: |
1031 message_elt.addElement("body", content=plaintext) | 1108 message_elt.addElement("body", content=plaintext) |
1032 post_treat.addCallback(client.encryption.markAsEncrypted) | 1109 post_treat.addCallback(client.encryption.markAsEncrypted) |
1033 defer.returnValue(True) | 1110 defer.returnValue(True) |
1111 | |
1112 def getJIDsForRoom(self, client, room_jid): | |
1113 if self._m is None: | |
1114 exceptions.InternalError("XEP-0045 plugin missing, can't encrypt for group chat") | |
1115 room = self._m.getRoom(client, room_jid) | |
1116 return [u.entity.userhostJID() for u in room.roster.values()] | |
1117 | |
1118 def _expireMUCCache(self, client): | |
1119 client._xep_0384_muc_cache_timer = None | |
1120 for (room_jid, uid), msg in client._xep_0384_muc_cache.items(): | |
1121 client.feedback( | |
1122 room_jid, | |
1123 D_("Our message with UID {uid} has not been received in time, it has " | |
1124 "probably been lost. The message was: {msg!r}").format( | |
1125 uid=uid, msg=str(msg))) | |
1126 | |
1127 client._xep_0384_muc_cache.clear() | |
1128 log.warning("Cache for OMEMO MUC has expired") | |
1034 | 1129 |
1035 @defer.inlineCallbacks | 1130 @defer.inlineCallbacks |
1036 def _sendMessageDataTrigger(self, client, mess_data): | 1131 def _sendMessageDataTrigger(self, client, mess_data): |
1037 encryption = mess_data.get(C.MESS_KEY_ENCRYPTION) | 1132 encryption = mess_data.get(C.MESS_KEY_ENCRYPTION) |
1038 if encryption is None or encryption['plugin'].namespace != NS_OMEMO: | 1133 if encryption is None or encryption['plugin'].namespace != NS_OMEMO: |
1039 return | 1134 return |
1040 message_elt = mess_data["xml"] | 1135 message_elt = mess_data["xml"] |
1041 to_jid = mess_data["to"].userhostJID() | 1136 if mess_data['type'] == C.MESS_TYPE_GROUPCHAT: |
1137 feedback_jid = room_jid = mess_data['to'] | |
1138 to_jids = self.getJIDsForRoom(client, room_jid) | |
1139 else: | |
1140 feedback_jid = to_jid = mess_data["to"].userhostJID() | |
1141 to_jids = [to_jid] | |
1042 log.debug("encrypting message") | 1142 log.debug("encrypting message") |
1043 body = None | 1143 body = None |
1044 for child in list(message_elt.children): | 1144 for child in list(message_elt.children): |
1045 if child.name == "body": | 1145 if child.name == "body": |
1046 # we remove all unencrypted body, | 1146 # we remove all unencrypted body, |
1054 | 1154 |
1055 if body is None: | 1155 if body is None: |
1056 log.warning("No message found") | 1156 log.warning("No message found") |
1057 return | 1157 return |
1058 | 1158 |
1059 encryption_data = yield self.encryptMessage(client, to_jid, str(body)) | 1159 body = str(body) |
1160 | |
1161 if mess_data['type'] == C.MESS_TYPE_GROUPCHAT: | |
1162 key = (room_jid, mess_data['uid']) | |
1163 # XXX: we can't encrypt message for our own device for security reason | |
1164 # so we keep the plain text version in cache until we receive the | |
1165 # message. We don't send it directly to bridge to keep a workflow | |
1166 # similar to plain text MUC, so when we see it in frontend we know | |
1167 # that it has been sent correctly. | |
1168 client._xep_0384_muc_cache[key] = body | |
1169 timer = client._xep_0384_muc_cache_timer | |
1170 if timer is None: | |
1171 client._xep_0384_muc_cache_timer = reactor.callLater( | |
1172 MUC_CACHE_TTL, self._expireMUCCache, client) | |
1173 else: | |
1174 timer.reset(MUC_CACHE_TTL) | |
1175 # we use origin-id when possible, to identifiy the message in a stable way | |
1176 if self._sid is not None: | |
1177 self._sid.addOriginId(message_elt, mess_data['uid']) | |
1178 | |
1179 encryption_data = yield self.encryptMessage( | |
1180 client, to_jids, body, feedback_jid=feedback_jid) | |
1060 | 1181 |
1061 encrypted_elt = message_elt.addElement((NS_OMEMO, 'encrypted')) | 1182 encrypted_elt = message_elt.addElement((NS_OMEMO, 'encrypted')) |
1062 header_elt = encrypted_elt.addElement('header') | 1183 header_elt = encrypted_elt.addElement('header') |
1063 header_elt['sid'] = str(encryption_data['sid']) | 1184 header_elt['sid'] = str(encryption_data['sid']) |
1064 bare_jid_s = to_jid.userhost() | 1185 |
1065 | 1186 for to_jid in to_jids: |
1066 for rid, data in encryption_data['keys'][bare_jid_s].items(): | 1187 bare_jid_s = to_jid.userhost() |
1067 key_elt = header_elt.addElement( | 1188 |
1068 'key', | 1189 for rid, data in encryption_data['keys'][bare_jid_s].items(): |
1069 content=b64enc(data['data'])) | 1190 key_elt = header_elt.addElement( |
1070 key_elt['rid'] = str(rid) | 1191 'key', |
1071 if data['pre_key']: | 1192 content=b64enc(data['data'])) |
1072 key_elt['prekey'] = 'true' | 1193 key_elt['rid'] = str(rid) |
1194 if data['pre_key']: | |
1195 key_elt['prekey'] = 'true' | |
1073 | 1196 |
1074 header_elt.addElement( | 1197 header_elt.addElement( |
1075 'iv', | 1198 'iv', |
1076 content=b64enc(encryption_data['iv'])) | 1199 content=b64enc(encryption_data['iv'])) |
1077 try: | 1200 try: |