comparison sat/plugins/plugin_xep_0384.py @ 2817:0ab62dd3cf05

plugin XEP-0384: better bundle handling + misc improvments - public bundles are now retrieved only when needed (i.e. on MissingBundleException when encrypting a message) - a feedback is given to user when a message is received but it has not been encrypted for our device - fixed feedback on decryption error if message come from an other device of the client (i.e. it's a carbon copy) - setBundle if called in a "finally" if republish_bundle is set after decrypting a message
author Goffi <goffi@goffi.org>
date Thu, 28 Feb 2019 18:57:26 +0100
parents 003b8b4b56a7
children 11afbbce40d1
comparison
equal deleted inserted replaced
2816:114cdde9ff96 2817:0ab62dd3cf05
781 ## PEP node events callbacks 781 ## PEP node events callbacks
782 782
783 @defer.inlineCallbacks 783 @defer.inlineCallbacks
784 def onNewDevices(self, itemsEvent, profile): 784 def onNewDevices(self, itemsEvent, profile):
785 client = self.host.getClient(profile) 785 client = self.host.getClient(profile)
786 cache = client._xep_0384_cache
787 omemo_session = client._xep_0384_session 786 omemo_session = client._xep_0384_session
788 entity = itemsEvent.sender 787 entity = itemsEvent.sender
789 entity_cache = cache.setdefault(entity, {}) 788
790 devices = self.parseDevices(itemsEvent.items) 789 devices = self.parseDevices(itemsEvent.items)
791 omemo_session.newDeviceList(entity, devices) 790 omemo_session.newDeviceList(entity, devices)
792 missing_devices = devices.difference(entity_cache.keys()) 791
793 if missing_devices:
794 bundles, bundles_not_found = yield self.getBundles(
795 client, entity, missing_devices)
796 entity_cache.update(bundles)
797 if bundles_not_found and entity == client.jid.userhostJID():
798 # we have devices announced in our own public list
799 # with missing bundles
800 own_device = client._xep_0384_device_id
801 if own_device in bundles_not_found:
802 log.warning(_(u"Our own device has no attached bundle, fixing it"))
803 bundles_not_found.remove(own_device)
804 yield self.setBundle(client, omemo_session.public_bundle, own_device)
805
806 if bundles_not_found:
807 # some announced devices have no bundle, we update our public
808 # list to remove missing devices.
809 log.warning(_(
810 u"Some devices have missing bundles, cleaning out public "
811 u"devices list"))
812 existing_devices = devices - bundles_not_found
813 yield self.setDevices(client, existing_devices)
814 # we check that our device has not been removed from the list
815 if entity == client.jid.userhostJID(): 792 if entity == client.jid.userhostJID():
816 own_device = client._xep_0384_device_id 793 own_device = client._xep_0384_device_id
817 if own_device not in devices: 794 if own_device not in devices:
818 log.warning(_(u"Our own device is missing from devices list, fixing it")) 795 log.warning(_(u"Our own device is missing from devices list, fixing it"))
819 devices.add(own_device) 796 devices.add(own_device)
820 yield self.setDevices(client, devices) 797 yield self.setDevices(client, devices)
821
822 798
823 ## triggers 799 ## triggers
824 800
825 @defer.inlineCallbacks 801 @defer.inlineCallbacks
826 def handleProblems(self, client, entity, bundles, problems): 802 def handleProblems(self, client, entity, bundles, problems):
835 this dict will list devices where problems can be ignored 811 this dict will list devices where problems can be ignored
836 (those devices won't receive the encrypted data) 812 (those devices won't receive the encrypted data)
837 """ 813 """
838 # FIXME: not all problems are handled yet 814 # FIXME: not all problems are handled yet
839 untrusted = {} 815 untrusted = {}
816 missing_bundles = {}
840 expect_problems = {} 817 expect_problems = {}
818 cache = client._xep_0384_cache
841 for problem in problems: 819 for problem in problems:
842 if isinstance(problem, omemo_excpt.UntrustedException): 820 if isinstance(problem, omemo_excpt.UntrustedException):
843 untrusted[unicode(hash(problem))] = problem 821 untrusted[unicode(hash(problem))] = problem
822 if isinstance(problem, omemo_excpt.MissingBundleException):
823 pb_entity = jid.JID(problem.bare_jid)
824 entity_cache = cache.setdefault(pb_entity, {})
825 entity_bundles = bundles.setdefault(pb_entity, {})
826 if problem.device in entity_cache:
827 entity_bundles[problem.device] = entity_cache[problem.device]
828 else:
829 found_bundles, missing = yield self.getBundles(
830 client, pb_entity, [problem.device])
831 entity_cache.update(bundles)
832 entity_bundles.update(found_bundles)
833 if problem.device in missing:
834 missing_bundles.setdefault(pb_entity, set()).add(
835 problem.device)
836 expect_problems.setdefault(problem.bare_jid, set()).add(
837 problem.device)
844 elif isinstance(problem, omemo_excpt.NoEligibleDevicesException): 838 elif isinstance(problem, omemo_excpt.NoEligibleDevicesException):
845 pass 839 pass
840
841 for peer_jid, devices in missing_bundles.iteritems():
842 devices_s = [unicode(d) for d in devices]
843 log.warning(
844 _(u"Can't retrieve bundle for device(s) {devices} of entity {peer}, "
845 u"the message will not be readable on this/those device(s)").format(
846 devices=u", ".join(devices_s), peer=peer_jid.full()))
847 client.feedback(
848 entity,
849 D_(u"You're destinee {peer} has missing encryption data on some of "
850 u"his/her device(s) (bundle on device {devices}), the message won't "
851 u"be readable on this/those device.").format(
852 peer=peer_jid.full(), devices=u", ".join(devices_s)))
846 853
847 if untrusted: 854 if untrusted:
848 trust_data = {} 855 trust_data = {}
849 for trust_id, data in untrusted.iteritems(): 856 for trust_id, data in untrusted.iteritems():
850 trust_data[trust_id] = { 857 trust_data[trust_id] = {
871 defer.returnValue(expect_problems) 878 defer.returnValue(expect_problems)
872 879
873 @defer.inlineCallbacks 880 @defer.inlineCallbacks
874 def encryptMessage(self, client, entity_bare_jid, message): 881 def encryptMessage(self, client, entity_bare_jid, message):
875 omemo_session = client._xep_0384_session 882 omemo_session = client._xep_0384_session
876 cache = client._xep_0384_cache
877 try:
878 bundles = {entity_bare_jid: cache[entity_bare_jid]}
879 except KeyError:
880 # No devices know for this entity, let try to find them.
881 # This can happen if the entity is not in our roster, or doesn't handle OMEMO
882 # or if we haven't received the devices from PEP yet.
883 try:
884 devices = yield self.getDevices(client, entity_bare_jid)
885 bundles, __ = yield self.getBundles(client, entity_bare_jid, devices)
886 except Exception as e:
887 raise exceptions.NotFound(
888 _(u"Can retrieve bundles for {entity}: {reason}" .format(
889 entity=entity_bare_jid.full(), reason=e)))
890 else:
891 cache[entity_bare_jid] = bundles
892 bundles = {entity_bare_jid: bundles}
893
894 own_jid = client.jid.userhostJID()
895 if entity_bare_jid != own_jid:
896 # message will be copied to our devices, so we need to add our own bundles
897 bundles[own_jid] = cache[own_jid]
898
899 try: 883 try:
900 # first try may fail, in case of e.g. trust issue or missing bundle 884 # first try may fail, in case of e.g. trust issue or missing bundle
901 encrypted = yield omemo_session.encryptMessage( 885 encrypted = yield omemo_session.encryptMessage(
902 entity_bare_jid, 886 entity_bare_jid,
903 message, 887 message)
904 bundles)
905 except omemo_excpt.EncryptionProblemsException as e: 888 except omemo_excpt.EncryptionProblemsException as e:
906 # we know the problem to solve, we can try to fix them 889 # we know the problem to solve, we can try to fix them
907 expect_problems = yield self.handleProblems(client, entity_bare_jid, bundles, e.problems) 890 bundles = {}
891 expect_problems = yield self.handleProblems(client, entity_bare_jid, bundles,
892 e.problems)
908 # and try an encryption again. 893 # and try an encryption again.
909 try: 894 try:
910 encrypted = yield omemo_session.encryptMessage( 895 encrypted = yield omemo_session.encryptMessage(
911 entity_bare_jid, 896 entity_bare_jid,
912 message, 897 message,
930 # no OMEMO message here 915 # no OMEMO message here
931 defer.returnValue(True) 916 defer.returnValue(True)
932 917
933 # we have an encrypted message let's decrypt it 918 # we have an encrypted message let's decrypt it
934 from_jid = jid.JID(message_elt['from']) 919 from_jid = jid.JID(message_elt['from'])
920 if from_jid.userhostJID() == client.jid.userhostJID():
921 feedback_jid = jid.JID(message_elt['to'])
922 else:
923 feedback_jid = from_jid
935 try: 924 try:
936 omemo_session = client._xep_0384_session 925 omemo_session = client._xep_0384_session
937 except AttributeError: 926 except AttributeError:
938 # on startup, message can ve received before session actually exists 927 # on startup, message can ve received before session actually exists
939 # so we need to synchronise here 928 # so we need to synchronise here
963 u"for our device (device_id: {device_id}, fingerprint: " 952 u"for our device (device_id: {device_id}, fingerprint: "
964 u"{fingerprint}): {xml}").format( 953 u"{fingerprint}): {xml}").format(
965 device_id=device_id, 954 device_id=device_id,
966 fingerprint=omemo_session.public_bundle.ik.encode('hex'), 955 fingerprint=omemo_session.public_bundle.ik.encode('hex'),
967 xml=encrypted_elt.toXml())) 956 xml=encrypted_elt.toXml()))
957 user_msg = (D_(u"An OMEMO message from {sender} has not been encrypted for "
958 u"our device, we can't decrypt it").format(
959 sender=from_jid.full()))
960 extra = {C.MESS_EXTRA_INFO: C.EXTRA_INFO_DECR_ERR}
961 client.feedback(feedback_jid, user_msg, extra)
968 defer.returnValue(False) 962 defer.returnValue(False)
969 except ValueError as e: 963 except ValueError as e:
970 log.warning(_(u"Invalid recipient ID: {msg}".format(msg=e))) 964 log.warning(_(u"Invalid recipient ID: {msg}".format(msg=e)))
971 defer.returnValue(False) 965 defer.returnValue(False)
972 is_pre_key = C.bool(key_elt.getAttribute('prekey', 'false')) 966 is_pre_key = C.bool(key_elt.getAttribute('prekey', 'false'))
998 log.warning(_(u"Can't decrypt message: {reason}\n{xml}").format( 992 log.warning(_(u"Can't decrypt message: {reason}\n{xml}").format(
999 reason=e, xml=message_elt.toXml())) 993 reason=e, xml=message_elt.toXml()))
1000 user_msg = (D_(u"An OMEMO message from {sender} can't be decrypted: {reason}") 994 user_msg = (D_(u"An OMEMO message from {sender} can't be decrypted: {reason}")
1001 .format(sender=from_jid.full(), reason=e)) 995 .format(sender=from_jid.full(), reason=e))
1002 extra = {C.MESS_EXTRA_INFO: C.EXTRA_INFO_DECR_ERR} 996 extra = {C.MESS_EXTRA_INFO: C.EXTRA_INFO_DECR_ERR}
1003 client.feedback(from_jid, user_msg, extra) 997 client.feedback(feedback_jid, user_msg, extra)
1004 defer.returnValue(False) 998 defer.returnValue(False)
1005 if omemo_session.republish_bundle: 999 finally:
1006 # we don't wait for the Deferred (i.e. no yield) on purpose 1000 if omemo_session.republish_bundle:
1007 # there is no need to block the whole message workflow while 1001 # we don't wait for the Deferred (i.e. no yield) on purpose
1008 # updating the bundle 1002 # there is no need to block the whole message workflow while
1009 self.setBundle(client, omemo_session.public_bundle, device_id) 1003 # updating the bundle
1004 self.setBundle(client, omemo_session.public_bundle, device_id)
1010 1005
1011 message_elt.children.remove(encrypted_elt) 1006 message_elt.children.remove(encrypted_elt)
1012 if plaintext: 1007 if plaintext:
1013 message_elt.addElement("body", content=plaintext.decode('utf-8')) 1008 message_elt.addElement("body", content=plaintext.decode('utf-8'))
1014 post_treat.addCallback(client.encryption.markAsEncrypted) 1009 post_treat.addCallback(client.encryption.markAsEncrypted)