Mercurial > libervia-backend
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 |