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: