Mercurial > libervia-backend
diff sat/plugins/plugin_sec_otr.py @ 2743:da59ff099b32
core (memory/encryption), plugin OTR: finished OTR integration in encryption:
- messageEncryptionStart and messageEncryptionStop are now async
- an encryption plugin can now have a startEncryption and endEncryption method, which are called automatically when suitable
- trust is now put in "trusted" key of message_data, and markAsTrusted has been added, it is used in OTR
- getTrustUI implemented for OTR, using legacy authenticate code
- catch potr.crypt.InvalidParameterError on decryption
- fixed some service OTR messages which were not correctly marked as not for storing, and thus were added to MAM archive/carbon copied
- other bug fixes
author | Goffi <goffi@goffi.org> |
---|---|
date | Thu, 03 Jan 2019 21:00:00 +0100 |
parents | a86f494457c2 |
children | 3dd265d281e1 |
line wrap: on
line diff
--- a/sat/plugins/plugin_sec_otr.py Thu Jan 03 20:51:08 2019 +0100 +++ b/sat/plugins/plugin_sec_otr.py Thu Jan 03 21:00:00 2019 +0100 @@ -74,9 +74,21 @@ class Context(potr.context.Context): - def __init__(self, host, account, other_jid): - super(Context, self).__init__(account, other_jid) - self.host = host + def __init__(self, context_manager, other_jid): + self.context_manager = context_manager + super(Context, self).__init__(context_manager.account, other_jid) + + @property + def host(self): + return self.context_manager.host + + @property + def _p_hints(self): + return self.context_manager.parent._p_hints + + @property + def _p_carbons(self): + return self.context_manager.parent._p_carbons def getPolicy(self, key): if key in DEFAULT_POLICY_FLAGS: @@ -110,12 +122,42 @@ "timestamp": time.time(), } client.generateMessageXML(mess_data) + xml = mess_data[u'xml'] + self._p_carbons.setPrivate(xml) + self._p_hints.addHintElements(xml, [ + self._p_hints.HINT_NO_COPY, + self._p_hints.HINT_NO_PERMANENT_STORE]) client.send(mess_data["xml"]) else: message_elt = appdata[u"xml"] assert message_elt.name == u"message" message_elt.addElement("body", content=msg) + def stopCb(self, __, feedback): + client = self.user.client + self.host.bridge.otrState( + OTR_STATE_UNENCRYPTED, self.peer.full(), client.profile + ) + client.feedback(self.peer, feedback) + + def stopEb(self, failure_): + # encryption may be already stopped in case of manual stop + if not failure_.check(exceptions.NotFound): + log.error(u"Error while stopping OTR encryption: {msg}".format(msg=failure_)) + + def isTrusted(self): + # we have to check value because potr code says that a 2-tuples should be + # returned while in practice it's either None or u"trusted" + trusted = self.getCurrentTrust() + if trusted is None: + return False + elif trusted == u'trusted': + return True + else: + log.error(u"Unexpected getCurrentTrust() value: {value}".format( + value=trusted)) + return False + def setState(self, state): client = self.user.client old_state = self.state @@ -123,17 +165,17 @@ log.debug(u"setState: %s (old_state=%s)" % (state, old_state)) if state == potr.context.STATE_PLAINTEXT: - client.encryption.stop(self.peer, NS_OTR) feedback = _(u"/!\\ conversation with %(other_jid)s is now UNENCRYPTED") % { "other_jid": self.peer.full() } - self.host.bridge.otrState( - OTR_STATE_UNENCRYPTED, self.peer.full(), client.profile - ) + d = client.encryption.stop(self.peer, NS_OTR) + d.addCallback(self.stopCb, feedback=feedback) + d.addErrback(self.stopEb) + return elif state == potr.context.STATE_ENCRYPTED: client.encryption.start(self.peer, NS_OTR) try: - trusted = self.getCurrentTrust() + trusted = self.isTrusted() except TypeError: trusted = False trusted_str = _(u"trusted") if trusted else _(u"untrusted") @@ -155,13 +197,13 @@ OTR_STATE_ENCRYPTED, self.peer.full(), client.profile ) elif state == potr.context.STATE_FINISHED: - client.encryption.stop(self.peer, NS_OTR) feedback = D_(u"OTR conversation with {other_jid} is FINISHED").format( other_jid=self.peer.full() ) - self.host.bridge.otrState( - OTR_STATE_UNENCRYPTED, self.peer.full(), client.profile - ) + d = client.encryption.stop(self.peer, NS_OTR) + d.addCallback(self.stopCb, feedback=feedback) + d.addErrback(self.stopEb) + return else: log.error(D_(u"Unknown OTR state")) return @@ -240,15 +282,19 @@ class ContextManager(object): - def __init__(self, host, client): - self.host = host - self.account = Account(host, client) + def __init__(self, parent, client): + self.parent = parent + self.account = Account(parent.host, client) self.contexts = {} + @property + def host(self): + return self.parent.host + def startContext(self, other_jid): assert isinstance(other_jid, jid.JID) context = self.contexts.setdefault( - other_jid, Context(self.host, self.account, other_jid) + other_jid, Context(self, other_jid) ) return context @@ -323,7 +369,7 @@ def profileConnected(self, client): if client.profile in self.skipped_profiles: return - ctxMng = client._otr_context_manager = ContextManager(self.host, client) + ctxMng = client._otr_context_manager = ContextManager(self, client) client._otr_data = persistent.PersistentBinaryDict(NS_OTR, client.profile) yield client._otr_data.load() encrypted_priv_key = client._otr_data.get(PRIVATE_KEY, None) @@ -346,6 +392,110 @@ context.disconnect() del client._otr_context_manager + # encryption plugin methods + + def startEncryption(self, client, entity_jid): + self.startRefresh(client, entity_jid) + + def stopEncryption(self, client, entity_jid): + self.endSession(client, entity_jid) + + def getTrustUI(self, client, entity_jid): + if not entity_jid.resource: + entity_jid.resource = self.host.memory.getMainResource( + client, entity_jid + ) # FIXME: temporary and unsecure, must be changed when frontends + # are refactored + ctxMng = client._otr_context_manager + otrctx = ctxMng.getContextForUser(entity_jid) + priv_key = ctxMng.account.privkey + + if priv_key is None: + # we have no private key yet + dialog = xml_tools.XMLUI( + C.XMLUI_DIALOG, + dialog_opt={ + C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE, + C.XMLUI_DATA_MESS: _( + u"You have no private key yet, start an OTR conversation to " + u"have one" + ), + C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_WARNING, + }, + title=_(u"No private key"), + ) + return dialog + + other_fingerprint = otrctx.getCurrentKey() + + if other_fingerprint is None: + # we have a private key, but not the fingerprint of our correspondent + dialog = xml_tools.XMLUI( + C.XMLUI_DIALOG, + dialog_opt={ + C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE, + C.XMLUI_DATA_MESS: _( + u"Your fingerprint is:\n{fingerprint}\n\n" + u"Start an OTR conversation to have your correspondent one." + ).format(fingerprint=priv_key), + C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_INFO, + }, + title=_(u"Fingerprint"), + ) + return dialog + + def setTrust(raw_data, profile): + if xml_tools.isXMLUICancelled(raw_data): + return {} + # This method is called when authentication form is submited + data = xml_tools.XMLUIResult2DataFormResult(raw_data) + if data["match"] == "yes": + otrctx.setCurrentTrust(OTR_STATE_TRUSTED) + note_msg = _(u"Your correspondent {correspondent} is now TRUSTED") + self.host.bridge.otrState( + OTR_STATE_TRUSTED, entity_jid.full(), client.profile + ) + else: + otrctx.setCurrentTrust("") + note_msg = _(u"Your correspondent {correspondent} is now UNTRUSTED") + self.host.bridge.otrState( + OTR_STATE_UNTRUSTED, entity_jid.full(), client.profile + ) + note = xml_tools.XMLUI( + C.XMLUI_DIALOG, + dialog_opt={ + C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE, + C.XMLUI_DATA_MESS: note_msg.format(correspondent=otrctx.peer), + }, + ) + return {"xmlui": note.toXml()} + + submit_id = self.host.registerCallback(setTrust, with_data=True, one_shot=True) + trusted = otrctx.isTrusted() + + xmlui = xml_tools.XMLUI( + C.XMLUI_FORM, + title=_(u"Authentication ({entity_jid})").format(entity_jid=entity_jid.full()), + submit_id=submit_id, + ) + xmlui.addText(_(AUTH_TXT)) + xmlui.addDivider() + xmlui.addText( + D_(u"Your own fingerprint is:\n{fingerprint}").format(fingerprint=priv_key) + ) + xmlui.addText( + D_(u"Your correspondent fingerprint should be:\n{fingerprint}").format( + fingerprint=other_fingerprint + ) + ) + xmlui.addDivider("blank") + xmlui.changeContainer("pairs") + xmlui.addLabel(D_(u"Is your correspondent fingerprint the same as here ?")) + xmlui.addList( + "match", [("yes", _("yes")), ("no", _("no"))], ["yes" if trusted else "no"] + ) + return xmlui + def _otrStartRefresh(self, menu_data, profile): """Start or refresh an OTR session @@ -422,97 +572,7 @@ def authenticate(self, client, to_jid): """Authenticate other user and see our own fingerprint""" - if not to_jid.resource: - to_jid.resource = self.host.memory.getMainResource( - client, to_jid - ) # FIXME: temporary and unsecure, must be changed when frontends - # are refactored - ctxMng = client._otr_context_manager - otrctx = ctxMng.getContextForUser(to_jid) - priv_key = ctxMng.account.privkey - - if priv_key is None: - # we have no private key yet - dialog = xml_tools.XMLUI( - C.XMLUI_DIALOG, - dialog_opt={ - C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE, - C.XMLUI_DATA_MESS: _( - u"You have no private key yet, start an OTR conversation to " - u"have one" - ), - C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_WARNING, - }, - title=_(u"No private key"), - ) - return {"xmlui": dialog.toXml()} - - other_fingerprint = otrctx.getCurrentKey() - - if other_fingerprint is None: - # we have a private key, but not the fingerprint of our correspondent - dialog = xml_tools.XMLUI( - C.XMLUI_DIALOG, - dialog_opt={ - C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE, - C.XMLUI_DATA_MESS: _( - u"Your fingerprint is:\n{fingerprint}\n\n" - u"Start an OTR conversation to have your correspondent one." - ).format(fingerprint=priv_key), - C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_INFO, - }, - title=_(u"Fingerprint"), - ) - return {"xmlui": dialog.toXml()} - - def setTrust(raw_data, profile): - # This method is called when authentication form is submited - data = xml_tools.XMLUIResult2DataFormResult(raw_data) - if data["match"] == "yes": - otrctx.setCurrentTrust(OTR_STATE_TRUSTED) - note_msg = _(u"Your correspondent {correspondent} is now TRUSTED") - self.host.bridge.otrState( - OTR_STATE_TRUSTED, to_jid.full(), client.profile - ) - else: - otrctx.setCurrentTrust("") - note_msg = _(u"Your correspondent {correspondent} is now UNTRUSTED") - self.host.bridge.otrState( - OTR_STATE_UNTRUSTED, to_jid.full(), client.profile - ) - note = xml_tools.XMLUI( - C.XMLUI_DIALOG, - dialog_opt={ - C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE, - C.XMLUI_DATA_MESS: note_msg.format(correspondent=otrctx.peer), - }, - ) - return {"xmlui": note.toXml()} - - submit_id = self.host.registerCallback(setTrust, with_data=True, one_shot=True) - trusted = bool(otrctx.getCurrentTrust()) - - xmlui = xml_tools.XMLUI( - C.XMLUI_FORM, - title=_("Authentication (%s)") % to_jid.full(), - submit_id=submit_id, - ) - xmlui.addText(_(AUTH_TXT)) - xmlui.addDivider() - xmlui.addText( - D_(u"Your own fingerprint is:\n{fingerprint}").format(fingerprint=priv_key) - ) - xmlui.addText( - D_(u"Your correspondent fingerprint should be:\n{fingerprint}").format( - fingerprint=other_fingerprint - ) - ) - xmlui.addDivider("blank") - xmlui.changeContainer("pairs") - xmlui.addLabel(D_(u"Is your correspondent fingerprint the same as here ?")) - xmlui.addList( - "match", [("yes", _("yes")), ("no", _("no"))], ["yes" if trusted else "no"] - ) + xmlui = self.getTrustUI(client, to_jid) return {"xmlui": xmlui.toXml()} def _dropPrivKey(self, menu_data, profile): @@ -601,6 +661,12 @@ feedback = msg client.feedback(from_jid, msg) raise failure.Failure(exceptions.CancelError(msg)) + except potr.crypt.InvalidParameterError as e: + msg = D_(u"Error while trying de decrypt OTR message: {msg}".format(msg=e)) + log.warning(msg) + feedback = msg + client.feedback(from_jid, msg) + raise failure.Failure(exceptions.CancelError(msg)) except StopIteration: return data else: @@ -629,6 +695,13 @@ exceptions.CancelError("Cancelled by OTR") ) # no message at all (no history, no signal) client.encryption.markAsEncrypted(data) + trusted = otrctx.isTrusted() + + if trusted: + client.encryption.markAsTrusted(data) + else: + client.encryption.markAsUntrusted(data) + return data def _receivedTreatmentForSkippedProfiles(self, data):