changeset 2658:4e130cc9bfc0

core (memore/encryption): new methods and checks: Following methods are now available though bridge: - messageEncryptionStop - messageEncryptionGet: retrieve encryption data for a message session - encryptionPluginsGet: retrieve all registered encryption plugin Following methods are available for internal use: - getPlugins: retrieve registerd plugins - getNSFromName: retrieve namespace from plugin name - getBridgeData: serialise session data (to be used with bridge) - markAsEncrypted: mark message data as encrypted (to be set by encryption plugin in MessageReceived trigger) Behaviours improvments: - start and stop send messageEncryptionStarted and messageEncryptionStopped signals, and a message feedback - new "replace" arguments in start allows to replace a plugin if one is already running (instead of raising a ConflictError) - plugins declare themselves as "directed" (i.e. working with only one device at a time) or not. This is checked while dealing with jids, an exception is raised when a full jid is received for a non directed encryption. - use of new data_format (de)serialise
author Goffi <goffi@goffi.org>
date Sat, 11 Aug 2018 18:24:55 +0200
parents 9190874a8ac5
children c26492bd2144
files sat/bridge/bridge_constructor/bridge_template.ini sat/bridge/dbus_bridge.py sat/core/sat_main.py sat/core/xmpp.py sat/memory/encryption.py sat_frontends/bridge/dbus_bridge.py sat_frontends/bridge/pb.py
diffstat 7 files changed, 311 insertions(+), 55 deletions(-) [+]
line wrap: on
line diff
--- a/sat/bridge/bridge_constructor/bridge_template.ini	Sat Aug 11 18:24:55 2018 +0200
+++ b/sat/bridge/bridge_constructor/bridge_template.ini	Sat Aug 11 18:24:55 2018 +0200
@@ -438,14 +438,48 @@
 [messageEncryptionStart]
 type=method
 category=core
-sig_in=sss
+sig_in=ssbs
 sig_out=
 param_1_default=''
-param_2_default="@NONE@"
+param_2_default=False
+param_3_default="@NONE@"
+doc=Start an encryption session
+doc_param_0=to_jid: JID of the recipient (bare jid if it must be encrypted for all devices)
+doc_param_1=encryption_ns: Namespace of the encryption algorithm to use
+doc_param_2=replace: If True and an encryption session already exists, it will be replaced by this one
+    else a ConflictError will be raised
+doc_param_3=%(doc_profile_key)s
+
+[messageEncryptionStop]
+type=method
+category=core
+sig_in=ss
+sig_out=
 doc=Start an encryption session
-doc_param_0=to_jid: JID of the recipient (bare_jid if it must be encoded for all devices)
-doc_param_1=encryption_ns: Namespace of the encryption algorithm to use
-doc_param_2=%(doc_profile_key)s
+doc_param_0=to_jid: JID of the recipient (full jid if encryption must be stopped for one device only)
+doc_param_1=%(doc_profile_key)s
+
+[messageEncryptionGet]
+type=method
+category=core
+sig_in=ss
+sig_out=s
+doc=Retrieve encryption data for a given entity
+doc_param_0=to_jid: bare JID of the recipient
+doc_param_1=%(doc_profile_key)s
+doc_return=[JSON_OBJ] empty string if session is unencrypted, else a json encoded objects.
+    In case of dict, following keys are always present:
+        - name: human readable name of the encryption algorithm
+        - namespace: namespace of the plugin
+    following key can be present if suitable:
+        - directed_devices: list or resource where session is encrypted
+
+[encryptionPluginsGet]
+type=method
+category=core
+sig_in=
+sig_out=aa{ss}
+doc=Retrieve registered plugins for encryption
 
 [setPresence]
 type=method
--- a/sat/bridge/dbus_bridge.py	Sat Aug 11 18:24:55 2018 +0200
+++ b/sat/bridge/dbus_bridge.py	Sat Aug 11 18:24:55 2018 +0200
@@ -272,6 +272,12 @@
         return self._callback("disconnect", unicode(profile_key), callback=callback, errback=errback)
 
     @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
+                         in_signature='', out_signature='aa{ss}',
+                         async_callbacks=None)
+    def encryptionPluginsGet(self, ):
+        return self._callback("encryptionPluginsGet", )
+
+    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
                          in_signature='ss', out_signature='s',
                          async_callbacks=None)
     def getConfig(self, section, name):
@@ -398,10 +404,22 @@
         return self._callback("menusGet", unicode(language), security_limit)
 
     @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
-                         in_signature='sss', out_signature='',
+                         in_signature='ss', out_signature='s',
+                         async_callbacks=None)
+    def messageEncryptionGet(self, to_jid, profile_key):
+        return self._callback("messageEncryptionGet", unicode(to_jid), unicode(profile_key))
+
+    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
+                         in_signature='ssbs', out_signature='',
                          async_callbacks=None)
-    def messageEncryptionStart(self, to_jid, encryption_ns='', profile_key="@NONE@"):
-        return self._callback("messageEncryptionStart", unicode(to_jid), unicode(encryption_ns), unicode(profile_key))
+    def messageEncryptionStart(self, to_jid, encryption_ns='', replace=False, profile_key="@NONE@"):
+        return self._callback("messageEncryptionStart", unicode(to_jid), unicode(encryption_ns), replace, unicode(profile_key))
+
+    @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))
 
     @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
                          in_signature='sa{ss}a{ss}sa{ss}s', out_signature='',
--- a/sat/core/sat_main.py	Sat Aug 11 18:24:55 2018 +0200
+++ b/sat/core/sat_main.py	Sat Aug 11 18:24:55 2018 +0200
@@ -113,6 +113,11 @@
         self.bridge.register_method("messageSend", self._messageSend)
         self.bridge.register_method("messageEncryptionStart",
                                     self._messageEncryptionStart)
+        self.bridge.register_method("messageEncryptionStop",
+                                    self._messageEncryptionStop)
+        self.bridge.register_method("messageEncryptionGet",
+                                    self._messageEncryptionGet)
+        self.bridge.register_method("encryptionPluginsGet", self._encryptionPluginsGet)
         self.bridge.register_method("getConfig", self._getConfig)
         self.bridge.register_method("setParam", self.setParam)
         self.bridge.register_method("getParamA", self.memory.getStringParamA)
@@ -657,6 +662,17 @@
     def registerEncryptionPlugin(self, *args, **kwargs):
         return encryption.EncryptionHandler.registerPlugin(*args, **kwargs)
 
+    def _encryptionPluginsGet(self):
+        plugins = encryption.EncryptionHandler.getPlugins()
+        ret = []
+        for p in plugins:
+            ret.append({
+                u"name": p.name,
+                u"namespace": p.namespace,
+                u"priority": unicode(p.priority),
+                })
+        return ret
+
     ## XMPP methods ##
 
     def _messageSend(self, to_jid_s, message, subject=None, mess_type="auto", extra=None,
@@ -673,18 +689,28 @@
             {unicode(key): unicode(value) for key, value in extra.items()},
         )
 
