Преглед на файлове

updates #497

Initial work on adding OMEMO support
JC Brand преди 7 години
родител
ревизия
7b28cb7943
променени са 5 файла, в които са добавени 214 реда и са изтрити 1 реда
  1. 1 0
      .gitignore
  2. 1 0
      dev.html
  3. 0 1
      src/converse-disco.js
  4. 211 0
      src/converse-omemo.js
  5. 1 0
      src/templates/toolbar_omemo.html

+ 1 - 0
.gitignore

@@ -12,6 +12,7 @@
 .idea
 .su?
 builds/*
+3rdparty/libsignal-protocol-javascript/
 
 analytics.js
 inverse-analytics.js

+ 1 - 0
dev.html

@@ -10,6 +10,7 @@
     <meta name="keywords" content="xmpp chat webchat converse.js" />
     <link rel="shortcut icon" type="image/ico" href="css/images/favicon.ico"/>
     <link type="text/css" rel="stylesheet" media="screen" href="css/inverse.css" />
+    <script src="3rdparty/libsignal-protocol-javascript/dist/libsignal-protocol.js"/>
     <script src="node_modules/requirejs/require.js"></script>
     <script src="src/config.js"></script>
 </head>

+ 0 - 1
src/converse-disco.js

@@ -6,7 +6,6 @@
 
 /* This is a Converse.js plugin which add support for XEP-0030: Service Discovery */
 
