comparison sat/plugins/plugin_sec_otr.py @ 2624:56f94936df1e

code style reformatting using black
author Goffi <goffi@goffi.org>
date Wed, 27 Jun 2018 20:14:46 +0200
parents 26edcf3a30eb
children 189e38fb11ff
comparison
equal deleted inserted replaced
2623:49533de4540b 2624:56f94936df1e
22 22
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
27 log = getLogger(__name__) 28 log = getLogger(__name__)
28 from sat.tools import xml_tools 29 from sat.tools import xml_tools
29 from twisted.words.protocols.jabber import jid 30 from twisted.words.protocols.jabber import jid
30 from twisted.python import failure 31 from twisted.python import failure
31 from twisted.internet import defer 32 from twisted.internet import defer
42 C.PI_TYPE: u"SEC", 43 C.PI_TYPE: u"SEC",
43 C.PI_PROTOCOLS: [u"XEP-0364"], 44 C.PI_PROTOCOLS: [u"XEP-0364"],
44 C.PI_DEPENDENCIES: [u"XEP-0280", u"XEP-0334"], 45 C.PI_DEPENDENCIES: [u"XEP-0280", u"XEP-0334"],
45 C.PI_MAIN: u"OTR", 46 C.PI_MAIN: u"OTR",
46 C.PI_HANDLER: u"no", 47 C.PI_HANDLER: u"no",
47 C.PI_DESCRIPTION: _(u"""Implementation of OTR""") 48 C.PI_DESCRIPTION: _(u"""Implementation of OTR"""),
48 } 49 }
49 50
50 NS_OTR = "otr_plugin" 51 NS_OTR = "otr_plugin"
51 PRIVATE_KEY = "PRIVATE KEY" 52 PRIVATE_KEY = "PRIVATE KEY"
52 OTR_MENU = D_(u'OTR') 53 OTR_MENU = D_(u"OTR")
53 AUTH_TXT = D_(u"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 gives you is the same as below. If there is a mismatch, there can be a spy between you!") 54 AUTH_TXT = D_(
54 DROP_TXT = D_(u"You private key is used to encrypt messages for your correspondent, nobody except you must know it, if you are in doubt, you should drop it!\n\nAre you sure you want to drop your private key?") 55 u"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 gives you is the same as below. If there is a mismatch, there can be a spy between you!"
56 )
57 DROP_TXT = D_(
58 u"You private key is used to encrypt messages for your correspondent, nobody except you must know it, if you are in doubt, you should drop it!\n\nAre you sure you want to drop your private key?"
59 )
55 # NO_LOG_AND = D_(u"/!\\Your history is not logged anymore, and") # FIXME: not used at the moment 60 # NO_LOG_AND = D_(u"/!\\Your history is not logged anymore, and") # FIXME: not used at the moment
56 NO_ADV_FEATURES = D_(u"Some of advanced features are disabled !") 61 NO_ADV_FEATURES = D_(u"Some of advanced features are disabled !")
57 62
58 DEFAULT_POLICY_FLAGS = { 63 DEFAULT_POLICY_FLAGS = {"ALLOW_V1": False, "ALLOW_V2": True, "REQUIRE_ENCRYPTION": True}
59 'ALLOW_V1':False, 64
60 'ALLOW_V2':True, 65 OTR_STATE_TRUSTED = "trusted"
61 'REQUIRE_ENCRYPTION':True, 66 OTR_STATE_UNTRUSTED = "untrusted"
62 } 67 OTR_STATE_UNENCRYPTED = "unencrypted"
63 68 OTR_STATE_ENCRYPTED = "encrypted"
64 OTR_STATE_TRUSTED = 'trusted'
65 OTR_STATE_UNTRUSTED = 'untrusted'
66 OTR_STATE_UNENCRYPTED = 'unencrypted'
67 OTR_STATE_ENCRYPTED = 'encrypted'
68 69
69 70
70 class Context(potr.context.Context): 71 class Context(potr.context.Context):
71 def __init__(self, host, account, other_jid): 72 def __init__(self, host, account, other_jid):
72 super(Context, self).__init__(account, other_jid) 73 super(Context, self).__init__(account, other_jid)
87 @param msg_str(str): encrypted message body 88 @param msg_str(str): encrypted message body
88 @param appdata(None, dict): None for signal message, 89 @param appdata(None, dict): None for signal message,
89 message data when an encrypted message is going to be sent 90 message data when an encrypted message is going to be sent
90 """ 91 """
91 assert isinstance(self.peer, jid.JID) 92 assert isinstance(self.peer, jid.JID)
92 msg = msg_str.decode('utf-8') 93 msg = msg_str.decode("utf-8")
93 client = self.user.client 94 client = self.user.client
94 log.debug(u'injecting encrypted message to {to}'.format(to=self.peer)) 95 log.debug(u"injecting encrypted message to {to}".format(to=self.peer))
95 if appdata is None: 96 if appdata is None:
96 mess_data = { 97 mess_data = {
97 'from': client.jid, 98 "from": client.jid,
98 'to': self.peer, 99 "to": self.peer,
99 'uid': unicode(uuid.uuid4()), 100 "uid": unicode(uuid.uuid4()),
100 'message': {'': msg}, 101 "message": {"": msg},
101 'subject': {}, 102 "subject": {},
102 'type': 'chat', 103 "type": "chat",
103 'extra': {}, 104 "extra": {},
104 'timestamp': time.time(), 105 "timestamp": time.time(),
105 } 106 }
106 client.generateMessageXML(mess_data) 107 client.generateMessageXML(mess_data)
107 client.send(mess_data['xml']) 108 client.send(mess_data["xml"])
108 else: 109 else:
109 message_elt = appdata[u'xml'] 110 message_elt = appdata[u"xml"]
110 assert message_elt.name == u'message' 111 assert message_elt.name == u"message"
111 message_elt.addElement("body", content=msg) 112 message_elt.addElement("body", content=msg)
112 113
113 def setState(self, state): 114 def setState(self, state):
114 client = self.user.client 115 client = self.user.client
115 old_state = self.state 116 old_state = self.state
116 super(Context, self).setState(state) 117 super(Context, self).setState(state)
117 log.debug(u"setState: %s (old_state=%s)" % (state, old_state)) 118 log.debug(u"setState: %s (old_state=%s)" % (state, old_state))
118 119
119 if state == potr.context.STATE_PLAINTEXT: 120 if state == potr.context.STATE_PLAINTEXT:
120 feedback = _(u"/!\\ conversation with %(other_jid)s is now UNENCRYPTED") % {'other_jid': self.peer.full()} 121 feedback = _(u"/!\\ conversation with %(other_jid)s is now UNENCRYPTED") % {
121 self.host.bridge.otrState(OTR_STATE_UNENCRYPTED, self.peer.full(), client.profile) 122 "other_jid": self.peer.full()
123 }
124 self.host.bridge.otrState(
125 OTR_STATE_UNENCRYPTED, self.peer.full(), client.profile
126 )
122 elif state == potr.context.STATE_ENCRYPTED: 127 elif state == potr.context.STATE_ENCRYPTED:
123 try: 128 try:
124 trusted = self.getCurrentTrust() 129 trusted = self.getCurrentTrust()
125 except TypeError: 130 except TypeError:
126 trusted = False 131 trusted = False
127 trusted_str = _(u"trusted") if trusted else _(u"untrusted") 132 trusted_str = _(u"trusted") if trusted else _(u"untrusted")
128 133
129 if old_state == potr.context.STATE_ENCRYPTED: 134 if old_state == potr.context.STATE_ENCRYPTED:
130 feedback = D_(u"{trusted} OTR conversation with {other_jid} REFRESHED").format( 135 feedback = D_(
131 trusted = trusted_str, 136 u"{trusted} OTR conversation with {other_jid} REFRESHED"
132 other_jid = self.peer.full()) 137 ).format(trusted=trusted_str, other_jid=self.peer.full())
133 else: 138 else:
134 feedback = D_(u"{trusted} encrypted OTR conversation started with {other_jid}\n{extra_info}").format( 139 feedback = D_(
135 trusted = trusted_str, 140 u"{trusted} encrypted OTR conversation started with {other_jid}\n{extra_info}"
136 other_jid = self.peer.full(), 141 ).format(
137 extra_info = NO_ADV_FEATURES) 142 trusted=trusted_str,
138 self.host.bridge.otrState(OTR_STATE_ENCRYPTED, self.peer.full(), client.profile) 143 other_jid=self.peer.full(),
144 extra_info=NO_ADV_FEATURES,
145 )
146 self.host.bridge.otrState(
147 OTR_STATE_ENCRYPTED, self.peer.full(), client.profile
148 )
139 elif state == potr.context.STATE_FINISHED: 149 elif state == potr.context.STATE_FINISHED:
140 feedback = D_(u"OTR conversation with {other_jid} is FINISHED").format(other_jid = self.peer.full()) 150 feedback = D_(u"OTR conversation with {other_jid} is FINISHED").format(
141 self.host.bridge.otrState(OTR_STATE_UNENCRYPTED, self.peer.full(), client.profile) 151 other_jid=self.peer.full()
152 )
153 self.host.bridge.otrState(
154 OTR_STATE_UNENCRYPTED, self.peer.full(), client.profile
155 )
142 else: 156 else:
143 log.error(D_(u"Unknown OTR state")) 157 log.error(D_(u"Unknown OTR state"))
144 return 158 return
145 159
146 client.feedback(self.peer, feedback) 160 client.feedback(self.peer, feedback)
174 188
175 def savePrivkey(self): 189 def savePrivkey(self):
176 log.debug(u"savePrivkey") 190 log.debug(u"savePrivkey")
177 if self.privkey is None: 191 if self.privkey is None:
178 raise exceptions.InternalError(_(u"Save is called but privkey is None !")) 192 raise exceptions.InternalError(_(u"Save is called but privkey is None !"))
179 priv_key = self.privkey.serializePrivateKey().encode('hex') 193 priv_key = self.privkey.serializePrivateKey().encode("hex")
180 d = self.host.memory.encryptValue(priv_key, self.client.profile) 194 d = self.host.memory.encryptValue(priv_key, self.client.profile)
195
181 def save_encrypted_key(encrypted_priv_key): 196 def save_encrypted_key(encrypted_priv_key):
182 self.client._otr_data[PRIVATE_KEY] = encrypted_priv_key 197 self.client._otr_data[PRIVATE_KEY] = encrypted_priv_key
198
183 d.addCallback(save_encrypted_key) 199 d.addCallback(save_encrypted_key)
184 200
185 def loadTrusts(self): 201 def loadTrusts(self):
186 trust_data = self.client._otr_data.get('trust', {}) 202 trust_data = self.client._otr_data.get("trust", {})
187 for jid_, jid_data in trust_data.iteritems(): 203 for jid_, jid_data in trust_data.iteritems():
188 for fingerprint, trust_level in jid_data.iteritems(): 204 for fingerprint, trust_level in jid_data.iteritems():
189 log.debug(u'setting trust for {jid}: [{fingerprint}] = "{trust_level}"'.format(jid=jid_, fingerprint=fingerprint, trust_level=trust_level)) 205 log.debug(
206 u'setting trust for {jid}: [{fingerprint}] = "{trust_level}"'.format(
207 jid=jid_, fingerprint=fingerprint, trust_level=trust_level
208 )
209 )
190 self.trusts.setdefault(jid.JID(jid_), {})[fingerprint] = trust_level 210 self.trusts.setdefault(jid.JID(jid_), {})[fingerprint] = trust_level
191 211
192 def saveTrusts(self): 212 def saveTrusts(self):
193 log.debug(u"saving trusts for {profile}".format(profile=self.client.profile)) 213 log.debug(u"saving trusts for {profile}".format(profile=self.client.profile))
194 log.debug(u"trusts = {}".format(self.client._otr_data['trust'])) 214 log.debug(u"trusts = {}".format(self.client._otr_data["trust"]))
195 self.client._otr_data.force('trust') 215 self.client._otr_data.force("trust")
196 216
197 def setTrust(self, other_jid, fingerprint, trustLevel): 217 def setTrust(self, other_jid, fingerprint, trustLevel):
198 try: 218 try:
199 trust_data = self.client._otr_data['trust'] 219 trust_data = self.client._otr_data["trust"]
200 except KeyError: 220 except KeyError:
201 trust_data = {} 221 trust_data = {}
202 self.client._otr_data['trust'] = trust_data 222 self.client._otr_data["trust"] = trust_data
203 jid_data = trust_data.setdefault(other_jid.full(), {}) 223 jid_data = trust_data.setdefault(other_jid.full(), {})
204 jid_data[fingerprint] = trustLevel 224 jid_data[fingerprint] = trustLevel
205 super(Account, self).setTrust(other_jid, fingerprint, trustLevel) 225 super(Account, self).setTrust(other_jid, fingerprint, trustLevel)
206 226
207 227
208 class ContextManager(object): 228 class ContextManager(object):
209
210 def __init__(self, host, client): 229 def __init__(self, host, client):
211 self.host = host 230 self.host = host
212 self.account = Account(host, client) 231 self.account = Account(host, client)
213 self.contexts = {} 232 self.contexts = {}
214 233
215 def startContext(self, other_jid): 234 def startContext(self, other_jid):
216 assert isinstance(other_jid, jid.JID) 235 assert isinstance(other_jid, jid.JID)
217 context = self.contexts.setdefault(other_jid, Context(self.host, self.account, other_jid)) 236 context = self.contexts.setdefault(
237 other_jid, Context(self.host, self.account, other_jid)
238 )
218 return context 239 return context
219 240
220 def getContextForUser(self, other): 241 def getContextForUser(self, other):
221 log.debug(u"getContextForUser [%s]" % other) 242 log.debug(u"getContextForUser [%s]" % other)
222 if not other.resource: 243 if not other.resource:
223 log.warning(u"getContextForUser called with a bare jid: %s" % other.full()) 244 log.warning(u"getContextForUser called with a bare jid: %s" % other.full())
224 return self.startContext(other) 245 return self.startContext(other)
225 246
226 247
227 class OTR(object): 248 class OTR(object):
228
229 def __init__(self, host): 249 def __init__(self, host):
230 log.info(_(u"OTR plugin initialization")) 250 log.info(_(u"OTR plugin initialization"))
231 self.host = host 251 self.host = host
232 self.context_managers = {} 252 self.context_managers = {}
233 self.skipped_profiles = set() # FIXME: OTR should not be skipped per profile, this need to be refactored 253 self.skipped_profiles = (
234 self._p_hints = host.plugins[u'XEP-0334'] 254 set()
235 self._p_carbons = host.plugins[u'XEP-0280'] 255 ) #  FIXME: OTR should not be skipped per profile, this need to be refactored
256 self._p_hints = host.plugins[u"XEP-0334"]
257 self._p_carbons = host.plugins[u"XEP-0280"]
236 host.trigger.add("MessageReceived", self.MessageReceivedTrigger, priority=100000) 258 host.trigger.add("MessageReceived", self.MessageReceivedTrigger, priority=100000)
237 host.trigger.add("sendMessage", self.sendMessageTrigger, priority=100000) 259 host.trigger.add("sendMessage", self.sendMessageTrigger, priority=100000)
238 host.trigger.add("sendMessageData", self._sendMessageDataTrigger) 260 host.trigger.add("sendMessageData", self._sendMessageDataTrigger)
239 host.bridge.addMethod("skipOTR", ".plugin", in_sign='s', out_sign='', method=self._skipOTR) # FIXME: must be removed, must be done on per-message basis 261 host.bridge.addMethod(
240 host.bridge.addSignal("otrState", ".plugin", signature='sss') # args: state, destinee_jid, profile 262 "skipOTR", ".plugin", in_sign="s", out_sign="", method=self._skipOTR
241 host.importMenu((OTR_MENU, D_(u"Start/Refresh")), self._otrStartRefresh, security_limit=0, help_string=D_(u"Start or refresh an OTR session"), type_=C.MENU_SINGLE) 263 ) # FIXME: must be removed, must be done on per-message basis
242 host.importMenu((OTR_MENU, D_(u"End session")), self._otrSessionEnd, security_limit=0, help_string=D_(u"Finish an OTR session"), type_=C.MENU_SINGLE) 264 host.bridge.addSignal(
243 host.importMenu((OTR_MENU, D_(u"Authenticate")), self._otrAuthenticate, security_limit=0, help_string=D_(u"Authenticate user/see your fingerprint"), type_=C.MENU_SINGLE) 265 "otrState", ".plugin", signature="sss"
244 host.importMenu((OTR_MENU, D_(u"Drop private key")), self._dropPrivKey, security_limit=0, type_=C.MENU_SINGLE) 266 ) # args: state, destinee_jid, profile
267 host.importMenu(
268 (OTR_MENU, D_(u"Start/Refresh")),
269 self._otrStartRefresh,
270 security_limit=0,
271 help_string=D_(u"Start or refresh an OTR session"),
272 type_=C.MENU_SINGLE,
273 )
274 host.importMenu(
275 (OTR_MENU, D_(u"End session")),
276 self._otrSessionEnd,
277 security_limit=0,
278 help_string=D_(u"Finish an OTR session"),
279 type_=C.MENU_SINGLE,
280 )
281 host.importMenu(
282 (OTR_MENU, D_(u"Authenticate")),
283 self._otrAuthenticate,
284 security_limit=0,
285 help_string=D_(u"Authenticate user/see your fingerprint"),
286 type_=C.MENU_SINGLE,
287 )
288 host.importMenu(
289 (OTR_MENU, D_(u"Drop private key")),
290 self._dropPrivKey,
291 security_limit=0,
292 type_=C.MENU_SINGLE,
293 )
245 host.trigger.add("presenceReceived", self._presenceReceivedTrigger) 294 host.trigger.add("presenceReceived", self._presenceReceivedTrigger)
246 295
247 def _skipOTR(self, profile): 296 def _skipOTR(self, profile):
248 """Tell the backend to not handle OTR for this profile. 297 """Tell the backend to not handle OTR for this profile.
249 298
261 ctxMng = client._otr_context_manager = ContextManager(self.host, client) 310 ctxMng = client._otr_context_manager = ContextManager(self.host, client)
262 client._otr_data = persistent.PersistentBinaryDict(NS_OTR, client.profile) 311 client._otr_data = persistent.PersistentBinaryDict(NS_OTR, client.profile)
263 yield client._otr_data.load() 312 yield client._otr_data.load()
264 encrypted_priv_key = client._otr_data.get(PRIVATE_KEY, None) 313 encrypted_priv_key = client._otr_data.get(PRIVATE_KEY, None)
265 if encrypted_priv_key is not None: 314 if encrypted_priv_key is not None:
266 priv_key = yield self.host.memory.decryptValue(encrypted_priv_key, client.profile) 315 priv_key = yield self.host.memory.decryptValue(
267 ctxMng.account.privkey = potr.crypt.PK.parsePrivateKey(priv_key.decode('hex'))[0] 316 encrypted_priv_key, client.profile
317 )
318 ctxMng.account.privkey = potr.crypt.PK.parsePrivateKey(
319 priv_key.decode("hex")
320 )[0]
268 else: 321 else:
269 ctxMng.account.privkey = None 322 ctxMng.account.privkey = None
270 ctxMng.account.loadTrusts() 323 ctxMng.account.loadTrusts()
271 324
272 def profileDisconnected(self, client): 325 def profileDisconnected(self, client):
283 @param menu_data: %(menu_data)s 336 @param menu_data: %(menu_data)s
284 @param profile: %(doc_profile)s 337 @param profile: %(doc_profile)s
285 """ 338 """
286 client = self.host.getClient(profile) 339 client = self.host.getClient(profile)
287 try: 340 try:
288 to_jid = jid.JID(menu_data['jid']) 341 to_jid = jid.JID(menu_data["jid"])
289 except KeyError: 342 except KeyError:
290 log.error(_(u"jid key is not present !")) 343 log.error(_(u"jid key is not present !"))
291 return defer.fail(exceptions.DataError) 344 return defer.fail(exceptions.DataError)
292 self.startRefresh(client, to_jid) 345 self.startRefresh(client, to_jid)
293 return {} 346 return {}
296 """Start or refresh an OTR session 349 """Start or refresh an OTR session
297 350
298 @param to_jid(jid.JID): jid to start encrypted session with 351 @param to_jid(jid.JID): jid to start encrypted session with
299 """ 352 """
300 if not to_jid.resource: 353 if not to_jid.resource:
301 to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored 354 to_jid.resource = self.host.memory.getMainResource(
355 client, to_jid
356 ) # FIXME: temporary and unsecure, must be changed when frontends are refactored
302 otrctx = client._otr_context_manager.getContextForUser(to_jid) 357 otrctx = client._otr_context_manager.getContextForUser(to_jid)
303 query = otrctx.sendMessage(0, '?OTRv?') 358 query = otrctx.sendMessage(0, "?OTRv?")
304 otrctx.inject(query) 359 otrctx.inject(query)
305 360
306 def _otrSessionEnd(self, menu_data, profile): 361 def _otrSessionEnd(self, menu_data, profile):
307 """End an OTR session 362 """End an OTR session
308 363
309 @param menu_data: %(menu_data)s 364 @param menu_data: %(menu_data)s
310 @param profile: %(doc_profile)s 365 @param profile: %(doc_profile)s
311 """ 366 """
312 client = self.host.getClient(profile) 367 client = self.host.getClient(profile)
313 try: 368 try:
314 to_jid = jid.JID(menu_data['jid']) 369 to_jid = jid.JID(menu_data["jid"])
315 except KeyError: 370 except KeyError:
316 log.error(_(u"jid key is not present !")) 371 log.error(_(u"jid key is not present !"))
317 return defer.fail(exceptions.DataError) 372 return defer.fail(exceptions.DataError)
318 self.endSession(client, to_jid) 373 self.endSession(client, to_jid)
319 return {} 374 return {}
320 375
321 def endSession(self, client, to_jid): 376 def endSession(self, client, to_jid):
322 """End an OTR session""" 377 """End an OTR session"""
323 if not to_jid.resource: 378 if not to_jid.resource:
324 to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored 379 to_jid.resource = self.host.memory.getMainResource(
380 client, to_jid
381 ) # FIXME: temporary and unsecure, must be changed when frontends are refactored
325 otrctx = client._otr_context_manager.getContextForUser(to_jid) 382 otrctx = client._otr_context_manager.getContextForUser(to_jid)
326 otrctx.disconnect() 383 otrctx.disconnect()
327 return {} 384 return {}
328 385
329 def _otrAuthenticate(self, menu_data, profile): 386 def _otrAuthenticate(self, menu_data, profile):
332 @param menu_data: %(menu_data)s 389 @param menu_data: %(menu_data)s
333 @param profile: %(doc_profile)s 390 @param profile: %(doc_profile)s
334 """ 391 """
335 client = self.host.getClient(profile) 392 client = self.host.getClient(profile)
336 try: 393 try:
337 to_jid = jid.JID(menu_data['jid']) 394 to_jid = jid.JID(menu_data["jid"])
338 except KeyError: 395 except KeyError:
339 log.error(_(u"jid key is not present !")) 396 log.error(_(u"jid key is not present !"))
340 return defer.fail(exceptions.DataError) 397 return defer.fail(exceptions.DataError)
341 return self.authenticate(client, to_jid) 398 return self.authenticate(client, to_jid)
342 399
343 def authenticate(self, client, to_jid): 400 def authenticate(self, client, to_jid):
344 """Authenticate other user and see our own fingerprint""" 401 """Authenticate other user and see our own fingerprint"""
345 if not to_jid.resource: 402 if not to_jid.resource:
346 to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored 403 to_jid.resource = self.host.memory.getMainResource(
404 client, to_jid
405 ) # FIXME: temporary and unsecure, must be changed when frontends are refactored
347 ctxMng = client._otr_context_manager 406 ctxMng = client._otr_context_manager
348 otrctx = ctxMng.getContextForUser(to_jid) 407 otrctx = ctxMng.getContextForUser(to_jid)
349 priv_key = ctxMng.account.privkey 408 priv_key = ctxMng.account.privkey
350 409
351 if priv_key is None: 410 if priv_key is None:
352 # we have no private key yet 411 # we have no private key yet
353 dialog = xml_tools.XMLUI(C.XMLUI_DIALOG, 412 dialog = xml_tools.XMLUI(
354 dialog_opt = {C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE, 413 C.XMLUI_DIALOG,
355 C.XMLUI_DATA_MESS: _(u"You have no private key yet, start an OTR conversation to have one"), 414 dialog_opt={
356 C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_WARNING 415 C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE,
357 }, 416 C.XMLUI_DATA_MESS: _(
358 title = _(u"No private key"), 417 u"You have no private key yet, start an OTR conversation to have one"
359 ) 418 ),
360 return {'xmlui': dialog.toXml()} 419 C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_WARNING,
420 },
421 title=_(u"No private key"),
422 )
423 return {"xmlui": dialog.toXml()}
361 424
362 other_fingerprint = otrctx.getCurrentKey() 425 other_fingerprint = otrctx.getCurrentKey()
363 426
364 if other_fingerprint is None: 427 if other_fingerprint is None:
365 # we have a private key, but not the fingerprint of our correspondent 428 # we have a private key, but not the fingerprint of our correspondent
366 dialog = xml_tools.XMLUI(C.XMLUI_DIALOG, 429 dialog = xml_tools.XMLUI(
367 dialog_opt = {C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE, 430 C.XMLUI_DIALOG,
368 C.XMLUI_DATA_MESS: _(u"Your fingerprint is:\n{fingerprint}\n\nStart an OTR conversation to have your correspondent one.").format(fingerprint=priv_key), 431 dialog_opt={
369 C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_INFO 432 C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE,
370 }, 433 C.XMLUI_DATA_MESS: _(
371 title = _(u"Fingerprint"), 434 u"Your fingerprint is:\n{fingerprint}\n\nStart an OTR conversation to have your correspondent one."
372 ) 435 ).format(fingerprint=priv_key),
373 return {'xmlui': dialog.toXml()} 436 C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_INFO,
437 },
438 title=_(u"Fingerprint"),
439 )
440 return {"xmlui": dialog.toXml()}
374 441
375 def setTrust(raw_data, profile): 442 def setTrust(raw_data, profile):
376 # This method is called when authentication form is submited 443 # This method is called when authentication form is submited
377 data = xml_tools.XMLUIResult2DataFormResult(raw_data) 444 data = xml_tools.XMLUIResult2DataFormResult(raw_data)
378 if data['match'] == 'yes': 445 if data["match"] == "yes":
379 otrctx.setCurrentTrust(OTR_STATE_TRUSTED) 446 otrctx.setCurrentTrust(OTR_STATE_TRUSTED)
380 note_msg = _(u"Your correspondent {correspondent} is now TRUSTED") 447 note_msg = _(u"Your correspondent {correspondent} is now TRUSTED")
381 self.host.bridge.otrState(OTR_STATE_TRUSTED, to_jid.full(), client.profile) 448 self.host.bridge.otrState(
449 OTR_STATE_TRUSTED, to_jid.full(), client.profile
450 )
382 else: 451 else:
383 otrctx.setCurrentTrust('') 452 otrctx.setCurrentTrust("")
384 note_msg = _(u"Your correspondent {correspondent} is now UNTRUSTED") 453 note_msg = _(u"Your correspondent {correspondent} is now UNTRUSTED")
385 self.host.bridge.otrState(OTR_STATE_UNTRUSTED, to_jid.full(), client.profile) 454 self.host.bridge.otrState(
386 note = xml_tools.XMLUI(C.XMLUI_DIALOG, dialog_opt = { 455 OTR_STATE_UNTRUSTED, to_jid.full(), client.profile
387 C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE, 456 )
388 C.XMLUI_DATA_MESS: note_msg.format(correspondent=otrctx.peer)} 457 note = xml_tools.XMLUI(
389 ) 458 C.XMLUI_DIALOG,
390 return {'xmlui': note.toXml()} 459 dialog_opt={
460 C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE,
461 C.XMLUI_DATA_MESS: note_msg.format(correspondent=otrctx.peer),
462 },
463 )
464 return {"xmlui": note.toXml()}
391 465
392 submit_id = self.host.registerCallback(setTrust, with_data=True, one_shot=True) 466 submit_id = self.host.registerCallback(setTrust, with_data=True, one_shot=True)
393 trusted = bool(otrctx.getCurrentTrust()) 467 trusted = bool(otrctx.getCurrentTrust())
394 468
395 xmlui = xml_tools.XMLUI(C.XMLUI_FORM, title=_('Authentication (%s)') % to_jid.full(), submit_id=submit_id) 469 xmlui = xml_tools.XMLUI(
470 C.XMLUI_FORM,
471 title=_("Authentication (%s)") % to_jid.full(),
472 submit_id=submit_id,
473 )
396 xmlui.addText(_(AUTH_TXT)) 474 xmlui.addText(_(AUTH_TXT))
397 xmlui.addDivider() 475 xmlui.addDivider()
398 xmlui.addText(D_(u"Your own fingerprint is:\n{fingerprint}").format(fingerprint=priv_key)) 476 xmlui.addText(
399 xmlui.addText(D_(u"Your correspondent fingerprint should be:\n{fingerprint}").format(fingerprint=other_fingerprint)) 477 D_(u"Your own fingerprint is:\n{fingerprint}").format(fingerprint=priv_key)
400 xmlui.addDivider('blank') 478 )
401 xmlui.changeContainer('pairs') 479 xmlui.addText(
402 xmlui.addLabel(D_(u'Is your correspondent fingerprint the same as here ?')) 480 D_(u"Your correspondent fingerprint should be:\n{fingerprint}").format(
403 xmlui.addList("match", [('yes', _('yes')),('no', _('no'))], ['yes' if trusted else 'no']) 481 fingerprint=other_fingerprint
404 return {'xmlui': xmlui.toXml()} 482 )
483 )
484 xmlui.addDivider("blank")
485 xmlui.changeContainer("pairs")
486 xmlui.addLabel(D_(u"Is your correspondent fingerprint the same as here ?"))
487 xmlui.addList(
488 "match", [("yes", _("yes")), ("no", _("no"))], ["yes" if trusted else "no"]
489 )
490 return {"xmlui": xmlui.toXml()}
405 491
406 def _dropPrivKey(self, menu_data, profile): 492 def _dropPrivKey(self, menu_data, profile):
407 """Drop our private Key 493 """Drop our private Key
408 494
409 @param menu_data: %(menu_data)s 495 @param menu_data: %(menu_data)s
410 @param profile: %(doc_profile)s 496 @param profile: %(doc_profile)s
411 """ 497 """
412 client = self.host.getClient(profile) 498 client = self.host.getClient(profile)
413 try: 499 try:
414 to_jid = jid.JID(menu_data['jid']) 500 to_jid = jid.JID(menu_data["jid"])
415 if not to_jid.resource: 501 if not to_jid.resource:
416 to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: temporary and unsecure, must be changed when frontends are refactored 502 to_jid.resource = self.host.memory.getMainResource(
503 client, to_jid
504 ) # FIXME: temporary and unsecure, must be changed when frontends are refactored
417 except KeyError: 505 except KeyError:
418 log.error(_(u"jid key is not present !")) 506 log.error(_(u"jid key is not present !"))
419 return defer.fail(exceptions.DataError) 507 return defer.fail(exceptions.DataError)
420 508
421 ctxMng = client._otr_context_manager 509 ctxMng = client._otr_context_manager
422 if ctxMng.account.privkey is None: 510 if ctxMng.account.privkey is None:
423 return {'xmlui': xml_tools.note(_(u"You don't have a private key yet !")).toXml()} 511 return {
512 "xmlui": xml_tools.note(_(u"You don't have a private key yet !")).toXml()
513 }
424 514
425 def dropKey(data, profile): 515 def dropKey(data, profile):
426 if C.bool(data['answer']): 516 if C.bool(data["answer"]):
427 # we end all sessions 517 # we end all sessions
428 for context in ctxMng.contexts.values(): 518 for context in ctxMng.contexts.values():
429 context.disconnect() 519 context.disconnect()
430 ctxMng.account.privkey = None 520 ctxMng.account.privkey = None
431 ctxMng.account.getPrivkey() # as account.privkey is None, getPrivkey will generate a new key, and save it 521 ctxMng.account.getPrivkey() # as account.privkey is None, getPrivkey will generate a new key, and save it
432 return {'xmlui': xml_tools.note(D_(u"Your private key has been dropped")).toXml()} 522 return {
523 "xmlui": xml_tools.note(
524 D_(u"Your private key has been dropped")
525 ).toXml()
526 }
433 return {} 527 return {}
434 528
435 submit_id = self.host.registerCallback(dropKey, with_data=True, one_shot=True) 529 submit_id = self.host.registerCallback(dropKey, with_data=True, one_shot=True)
436 530
437 confirm = xml_tools.XMLUI(C.XMLUI_DIALOG, title=_(u'Confirm private key drop'), dialog_opt = {'type': C.XMLUI_DIALOG_CONFIRM, 'message': _(DROP_TXT)}, submit_id = submit_id) 531 confirm = xml_tools.XMLUI(
438 return {'xmlui': confirm.toXml()} 532 C.XMLUI_DIALOG,
533 title=_(u"Confirm private key drop"),
534 dialog_opt={"type": C.XMLUI_DIALOG_CONFIRM, "message": _(DROP_TXT)},
535 submit_id=submit_id,
536 )
537 return {"xmlui": confirm.toXml()}
439 538
440 def _receivedTreatment(self, data, client): 539 def _receivedTreatment(self, data, client):
441 from_jid = data['from'] 540 from_jid = data["from"]
442 log.debug(u"_receivedTreatment [from_jid = %s]" % from_jid) 541 log.debug(u"_receivedTreatment [from_jid = %s]" % from_jid)
443 otrctx = client._otr_context_manager.getContextForUser(from_jid) 542 otrctx = client._otr_context_manager.getContextForUser(from_jid)
444 543
445 try: 544 try:
446 message = data['message'].itervalues().next() # FIXME: Q&D fix for message refactoring, message is now a dict 545 message = (
447 res = otrctx.receiveMessage(message.encode('utf-8')) 546 data["message"].itervalues().next()
547 ) # FIXME: Q&D fix for message refactoring, message is now a dict
548 res = otrctx.receiveMessage(message.encode("utf-8"))
448 except potr.context.UnencryptedMessage: 549 except potr.context.UnencryptedMessage:
449 encrypted = False 550 encrypted = False
450 if otrctx.state == potr.context.STATE_ENCRYPTED: 551 if otrctx.state == potr.context.STATE_ENCRYPTED:
451 log.warning(u"Received unencrypted message in an encrypted context (from {jid})".format( 552 log.warning(
452 jid = from_jid.full())) 553 u"Received unencrypted message in an encrypted context (from {jid})".format(
453 554 jid=from_jid.full()
454 feedback=D_(u"WARNING: received unencrypted data in a supposedly encrypted context"), 555 )
556 )
557
558 feedback = (
559 D_(
560 u"WARNING: received unencrypted data in a supposedly encrypted context"
561 ),
562 )
455 client.feedback(from_jid, feedback) 563 client.feedback(from_jid, feedback)
456 except StopIteration: 564 except StopIteration:
457 return data 565 return data
458 else: 566 else:
459 encrypted = True 567 encrypted = True
460 568
461 if encrypted: 569 if encrypted:
462 if res[0] != None: 570 if res[0] != None:
463 # decrypted messages handling. 571 # decrypted messages handling.
464 # receiveMessage() will return a tuple, the first part of which will be the decrypted message 572 # receiveMessage() will return a tuple, the first part of which will be the decrypted message
465 data['message'] = {'':res[0].decode('utf-8')} # FIXME: Q&D fix for message refactoring, message is now a dict 573 data["message"] = {
574 "": res[0].decode("utf-8")
575 } # FIXME: Q&D fix for message refactoring, message is now a dict
466 try: 576 try:
467 # we want to keep message in history, even if no store is requested in message hints 577 # we want to keep message in history, even if no store is requested in message hints
468 del data[u'history'] 578 del data[u"history"]
469 except KeyError: 579 except KeyError:
470 pass 580 pass
471 # TODO: add skip history as an option, but by default we don't skip it 581 # TODO: add skip history as an option, but by default we don't skip it
472 # data[u'history'] = C.HISTORY_SKIP # we send the decrypted message to frontends, but we don't want it in history 582 # data[u'history'] = C.HISTORY_SKIP # we send the decrypted message to frontends, but we don't want it in history
473 else: 583 else:
474 log.warning(u"An encrypted message was expected, but got {}".format(data['message'])) 584 log.warning(
475 raise failure.Failure(exceptions.CancelError('Cancelled by OTR')) # no message at all (no history, no signal) 585 u"An encrypted message was expected, but got {}".format(
586 data["message"]
587 )
588 )
589 raise failure.Failure(
590 exceptions.CancelError("Cancelled by OTR")
591 ) # no message at all (no history, no signal)
476 return data 592 return data
477 593
478 def _receivedTreatmentForSkippedProfiles(self, data): 594 def _receivedTreatmentForSkippedProfiles(self, data):
479 """This profile must be skipped because the frontend manages OTR itself, 595 """This profile must be skipped because the frontend manages OTR itself,
480 596
481 but we still need to check if the message must be stored in history or not 597 but we still need to check if the message must be stored in history or not
482 """ 598 """
483 # XXX: FIXME: this should not be done on a per-profile basis, but per-message 599 #  XXX: FIXME: this should not be done on a per-profile basis, but per-message
484 try: 600 try:
485 message = data['message'].itervalues().next().encode('utf-8') # FIXME: Q&D fix for message refactoring, message is now a dict 601 message = (
602 data["message"].itervalues().next().encode("utf-8")
603 ) # FIXME: Q&D fix for message refactoring, message is now a dict
486 except StopIteration: 604 except StopIteration:
487 return data 605 return data
488 if message.startswith(potr.proto.OTRTAG): 606 if message.startswith(potr.proto.OTRTAG):
489 # FIXME: it may be better to cancel the message and send it direclty to bridge 607 #  FIXME: it may be better to cancel the message and send it direclty to bridge
490 # this is used by Libervia, but this may send garbage message to other frontends 608 # this is used by Libervia, but this may send garbage message to other frontends
491 # if they are used at the same time as Libervia. 609 # if they are used at the same time as Libervia.
492 # Hard to avoid with decryption on Libervia though. 610 # Hard to avoid with decryption on Libervia though.
493 data[u'history'] = C.HISTORY_SKIP 611 data[u"history"] = C.HISTORY_SKIP
494 return data 612 return data
495 613
496 def MessageReceivedTrigger(self, client, message_elt, post_treat): 614 def MessageReceivedTrigger(self, client, message_elt, post_treat):
497 if message_elt.getAttribute('type') == C.MESS_TYPE_GROUPCHAT: 615 if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT:
498 # OTR is not possible in group chats 616 # OTR is not possible in group chats
499 return True 617 return True
500 if client.profile in self.skipped_profiles: 618 if client.profile in self.skipped_profiles:
501 post_treat.addCallback(self._receivedTreatmentForSkippedProfiles) 619 post_treat.addCallback(self._receivedTreatmentForSkippedProfiles)
502 else: 620 else:
503 post_treat.addCallback(self._receivedTreatment, client) 621 post_treat.addCallback(self._receivedTreatment, client)
504 return True 622 return True
505 623
506 def _sendMessageDataTrigger(self, client, mess_data): 624 def _sendMessageDataTrigger(self, client, mess_data):
507 if not 'OTR' in mess_data: 625 if not "OTR" in mess_data:
508 return 626 return
509 otrctx = mess_data['OTR'] 627 otrctx = mess_data["OTR"]
510 message_elt = mess_data['xml'] 628 message_elt = mess_data["xml"]
511 to_jid = mess_data['to'] 629 to_jid = mess_data["to"]
512 if otrctx.state == potr.context.STATE_ENCRYPTED: 630 if otrctx.state == potr.context.STATE_ENCRYPTED:
513 log.debug(u"encrypting message") 631 log.debug(u"encrypting message")
514 body = None 632 body = None
515 for child in list(message_elt.children): 633 for child in list(message_elt.children):
516 if child.name == 'body': 634 if child.name == "body":
517 # we remove all unencrypted body, 635 # we remove all unencrypted body,
518 # and will only encrypt the first one 636 # and will only encrypt the first one
519 if body is None: 637 if body is None:
520 body = child 638 body = child
521 message_elt.children.remove(child) 639 message_elt.children.remove(child)
522 elif child.name == 'html': 640 elif child.name == "html":
523 # we don't want any XHTML-IM element 641 # we don't want any XHTML-IM element
524 message_elt.children.remove(child) 642 message_elt.children.remove(child)
525 if body is None: 643 if body is None:
526 log.warning(u"No message found") 644 log.warning(u"No message found")
527 else: 645 else:
528 self._p_carbons.setPrivate(message_elt) 646 self._p_carbons.setPrivate(message_elt)
529 otrctx.sendMessage(0, unicode(body).encode('utf-8'), appdata=mess_data) 647 otrctx.sendMessage(0, unicode(body).encode("utf-8"), appdata=mess_data)
530 else: 648 else:
531 feedback = D_(u"Your message was not sent because your correspondent closed the encrypted conversation on his/her side. " 649 feedback = D_(
532 u"Either close your own side, or refresh the session.") 650 u"Your message was not sent because your correspondent closed the encrypted conversation on his/her side. "
651 u"Either close your own side, or refresh the session."
652 )
533 log.warning(_(u"Message discarded because closed encryption channel")) 653 log.warning(_(u"Message discarded because closed encryption channel"))
534 client.feedback(to_jid, feedback) 654 client.feedback(to_jid, feedback)
535 raise failure.Failure(exceptions.CancelError(u'Cancelled by OTR plugin')) 655 raise failure.Failure(exceptions.CancelError(u"Cancelled by OTR plugin"))
536 656
537 def sendMessageTrigger(self, client, mess_data, pre_xml_treatments, post_xml_treatments): 657 def sendMessageTrigger(
538 if mess_data['type'] == 'groupchat': 658 self, client, mess_data, pre_xml_treatments, post_xml_treatments
659 ):
660 if mess_data["type"] == "groupchat":
539 return True 661 return True
540 if client.profile in self.skipped_profiles: # FIXME: should not be done on a per-profile basis 662 if (
663 client.profile in self.skipped_profiles
664 ): #  FIXME: should not be done on a per-profile basis
541 return True 665 return True
542 to_jid = copy.copy(mess_data['to']) 666 to_jid = copy.copy(mess_data["to"])
543 if not to_jid.resource: 667 if not to_jid.resource:
544 to_jid.resource = self.host.memory.getMainResource(client, to_jid) # FIXME: full jid may not be known 668 to_jid.resource = self.host.memory.getMainResource(
669 client, to_jid
670 ) # FIXME: full jid may not be known
545 otrctx = client._otr_context_manager.getContextForUser(to_jid) 671 otrctx = client._otr_context_manager.getContextForUser(to_jid)
546 if otrctx.state != potr.context.STATE_PLAINTEXT: 672 if otrctx.state != potr.context.STATE_PLAINTEXT:
547 self._p_hints.addHint(mess_data, self._p_hints.HINT_NO_COPY) 673 self._p_hints.addHint(mess_data, self._p_hints.HINT_NO_COPY)
548 self._p_hints.addHint(mess_data, self._p_hints.HINT_NO_PERMANENT_STORE) 674 self._p_hints.addHint(mess_data, self._p_hints.HINT_NO_PERMANENT_STORE)
549 mess_data['OTR'] = otrctx # this indicate that encryption is needed in sendMessageData trigger 675 mess_data[
550 if not mess_data['to'].resource: # if not resource was given, we force it here 676 "OTR"
551 mess_data['to'] = to_jid 677 ] = (
678 otrctx
679 ) #  this indicate that encryption is needed in sendMessageData trigger
680 if not mess_data[
681 "to"
682 ].resource: #  if not resource was given, we force it here
683 mess_data["to"] = to_jid
552 return True 684 return True
553 685
554 def _presenceReceivedTrigger(self, entity, show, priority, statuses, profile): 686 def _presenceReceivedTrigger(self, entity, show, priority, statuses, profile):
555 if show != C.PRESENCE_UNAVAILABLE: 687 if show != C.PRESENCE_UNAVAILABLE:
556 return True 688 return True
557 client = self.host.getClient(profile) 689 client = self.host.getClient(profile)
558 if not entity.resource: 690 if not entity.resource:
559 try: 691 try:
560 entity.resource = self.host.memory.getMainResource(client, entity) # FIXME: temporary and unsecure, must be changed when frontends are refactored 692 entity.resource = self.host.memory.getMainResource(
693 client, entity
694 ) # FIXME: temporary and unsecure, must be changed when frontends are refactored
561 except exceptions.UnknownEntityError: 695 except exceptions.UnknownEntityError:
562 return True # entity was not connected 696 return True # entity was not connected
563 if entity in client._otr_context_manager.contexts: 697 if entity in client._otr_context_manager.contexts:
564 otrctx = client._otr_context_manager.getContextForUser(entity) 698 otrctx = client._otr_context_manager.getContextForUser(entity)
565 otrctx.disconnect() 699 otrctx.disconnect()