comparison 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
comparison
equal deleted inserted replaced
2742:1f5b02623355 2743:da59ff099b32
72 OTR_STATE_UNENCRYPTED = "unencrypted" 72 OTR_STATE_UNENCRYPTED = "unencrypted"
73 OTR_STATE_ENCRYPTED = "encrypted" 73 OTR_STATE_ENCRYPTED = "encrypted"
74 74
75 75
76 class Context(potr.context.Context): 76 class Context(potr.context.Context):
77 def __init__(self, host, account, other_jid): 77 def __init__(self, context_manager, other_jid):
78 super(Context, self).__init__(account, other_jid) 78 self.context_manager = context_manager
79 self.host = host 79 super(Context, self).__init__(context_manager.account, other_jid)
80
81 @property
82 def host(self):
83 return self.context_manager.host
84
85 @property
86 def _p_hints(self):
87 return self.context_manager.parent._p_hints
88
89 @property
90 def _p_carbons(self):
91 return self.context_manager.parent._p_carbons
80 92
81 def getPolicy(self, key): 93 def getPolicy(self, key):
82 if key in DEFAULT_POLICY_FLAGS: 94 if key in DEFAULT_POLICY_FLAGS:
83 return DEFAULT_POLICY_FLAGS[key] 95 return DEFAULT_POLICY_FLAGS[key]
84 else: 96 else:
108 "type": "chat", 120 "type": "chat",
109 "extra": {}, 121 "extra": {},
110 "timestamp": time.time(), 122 "timestamp": time.time(),
111 } 123 }
112 client.generateMessageXML(mess_data) 124 client.generateMessageXML(mess_data)
125 xml = mess_data[u'xml']
126 self._p_carbons.setPrivate(xml)
127 self._p_hints.addHintElements(xml, [
128 self._p_hints.HINT_NO_COPY,
129 self._p_hints.HINT_NO_PERMANENT_STORE])
113 client.send(mess_data["xml"]) 130 client.send(mess_data["xml"])
114 else: 131 else:
115 message_elt = appdata[u"xml"] 132 message_elt = appdata[u"xml"]
116 assert message_elt.name == u"message" 133 assert message_elt.name == u"message"
117 message_elt.addElement("body", content=msg) 134 message_elt.addElement("body", content=msg)
118 135
136 def stopCb(self, __, feedback):
137 client = self.user.client
138 self.host.bridge.otrState(
139 OTR_STATE_UNENCRYPTED, self.peer.full(), client.profile
140 )
141 client.feedback(self.peer, feedback)
142
143 def stopEb(self, failure_):
144 # encryption may be already stopped in case of manual stop
145 if not failure_.check(exceptions.NotFound):
146 log.error(u"Error while stopping OTR encryption: {msg}".format(msg=failure_))
147
148 def isTrusted(self):
149 # we have to check value because potr code says that a 2-tuples should be
150 # returned while in practice it's either None or u"trusted"
151 trusted = self.getCurrentTrust()
152 if trusted is None:
153 return False
154 elif trusted == u'trusted':
155 return True
156 else:
157 log.error(u"Unexpected getCurrentTrust() value: {value}".format(
158 value=trusted))
159 return False
160
119 def setState(self, state): 161 def setState(self, state):
120 client = self.user.client 162 client = self.user.client
121 old_state = self.state 163 old_state = self.state
122 super(Context, self).setState(state) 164 super(Context, self).setState(state)
123 log.debug(u"setState: %s (old_state=%s)" % (state, old_state)) 165 log.debug(u"setState: %s (old_state=%s)" % (state, old_state))
124 166
125 if state == potr.context.STATE_PLAINTEXT: 167 if state == potr.context.STATE_PLAINTEXT:
126 client.encryption.stop(self.peer, NS_OTR)
127 feedback = _(u"/!\\ conversation with %(other_jid)s is now UNENCRYPTED") % { 168 feedback = _(u"/!\\ conversation with %(other_jid)s is now UNENCRYPTED") % {
128 "other_jid": self.peer.full() 169 "other_jid": self.peer.full()
129 } 170 }
130 self.host.bridge.otrState( 171 d = client.encryption.stop(self.peer, NS_OTR)
131 OTR_STATE_UNENCRYPTED, self.peer.full(), client.profile 172 d.addCallback(self.stopCb, feedback=feedback)
132 ) 173 d.addErrback(self.stopEb)
174 return
133 elif state == potr.context.STATE_ENCRYPTED: 175 elif state == potr.context.STATE_ENCRYPTED:
134 client.encryption.start(self.peer, NS_OTR) 176 client.encryption.start(self.peer, NS_OTR)
135 try: 177 try:
136 trusted = self.getCurrentTrust() 178 trusted = self.isTrusted()
137 except TypeError: 179 except TypeError:
138 trusted = False 180 trusted = False
139 trusted_str = _(u"trusted") if trusted else _(u"untrusted") 181 trusted_str = _(u"trusted") if trusted else _(u"untrusted")
140 182
141 if old_state == potr.context.STATE_ENCRYPTED: 183 if old_state == potr.context.STATE_ENCRYPTED:
153 ) 195 )
154 self.host.bridge.otrState( 196 self.host.bridge.otrState(
155 OTR_STATE_ENCRYPTED, self.peer.full(), client.profile 197 OTR_STATE_ENCRYPTED, self.peer.full(), client.profile
156 ) 198 )
157 elif state == potr.context.STATE_FINISHED: 199 elif state == potr.context.STATE_FINISHED:
158 client.encryption.stop(self.peer, NS_OTR)
159 feedback = D_(u"OTR conversation with {other_jid} is FINISHED").format( 200 feedback = D_(u"OTR conversation with {other_jid} is FINISHED").format(
160 other_jid=self.peer.full() 201 other_jid=self.peer.full()
161 ) 202 )
162 self.host.bridge.otrState( 203 d = client.encryption.stop(self.peer, NS_OTR)
163 OTR_STATE_UNENCRYPTED, self.peer.full(), client.profile 204 d.addCallback(self.stopCb, feedback=feedback)
164 ) 205 d.addErrback(self.stopEb)
206 return
165 else: 207 else:
166 log.error(D_(u"Unknown OTR state")) 208 log.error(D_(u"Unknown OTR state"))
167 return 209 return
168 210
169 client.feedback(self.peer, feedback) 211 client.feedback(self.peer, feedback)
238 jid_data[fingerprint] = trustLevel 280 jid_data[fingerprint] = trustLevel
239 super(Account, self).setTrust(other_jid, fingerprint, trustLevel) 281 super(Account, self).setTrust(other_jid, fingerprint, trustLevel)
240 282
241 283
242 class ContextManager(object): 284 class ContextManager(object):
243 def __init__(self, host, client): 285 def __init__(self, parent, client):
244 self.host = host 286 self.parent = parent
245 self.account = Account(host, client) 287 self.account = Account(parent.host, client)
246 self.contexts = {} 288 self.contexts = {}
289
290 @property
291 def host(self):
292 return self.parent.host
247 293
248 def startContext(self, other_jid): 294 def startContext(self, other_jid):
249 assert isinstance(other_jid, jid.JID) 295 assert isinstance(other_jid, jid.JID)
250 context = self.contexts.setdefault( 296 context = self.contexts.setdefault(
251 other_jid, Context(self.host, self.account, other_jid) 297 other_jid, Context(self, other_jid)
252 ) 298 )
253 return context 299 return context
254 300
255 def getContextForUser(self, other): 301 def getContextForUser(self, other):
256 log.debug(u"getContextForUser [%s]" % other) 302 log.debug(u"getContextForUser [%s]" % other)
321 367
322 @defer.inlineCallbacks 368 @defer.inlineCallbacks
323 def profileConnected(self, client): 369 def profileConnected(self, client):
324 if client.profile in self.skipped_profiles: 370 if client.profile in self.skipped_profiles:
325 return 371 return
326 ctxMng = client._otr_context_manager = ContextManager(self.host, client) 372 ctxMng = client._otr_context_manager = ContextManager(self, client)
327 client._otr_data = persistent.PersistentBinaryDict(NS_OTR, client.profile) 373 client._otr_data = persistent.PersistentBinaryDict(NS_OTR, client.profile)
328 yield client._otr_data.load() 374 yield client._otr_data.load()
329 encrypted_priv_key = client._otr_data.get(PRIVATE_KEY, None) 375 encrypted_priv_key = client._otr_data.get(PRIVATE_KEY, None)
330 if encrypted_priv_key is not None: 376 if encrypted_priv_key is not None:
331 priv_key = yield self.host.memory.decryptValue( 377 priv_key = yield self.host.memory.decryptValue(
343 self.skipped_profiles.remove(client.profile) 389 self.skipped_profiles.remove(client.profile)
344 return 390 return
345 for context in client._otr_context_manager.contexts.values(): 391 for context in client._otr_context_manager.contexts.values():
346 context.disconnect() 392 context.disconnect()
347 del client._otr_context_manager 393 del client._otr_context_manager
394
395 # encryption plugin methods
396
397 def startEncryption(self, client, entity_jid):
398 self.startRefresh(client, entity_jid)
399
400 def stopEncryption(self, client, entity_jid):
401 self.endSession(client, entity_jid)
402
403 def getTrustUI(self, client, entity_jid):
404 if not entity_jid.resource:
405 entity_jid.resource = self.host.memory.getMainResource(
406 client, entity_jid
407 ) # FIXME: temporary and unsecure, must be changed when frontends
408 # are refactored
409 ctxMng = client._otr_context_manager
410 otrctx = ctxMng.getContextForUser(entity_jid)
411 priv_key = ctxMng.account.privkey
412
413 if priv_key is None:
414 # we have no private key yet
415 dialog = xml_tools.XMLUI(
416 C.XMLUI_DIALOG,
417 dialog_opt={
418 C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE,
419 C.XMLUI_DATA_MESS: _(
420 u"You have no private key yet, start an OTR conversation to "
421 u"have one"
422 ),
423 C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_WARNING,
424 },
425 title=_(u"No private key"),
426 )
427 return dialog
428
429 other_fingerprint = otrctx.getCurrentKey()
430
431 if other_fingerprint is None:
432 # we have a private key, but not the fingerprint of our correspondent
433 dialog = xml_tools.XMLUI(
434 C.XMLUI_DIALOG,
435 dialog_opt={
436 C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE,
437 C.XMLUI_DATA_MESS: _(
438 u"Your fingerprint is:\n{fingerprint}\n\n"
439 u"Start an OTR conversation to have your correspondent one."
440 ).format(fingerprint=priv_key),
441 C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_INFO,
442 },
443 title=_(u"Fingerprint"),
444 )
445 return dialog
446
447 def setTrust(raw_data, profile):
448 if xml_tools.isXMLUICancelled(raw_data):
449 return {}
450 # This method is called when authentication form is submited
451 data = xml_tools.XMLUIResult2DataFormResult(raw_data)
452 if data["match"] == "yes":
453 otrctx.setCurrentTrust(OTR_STATE_TRUSTED)
454 note_msg = _(u"Your correspondent {correspondent} is now TRUSTED")
455 self.host.bridge.otrState(
456 OTR_STATE_TRUSTED, entity_jid.full(), client.profile
457 )
458 else:
459 otrctx.setCurrentTrust("")
460 note_msg = _(u"Your correspondent {correspondent} is now UNTRUSTED")
461 self.host.bridge.otrState(
462 OTR_STATE_UNTRUSTED, entity_jid.full(), client.profile
463 )
464 note = xml_tools.XMLUI(
465 C.XMLUI_DIALOG,
466 dialog_opt={
467 C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE,
468 C.XMLUI_DATA_MESS: note_msg.format(correspondent=otrctx.peer),
469 },
470 )
471 return {"xmlui": note.toXml()}
472
473 submit_id = self.host.registerCallback(setTrust, with_data=True, one_shot=True)
474 trusted = otrctx.isTrusted()
475
476 xmlui = xml_tools.XMLUI(
477 C.XMLUI_FORM,
478 title=_(u"Authentication ({entity_jid})").format(entity_jid=entity_jid.full()),
479 submit_id=submit_id,
480 )
481 xmlui.addText(_(AUTH_TXT))
482 xmlui.addDivider()
483 xmlui.addText(
484 D_(u"Your own fingerprint is:\n{fingerprint}").format(fingerprint=priv_key)
485 )
486 xmlui.addText(
487 D_(u"Your correspondent fingerprint should be:\n{fingerprint}").format(
488 fingerprint=other_fingerprint
489 )
490 )
491 xmlui.addDivider("blank")
492 xmlui.changeContainer("pairs")
493 xmlui.addLabel(D_(u"Is your correspondent fingerprint the same as here ?"))
494 xmlui.addList(
495 "match", [("yes", _("yes")), ("no", _("no"))], ["yes" if trusted else "no"]
496 )
497 return xmlui
348 498
349 def _otrStartRefresh(self, menu_data, profile): 499 def _otrStartRefresh(self, menu_data, profile):
350 """Start or refresh an OTR session 500 """Start or refresh an OTR session
351 501
352 @param menu_data: %(menu_data)s 502 @param menu_data: %(menu_data)s
420 return defer.fail(exceptions.DataError) 570 return defer.fail(exceptions.DataError)
421 return self.authenticate(client, to_jid) 571 return self.authenticate(client, to_jid)
422 572
423 def authenticate(self, client, to_jid): 573 def authenticate(self, client, to_jid):
424 """Authenticate other user and see our own fingerprint""" 574 """Authenticate other user and see our own fingerprint"""
425 if not to_jid.resource: 575 xmlui = self.getTrustUI(client, to_jid)
426 to_jid.resource = self.host.memory.getMainResource(
427 client, to_jid
428 ) # FIXME: temporary and unsecure, must be changed when frontends
429 # are refactored
430 ctxMng = client._otr_context_manager
431 otrctx = ctxMng.getContextForUser(to_jid)
432 priv_key = ctxMng.account.privkey
433
434 if priv_key is None:
435 # we have no private key yet
436 dialog = xml_tools.XMLUI(
437 C.XMLUI_DIALOG,
438 dialog_opt={
439 C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE,
440 C.XMLUI_DATA_MESS: _(
441 u"You have no private key yet, start an OTR conversation to "
442 u"have one"
443 ),
444 C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_WARNING,
445 },
446 title=_(u"No private key"),
447 )
448 return {"xmlui": dialog.toXml()}
449
450 other_fingerprint = otrctx.getCurrentKey()
451
452 if other_fingerprint is None:
453 # we have a private key, but not the fingerprint of our correspondent
454 dialog = xml_tools.XMLUI(
455 C.XMLUI_DIALOG,
456 dialog_opt={
457 C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE,
458 C.XMLUI_DATA_MESS: _(
459 u"Your fingerprint is:\n{fingerprint}\n\n"
460 u"Start an OTR conversation to have your correspondent one."
461 ).format(fingerprint=priv_key),
462 C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_INFO,
463 },
464 title=_(u"Fingerprint"),
465 )
466 return {"xmlui": dialog.toXml()}
467
468 def setTrust(raw_data, profile):
469 # This method is called when authentication form is submited
470 data = xml_tools.XMLUIResult2DataFormResult(raw_data)
471 if data["match"] == "yes":
472 otrctx.setCurrentTrust(OTR_STATE_TRUSTED)
473 note_msg = _(u"Your correspondent {correspondent} is now TRUSTED")
474 self.host.bridge.otrState(
475 OTR_STATE_TRUSTED, to_jid.full(), client.profile
476 )
477 else:
478 otrctx.setCurrentTrust("")
479 note_msg = _(u"Your correspondent {correspondent} is now UNTRUSTED")
480 self.host.bridge.otrState(
481 OTR_STATE_UNTRUSTED, to_jid.full(), client.profile
482 )
483 note = xml_tools.XMLUI(
484 C.XMLUI_DIALOG,
485 dialog_opt={
486 C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE,
487 C.XMLUI_DATA_MESS: note_msg.format(correspondent=otrctx.peer),
488 },
489 )
490 return {"xmlui": note.toXml()}
491
492 submit_id = self.host.registerCallback(setTrust, with_data=True, one_shot=True)
493 trusted = bool(otrctx.getCurrentTrust())
494
495 xmlui = xml_tools.XMLUI(
496 C.XMLUI_FORM,
497 title=_("Authentication (%s)") % to_jid.full(),
498 submit_id=submit_id,
499 )
500 xmlui.addText(_(AUTH_TXT))
501 xmlui.addDivider()
502 xmlui.addText(
503 D_(u"Your own fingerprint is:\n{fingerprint}").format(fingerprint=priv_key)
504 )
505 xmlui.addText(
506 D_(u"Your correspondent fingerprint should be:\n{fingerprint}").format(
507 fingerprint=other_fingerprint
508 )
509 )
510 xmlui.addDivider("blank")
511 xmlui.changeContainer("pairs")
512 xmlui.addLabel(D_(u"Is your correspondent fingerprint the same as here ?"))
513 xmlui.addList(
514 "match", [("yes", _("yes")), ("no", _("no"))], ["yes" if trusted else "no"]
515 )
516 return {"xmlui": xmlui.toXml()} 576 return {"xmlui": xmlui.toXml()}
517 577
518 def _dropPrivKey(self, menu_data, profile): 578 def _dropPrivKey(self, menu_data, profile):
519 """Drop our private Key 579 """Drop our private Key
520 580
595 feedback = msg 655 feedback = msg
596 client.feedback(from_jid, msg) 656 client.feedback(from_jid, msg)
597 raise failure.Failure(exceptions.CancelError(msg)) 657 raise failure.Failure(exceptions.CancelError(msg))
598 except potr.context.ErrorReceived as e: 658 except potr.context.ErrorReceived as e:
599 msg = D_(u"WARNING: received OTR error message: {msg}".format(msg=e)) 659 msg = D_(u"WARNING: received OTR error message: {msg}".format(msg=e))
660 log.warning(msg)
661 feedback = msg
662 client.feedback(from_jid, msg)
663 raise failure.Failure(exceptions.CancelError(msg))
664 except potr.crypt.InvalidParameterError as e:
665 msg = D_(u"Error while trying de decrypt OTR message: {msg}".format(msg=e))
600 log.warning(msg) 666 log.warning(msg)
601 feedback = msg 667 feedback = msg
602 client.feedback(from_jid, msg) 668 client.feedback(from_jid, msg)
603 raise failure.Failure(exceptions.CancelError(msg)) 669 raise failure.Failure(exceptions.CancelError(msg))
604 except StopIteration: 670 except StopIteration:
627 else: 693 else:
628 raise failure.Failure( 694 raise failure.Failure(
629 exceptions.CancelError("Cancelled by OTR") 695 exceptions.CancelError("Cancelled by OTR")
630 ) # no message at all (no history, no signal) 696 ) # no message at all (no history, no signal)
631 client.encryption.markAsEncrypted(data) 697 client.encryption.markAsEncrypted(data)
698 trusted = otrctx.isTrusted()
699
700 if trusted:
701 client.encryption.markAsTrusted(data)
702 else:
703 client.encryption.markAsUntrusted(data)
704
632 return data 705 return data
633 706
634 def _receivedTreatmentForSkippedProfiles(self, data): 707 def _receivedTreatmentForSkippedProfiles(self, data):
635 """This profile must be skipped because the frontend manages OTR itself, 708 """This profile must be skipped because the frontend manages OTR itself,
636 709