# HG changeset patch # User Goffi # Date 1409091794 -7200 # Node ID 7fcafc3206b111b0f740469fdfb017b3c5c56b5b # Parent 7f32371568e4f54d8e37757aa758bc6241650564 plugin OTR: authentication management + fixed a bug in setState (due to a wrong docstring in potr.context.getCurrentTrust) diff -r 7f32371568e4 -r 7fcafc3206b1 src/plugins/plugin_sec_otr.py --- a/src/plugins/plugin_sec_otr.py Tue Aug 26 13:33:12 2014 +0200 +++ b/src/plugins/plugin_sec_otr.py Wed Aug 27 00:23:14 2014 +0200 @@ -25,6 +25,7 @@ from sat.core.log import getLogger from sat.core import exceptions log = getLogger(__name__) +from sat.tools import xml_tools from twisted.words.protocols.jabber import jid from twisted.python import failure from twisted.internet import defer @@ -34,6 +35,7 @@ NS_OTR = "otr_plugin" PRIVATE_KEY = "PRIVATE KEY" MAIN_MENU = D_('OTR') +AUTH_TXT = D_("To authenticate your correspondent, you need to give your below fingerprint *BY AN EXTERNAL CANAL* (i.e. not in this chat), and check that the one he give you is the same as below. If there is a mismatch, there can be a spy between you !") DEFAULT_POLICY_FLAGS = { 'ALLOW_V1':False, @@ -54,7 +56,6 @@ class Context(potr.context.Context): - def __init__(self, host, account, other_jid): super(Context, self).__init__(account, other_jid) self.host = host @@ -88,7 +89,7 @@ feedback = _(u"/!\\ conversation with %(other_jid)s is now UNENCRYPTED") % {'other_jid': self.peer.full()} elif state == potr.context.STATE_ENCRYPTED: try: - fingerprint, trusted = self.getCurrentTrust() + trusted = self.getCurrentTrust() except TypeError: trusted = False trusted_str = _(u"trusted") if trusted else _(u"untrusted") @@ -138,6 +139,28 @@ self.client.otr_data[PRIVATE_KEY] = encrypted_priv_key d.addCallback(save_encrypted_key) + def loadTrusts(self): + trust_data = self.client.otr_data.get('trust', {}) + for jid_, jid_data in trust_data.iteritems(): + for fingerprint, trust_level in jid_data.iteritems(): + log.debug('setting trust for {jid}: [{fingerprint}] = "{trust_level}"'.format(jid=jid_, fingerprint=fingerprint, trust_level=trust_level)) + self.trusts.setdefault(jid.JID(jid_), {})[fingerprint] = trust_level + + def saveTrusts(self): + log.debug("saving trusts for {profile}".format(profile=self.client.profile)) + log.debug("trusts = {}".format(self.client.otr_data['trust'])) + self.client.otr_data.force('trust') + + def setTrust(self, other_jid, fingerprint, trustLevel): + try: + trust_data = self.client.otr_data['trust'] + except KeyError: + trust_data = {} + self.client.otr_data['trust'] = trust_data + jid_data = trust_data.setdefault(other_jid.full(), {}) + jid_data[fingerprint] = trustLevel + super(Account, self).setTrust(other_jid, fingerprint, trustLevel) + class ContextManager(object): @@ -169,6 +192,7 @@ host.trigger.add("sendMessage", self.sendMessageTrigger, priority=100000) host.importMenu((MAIN_MENU, D_("Start/Refresh")), self._startRefresh, security_limit=0, help_string=D_("Start or refresh an OTR session"), type_=C.MENU_SINGLE) host.importMenu((MAIN_MENU, D_("End session")), self._endSession, security_limit=0, help_string=D_("Finish an OTR session"), type_=C.MENU_SINGLE) + host.importMenu((MAIN_MENU, D_("Authenticate")), self._authenticate, security_limit=0, help_string=D_("Authenticate user/see your fingerprint"), type_=C.MENU_SINGLE) def _fixPotr(self): # FIXME: potr fix for bad unicode handling @@ -189,7 +213,7 @@ @defer.inlineCallbacks def profileConnected(self, profile): client = self.host.getClient(profile) - self.context_managers[profile] = ContextManager(self.host, client) + ctxMng = self.context_managers[profile] = ContextManager(self.host, client) client.otr_data = persistent.PersistentBinaryDict(NS_OTR, profile) yield client.otr_data.load() encrypted_priv_key = client.otr_data.get(PRIVATE_KEY, None) @@ -198,6 +222,7 @@ client.otr_priv_key = potr.crypt.PK.parsePrivateKey(priv_key.decode('hex'))[0] else: client.otr_priv_key = None + ctxMng.account.loadTrusts() def _startRefresh(self, menu_data, profile): """Start or refresh an OTR session @@ -234,6 +259,76 @@ otrctx.disconnect() return {} + def _authenticate(self, menu_data, profile): + """Authenticate other user and see our own fingerprint + + @param menu_data: %(menu_data)s + @param profile: %(doc_profile)s + """ + try: + to_jid = jid.JID(menu_data['jid']) + if not to_jid.resource: + to_jid.resource = self.host.memory.getLastResource(to_jid, profile) # FIXME: temporary and unsecure, must be changed when frontends are refactored + except KeyError: + log.error(_("jid key is not present !")) + return defer.fail(exceptions.DataError) + otrctx = self.context_managers[profile].getContextForUser(to_jid) + + priv_key = otrctx.user.client.otr_priv_key + + 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: _("You have no private key yet, start an OTR conversation to have one"), + C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_WARNING + }, + title = _("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: _("Your fingerprint is\n{fingerprint}\n\nStart an OTR conversation to have your correspondent one.").format(fingerprint=priv_key), + C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_INFO + }, + title = _("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('verified') + note_msg = _("Your correspondant {correspondent} is now TRUSTED") + else: + otrctx.setCurrentTrust('') + note_msg = _("Your correspondant {correspondent} is now UNTRUSTED") + 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.user.name)} + ) + 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(_("Your own fingerprint is:\n{fingerprint}").format(fingerprint=priv_key)) + xmlui.addText(_("Your correspondent fingerprint should be:\n{fingerprint}").format(fingerprint=other_fingerprint)) + xmlui.addDivider('blank') + xmlui.changeContainer('pairs') + xmlui.addLabel(_('Is your correspondent fingerprint the same as here ?')) + xmlui.addList("match", [('yes', _('yes')),('no', _('no'))], ['yes' if trusted else 'no']) + return {'xmlui': xmlui.toXml()} + def _receivedTreatment(self, data, profile): from_jid = jid.JID(data['from']) log.debug(u"_receivedTreatment [from_jid = %s]" % from_jid) diff -r 7f32371568e4 -r 7fcafc3206b1 src/tools/xml_tools.py --- a/src/tools/xml_tools.py Tue Aug 26 13:33:12 2014 +0200 +++ b/src/tools/xml_tools.py Wed Aug 27 00:23:14 2014 +0200 @@ -740,9 +740,9 @@ """ @param xmlui - @param options (list[string, tuple]): each option can be given as: - - a string if the label and the value are the same - - a couple if the label and the value differ + @param options (list[option]): each option can be given as: + - a single string if the label and the value are the same + - a tuple with a couple of string (value,label) if the label and the value differ @param selected (list[string]): list of the selected values @param style (string) @param name (string)