-/*global Backbone, define, window */
 (function (root, factory) {
     define(["converse-core", "sizzle"], factory);
 }(this, function (converse, sizzle) {

+ 211 - 0
src/converse-omemo.js

@@ -0,0 +1,211 @@
+// Converse.js
+// http://conversejs.org
+//
+// Copyright (c) 2013-2018, the Converse.js developers
+// Licensed under the Mozilla Public License (MPLv2)
+
+/*global libsignal */
+(function (root, factory) {
+    define([
+        "converse-core",
+        "tpl!toolbar_omemo"
+    ], factory);
+}(this, function (converse, tpl_toolbar_omemo) {
+
+    const { Backbone, Promise, Strophe, sizzle, $build, _, b64_sha1 } = converse.env;
+
+    Strophe.addNamespace('OMEMO', "eu.siacs.conversations.axolotl");
+    Strophe.addNamespace('OMEMO_DEVICELIST', Strophe.NS.OMEMO+".devicelist");
+    Strophe.addNamespace('OMEMO_VERIFICATION', Strophe.NS.OMEMO+".verification");
+    Strophe.addNamespace('OMEMO_WHITELISTED', Strophe.NS.OMEMO+".whitelisted");
+
+    const UNDECIDED = 0;
+    const TRUSTED = 1;
+    const UNTRUSTED = -1;
+
+    converse.plugins.add('converse-omemo', {
+
+        enabled (_converse) {
+            return !_.isNil(window.libsignal);
+        },
+
+        overrides: {
+            ChatBoxView:  {
+
+                addOMEMOToolbarButton (options) {
+                    const { _converse } = this.__super__,
+                          { __ } = _converse,
+                          data = this.model.toJSON();
+                    this.el.querySelector('.chat-toolbar').insertAdjacentHTML(
+                        'beforeend',
+                        tpl_toolbar_omemo({'__': __}));
+                },
+
+                renderToolbar (toolbar, options) {
+                    const result = this.__super__.renderToolbar.apply(this, arguments);
+                    this.addOMEMOToolbarButton(options);
+                    return result;
+                }
+            }
+        },
+
+        initialize () {
+            /* The initialize function gets called as soon as the plugin is
+             * loaded by Converse.js's plugin machinery.
+             */
+            const { _converse } = this;
+
+            _converse.OMEMOSession = Backbone.Model.extend({
+
+                initialize () {
+                    this.keyhelper = libsignal.KeyHelper;
+                },
+
+                fetchSession () {
+                    return new Promise((resolve, reject) => {
+                        this.fetch({
+                            'success': () => {
+                                if (!_converse.omemo_session.get('registration_id')) {
+                                    this.keyhelper.generateIdentityKeyPair().then(function (keypair) {
+                                        _converse.omemo_session.set({
+                                            'registration_id': this.keyhelper.generateRegistrationId(),
+                                            'pub_key': keypair.pubKey,
+                                            'priv_key': keypair.privKey
+                                        });
+                                        resolve();
+                                    });
+                                } else {
+                                    resolve();
+                                }
+                            }
+                        });
+                    });
+                }
+            });
+
+            _converse.Device = Backbone.Model.extend({
+                defaults: {
+                    'active': true,
+                    'trusted': UNDECIDED
+                }
+            });
+
+            _converse.Devices = Backbone.Collection.extend({
+                model: _converse.Device,
+            });
+
+            _converse.DeviceList = Backbone.Model.extend({
+                idAttribute: 'jid',
+
+                initialize () {
+                    this.devices = new _converse.Devices();
+                },
+
+                fetchDevices () {
+                    return new Promise((resolve, reject) => {
+                        this.devices.fetch({
+                            success (collection) {
+                                if (collection.length === 0) {
+                                    this.fetchDevicesFromServer().then(resolve).catch(reject);
+                                } else {
+                                    resolve();
+                                }
+                            }
+                        });
+                    });
+                },
+
+                fetchDevicesFromServer () {
+                    // TODO: send IQ stanza to get device list.
+                    return Promise.resolve([]);
+                }
+
+            });
+
+            _converse.DeviceLists = Backbone.Collection.extend({
+                model: _converse.DeviceList,
+            });
+
+
+            function publishBundle () {
+                // TODO: publish bundle information (public key and pre keys)
+                // Keep the used device id consistant. You have to republish
+                // this because you don't know if the server was restarted or might have
+                // otherwise lost the information.
+                return Promise.resolve();
+            }
+
+            function fetchDeviceLists () {
+                return new Promise((resolve, reject) => _converse.devicelists.fetch({'success': resolve}));
+            }
+
+            function updateOwnDeviceList () {
+                /* If our own device is not on the list, add it.
+                 * Also, deduplicate devices if necessary.
+                 */
+                // TODO:
+                const devicelist = _converse.devicelists.get(_converse.bare_jid);
+            }
+
+            function updateDevicesFromStanza (stanza) {
+                const device_ids = _.map(
+                    sizzle(`items[node="${Strophe.NS.OMEMO_DEVICELIST}"] item[xmlns="${Strophe.NS.OMEMO}"] device`, stanza),
+                    (device) => device.getAttribute('id'));
+
+                const removed_ids = _.difference(_converse.devices.pluck('id'), device_ids);
+                _.forEach(removed_ids, (removed_id) => _converse.devices.get(removed_id).set('active', false));
+
+                _.forEach(device_ids, (device_id) => {
+                    const dev = _converse.devices.get(device_id);
+                    if (dev) {
+                        dev.save({'active': true});
+                    } else {
+                        _converse.devices.create({'id': device_id})
+                    }
+                });
+            }
+
+            function registerPEPPushHandler () {
+                // Add a handler for devices pushed from other connected clients
+                _converse.connection.addHandler((message) => {
+                    if (message.querySelector('event[xmlns="'+Strophe.NS.PUBSUB+'#event"]')) {
+                        _converse.bookmarks.updateDevicesFromStanza(message);
+                    }
+                }, null, 'message', 'headline', null, _converse.bare_jid);
+            }
+
+            function initOMEMO () {
+                /* Publish our bundle and then fetch our own device list.
+                 * If our device list does not contain this device's id, publish the
+                 * device list with the id added. Also deduplicate device ids in the list.
+                 */
+                publishBundle()
+                    .then(() => fetchDeviceLists())
+                    .then(() => _converse.devicelists.get(_converse.bare_jid).fetchDevices())
+                    .then(updateOwnDeviceList)
+                    .catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
+            }
+
+            function onStatusInitialized () {
+                _converse.devicelists = new _converse.DeviceLists();
+                _converse.devicelists.browserStorage = new Backbone.BrowserStorage.session(
+                    b64_sha1(`converse.devicelists-${_converse.bare_jid}`)
+                );
+
+                _converse.omemo_session = new Backbone.Model();
+                _converse.omemo_session.browserStorage =  new Backbone.BrowserStorage.session(
+                    b64_sha1(`converse.omemosession-${_converse.bare_jid}`)
+                );
+                _converse.omemo_session.fetchSession()
+                    .then(initOMEMO)
+                    .catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
+            }
+
+            _converse.api.listen.on('statusInitialized', onStatusInitialized);
+            _converse.api.listen.on('connected', registerPEPPushHandler);
+            _converse.api.listen.on('afterTearDown', () => _converse.devices.reset());
+            _converse.api.listen.on('addClientFeatures',
+                () => _converse.api.disco.own.features.add(Strophe.NS.OMEMO_DEVICELIST+"notify"));
+        }
+    });
+}));

+ 1 - 0
src/templates/toolbar_omemo.html

@@ -0,0 +1 @@
+<li class="toggle-omemo fa fa-unlock" title="{{{__('Messages are being sent in plaintext')}}}"></li>