changeset 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 1f5b02623355
children e6716d90c2fe
files sat/bridge/bridge_constructor/bridge_template.ini sat/bridge/dbus_bridge.py sat/memory/encryption.py sat/plugins/plugin_sec_otr.py sat_frontends/bridge/dbus_bridge.py
diffstat 5 files changed, 263 insertions(+), 134 deletions(-) [+]
line wrap: on
line diff
--- a/sat/bridge/bridge_constructor/bridge_template.ini	Thu Jan 03 20:51:08 2019 +0100
+++ b/sat/bridge/bridge_constructor/bridge_template.ini	Thu Jan 03 21:00:00 2019 +0100
@@ -436,6 +436,7 @@
 doc_param_5=%(doc_profile_key)s
 
 [messageEncryptionStart]
+async=
 type=method
 category=core
 sig_in=ssbs
@@ -451,6 +452,7 @@
 doc_param_3=%(doc_profile_key)s
 
 [messageEncryptionStop]
+async=
 type=method
 category=core
 sig_in=ss
--- a/sat/bridge/dbus_bridge.py	Thu Jan 03 20:51:08 2019 +0100
+++ b/sat/bridge/dbus_bridge.py	Thu Jan 03 21:00:00 2019 +0100
@@ -423,15 +423,15 @@
 
     @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
                          in_signature='ssbs', out_signature='',
-                         async_callbacks=None)
-    def messageEncryptionStart(self, to_jid, namespace='', replace=False, profile_key="@NONE@"):
-        return self._callback("messageEncryptionStart", unicode(to_jid), unicode(namespace), replace, unicode(profile_key))
+                         async_callbacks=('callback', 'errback'))
+    def messageEncryptionStart(self, to_jid, namespace='', replace=False, profile_key="@NONE@", callback=None, errback=None):
+        return self._callback("messageEncryptionStart", unicode(to_jid), unicode(namespace), replace, unicode(profile_key), callback=callback, errback=errback)
 
     @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
                          in_signature='ss', out_signature='',
-                         async_callbacks=None)
-    def messageEncryptionStop(self, to_jid, profile_key):
-        return self._callback("messageEncryptionStop", unicode(to_jid), unicode(profile_key))
+                         async_callbacks=('callback', 'errback'))
+    def messageEncryptionStop(self, to_jid, profile_key, callback=None, errback=None):
+        return self._callback("messageEncryptionStop", unicode(to_jid), unicode(profile_key), callback=callback, errback=errback)
 
     @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
                          in_signature='sa{ss}a{ss}sa{ss}s', out_signature='',
--- a/sat/memory/encryption.py	Thu Jan 03 20:51:08 2019 +0100
+++ b/sat/memory/encryption.py	Thu Jan 03 21:00:00 2019 +0100
@@ -22,9 +22,13 @@
 from sat.core import exceptions
 from collections import namedtuple
 from sat.core.log import getLogger
+from sat.tools.common import data_format
+from twisted.internet import defer
+from twisted.python import failure
+import copy
 log = getLogger(__name__)
-from sat.tools.common import data_format
 
+log = getLogger(__name__)
 
 EncryptionPlugin = namedtuple("EncryptionPlugin", ("instance",
                                                    "name",
@@ -54,6 +58,12 @@
                 - getTrustUI(entity): return a XMLUI for trust management
                     entity(jid.JID): entity to manage
                     The returned XMLUI must be a form
+            if may have the following methods:
+                - startEncryption(entity): start encrypted session
+                    entity(jid.JID): entity to start encrypted session with
+                - stopEncryption(entity): start encrypted session
+                    entity(jid.JID): entity to stop encrypted session with
+            if they don't exists, those 2 methods will be ignored.
 
         @param name(unicode): human readable name of the encryption algorithm
         @param namespace(unicode): namespace of the encryption algorithm
@@ -125,6 +135,39 @@
 
         return data_format.serialise(bridge_data)
 
+    def _startEncryption(self, plugin, entity):
+        """Start encryption with a plugin
+
+        This method must be called just before adding a plugin session.
+        StartEncryptionn method of plugin will be called if it exists.
+        """
+        try:
+            start_encryption = plugin.instance.startEncryption
+        except AttributeError:
+            log.debug(u"No startEncryption method found for {plugin}".format(
+                plugin = plugin.namespace))
+            return defer.succeed(None)
+        else:
+            # we copy entity to avoid having the resource changed by stop_encryption
+            return defer.maybeDeferred(start_encryption, self.client, copy.copy(entity))
+
+    def _stopEncryption(self, plugin, entity):
+        """Stop encryption with a plugin
+
+        This method must be called just before removing a plugin session.
+        StopEncryptionn method of plugin will be called if it exists.
+        """
+        try:
+            stop_encryption = plugin.instance.stopEncryption
+        except AttributeError:
+            log.debug(u"No stopEncryption method found for {plugin}".format(
+                plugin = plugin.namespace))
+            return defer.succeed(None)
+        else:
+            # we copy entity to avoid having the resource changed by stop_encryption
+            return defer.maybeDeferred(stop_encryption, self.client, copy.copy(entity))
+
+    @defer.inlineCallbacks
     def start(self, entity, namespace=None, replace=False):
         """Start an encryption session with an entity
 
@@ -148,7 +191,7 @@
         bare_jid = entity.userhostJID()
         if bare_jid in self._sessions:
             # we have already an encryption session with this contact
-            former_plugin = self._sessions[bare_jid]['plugin']
+            former_plugin = self._sessions[bare_jid][u"plugin"]
             if former_plugin.namespace == namespace:
                 log.info(_(u"Session with {bare_jid} is already encrypted with {name}. "
                            u"Nothing to do.").format(
@@ -159,6 +202,7 @@
                 # there is a conflict, but replacement is requested
                 # so we stop previous encryption to use new one
                 del self._sessions[bare_jid]
+                yield self._stopEncryption(former_plugin, entity)
             else:
                 msg = (_(u"Session with {bare_jid} is already encrypted with {name}. "
                          u"Please stop encryption session before changing algorithm.")
@@ -182,6 +226,7 @@
         elif entity.resource:
             raise ValueError(_(u"{name} encryption must be used with bare jids."))
 
+        yield self._startEncryption(plugin, entity)
         self._sessions[entity.userhostJID()] = data
         log.info(_(u"Encryption session has been set for {entity_jid} with "
                    u"{encryption_name}").format(
@@ -202,6 +247,7 @@
 
         self.client.feedback(bare_jid, msg)
 
+    @defer.inlineCallbacks
     def stop(self, entity, namespace=None):
         """Stop an encryption session with an entity
 
@@ -212,8 +258,9 @@
         """
         session = self.getSession(entity.userhostJID())
         if not session:
-            raise exceptions.NotFound(_(u"There is no encryption session with this "
-                                        u"entity."))
+            raise failure.Failure(
+                exceptions.NotFound(_(u"There is no encryption session with this "
+                                      u"entity.")))
         plugin = session['plugin']
         if namespace is not None and plugin.namespace != namespace:
             raise exceptions.InternalError(_(
@@ -237,9 +284,16 @@
                                             u"entity."))
             else:
                 if not directed_devices:
-                    del session[u'directed_devices']
+                    # if we have no more directed device sessions,
+                    # we stop the whole session
+                    # see comment below for deleting session before stopping encryption
+                    del self._sessions[entity.userhostJID()]
+                    yield self._stopEncryption(plugin, entity)
         else:
-            del self._sessions[entity]
+            # plugin's stopEncryption may call stop again (that's the case with OTR)
+            # so we need to remove plugin from session before calling self._stopEncryption
+            del self._sessions[entity.userhostJID()]
+            yield self._stopEncryption(plugin, entity)
 
         log.info(_(u"encryption session stopped with entity {entity}").format(
             entity=entity.full()))
@@ -296,7 +350,7 @@
             raise NotImplementedError(
                 u"Encryption plugin doesn't handle trust management UI")
         else:
-            return get_trust_ui(self.client, entity_jid)
+            return defer.maybeDeferred(get_trust_ui, self.client, entity_jid)
 
     ## Triggers ##
 
@@ -324,6 +378,16 @@
         mess_data['encrypted'] = True
         return mess_data
 
+    def markAsTrusted(self, mess_data):
+        """Helper methor to mark a message as sent from a trusted entity.
+
+        This should be used in the post_treat workflow of MessageReceived trigger of
+        the plugin
+        @param mess_data(dict): message data as used in post treat workflow
+        """
+        mess_data['trusted'] = True
+        return mess_data
+
     def markAsUntrusted(self, mess_data):
         """Helper methor to mark a message as sent from an untrusted entity.
 
@@ -331,5 +395,5 @@
         the plugin
         @param mess_data(dict): message data as used in post treat workflow
         """
-        mess_data['untrusted'] = True
+        mess_data['trusted'] = False
         return mess_data
--- a/sat/plugins/plugin_sec_otr.py	Thu Jan 03 20:51:08 2019 +0100
+++ b/sat/plugins/plugin_sec_otr.py	Thu Jan 03 21:00:00 2019 +0100
@@ -74,9 +74,21 @@
 
 
 class Context(potr.context.Context):
-    def __init__(self, host, account, other_jid):
-        super(Context, self).__init__(account, other_jid)
-        self.host = host
+    def __init__(self, context_manager, other_jid):
+        self.context_manager = context_manager
+        super(Context, self).__init__(context_manager.account, other_jid)
+
+    @property
+    def host(self):
+        return self.context_manager.host
+
+    @property
+    def _p_hints(self):
+        return self.context_manager.parent._p_hints
+
+    @property
+    def _p_carbons(self):
+        return self.context_manager.parent._p_carbons
 
     def getPolicy(self, key):
         if key in DEFAULT_POLICY_FLAGS:
@@ -110,12 +122,42 @@
                 "timestamp": time.time(),
             }
             client.generateMessageXML(mess_data)
+            xml = mess_data[u'xml']
+            self._p_carbons.setPrivate(xml)
+            self._p_hints.addHintElements(xml, [
+                self._p_hints.HINT_NO_COPY,
+                self._p_hints.HINT_NO_PERMANENT_STORE])
             client.send(mess_data["xml"])
         else:
             message_elt = appdata[u"xml"]
             assert message_elt.name == u"message"
             message_elt.addElement("body", content=msg)
 
+    def stopCb(self, __, feedback):
+        client = self.user.client
+        self.host.bridge.otrState(
+            OTR_STATE_UNENCRYPTED, self.peer.full(), client.profile
+        )
+        client.feedback(self.peer, feedback)
+
+    def stopEb(self, failure_):
+        # encryption may be already stopped in case of manual stop
+        if not failure_.check(exceptions.NotFound):
+            log.error(u"Error while stopping OTR encryption: {msg}".format(msg=failure_))
+
+    def isTrusted(self):
+        # we have to check value because potr code says that a 2-tuples should be
+        # returned while in practice it's either None or u"trusted"
+        trusted = self.getCurrentTrust()
+        if trusted is None:
+            return False
+        elif trusted == u'trusted':
+            return True
+        else:
+            log.error(u"Unexpected getCurrentTrust() value: {value}".format(
+                value=trusted))
+            return False
+
     def setState(self, state):
         client = self.user.client
         old_state = self.state
@@ -123,17 +165,17 @@
         log.debug(u"setState: %s (old_state=%s)" % (state, old_state))
 
         if state == potr.context.STATE_PLAINTEXT:
-            client.encryption.stop(self.peer, NS_OTR)
             feedback = _(u"/!\\ conversation with %(other_jid)s is now UNENCRYPTED") % {
                 "other_jid": self.peer.full()
             }
-            self.host.bridge.otrState(
-                OTR_STATE_UNENCRYPTED, self.peer.full(), client.profile
-            )
+            d = client.encryption.stop(self.peer, NS_OTR)
+            d.addCallback(self.stopCb, feedback=feedback)
+            d.addErrback(self.stopEb)
+            return
         elif state == potr.context.STATE_ENCRYPTED:
             client.encryption.start(self.peer, NS_OTR)
             try:
-                trusted = self.getCurrentTrust()
+                trusted = self.isTrusted()
             except TypeError:
                 trusted = False
             trusted_str = _(u"trusted") if trusted else _(u"untrusted")
@@ -155,13 +197,13 @@
                 OTR_STATE_ENCRYPTED, self.peer.full(), client.profile
             )
         elif state == potr.context.STATE_FINISHED:
-            client.encryption.stop(self.peer, NS_OTR)
             feedback = D_(u"OTR conversation with {other_jid} is FINISHED").format(
                 other_jid=self.peer.full()
             )
-            self.host.bridge.otrState(
-                OTR_STATE_UNENCRYPTED, self.peer.full(), client.profile
-            )
+            d = client.encryption.stop(self.peer, NS_OTR)
+            d.addCallback(self.stopCb, feedback=feedback)
+            d.addErrback(self.stopEb)
+            return
         else:
             log.error(D_(u"Unknown OTR state"))
             return
@@ -240,15 +282,19 @@
 
 
 class ContextManager(object):
-    def __init__(self, host, client):
-        self.host = host
-        self.account = Account(host, client)
+    def __init__(self, parent, client):
+        self.parent = parent
+        self.account = Account(parent.host, client)
         self.contexts = {}
 
+    @property
+    def host(self):
+        return self.parent.host
+
     def startContext(self, other_jid):
         assert isinstance(other_jid, jid.JID)
         context = self.contexts.setdefault(
-            other_jid, Context(self.host, self.account, other_jid)
+            other_jid, Context(self, other_jid)
         )
         return context
 
@@ -323,7 +369,7 @@
     def profileConnected(self, client):
         if client.profile in self.skipped_profiles:
             return
-        ctxMng = client._otr_context_manager = ContextManager(self.host, client)
+        ctxMng = client._otr_context_manager = ContextManager(self, client)
         client._otr_data = persistent.PersistentBinaryDict(NS_OTR, client.profile)
         yield client._otr_data.load()
         encrypted_priv_key = client._otr_data.get(PRIVATE_KEY, None)
@@ -346,6 +392,110 @@
             context.disconnect()
         del client._otr_context_manager
 
+    # encryption plugin methods
+
+    def startEncryption(self, client, entity_jid):
+        self.startRefresh(client, entity_jid)
+
+    def stopEncryption(self, client, entity_jid):
+        self.endSession(client, entity_jid)
+
+    def getTrustUI(self, client, entity_jid):
+        if not entity_jid.resource:
+            entity_jid.resource = self.host.memory.getMainResource(
+                client, entity_jid
+            )  # FIXME: temporary and unsecure, must be changed when frontends
+               #        are refactored
+        ctxMng = client._otr_context_manager
+        otrctx = ctxMng.getContextForUser(entity_jid)
+        priv_key = ctxMng.account.privkey
+
+        if priv_key is None:
+            # we have no private key yet
+            dialog = xml_tools.XMLUI(
+                C.XMLUI_DIALOG,
+                dialog_opt={
+                    C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE,
+                    C.XMLUI_DATA_MESS: _(
+                        u"You have no private key yet, start an OTR conversation to "
+                        u"have one"
+                    ),
+                    C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_WARNING,
+                },
+                title=_(u"No private key"),
+            )
+            return dialog
+
+        other_fingerprint = otrctx.getCurrentKey()
+
+        if other_fingerprint is None:
+            # we have a private key, but not the fingerprint of our correspondent
+            dialog = xml_tools.XMLUI(
+                C.XMLUI_DIALOG,
+                dialog_opt={
+                    C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE,
+                    C.XMLUI_DATA_MESS: _(
+                        u"Your fingerprint is:\n{fingerprint}\n\n"
+                        u"Start an OTR conversation to have your correspondent one."
+                    ).format(fingerprint=priv_key),
+                    C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_INFO,
+                },
+                title=_(u"Fingerprint"),
+            )
+            return dialog
+
+        def setTrust(raw_data, profile):
+            if xml_tools.isXMLUICancelled(raw_data):
+                return {}
+            # This method is called when authentication form is submited
+            data = xml_tools.XMLUIResult2DataFormResult(raw_data)
+            if data["match"] == "yes":
+                otrctx.setCurrentTrust(OTR_STATE_TRUSTED)
+                note_msg = _(u"Your correspondent {correspondent} is now TRUSTED")
+                self.host.bridge.otrState(
+                    OTR_STATE_TRUSTED, entity_jid.full(), client.profile
+                )
+            else:
+                otrctx.setCurrentTrust("")
+                note_msg = _(u"Your correspondent {correspondent} is now UNTRUSTED")
+                self.host.bridge.otrState(
+                    OTR_STATE_UNTRUSTED, entity_jid.full(), client.profile
+                )
+            note = xml_tools.XMLUI(
+                C.XMLUI_DIALOG,
+                dialog_opt={
+                    C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE,
+                    C.XMLUI_DATA_MESS: note_msg.format(correspondent=otrctx.peer),
+                },
+            )
+            return {"xmlui": note.toXml()}
+
+        submit_id = self.host.registerCallback(setTrust, with_data=True, one_shot=True)
+        trusted = otrctx.isTrusted()
+
+        xmlui = xml_tools.XMLUI(
+            C.XMLUI_FORM,
+            title=_(u"Authentication ({entity_jid})").format(entity_jid=entity_jid.full()),
+            submit_id=submit_id,
+        )
+        xmlui.addText(_(AUTH_TXT))
+        xmlui.addDivider()
+        xmlui.addText(
+            D_(u"Your own fingerprint is:\n{fingerprint}").format(fingerprint=priv_key)
+        )
+        xmlui.addText(
+            D_(u"Your correspondent fingerprint should be:\n{fingerprint}").format(
+                fingerprint=other_fingerprint
+            )
+        )
+        xmlui.addDivider("blank")
+        xmlui.changeContainer("pairs")
+        xmlui.addLabel(D_(u"Is your correspondent fingerprint the same as here ?"))
+        xmlui.addList(
+            "match", [("yes", _("yes")), ("no", _("no"))], ["yes" if trusted else "no"]
+        )
+        return xmlui
+
     def _otrStartRefresh(self, menu_data, profile):
         """Start or refresh an OTR session
 
@@ -422,97 +572,7 @@
 
     def authenticate(self, client, to_jid):
         """Authenticate other user and see our own fingerprint"""
-        if not to_jid.resource:
-            to_jid.resource = self.host.memory.getMainResource(
-                client, to_jid
-            )  # FIXME: temporary and unsecure, must be changed when frontends
-               #        are refactored
-        ctxMng = client._otr_context_manager
-        otrctx = ctxMng.getContextForUser(to_jid)
-        priv_key = ctxMng.account.privkey
-
-        if priv_key is None:
-            # we have no private key yet
-            dialog = xml_tools.XMLUI(
-                C.XMLUI_DIALOG,
-                dialog_opt={
-                    C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE,
-                    C.XMLUI_DATA_MESS: _(
-                        u"You have no private key yet, start an OTR conversation to "
-                        u"have one"
-                    ),
-                    C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_WARNING,
-                },
-                title=_(u"No private key"),
-            )
-            return {"xmlui": dialog.toXml()}
-
-        other_fingerprint = otrctx.getCurrentKey()
-
-        if other_fingerprint is None:
-            # we have a private key, but not the fingerprint of our correspondent
-            dialog = xml_tools.XMLUI(
-                C.XMLUI_DIALOG,
-                dialog_opt={
-                    C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_MESSAGE,
-                    C.XMLUI_DATA_MESS: _(
-                        u"Your fingerprint is:\n{fingerprint}\n\n"
-                        u"Start an OTR conversation to have your correspondent one."
-                    ).format(fingerprint=priv_key),
-                    C.XMLUI_DATA_LVL: C.XMLUI_DATA_LVL_INFO,
-                },
-                title=_(u"Fingerprint"),
-            )
-            return {"xmlui": dialog.toXml()}
-
-        def setTrust(raw_data, profile):
-            # This method is called when authentication form is submited
-            data = xml_tools.XMLUIResult2DataFormResult(raw_data)
-            if data["match"] == "yes":
-                otrctx.setCurrentTrust(OTR_STATE_TRUSTED)
-                note_msg = _(u"Your correspondent {correspondent} is now TRUSTED")
-                self.host.bridge.otrState(
-                    OTR_STATE_TRUSTED, to_jid.full(), client.profile
-                )
-            else:
-                otrctx.setCurrentTrust("")
-                note_msg = _(u"Your correspondent {correspondent} is now UNTRUSTED")
-                self.host.bridge.otrState(
-                    OTR_STATE_UNTRUSTED, to_jid.full(), client.profile
-                )
-            note = xml_tools.XMLUI(
-                C.XMLUI_DIALOG,
-                dialog_opt={
-                    C.XMLUI_DATA_TYPE: C.XMLUI_DIALOG_NOTE,
-                    C.XMLUI_DATA_MESS: note_msg.format(correspondent=otrctx.peer),
-                },
-            )
-            return {"xmlui": note.toXml()}
-
-        submit_id = self.host.registerCallback(setTrust, with_data=True, one_shot=True)
-        trusted = bool(otrctx.getCurrentTrust())
-
-        xmlui = xml_tools.XMLUI(
-            C.XMLUI_FORM,
-            title=_("Authentication (%s)") % to_jid.full(),
-            submit_id=submit_id,
-        )
-        xmlui.addText(_(AUTH_TXT))
-        xmlui.addDivider()
-        xmlui.addText(
-            D_(u"Your own fingerprint is:\n{fingerprint}").format(fingerprint=priv_key)
-        )
-        xmlui.addText(
-            D_(u"Your correspondent fingerprint should be:\n{fingerprint}").format(
-                fingerprint=other_fingerprint
-            )
-        )
-        xmlui.addDivider("blank")
-        xmlui.changeContainer("pairs")
-        xmlui.addLabel(D_(u"Is your correspondent fingerprint the same as here ?"))
-        xmlui.addList(
-            "match", [("yes", _("yes")), ("no", _("no"))], ["yes" if trusted else "no"]
-        )
+        xmlui = self.getTrustUI(client, to_jid)
         return {"xmlui": xmlui.toXml()}
 
     def _dropPrivKey(self, menu_data, profile):
@@ -601,6 +661,12 @@
             feedback = msg
             client.feedback(from_jid, msg)
             raise failure.Failure(exceptions.CancelError(msg))
+        except potr.crypt.InvalidParameterError as e:
+            msg = D_(u"Error while trying de decrypt OTR message: {msg}".format(msg=e))
+            log.warning(msg)
+            feedback = msg
+            client.feedback(from_jid, msg)
+            raise failure.Failure(exceptions.CancelError(msg))
         except StopIteration:
             return data
         else:
@@ -629,6 +695,13 @@
                     exceptions.CancelError("Cancelled by OTR")
                 )  # no message at all (no history, no signal)
         client.encryption.markAsEncrypted(data)
+        trusted = otrctx.isTrusted()
+
+        if trusted:
+            client.encryption.markAsTrusted(data)
+        else:
+            client.encryption.markAsUntrusted(data)
+
         return data
 
     def _receivedTreatmentForSkippedProfiles(self, data):
--- a/sat_frontends/bridge/dbus_bridge.py	Thu Jan 03 20:51:08 2019 +0100
+++ b/sat_frontends/bridge/dbus_bridge.py	Thu Jan 03 21:00:00 2019 +0100
@@ -562,12 +562,7 @@
             if errback is None:
                 errback = log.error
             error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return self.db_core_iface.messageEncryptionStart(to_jid, namespace, replace, profile_key, **kwargs)
+        return self.db_core_iface.messageEncryptionStart(to_jid, namespace, replace, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
 
     def messageEncryptionStop(self, to_jid, profile_key, callback=None, errback=None):
         if callback is None:
@@ -576,12 +571,7 @@
             if errback is None:
                 errback = log.error
             error_handler = lambda err:errback(dbus_to_bridge_exception(err))
-        kwargs={}
-        if callback is not None:
-            kwargs['timeout'] = const_TIMEOUT
-            kwargs['reply_handler'] = callback
-            kwargs['error_handler'] = error_handler
-        return self.db_core_iface.messageEncryptionStop(to_jid, profile_key, **kwargs)
+        return self.db_core_iface.messageEncryptionStop(to_jid, profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
 
     def messageSend(self, to_jid, message, subject={}, mess_type="auto", extra={}, profile_key="@NONE@", callback=None, errback=None):
         if callback is None: