Mercurial > libervia-backend
comparison src/plugins/plugin_sec_otr.py @ 1141:7fcafc3206b1
plugin OTR: authentication management + fixed a bug in setState (due to a wrong docstring in potr.context.getCurrentTrust)
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 27 Aug 2014 00:23:14 +0200 |
parents | 768f1f1ef12c |
children | 2481fa96ac1c |
comparison
equal
deleted
inserted
replaced
1140:7f32371568e4 | 1141:7fcafc3206b1 |
---|---|
23 from sat.core.i18n import _, D_ | 23 from sat.core.i18n import _, D_ |
24 from sat.core.constants import Const as C | 24 from sat.core.constants import Const as C |
25 from sat.core.log import getLogger | 25 from sat.core.log import getLogger |
26 from sat.core import exceptions | 26 from sat.core import exceptions |
27 log = getLogger(__name__) | 27 log = getLogger(__name__) |
28 from sat.tools import xml_tools | |
28 from twisted.words.protocols.jabber import jid | 29 from twisted.words.protocols.jabber import jid |
29 from twisted.python import failure | 30 from twisted.python import failure |
30 from twisted.internet import defer | 31 from twisted.internet import defer |
31 import potr | 32 import potr |
32 from sat.memory import persistent | 33 from sat.memory import persistent |
33 | 34 |
34 NS_OTR = "otr_plugin" | 35 NS_OTR = "otr_plugin" |
35 PRIVATE_KEY = "PRIVATE KEY" | 36 PRIVATE_KEY = "PRIVATE KEY" |
36 MAIN_MENU = D_('OTR') | 37 MAIN_MENU = D_('OTR') |
38 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 !") | |
37 | 39 |
38 DEFAULT_POLICY_FLAGS = { | 40 DEFAULT_POLICY_FLAGS = { |
39 'ALLOW_V1':False, | 41 'ALLOW_V1':False, |
40 'ALLOW_V2':True, | 42 'ALLOW_V2':True, |
41 'REQUIRE_ENCRYPTION':True, | 43 'REQUIRE_ENCRYPTION':True, |
52 "description": _("""Implementation of OTR""") | 54 "description": _("""Implementation of OTR""") |
53 } | 55 } |
54 | 56 |
55 | 57 |
56 class Context(potr.context.Context): | 58 class Context(potr.context.Context): |
57 | |
58 def __init__(self, host, account, other_jid): | 59 def __init__(self, host, account, other_jid): |
59 super(Context, self).__init__(account, other_jid) | 60 super(Context, self).__init__(account, other_jid) |
60 self.host = host | 61 self.host = host |
61 | 62 |
62 def getPolicy(self, key): | 63 def getPolicy(self, key): |
86 | 87 |
87 if state == potr.context.STATE_PLAINTEXT: | 88 if state == potr.context.STATE_PLAINTEXT: |
88 feedback = _(u"/!\\ conversation with %(other_jid)s is now UNENCRYPTED") % {'other_jid': self.peer.full()} | 89 feedback = _(u"/!\\ conversation with %(other_jid)s is now UNENCRYPTED") % {'other_jid': self.peer.full()} |
89 elif state == potr.context.STATE_ENCRYPTED: | 90 elif state == potr.context.STATE_ENCRYPTED: |
90 try: | 91 try: |
91 fingerprint, trusted = self.getCurrentTrust() | 92 trusted = self.getCurrentTrust() |
92 except TypeError: | 93 except TypeError: |
93 trusted = False | 94 trusted = False |
94 trusted_str = _(u"trusted") if trusted else _(u"untrusted") | 95 trusted_str = _(u"trusted") if trusted else _(u"untrusted") |
95 | 96 |
96 if old_state == potr.context.STATE_ENCRYPTED: | 97 if old_state == potr.context.STATE_ENCRYPTED: |
136 d = self.host.memory.encryptValue(priv_key, self.client.profile) | 137 d = self.host.memory.encryptValue(priv_key, self.client.profile) |
137 def save_encrypted_key(encrypted_priv_key): | 138 def save_encrypted_key(encrypted_priv_key): |
138 self.client.otr_data[PRIVATE_KEY] = encrypted_priv_key | 139 self.client.otr_data[PRIVATE_KEY] = encrypted_priv_key |
139 d.addCallback(save_encrypted_key) | 140 d.addCallback(save_encrypted_key) |
140 | 141 |
142 def loadTrusts(self): | |
143 trust_data = self.client.otr_data.get('trust', {}) | |
144 for jid_, jid_data in trust_data.iteritems(): | |
145 for fingerprint, trust_level in jid_data.iteritems(): | |
146 log.debug('setting trust for {jid}: [{fingerprint}] = "{trust_level}"'.format(jid=jid_, fingerprint=fingerprint, trust_level=trust_level)) | |
147 self.trusts.setdefault(jid.JID(jid_), {})[fingerprint] = trust_level | |
148 | |
149 def saveTrusts(self): | |
150 log.debug("saving trusts for {profile}".format(profile=self.client.profile)) | |
151 log.debug("trusts = {}".format(self.client.otr_data['trust'])) | |
152 self.client.otr_data.force('trust') | |
153 | |
154 def setTrust(self, other_jid, fingerprint, trustLevel): | |
155 try: | |
156 trust_data = self.client.otr_data['trust'] | |
157 except KeyError: | |
158 trust_data = {} | |
159 self.client.otr_data['trust'] = trust_data | |
160 jid_data = trust_data.setdefault(other_jid.full(), {}) | |
161 jid_data[fingerprint] = trustLevel | |
162 super(Account, self).setTrust(other_jid, fingerprint, trustLevel) | |
163 | |
141 | 164 |
142 class ContextManager(object): | 165 class ContextManager(object): |
143 | 166 |
144 def __init__(self, host, client): | 167 def __init__(self, host, client): |
145 self.host = host | 168 self.host = host |
167 self.context_managers = {} | 190 self.context_managers = {} |
168 host.trigger.add("MessageReceived", self.MessageReceivedTrigger, priority=100000) | 191 host.trigger.add("MessageReceived", self.MessageReceivedTrigger, priority=100000) |
169 host.trigger.add("sendMessage", self.sendMessageTrigger, priority=100000) | 192 host.trigger.add("sendMessage", self.sendMessageTrigger, priority=100000) |
170 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) | 193 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) |
171 host.importMenu((MAIN_MENU, D_("End session")), self._endSession, security_limit=0, help_string=D_("Finish an OTR session"), type_=C.MENU_SINGLE) | 194 host.importMenu((MAIN_MENU, D_("End session")), self._endSession, security_limit=0, help_string=D_("Finish an OTR session"), type_=C.MENU_SINGLE) |
195 host.importMenu((MAIN_MENU, D_("Authenticate")), self._authenticate, security_limit=0, help_string=D_("Authenticate user/see your fingerprint"), type_=C.MENU_SINGLE) | |
172 | 196 |
173 def _fixPotr(self): | 197 def _fixPotr(self): |
174 # FIXME: potr fix for bad unicode handling | 198 # FIXME: potr fix for bad unicode handling |
175 # this method monkeypatch it, must be removed when potr | 199 # this method monkeypatch it, must be removed when potr |
176 # is fixed | 200 # is fixed |
187 potr.context.Account.getDefaultQueryMessage = getDefaultQueryMessage | 211 potr.context.Account.getDefaultQueryMessage = getDefaultQueryMessage |
188 | 212 |
189 @defer.inlineCallbacks | 213 @defer.inlineCallbacks |
190 def profileConnected(self, profile): | 214 def profileConnected(self, profile): |
191 client = self.host.getClient(profile) | 215 client = self.host.getClient(profile) |
192 self.context_managers[profile] = ContextManager(self.host, client) | 216 ctxMng = self.context_managers[profile] = ContextManager(self.host, client) |
193 client.otr_data = persistent.PersistentBinaryDict(NS_OTR, profile) | 217 client.otr_data = persistent.PersistentBinaryDict(NS_OTR, profile) |
194 yield client.otr_data.load() | 218 yield client.otr_data.load() |
195 encrypted_priv_key = client.otr_data.get(PRIVATE_KEY, None) | 219 encrypted_priv_key = client.otr_data.get(PRIVATE_KEY, None) |
196 if encrypted_priv_key is not None: | 220 if encrypted_priv_key is not None: |
197 priv_key = yield self.host.memory.decryptValue(encrypted_priv_key, profile) | 221 priv_key = yield self.host.memory.decryptValue(encrypted_priv_key, profile) |
198 client.otr_priv_key = potr.crypt.PK.parsePrivateKey(priv_key.decode('hex'))[0] | 222 client.otr_priv_key = potr.crypt.PK.parsePrivateKey(priv_key.decode('hex'))[0] |
199 else: | 223 else: |
200 client.otr_priv_key = None | 224 client.otr_priv_key = None |
225 ctxMng.account.loadTrusts() | |
201 | 226 |
202 def _startRefresh(self, menu_data, profile): | 227 def _startRefresh(self, menu_data, profile): |
203 """Start or refresh an OTR session | 228 """Start or refresh an OTR session |
204 | 229 |
205 @param menu_data: %(menu_data)s | 230 @param menu_data: %(menu_data)s |
231 log.error(_("jid key is not present !")) | 256 log.error(_("jid key is not present !")) |
232 return defer.fail(exceptions.DataError) | 257 return defer.fail(exceptions.DataError) |
233 otrctx = self.context_managers[profile].getContextForUser(to_jid) | 258 otrctx = self.context_managers[profile].getContextForUser(to_jid) |
234 otrctx.disconnect() | 259 otrctx.disconnect() |
235 return {} | 260 return {} |
261 | |
262 def _authenticate(self, menu_data, profile): | |
263 """Authenticate other user and see our own fingerprint | |
264 | |
265 @param menu_data: %(menu_data)s | |
266 @param profile: %(doc_profile)s | |
267 """ | |
268 try: | |
269 to_jid = jid.JID(menu_data['jid']) | |
270 if not to_jid.resource: | |
271 to_jid.resource = self.host.memory.getLastResource(to_jid, profile) # FIXME: temporary and unsecure, must be changed when frontends are refactored | |
272 except KeyError: | |
273 log.error(_("jid key is not present !")) | |
274 return defer.fail(exceptions.DataError) | |
275 otrctx = self.context_managers[profile].getContextForUser(to_jid) | |
276 | |
277 priv_key = otrctx.user.client.otr_priv_key | |
278 | |
279 if priv_key is None: | |
280 # we have no private key yet | |
281 dialog = xml_tools.XMLUI(C.XMLUI_DIALOG, | |
282 dialog_opt = {C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE, | |
283 C.XMLUI_DATA_MESS: _("You have no private key yet, start an OTR conversation to have one"), | |
284 C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_WARNING | |
285 }, | |
286 title = _("No private key"), | |
287 ) | |
288 return {'xmlui': dialog.toXml()} | |
289 | |
290 other_fingerprint = otrctx.getCurrentKey() | |
291 | |
292 if other_fingerprint is None: | |
293 # we have a private key, but not the fingerprint of our correspondent | |
294 dialog = xml_tools.XMLUI(C.XMLUI_DIALOG, | |
295 dialog_opt = {C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE, | |
296 C.XMLUI_DATA_MESS: _("Your fingerprint is\n{fingerprint}\n\nStart an OTR conversation to have your correspondent one.").format(fingerprint=priv_key), | |
297 C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_INFO | |
298 }, | |
299 title = _("Fingerprint"), | |
300 ) | |
301 return {'xmlui': dialog.toXml()} | |
302 | |
303 def setTrust(raw_data, profile): | |
304 # This method is called when authentication form is submited | |
305 data = xml_tools.XMLUIResult2DataFormResult(raw_data) | |
306 if data['match'] == 'yes': | |
307 otrctx.setCurrentTrust('verified') | |
308 note_msg = _("Your correspondant {correspondent} is now TRUSTED") | |
309 else: | |
310 otrctx.setCurrentTrust('') | |
311 note_msg = _("Your correspondant {correspondent} is now UNTRUSTED") | |
312 note = xml_tools.XMLUI(C.XMLUI_DIALOG, dialog_opt = { | |
313 C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE, | |
314 C.XMLUI_DATA_MESS: note_msg.format(correspondent=otrctx.user.name)} | |
315 ) | |
316 return {'xmlui': note.toXml()} | |
317 | |
318 submit_id = self.host.registerCallback(setTrust, with_data=True, one_shot=True) | |
319 trusted = bool(otrctx.getCurrentTrust()) | |
320 | |
321 xmlui = xml_tools.XMLUI(C.XMLUI_FORM, title=_('Authentication (%s)') % to_jid.full(), submit_id=submit_id) | |
322 xmlui.addText(_(AUTH_TXT)) | |
323 xmlui.addDivider() | |
324 xmlui.addText(_("Your own fingerprint is:\n{fingerprint}").format(fingerprint=priv_key)) | |
325 xmlui.addText(_("Your correspondent fingerprint should be:\n{fingerprint}").format(fingerprint=other_fingerprint)) | |
326 xmlui.addDivider('blank') | |
327 xmlui.changeContainer('pairs') | |
328 xmlui.addLabel(_('Is your correspondent fingerprint the same as here ?')) | |
329 xmlui.addList("match", [('yes', _('yes')),('no', _('no'))], ['yes' if trusted else 'no']) | |
330 return {'xmlui': xmlui.toXml()} | |
236 | 331 |
237 def _receivedTreatment(self, data, profile): | 332 def _receivedTreatment(self, data, profile): |
238 from_jid = jid.JID(data['from']) | 333 from_jid = jid.JID(data['from']) |
239 log.debug(u"_receivedTreatment [from_jid = %s]" % from_jid) | 334 log.debug(u"_receivedTreatment [from_jid = %s]" % from_jid) |
240 otrctx = self.context_managers[profile].getContextForUser(from_jid) | 335 otrctx = self.context_managers[profile].getContextForUser(from_jid) |