-    def _messageEncryptionStart(self, to_jid_s, encryption_ns,
+    def _messageEncryptionStart(self, to_jid_s, encryption_ns, replace=False,
                                 profile_key=C.PROF_KEY_NONE):
         client = self.getClient(profile_key)
         to_jid = jid.JID(to_jid_s)
-        return client.encryption.start(to_jid, encryption_ns.strip() or None)
+        return client.encryption.start(to_jid, encryption_ns.strip() or None, replace)
+
+    def _messageEncryptionStop(self, to_jid_s, profile_key=C.PROF_KEY_NONE):
+        client = self.getClient(profile_key)
+        to_jid = jid.JID(to_jid_s)
+        return client.encryption.stop(to_jid)
+
+    def _messageEncryptionGet(self, to_jid_s, profile_key=C.PROF_KEY_NONE):
+        client = self.getClient(profile_key)
+        to_jid = jid.JID(to_jid_s)
+        session_data = client.encryption.getSession(to_jid)
+        return client.encryption.getBridgeData(session_data)
 
     def _setPresence(self, to="", show="", statuses=None, profile_key=C.PROF_KEY_NONE):
         return self.setPresence(jid.JID(to) if to else None, show, statuses, profile_key)
 
-    def setPresence(
-        self, to_jid=None, show="", statuses=None, profile_key=C.PROF_KEY_NONE
-    ):
+    def setPresence(self, to_jid=None, show="", statuses=None,
+                    profile_key=C.PROF_KEY_NONE):
         """Send our presence information"""
         if statuses is None:
             statuses = {}
--- a/sat/core/xmpp.py	Sat Aug 11 18:24:55 2018 +0200
+++ b/sat/core/xmpp.py	Sat Aug 11 18:24:55 2018 +0200
@@ -61,7 +61,7 @@
         self._progress_cb = {}  # callback called when a progress is requested
                                 # (key = progress id)
         self.actions = {}  # used to keep track of actions for retrieval (key = action_id)
-        self.encryption = encryption.EncryptionHandler(host_app)
+        self.encryption = encryption.EncryptionHandler(self)
 
     ## initialisation ##
 
@@ -919,6 +919,8 @@
             data["extra"]["delay_sender"] = data["delay_sender"]
         except KeyError:
             pass
+        if C.MESS_KEY_ENCRYPTION in data:
+            data[u"extra"][u"encrypted"] = C.BOOL_TRUE
         if data is not None:
             self.host.bridge.messageNew(
                 data["uid"],
--- a/sat/memory/encryption.py	Sat Aug 11 18:24:55 2018 +0200
+++ b/sat/memory/encryption.py	Sat Aug 11 18:24:55 2018 +0200
@@ -17,69 +17,120 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-from sat.core.i18n import _
+from sat.core.i18n import D_, _
 from sat.core.constants import Const as C
 from sat.core import exceptions
 from collections import namedtuple
 from sat.core.log import getLogger
 log = getLogger(__name__)
+from sat.tools.common import data_format
 
 
 EncryptionPlugin = namedtuple("EncryptionPlugin", ("instance",
                                                    "name",
                                                    "namespace",
-                                                   "priority"))
+                                                   "priority",
+                                                   "directed"))
 
 
 class EncryptionHandler(object):
     """Class to handle encryption sessions for a client"""
     plugins = []  # plugin able to encrypt messages
 
-    def __init__(self, host):
+    def __init__(self, client):
+        self.client = client
         self._sessions = {}  # bare_jid ==> encryption_data
 
+    @property
+    def host(self):
+        return self.client.host_app
+
     @classmethod
-    def registerPlugin(cls, plg_instance, name, namespace, priority=0):
+    def registerPlugin(cls, plg_instance, name, namespace, priority=0, directed=False):
         """Register a plugin handling an encryption algorithm
 
         @param plg_instance(object): instance of the plugin
             it must have the following methods:
                 - startEncryption(jid.JID): start an encryption session with a bare jid
                 - stopEncryption(jid.JID): stop an encryption session with a bare jid
-        @param name(unicode): human readable name of the encryption alrgorithm
+        @param name(unicode): human readable name of the encryption algorithm
         @param namespace(unicode): namespace of the encryption algorithm
         @param priority(int): priority of this plugin to encrypt an message when not
             selected manually
+        @param directed(bool): True if this plugin is directed (if it works with one
+                               device only at a time)
         """
-        existing_ns = [p.namespace for p in cls.plugins]
-        if namespace in existing_ns:
+        existing_ns = set()
+        existing_names = set()
+        for p in cls.plugins:
+            existing_ns.add(p.namespace.lower())
+            existing_names.add(p.name.lower())
+        if namespace.lower() in existing_ns:
             raise exceptions.ConflictError("A plugin with this namespace already exists!")
-        plg = EncryptionPlugin(
+        if name.lower() in existing_names:
+            raise exceptions.ConflictError("A plugin with this name already exists!")
+        plugin = EncryptionPlugin(
             instance=plg_instance,
             name=name,
             namespace=namespace,
-            priority=priority)
-        cls.plugins.append(plg)
+            priority=priority,
+            directed=directed)
+        cls.plugins.append(plugin)
         cls.plugins.sort(key=lambda p: p.priority)
         log.info(_(u"Encryption plugin registered: {name}").format(name=name))
 
-    def start(self, entity, namespace=None):
-        """Start an encrypted session with an entity
+    @classmethod
+    def getPlugins(cls):
+        return cls.plugins
+
+    @classmethod
+    def getNSFromName(cls, name):
+        """Retrieve plugin namespace from its name
+
+        @param name(unicode): name of the plugin (case insensitive)
+        @return (unicode): namespace of the plugin
+        @raise exceptions.NotFound: there is not encryption plugin of this name
+        """
+        for p in cls.plugins:
+            if p.name.lower() == name.lower():
+                return p.namespace
+        raise exceptions.NotFound
 
-        @param entity(jid.JID): entity to start an encrypted session with
+    def getBridgeData(self, session):
+        """Retrieve session data serialized for bridge.
+
+        @param session(dict): encryption session
+        @return (unicode): serialized data for bridge
+        """
+        if session is None:
+            return u''
+        plugin = session[u'plugin']
+        bridge_data = {'name': plugin.name,
+                       'namespace': plugin.namespace}
+        if u'directed_devices' in session:
+            bridge_data[u'directed_devices'] = session[u'directed_devices']
+
+        return data_format.serialise(bridge_data)
+
+    def start(self, entity, namespace=None, replace=False):
+        """Start an encryption session with an entity
+
+        @param entity(jid.JID): entity to start an encryption session with
             must be bare jid is the algorithm encrypt for all devices
         @param namespace(unicode, None): namespace of the encryption algorithm to use
             None to select automatically an algorithm
+        @param replace(bool): if True and an encrypted session already exists,
+            it will be replaced by the new one
         """
         if not self.plugins:
             raise exceptions.NotFound(_(u"No encryption plugin is registered, "
                                         u"an encryption session can't be started"))
 
         if namespace is None:
-            plg = self.plugins[0]
+            plugin = self.plugins[0]
         else:
             try:
-                plg = next(p for p in self.plugins if p.namespace == namespace)
+                plugin = next(p for p in self.plugins if p.namespace == namespace)
             except StopIteration:
                 raise exceptions.NotFound(_(
                     u"Can't find requested encryption plugin: {namespace}").format(
@@ -87,44 +138,76 @@
 
         bare_jid = entity.userhostJID()
         if bare_jid in self._sessions:
-            plg = self._sessions[bare_jid]['plugin']
-            if plg.namespace == namespace:
-                log.info(_(u"Session with {bare_jid} is already encrypted with {name}."
-                     u"Nothing to do.")
-                   .format(bare_jid=bare_jid, name=plg.name))
+            # we have already an encryption session with this contact
+            former_plugin = self._sessions[bare_jid]['plugin']
+            if former_plugin.namespace == namespace:
+                log.info(_(u"Session with {bare_jid} is already encrypted with {name}. "
+                           u"Nothing to do.").format(bare_jid=bare_jid, name=plugin.name))
                 return
 
-            msg = (_(u"Session with {bare_jid} is already encrypted with {name}. "
-                     u"Please stop encryption session before changing algorithm.")
-                   .format(bare_jid=bare_jid, name=plg.name))
-            log.warning(msg)
-            raise exceptions.ConflictError(msg)
+            if replace:
+                # there is a conflict, but replacement is requested
+                # so we stop previous encryption to use new one
+                del self._sessions[bare_jid]
+            else:
+                msg = (_(u"Session with {bare_jid} is already encrypted with {name}. "
+                         u"Please stop encryption session before changing algorithm.")
+                       .format(bare_jid=bare_jid, name=plugin.name))
+                log.warning(msg)
+                raise exceptions.ConflictError(msg)
 
-        data = {"plugin": plg}
-        if entity.resource:
+        data = {"plugin": plugin}
+        if plugin.directed:
+            if not entity.resource:
+                entity.resource = self.host.memory.getMainResource(self.client, entity)
+                if not entity.resource:
+                    raise exceptions.NotFound(
+                        _(u"No resource found for {destinee}, can't encrypt with {name}")
+                        .format(destinee=entity.full(), name=plugin.name))
+                log.info(_(u"No resource specified to encrypt with {name}, using "
+                           u"{destinee}.").format(destinee=entity.full(),
+                                                  name=plugin.name))
             # indicate that we encrypt only for some devices
-            data['directed_devices'] = [entity.resource]
+            directed_devices = data[u'directed_devices'] = [entity.resource]
+        elif entity.resource:
+            raise ValueError(_(u"{name} encryption must be used with bare jids."))
 
         self._sessions[entity.userhostJID()] = data
-        log.info(_(u"Encryption session has been set for {bare_jid} with "
+        log.info(_(u"Encryption session has been set for {entity_jid} with "
                    u"{encryption_name}").format(
-                   bare_jid=bare_jid.userhost(), encryption_name=plg.name))
+                   entity_jid=entity.full(), encryption_name=plugin.name))
+        self.host.bridge.messageEncryptionStarted(
+            entity.full(),
+            self.getBridgeData(data),
+            self.client.profile)
+        msg = D_(u"Encryption session started: your messages with {destinee} are "
+                 u"now end to end encrypted using {name} algorithm.").format(
+                 destinee=entity.full(), name=plugin.name)
+        directed_devices = data.get(u'directed_devices')
+        if directed_devices:
+            msg += u"\n" + D_(u"Message are encrypted only for {nb_devices} device(s): "
+                              u"{devices_list}.").format(
+                              nb_devices=len(directed_devices),
+                              devices_list = u', '.join(directed_devices))
+
+        self.client.feedback(bare_jid, msg)
 
     def stop(self, entity, namespace=None):
-        """Stop an encrypted session with an entity
+        """Stop an encryption session with an entity
 
-        @param entity(jid.JID): entity with who the encrypted session must be stopped
+        @param entity(jid.JID): entity with who the encryption session must be stopped
             must be bare jid is the algorithm encrypt for all devices
         @param namespace(unicode): namespace of the session to stop
             when specified, used to check we stop the right encryption session
         """
         session = self.getSession(entity.userhostJID())
         if not session:
