comparison libervia/backend/plugins/plugin_sec_otr.py @ 4270:0d7bb4df2343

Reformatted code base using black.
author Goffi <goffi@goffi.org>
date Wed, 19 Jun 2024 18:44:57 +0200
parents 4b842c1fb686
children
comparison
equal deleted inserted replaced
4269:64a85ce8be70 4270:0d7bb4df2343
107 @param msg_str(str): encrypted message body 107 @param msg_str(str): encrypted message body
108 @param appdata(None, dict): None for signal message, 108 @param appdata(None, dict): None for signal message,
109 message data when an encrypted message is going to be sent 109 message data when an encrypted message is going to be sent
110 """ 110 """
111 assert isinstance(self.peer, jid.JID) 111 assert isinstance(self.peer, jid.JID)
112 msg = msg_str.decode('utf-8') 112 msg = msg_str.decode("utf-8")
113 client = self.user.client 113 client = self.user.client
114 log.debug("injecting encrypted message to {to}".format(to=self.peer)) 114 log.debug("injecting encrypted message to {to}".format(to=self.peer))
115 if appdata is None: 115 if appdata is None:
116 mess_data = { 116 mess_data = {
117 "from": client.jid, 117 "from": client.jid,
122 "type": "chat", 122 "type": "chat",
123 "extra": {}, 123 "extra": {},
124 "timestamp": time.time(), 124 "timestamp": time.time(),
125 } 125 }
126 client.generate_message_xml(mess_data) 126 client.generate_message_xml(mess_data)
127 xml = mess_data['xml'] 127 xml = mess_data["xml"]
128 self._p_carbons.set_private(xml) 128 self._p_carbons.set_private(xml)
129 self._p_hints.add_hint_elements(xml, [ 129 self._p_hints.add_hint_elements(
130 self._p_hints.HINT_NO_COPY, 130 xml, [self._p_hints.HINT_NO_COPY, self._p_hints.HINT_NO_PERMANENT_STORE]
131 self._p_hints.HINT_NO_PERMANENT_STORE]) 131 )
132 client.send(mess_data["xml"]) 132 client.send(mess_data["xml"])
133 else: 133 else:
134 message_elt = appdata["xml"] 134 message_elt = appdata["xml"]
135 assert message_elt.name == "message" 135 assert message_elt.name == "message"
136 message_elt.addElement("body", content=msg) 136 message_elt.addElement("body", content=msg)
151 # we have to check value because potr code says that a 2-tuples should be 151 # we have to check value because potr code says that a 2-tuples should be
152 # returned while in practice it's either None or u"trusted" 152 # returned while in practice it's either None or u"trusted"
153 trusted = self.getCurrentTrust() 153 trusted = self.getCurrentTrust()
154 if trusted is None: 154 if trusted is None:
155 return False 155 return False
156 elif trusted == 'trusted': 156 elif trusted == "trusted":
157 return True 157 return True
158 else: 158 else:
159 log.error("Unexpected getCurrentTrust() value: {value}".format( 159 log.error("Unexpected getCurrentTrust() value: {value}".format(value=trusted))
160 value=trusted))
161 return False 160 return False
162 161
163 def set_state(self, state): 162 def set_state(self, state):
164 client = self.user.client 163 client = self.user.client
165 old_state = self.state 164 old_state = self.state
289 def host(self): 288 def host(self):
290 return self.parent.host 289 return self.parent.host
291 290
292 def start_context(self, other_jid): 291 def start_context(self, other_jid):
293 assert isinstance(other_jid, jid.JID) 292 assert isinstance(other_jid, jid.JID)
294 context = self.contexts.setdefault( 293 context = self.contexts.setdefault(other_jid, Context(self, other_jid))
295 other_jid, Context(self, other_jid)
296 )
297 return context 294 return context
298 295
299 def get_context_for_user(self, other): 296 def get_context_for_user(self, other):
300 log.debug("get_context_for_user [%s]" % other) 297 log.debug("get_context_for_user [%s]" % other)
301 if not other.resource: 298 if not other.resource:
312 self.skipped_profiles = ( 309 self.skipped_profiles = (
313 set() 310 set()
314 ) #  FIXME: OTR should not be skipped per profile, this need to be refactored 311 ) #  FIXME: OTR should not be skipped per profile, this need to be refactored
315 self._p_hints = host.plugins["XEP-0334"] 312 self._p_hints = host.plugins["XEP-0334"]
316 self._p_carbons = host.plugins["XEP-0280"] 313 self._p_carbons = host.plugins["XEP-0280"]
317 host.trigger.add("message_received", self.message_received_trigger, priority=100000) 314 host.trigger.add(
315 "message_received", self.message_received_trigger, priority=100000
316 )
318 host.trigger.add("sendMessage", self.send_message_trigger, priority=100000) 317 host.trigger.add("sendMessage", self.send_message_trigger, priority=100000)
319 host.trigger.add("send_message_data", self._send_message_data_trigger) 318 host.trigger.add("send_message_data", self._send_message_data_trigger)
320 host.bridge.add_method( 319 host.bridge.add_method(
321 "skip_otr", ".plugin", in_sign="s", out_sign="", method=self._skip_otr 320 "skip_otr", ".plugin", in_sign="s", out_sign="", method=self._skip_otr
322 ) # FIXME: must be removed, must be done on per-message basis 321 ) # FIXME: must be removed, must be done on per-message basis
372 ctxMng = client._otr_context_manager = ContextManager(self, client) 371 ctxMng = client._otr_context_manager = ContextManager(self, client)
373 client._otr_data = persistent.PersistentBinaryDict(NS_OTR, client.profile) 372 client._otr_data = persistent.PersistentBinaryDict(NS_OTR, client.profile)
374 yield client._otr_data.load() 373 yield client._otr_data.load()
375 encrypted_priv_key = client._otr_data.get(PRIVATE_KEY, None) 374 encrypted_priv_key = client._otr_data.get(PRIVATE_KEY, None)
376 if encrypted_priv_key is not None: 375 if encrypted_priv_key is not None:
377 priv_key = self.host.memory.decrypt_value( 376 priv_key = self.host.memory.decrypt_value(encrypted_priv_key, client.profile)
378 encrypted_priv_key, client.profile
379 )
380 ctxMng.account.privkey = potr.crypt.PK.parsePrivateKey( 377 ctxMng.account.privkey = potr.crypt.PK.parsePrivateKey(
381 unhexlify(priv_key.encode('utf-8')) 378 unhexlify(priv_key.encode("utf-8"))
382 )[0] 379 )[0]
383 else: 380 else:
384 ctxMng.account.privkey = None 381 ctxMng.account.privkey = None
385 ctxMng.account.load_trusts() 382 ctxMng.account.load_trusts()
386 383
403 def get_trust_ui(self, client, entity_jid): 400 def get_trust_ui(self, client, entity_jid):
404 if not entity_jid.resource: 401 if not entity_jid.resource:
405 entity_jid.resource = self.host.memory.main_resource_get( 402 entity_jid.resource = self.host.memory.main_resource_get(
406 client, entity_jid 403 client, entity_jid
407 ) # FIXME: temporary and unsecure, must be changed when frontends 404 ) # FIXME: temporary and unsecure, must be changed when frontends
408 # are refactored 405 # are refactored
409 ctxMng = client._otr_context_manager 406 ctxMng = client._otr_context_manager
410 otrctx = ctxMng.get_context_for_user(entity_jid) 407 otrctx = ctxMng.get_context_for_user(entity_jid)
411 priv_key = ctxMng.account.privkey 408 priv_key = ctxMng.account.privkey
412 409
413 if priv_key is None: 410 if priv_key is None:
515 """Start or refresh an OTR session 512 """Start or refresh an OTR session
516 513
517 @param to_jid(jid.JID): jid to start encrypted session with 514 @param to_jid(jid.JID): jid to start encrypted session with
518 """ 515 """
519 encrypted_session = client.encryption.getSession(to_jid.userhostJID()) 516 encrypted_session = client.encryption.getSession(to_jid.userhostJID())
520 if encrypted_session and encrypted_session['plugin'].namespace != NS_OTR: 517 if encrypted_session and encrypted_session["plugin"].namespace != NS_OTR:
521 raise exceptions.ConflictError(_( 518 raise exceptions.ConflictError(
522 "Can't start an OTR session, there is already an encrypted session " 519 _(
523 "with {name}").format(name=encrypted_session['plugin'].name)) 520 "Can't start an OTR session, there is already an encrypted session "
521 "with {name}"
522 ).format(name=encrypted_session["plugin"].name)
523 )
524 if not to_jid.resource: 524 if not to_jid.resource:
525 to_jid.resource = self.host.memory.main_resource_get( 525 to_jid.resource = self.host.memory.main_resource_get(
526 client, to_jid 526 client, to_jid
527 ) # FIXME: temporary and unsecure, must be changed when frontends 527 ) # FIXME: temporary and unsecure, must be changed when frontends
528 # are refactored 528 # are refactored
529 otrctx = client._otr_context_manager.get_context_for_user(to_jid) 529 otrctx = client._otr_context_manager.get_context_for_user(to_jid)
530 query = otrctx.sendMessage(0, b"?OTRv?") 530 query = otrctx.sendMessage(0, b"?OTRv?")
531 otrctx.inject(query) 531 otrctx.inject(query)
532 532
533 def _otr_session_end(self, menu_data, profile): 533 def _otr_session_end(self, menu_data, profile):
549 """End an OTR session""" 549 """End an OTR session"""
550 if not to_jid.resource: 550 if not to_jid.resource:
551 to_jid.resource = self.host.memory.main_resource_get( 551 to_jid.resource = self.host.memory.main_resource_get(
552 client, to_jid 552 client, to_jid
553 ) # FIXME: temporary and unsecure, must be changed when frontends 553 ) # FIXME: temporary and unsecure, must be changed when frontends
554 # are refactored 554 # are refactored
555 otrctx = client._otr_context_manager.get_context_for_user(to_jid) 555 otrctx = client._otr_context_manager.get_context_for_user(to_jid)
556 otrctx.disconnect() 556 otrctx.disconnect()
557 return {} 557 return {}
558 558
559 def _otr_authenticate(self, menu_data, profile): 559 def _otr_authenticate(self, menu_data, profile):
586 to_jid = jid.JID(menu_data["jid"]) 586 to_jid = jid.JID(menu_data["jid"])
587 if not to_jid.resource: 587 if not to_jid.resource:
588 to_jid.resource = self.host.memory.main_resource_get( 588 to_jid.resource = self.host.memory.main_resource_get(
589 client, to_jid 589 client, to_jid
590 ) # FIXME: temporary and unsecure, must be changed when frontends 590 ) # FIXME: temporary and unsecure, must be changed when frontends
591 # are refactored 591 # are refactored
592 except KeyError: 592 except KeyError:
593 log.error(_("jid key is not present !")) 593 log.error(_("jid key is not present !"))
594 return defer.fail(exceptions.DataError) 594 return defer.fail(exceptions.DataError)
595 595
596 ctxMng = client._otr_context_manager 596 ctxMng = client._otr_context_manager
604 # we end all sessions 604 # we end all sessions
605 for context in list(ctxMng.contexts.values()): 605 for context in list(ctxMng.contexts.values()):
606 context.disconnect() 606 context.disconnect()
607 ctxMng.account.privkey = None 607 ctxMng.account.privkey = None
608 ctxMng.account.getPrivkey() # as account.privkey is None, getPrivkey 608 ctxMng.account.getPrivkey() # as account.privkey is None, getPrivkey
609 # will generate a new key, and save it 609 # will generate a new key, and save it
610 return { 610 return {
611 "xmlui": xml_tools.note( 611 "xmlui": xml_tools.note(
612 D_("Your private key has been dropped") 612 D_("Your private key has been dropped")
613 ).toXml() 613 ).toXml()
614 } 614 }
628 from_jid = data["from"] 628 from_jid = data["from"]
629 log.debug("_received_treatment [from_jid = %s]" % from_jid) 629 log.debug("_received_treatment [from_jid = %s]" % from_jid)
630 otrctx = client._otr_context_manager.get_context_for_user(from_jid) 630 otrctx = client._otr_context_manager.get_context_for_user(from_jid)
631 631
632 try: 632 try:
633 message = ( 633 message = next(
634 next(iter(data["message"].values())) 634 iter(data["message"].values())
635 ) # FIXME: Q&D fix for message refactoring, message is now a dict 635 ) # FIXME: Q&D fix for message refactoring, message is now a dict
636 res = otrctx.receiveMessage(message.encode("utf-8")) 636 res = otrctx.receiveMessage(message.encode("utf-8"))
637 except (potr.context.UnencryptedMessage, potr.context.NotOTRMessage): 637 except (potr.context.UnencryptedMessage, potr.context.NotOTRMessage):
638 # potr has a bug with Python 3 and test message against str while bytes are 638 # potr has a bug with Python 3 and test message against str while bytes are
639 # expected, resulting in a NoOTRMessage raised instead of UnencryptedMessage; 639 # expected, resulting in a NoOTRMessage raised instead of UnencryptedMessage;
640 # so we catch NotOTRMessage as a workaround 640 # so we catch NotOTRMessage as a workaround
641 # TODO: report this upstream 641 # TODO: report this upstream
642 encrypted = False 642 encrypted = False
643 if otrctx.state == potr.context.STATE_ENCRYPTED: 643 if otrctx.state == potr.context.STATE_ENCRYPTED:
644 log.warning( 644 log.warning(
645 "Received unencrypted message in an encrypted context (from {jid})" 645 "Received unencrypted message in an encrypted context (from {jid})".format(
646 .format(jid=from_jid.full()) 646 jid=from_jid.full()
647 )
647 ) 648 )
648 649
649 feedback = ( 650 feedback = (
650 D_( 651 D_(
651 "WARNING: received unencrypted data in a supposedly encrypted " 652 "WARNING: received unencrypted data in a supposedly encrypted "
690 del data["history"] 691 del data["history"]
691 except KeyError: 692 except KeyError:
692 pass 693 pass
693 # TODO: add skip history as an option, but by default we don't skip it 694 # TODO: add skip history as an option, but by default we don't skip it
694 # data[u'history'] = C.HISTORY_SKIP # we send the decrypted message to 695 # data[u'history'] = C.HISTORY_SKIP # we send the decrypted message to
695 # frontends, but we don't want it in 696 # frontends, but we don't want it in
696 # history 697 # history
697 else: 698 else:
698 raise failure.Failure( 699 raise failure.Failure(
699 exceptions.CancelError("Cancelled by OTR") 700 exceptions.CancelError("Cancelled by OTR")
700 ) # no message at all (no history, no signal) 701 ) # no message at all (no history, no signal)
701 702
735 if client.is_component: 736 if client.is_component:
736 return True 737 return True
737 if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT: 738 if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT:
738 # OTR is not possible in group chats 739 # OTR is not possible in group chats
739 return True 740 return True
740 from_jid = jid.JID(message_elt['from']) 741 from_jid = jid.JID(message_elt["from"])
741 if not from_jid.resource or from_jid.userhostJID() == client.jid.userhostJID(): 742 if not from_jid.resource or from_jid.userhostJID() == client.jid.userhostJID():
742 # OTR is only usable when resources are present 743 # OTR is only usable when resources are present
743 return True 744 return True
744 if client.profile in self.skipped_profiles: 745 if client.profile in self.skipped_profiles:
745 post_treat.addCallback(self._received_treatment_for_skipped_profiles) 746 post_treat.addCallback(self._received_treatment_for_skipped_profiles)
749 750
750 def _send_message_data_trigger(self, client, mess_data): 751 def _send_message_data_trigger(self, client, mess_data):
751 if client.is_component: 752 if client.is_component:
752 return True 753 return True
753 encryption = mess_data.get(C.MESS_KEY_ENCRYPTION) 754 encryption = mess_data.get(C.MESS_KEY_ENCRYPTION)
754 if encryption is None or encryption['plugin'].namespace != NS_OTR: 755 if encryption is None or encryption["plugin"].namespace != NS_OTR:
755 return 756 return
756 to_jid = mess_data['to'] 757 to_jid = mess_data["to"]
757 if not to_jid.resource: 758 if not to_jid.resource:
758 to_jid.resource = self.host.memory.main_resource_get( 759 to_jid.resource = self.host.memory.main_resource_get(
759 client, to_jid 760 client, to_jid
760 ) # FIXME: temporary and unsecure, must be changed when frontends 761 ) # FIXME: temporary and unsecure, must be changed when frontends
761 otrctx = client._otr_context_manager.get_context_for_user(to_jid) 762 otrctx = client._otr_context_manager.get_context_for_user(to_jid)
775 message_elt.children.remove(child) 776 message_elt.children.remove(child)
776 if body is None: 777 if body is None:
777 log.warning("No message found") 778 log.warning("No message found")
778 else: 779 else:
779 self._p_carbons.set_private(message_elt) 780 self._p_carbons.set_private(message_elt)
780 self._p_hints.add_hint_elements(message_elt, [ 781 self._p_hints.add_hint_elements(
781 self._p_hints.HINT_NO_COPY, 782 message_elt,
782 self._p_hints.HINT_NO_PERMANENT_STORE]) 783 [self._p_hints.HINT_NO_COPY, self._p_hints.HINT_NO_PERMANENT_STORE],
784 )
783 otrctx.sendMessage(0, str(body).encode("utf-8"), appdata=mess_data) 785 otrctx.sendMessage(0, str(body).encode("utf-8"), appdata=mess_data)
784 else: 786 else:
785 feedback = D_( 787 feedback = D_(
786 "Your message was not sent because your correspondent closed the " 788 "Your message was not sent because your correspondent closed the "
787 "encrypted conversation on his/her side. " 789 "encrypted conversation on his/her side. "
789 ) 791 )
790 log.warning(_("Message discarded because closed encryption channel")) 792 log.warning(_("Message discarded because closed encryption channel"))
791 client.feedback(to_jid, feedback) 793 client.feedback(to_jid, feedback)
792 raise failure.Failure(exceptions.CancelError("Cancelled by OTR plugin")) 794 raise failure.Failure(exceptions.CancelError("Cancelled by OTR plugin"))
793 795
794 def send_message_trigger(self, client, mess_data, pre_xml_treatments, 796 def send_message_trigger(
795 post_xml_treatments): 797 self, client, mess_data, pre_xml_treatments, post_xml_treatments
798 ):
796 if client.is_component: 799 if client.is_component:
797 return True 800 return True
798 if mess_data["type"] == "groupchat": 801 if mess_data["type"] == "groupchat":
799 return True 802 return True
800 803
828 if not entity.resource: 831 if not entity.resource:
829 try: 832 try:
830 entity.resource = self.host.memory.main_resource_get( 833 entity.resource = self.host.memory.main_resource_get(
831 client, entity 834 client, entity
832 ) # FIXME: temporary and unsecure, must be changed when frontends 835 ) # FIXME: temporary and unsecure, must be changed when frontends
833 # are refactored 836 # are refactored
834 except exceptions.UnknownEntityError: 837 except exceptions.UnknownEntityError:
835 return True # entity was not connected 838 return True # entity was not connected
836 if entity in client._otr_context_manager.contexts: 839 if entity in client._otr_context_manager.contexts:
837 otrctx = client._otr_context_manager.get_context_for_user(entity) 840 otrctx = client._otr_context_manager.get_context_for_user(entity)
838 otrctx.disconnect() 841 otrctx.disconnect()