comparison sat/memory/encryption.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 e347e32aa07f
children 4b8271399f67
comparison
equal deleted inserted replaced
2742:1f5b02623355 2743:da59ff099b32
20 from sat.core.i18n import D_, _ 20 from sat.core.i18n import D_, _
21 from sat.core.constants import Const as C 21 from sat.core.constants import Const as C
22 from sat.core import exceptions 22 from sat.core import exceptions
23 from collections import namedtuple 23 from collections import namedtuple
24 from sat.core.log import getLogger 24 from sat.core.log import getLogger
25 from sat.tools.common import data_format
26 from twisted.internet import defer
27 from twisted.python import failure
28 import copy
25 log = getLogger(__name__) 29 log = getLogger(__name__)
26 from sat.tools.common import data_format 30
27 31 log = getLogger(__name__)
28 32
29 EncryptionPlugin = namedtuple("EncryptionPlugin", ("instance", 33 EncryptionPlugin = namedtuple("EncryptionPlugin", ("instance",
30 "name", 34 "name",
31 "namespace", 35 "namespace",
32 "priority", 36 "priority",
52 @param plg_instance(object): instance of the plugin 56 @param plg_instance(object): instance of the plugin
53 it must have the following methods: 57 it must have the following methods:
54 - getTrustUI(entity): return a XMLUI for trust management 58 - getTrustUI(entity): return a XMLUI for trust management
55 entity(jid.JID): entity to manage 59 entity(jid.JID): entity to manage
56 The returned XMLUI must be a form 60 The returned XMLUI must be a form
61 if may have the following methods:
62 - startEncryption(entity): start encrypted session
63 entity(jid.JID): entity to start encrypted session with
64 - stopEncryption(entity): start encrypted session
65 entity(jid.JID): entity to stop encrypted session with
66 if they don't exists, those 2 methods will be ignored.
57 67
58 @param name(unicode): human readable name of the encryption algorithm 68 @param name(unicode): human readable name of the encryption algorithm
59 @param namespace(unicode): namespace of the encryption algorithm 69 @param namespace(unicode): namespace of the encryption algorithm
60 @param priority(int): priority of this plugin to encrypt an message when not 70 @param priority(int): priority of this plugin to encrypt an message when not
61 selected manually 71 selected manually
123 if u'directed_devices' in session: 133 if u'directed_devices' in session:
124 bridge_data[u'directed_devices'] = session[u'directed_devices'] 134 bridge_data[u'directed_devices'] = session[u'directed_devices']
125 135
126 return data_format.serialise(bridge_data) 136 return data_format.serialise(bridge_data)
127 137
138 def _startEncryption(self, plugin, entity):
139 """Start encryption with a plugin
140
141 This method must be called just before adding a plugin session.
142 StartEncryptionn method of plugin will be called if it exists.
143 """
144 try:
145 start_encryption = plugin.instance.startEncryption
146 except AttributeError:
147 log.debug(u"No startEncryption method found for {plugin}".format(
148 plugin = plugin.namespace))
149 return defer.succeed(None)
150 else:
151 # we copy entity to avoid having the resource changed by stop_encryption
152 return defer.maybeDeferred(start_encryption, self.client, copy.copy(entity))
153
154 def _stopEncryption(self, plugin, entity):
155 """Stop encryption with a plugin
156
157 This method must be called just before removing a plugin session.
158 StopEncryptionn method of plugin will be called if it exists.
159 """
160 try:
161 stop_encryption = plugin.instance.stopEncryption
162 except AttributeError:
163 log.debug(u"No stopEncryption method found for {plugin}".format(
164 plugin = plugin.namespace))
165 return defer.succeed(None)
166 else:
167 # we copy entity to avoid having the resource changed by stop_encryption
168 return defer.maybeDeferred(stop_encryption, self.client, copy.copy(entity))
169
170 @defer.inlineCallbacks
128 def start(self, entity, namespace=None, replace=False): 171 def start(self, entity, namespace=None, replace=False):
129 """Start an encryption session with an entity 172 """Start an encryption session with an entity
130 173
131 @param entity(jid.JID): entity to start an encryption session with 174 @param entity(jid.JID): entity to start an encryption session with
132 must be bare jid is the algorithm encrypt for all devices 175 must be bare jid is the algorithm encrypt for all devices
146 plugin = self.getPlugin(namespace) 189 plugin = self.getPlugin(namespace)
147 190
148 bare_jid = entity.userhostJID() 191 bare_jid = entity.userhostJID()
149 if bare_jid in self._sessions: 192 if bare_jid in self._sessions:
150 # we have already an encryption session with this contact 193 # we have already an encryption session with this contact
151 former_plugin = self._sessions[bare_jid]['plugin'] 194 former_plugin = self._sessions[bare_jid][u"plugin"]
152 if former_plugin.namespace == namespace: 195 if former_plugin.namespace == namespace:
153 log.info(_(u"Session with {bare_jid} is already encrypted with {name}. " 196 log.info(_(u"Session with {bare_jid} is already encrypted with {name}. "
154 u"Nothing to do.").format( 197 u"Nothing to do.").format(
155 bare_jid=bare_jid, name=former_plugin.name)) 198 bare_jid=bare_jid, name=former_plugin.name))
156 return 199 return
157 200
158 if replace: 201 if replace:
159 # there is a conflict, but replacement is requested 202 # there is a conflict, but replacement is requested
160 # so we stop previous encryption to use new one 203 # so we stop previous encryption to use new one
161 del self._sessions[bare_jid] 204 del self._sessions[bare_jid]
205 yield self._stopEncryption(former_plugin, entity)
162 else: 206 else:
163 msg = (_(u"Session with {bare_jid} is already encrypted with {name}. " 207 msg = (_(u"Session with {bare_jid} is already encrypted with {name}. "
164 u"Please stop encryption session before changing algorithm.") 208 u"Please stop encryption session before changing algorithm.")
165 .format(bare_jid=bare_jid, name=plugin.name)) 209 .format(bare_jid=bare_jid, name=plugin.name))
166 log.warning(msg) 210 log.warning(msg)
180 # indicate that we encrypt only for some devices 224 # indicate that we encrypt only for some devices
181 directed_devices = data[u'directed_devices'] = [entity.resource] 225 directed_devices = data[u'directed_devices'] = [entity.resource]
182 elif entity.resource: 226 elif entity.resource:
183 raise ValueError(_(u"{name} encryption must be used with bare jids.")) 227 raise ValueError(_(u"{name} encryption must be used with bare jids."))
184 228
229 yield self._startEncryption(plugin, entity)
185 self._sessions[entity.userhostJID()] = data 230 self._sessions[entity.userhostJID()] = data
186 log.info(_(u"Encryption session has been set for {entity_jid} with " 231 log.info(_(u"Encryption session has been set for {entity_jid} with "
187 u"{encryption_name}").format( 232 u"{encryption_name}").format(
188 entity_jid=entity.full(), encryption_name=plugin.name)) 233 entity_jid=entity.full(), encryption_name=plugin.name))
189 self.host.bridge.messageEncryptionStarted( 234 self.host.bridge.messageEncryptionStarted(
200 nb_devices=len(directed_devices), 245 nb_devices=len(directed_devices),
201 devices_list = u', '.join(directed_devices)) 246 devices_list = u', '.join(directed_devices))
202 247
203 self.client.feedback(bare_jid, msg) 248 self.client.feedback(bare_jid, msg)
204 249
250 @defer.inlineCallbacks
205 def stop(self, entity, namespace=None): 251 def stop(self, entity, namespace=None):
206 """Stop an encryption session with an entity 252 """Stop an encryption session with an entity
207 253
208 @param entity(jid.JID): entity with who the encryption session must be stopped 254 @param entity(jid.JID): entity with who the encryption session must be stopped
209 must be bare jid is the algorithm encrypt for all devices 255 must be bare jid is the algorithm encrypt for all devices
210 @param namespace(unicode): namespace of the session to stop 256 @param namespace(unicode): namespace of the session to stop
211 when specified, used to check we stop the right encryption session 257 when specified, used to check we stop the right encryption session
212 """ 258 """
213 session = self.getSession(entity.userhostJID()) 259 session = self.getSession(entity.userhostJID())
214 if not session: 260 if not session:
215 raise exceptions.NotFound(_(u"There is no encryption session with this " 261 raise failure.Failure(
216 u"entity.")) 262 exceptions.NotFound(_(u"There is no encryption session with this "
263 u"entity.")))
217 plugin = session['plugin'] 264 plugin = session['plugin']
218 if namespace is not None and plugin.namespace != namespace: 265 if namespace is not None and plugin.namespace != namespace:
219 raise exceptions.InternalError(_( 266 raise exceptions.InternalError(_(
220 u"The encryption session is not run with the expected plugin: encrypted " 267 u"The encryption session is not run with the expected plugin: encrypted "
221 u"with {current_name} and was expecting {expected_name}").format( 268 u"with {current_name} and was expecting {expected_name}").format(
235 except ValueError: 282 except ValueError:
236 raise exceptions.NotFound(_(u"There is no directed session with this " 283 raise exceptions.NotFound(_(u"There is no directed session with this "
237 u"entity.")) 284 u"entity."))
238 else: 285 else:
239 if not directed_devices: 286 if not directed_devices:
240 del session[u'directed_devices'] 287 # if we have no more directed device sessions,
241 else: 288 # we stop the whole session
242 del self._sessions[entity] 289 # see comment below for deleting session before stopping encryption
290 del self._sessions[entity.userhostJID()]
291 yield self._stopEncryption(plugin, entity)
292 else:
293 # plugin's stopEncryption may call stop again (that's the case with OTR)
294 # so we need to remove plugin from session before calling self._stopEncryption
295 del self._sessions[entity.userhostJID()]
296 yield self._stopEncryption(plugin, entity)
243 297
244 log.info(_(u"encryption session stopped with entity {entity}").format( 298 log.info(_(u"encryption session stopped with entity {entity}").format(
245 entity=entity.full())) 299 entity=entity.full()))
246 self.host.bridge.messageEncryptionStopped( 300 self.host.bridge.messageEncryptionStopped(
247 entity.full(), 301 entity.full(),
294 get_trust_ui = plugin.instance.getTrustUI 348 get_trust_ui = plugin.instance.getTrustUI
295 except AttributeError: 349 except AttributeError:
296 raise NotImplementedError( 350 raise NotImplementedError(
297 u"Encryption plugin doesn't handle trust management UI") 351 u"Encryption plugin doesn't handle trust management UI")
298 else: 352 else:
299 return get_trust_ui(self.client, entity_jid) 353 return defer.maybeDeferred(get_trust_ui, self.client, entity_jid)
300 354
301 ## Triggers ## 355 ## Triggers ##
302 356
303 def setEncryptionFlag(self, mess_data): 357 def setEncryptionFlag(self, mess_data):
304 """Set "encryption" key in mess_data if session with destinee is encrypted""" 358 """Set "encryption" key in mess_data if session with destinee is encrypted"""
322 @param mess_data(dict): message data as used in post treat workflow 376 @param mess_data(dict): message data as used in post treat workflow
323 """ 377 """
324 mess_data['encrypted'] = True 378 mess_data['encrypted'] = True
325 return mess_data 379 return mess_data
326 380
327 def markAsUntrusted(self, mess_data): 381 def markAsTrusted(self, mess_data):
328 """Helper methor to mark a message as sent from an untrusted entity. 382 """Helper methor to mark a message as sent from a trusted entity.
329 383
330 This should be used in the post_treat workflow of MessageReceived trigger of 384 This should be used in the post_treat workflow of MessageReceived trigger of
331 the plugin 385 the plugin
332 @param mess_data(dict): message data as used in post treat workflow 386 @param mess_data(dict): message data as used in post treat workflow
333 """ 387 """
334 mess_data['untrusted'] = True 388 mess_data['trusted'] = True
335 return mess_data 389 return mess_data
390
391 def markAsUntrusted(self, mess_data):
392 """Helper methor to mark a message as sent from an untrusted entity.
393
394 This should be used in the post_treat workflow of MessageReceived trigger of
395 the plugin
396 @param mess_data(dict): message data as used in post treat workflow
397 """
398 mess_data['trusted'] = False
399 return mess_data