-            raise exceptions.NotFound(_(u"There is no encrypted session with this "
+            raise exceptions.NotFound(_(u"There is no encryption session with this "
                                         u"entity."))
-        if namespace is not None and session[u'plugin'].namespace != namespace:
+        plugin = session['plugin']
+        if namespace is not None and plugin.namespace != namespace:
             raise exceptions.InternalError(_(
-                u"The encrypted session is not run with the expected plugin: encrypted "
+                u"The encryption session is not run with the expected plugin: encrypted "
                 u"with {current_name} and was expecting {expected_name}").format(
                 current_name=session[u'plugin'].namespace,
                 expected_name=namespace))
@@ -142,18 +225,33 @@
             except ValueError:
                 raise exceptions.NotFound(_(u"There is no directed session with this "
                                             u"entity."))
+            else:
+                if not directed_devices:
+                    del session[u'directed_devices']
         else:
             del self._sessions[entity]
 
-        log.info(_(u"Encrypted session stopped with entity {entity}").format(
+        log.info(_(u"encryption session stopped with entity {entity}").format(
             entity=entity.full()))
+        self.host.bridge.messageEncryptionStopped(
+            entity.full(),
+            {'name': plugin.name,
+             'namespace': plugin.namespace,
+            },
+            self.client.profile)
+        msg = D_(u"Encryption session finished: your messages with {destinee} are "
+                 u"NOT end to end encrypted anymore.\nYour server administrators or "
+                 u"{destinee} server administrators will be able to read them.").format(
+                 destinee=entity.full())
+
+        self.client.feedback(entity, msg)
 
     def getSession(self, entity):
         """Get encryption session for this contact
 
         @param entity(jid.JID): get the session for this entity
             must be a bare jid
-        @return (dict, None): encrypted session data
+        @return (dict, None): encryption session data
             None if there is not encryption for this session with this jid
         """
         if entity.resource:
@@ -173,3 +271,15 @@
         encryption = self._sessions.get(to_jid.userhostJID())
         if encryption is not None:
             mess_data[C.MESS_KEY_ENCRYPTION] = encryption
+
+    ## Misc ##
+
+    def markAsEncrypted(self, mess_data):
+        """Helper method to mark a message as having been e2e encrypted.
+
+        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['encrypted'] = True
+        return mess_data
--- a/sat_frontends/bridge/dbus_bridge.py	Sat Aug 11 18:24:55 2018 +0200
+++ b/sat_frontends/bridge/dbus_bridge.py	Sat Aug 11 18:24:55 2018 +0200
@@ -245,6 +245,20 @@
             error_handler = lambda err:errback(dbus_to_bridge_exception(err))
         return self.db_core_iface.disconnect(profile_key, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
 
+    def encryptionPluginsGet(self, callback=None, errback=None):
+        if callback is None:
+            error_handler = None
+        else:
+            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.encryptionPluginsGet(**kwargs)
+
     def getConfig(self, section, name, callback=None, errback=None):
         if callback is None:
             error_handler = None
@@ -504,7 +518,7 @@
             kwargs['error_handler'] = error_handler
         return self.db_core_iface.menusGet(language, security_limit, **kwargs)
 
-    def messageEncryptionStart(self, to_jid, encryption_ns='', profile_key="@NONE@", callback=None, errback=None):
+    def messageEncryptionGet(self, to_jid, profile_key, callback=None, errback=None):
         if callback is None:
             error_handler = None
         else:
@@ -516,7 +530,35 @@
             kwargs['timeout'] = const_TIMEOUT
             kwargs['reply_handler'] = callback
             kwargs['error_handler'] = error_handler
-        return self.db_core_iface.messageEncryptionStart(to_jid, encryption_ns, profile_key, **kwargs)
+        return unicode(self.db_core_iface.messageEncryptionGet(to_jid, profile_key, **kwargs))
+
+    def messageEncryptionStart(self, to_jid, encryption_ns='', replace=False, profile_key="@NONE@", callback=None, errback=None):
+        if callback is None:
+            error_handler = None
+        else:
+            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, encryption_ns, replace, profile_key, **kwargs)
+
+    def messageEncryptionStop(self, to_jid, profile_key, callback=None, errback=None):
+        if callback is None:
+            error_handler = None
+        else:
+            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)
 
     def messageSend(self, to_jid, message, subject={}, mess_type="auto", extra={}, profile_key="@NONE@", callback=None, errback=None):
         if callback is None:
--- a/sat_frontends/bridge/pb.py	Sat Aug 11 18:24:55 2018 +0200
+++ b/sat_frontends/bridge/pb.py	Sat Aug 11 18:24:55 2018 +0200
@@ -214,6 +214,14 @@
             errback = self._generic_errback
         d.addErrback(errback)
 
+    def encryptionPluginsGet(self, callback=None, errback=None):
+        d = self.root.callRemote("encryptionPluginsGet")
+        if callback is not None:
+            d.addCallback(callback)
+        if errback is None:
+            errback = self._generic_errback
+        d.addErrback(errback)
+
     def getConfig(self, section, name, callback=None, errback=None):
         d = self.root.callRemote("getConfig", section, name)
         if callback is not None:
@@ -382,8 +390,24 @@
             errback = self._generic_errback
         d.addErrback(errback)
 
-    def messageEncryptionStart(self, to_jid, encryption_ns='', profile_key="@NONE@", callback=None, errback=None):
-        d = self.root.callRemote("messageEncryptionStart", to_jid, encryption_ns, profile_key)
+    def messageEncryptionGet(self, to_jid, profile_key, callback=None, errback=None):
+        d = self.root.callRemote("messageEncryptionGet", to_jid, profile_key)
+        if callback is not None:
+            d.addCallback(callback)
+        if errback is None:
+            errback = self._generic_errback
+        d.addErrback(errback)
+
+    def messageEncryptionStart(self, to_jid, encryption_ns='', replace=False, profile_key="@NONE@", callback=None, errback=None):
+        d = self.root.callRemote("messageEncryptionStart", to_jid, encryption_ns, replace, profile_key)
+        if callback is not None:
+            d.addCallback(lambda dummy: callback())
+        if errback is None:
+            errback = self._generic_errback
+        d.addErrback(errback)
+
+    def messageEncryptionStop(self, to_jid, profile_key, callback=None, errback=None):
+        d = self.root.callRemote("messageEncryptionStop", to_jid, profile_key)
         if callback is not None:
             d.addCallback(lambda dummy: callback())
         if errback is None: