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

Add code to generate and publish our bundle and update the test

updates #497
JC Brand преди 7 години
родител
ревизия
09eb1731b5
променени са 5 файла, в които са добавени 168 реда и са изтрити 79 реда
  1. 64 24
      spec/omemo.js
  2. 1 0
      src/converse-chatview.js
  3. 93 48
      src/converse-omemo.js
  4. 9 7
      src/utils/core.js
  5. 1 0
      tests/runner.js

+ 64 - 24
spec/omemo.js

@@ -15,7 +15,25 @@
                 });
             },
             'generateRegistrationId': function () {
-                return 1234;
+                return '31415';
+            },
+            'generatePreKey': function (keyid) {
+                return Promise.resolve({
+                    'keyId': keyid,
+                    'keyPair': {
+                        'pubKey': 1234,
+                        'privKey': 4321
+                    }
+                });
+            },
+            'generateSignedPreKey': function (identity_keypair, keyid) {
+                return Promise.resolve({
+                    'keyId': keyid,
+                    'keyPair': {
+                        'pubKey': 1234,
+                        'privKey': 4321
+                    }
+                });
             }
         }
     };
@@ -35,15 +53,14 @@
                 null, ['rosterGroupsFetched'], {},
                 function (done, _converse) {
 
-            let devicelist_iq,
-                disco_info_iq;
+            let iq_stanza;
             test_utils.createContacts(_converse, 'current');
             const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
 
             test_utils.waitUntil(function () {
                 return _.filter(_converse.connection.IQ_stanzas, function (iq) {
                     const node = iq.nodeTree.querySelector('iq[to="dummy@localhost"] query[xmlns="http://jabber.org/protocol/disco#info"]');
-                    if (node) { disco_info_iq = iq; }
+                    if (node) { iq_stanza = iq.nodeTree; }
                     return node;
                 }).length > 0;
             }, 1000).then(function () {
@@ -52,32 +69,57 @@
                     'type': 'result',
                     'from': 'dummy@localhost',
                     'to': 'dummy@localhost/resource',
-                    'id': disco_info_iq.nodeTree.getAttribute('id'),
+                    'id': iq_stanza.getAttribute('id'),
                 }).c('query', {'xmlns': 'http://jabber.org/protocol/disco#info'})
                     .c('identity', {
                         'category': 'pubsub',
                         'type': 'pep'});
                 _converse.connection._dataRecv(test_utils.createRequest(stanza));
+
+                return test_utils.waitUntil(() => {
+                    return _.filter(_converse.connection.IQ_stanzas, function (iq) {
+                        const node = iq.nodeTree.querySelector('publish[node="eu.siacs.conversations.axolotl.bundles:31415"]');
+                        if (node) { iq_stanza = iq.nodeTree; }
+                        return node;
+                    }).length;
+                });
+            }).then(function () {
+                expect(iq_stanza.getAttributeNames().sort().join()).toBe(["from", "type", "xmlns", "id"].sort().join());
+                expect(iq_stanza.querySelector('prekeys').childNodes.length).toBe(100);
+
+                const signed_prekeys = iq_stanza.querySelectorAll('signedPreKeyPublic');
+                expect(signed_prekeys.length).toBe(1);
+                const signed_prekey = signed_prekeys[0];
+                expect(signed_prekey.getAttribute('signedPreKeyId')).toBe('0')
+                expect(iq_stanza.querySelectorAll('signedPreKeySignature').length).toBe(1);
+                expect(iq_stanza.querySelectorAll('identityKey').length).toBe(1);
+
+                const stanza = $iq({
+                    'from': _converse.bare_jid,
+                    'id': iq_stanza.getAttribute('id'),
+                    'to': _converse.bare_jid,
+                    'type': 'result'});
+                _converse.connection._dataRecv(test_utils.createRequest(stanza));
+
                 return test_utils.waitUntil(() => {
                     return _.filter(
                         _converse.connection.IQ_stanzas,
                         (iq) => {
                             const node = iq.nodeTree.querySelector('iq[to="'+_converse.bare_jid+'"] query[node="eu.siacs.conversations.axolotl.devicelist"]');
-                            if (node) {
-                                devicelist_iq = iq;
-                            }
+                            if (node) { iq_stanza = iq.nodeTree;}
                             return node;
                         }).length;
                 });
             }).then(function () {
-                expect(devicelist_iq.toLocaleString()).toBe(
-                    "<iq type='get' from='dummy@localhost' to='dummy@localhost' xmlns='jabber:client' id='"+devicelist_iq.nodeTree.getAttribute('id')+"'>"+
-                        "<query xmlns='http://jabber.org/protocol/disco#items' "+
-                               "node='eu.siacs.conversations.axolotl.devicelist'/>"+
-                    "</iq>");
+                expect(iq_stanza.outerHTML).toBe(
+                    '<iq type="get" from="dummy@localhost" to="dummy@localhost" xmlns="jabber:client" id="'+iq_stanza.getAttribute("id")+'">'+
+                        '<query xmlns="http://jabber.org/protocol/disco#items" '+
+                               'node="eu.siacs.conversations.axolotl.devicelist"/>'+
+                    '</iq>');
+
                 const stanza = $iq({
                     'from': contact_jid,
-                    'id': devicelist_iq.nodeTree.getAttribute('id'),
+                    'id': iq_stanza.getAttribute('id'),
                     'to': _converse.bare_jid,
                     'type': 'result',
                 }).c('query', {
@@ -97,20 +139,18 @@
                         _converse.connection.IQ_stanzas,
                         (iq) => {
                             const node = iq.nodeTree.querySelector('iq[to="'+contact_jid+'"] query[node="eu.siacs.conversations.axolotl.devicelist"]');
-                            if (node) {
-                                devicelist_iq = iq;
-                            }
+                            if (node) { iq_stanza = iq.nodeTree; }
                             return node;
                         }).length;});
             }).then(function () {
-                expect(devicelist_iq.toLocaleString()).toBe(
-                    "<iq type='get' from='dummy@localhost' to='"+contact_jid+"' xmlns='jabber:client' id='"+devicelist_iq.nodeTree.getAttribute('id')+"'>"+
-                        "<query xmlns='http://jabber.org/protocol/disco#items' "+
-                               "node='eu.siacs.conversations.axolotl.devicelist'/>"+
-                    "</iq>");
+                expect(iq_stanza.outerHTML).toBe(
+                    '<iq type="get" from="dummy@localhost" to="'+contact_jid+'" xmlns="jabber:client" id="'+iq_stanza.getAttribute("id")+'">'+
+                        '<query xmlns="http://jabber.org/protocol/disco#items" '+
+                               'node="eu.siacs.conversations.axolotl.devicelist"/>'+
+                    '</iq>');
                 const stanza = $iq({
                     'from': contact_jid,
-                    'id': devicelist_iq.nodeTree.getAttribute('id'),
+                    'id': iq_stanza.getAttribute('id'),
                     'to': _converse.bare_jid,
                     'type': 'result',
                 }).c('query', {
@@ -139,7 +179,7 @@
                 toolbar.querySelector('.toggle-omemo').click();
                 expect(view.toggleOMEMO).toHaveBeenCalled();
                 done();
-            });
+            }).catch(_.partial(console.error, _));
         }));
     });
 

+ 1 - 0
src/converse-chatview.js

@@ -387,6 +387,7 @@
                     this.addSpoilerButton(options);
                     this.addFileUploadButton();
                     this.insertEmojiPicker();
+                    _converse.emit('renderToolbar', this);
                     return this;
                 },
 

+ 93 - 48
src/converse-omemo.js

@@ -4,6 +4,8 @@
 // Copyright (c) 2013-2018, the Converse.js developers
 // Licensed under the Mozilla Public License (MPLv2)
 
+/* global libsignal */
+
 (function (root, factory) {
     define([
         "converse-core",
@@ -12,11 +14,13 @@
 }(this, function (converse, tpl_toolbar_omemo) {
 
     const { Backbone, Promise, Strophe, sizzle, $iq, _, b64_sha1 } = converse.env;
+    const u = converse.env.utils;
 
     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");
+    Strophe.addNamespace('OMEMO_BUNDLES', Strophe.NS.OMEMO+".bundles");
 
     const UNDECIDED = 0;
     const TRUSTED = 1;
@@ -37,6 +41,7 @@
     }
 
     function contactHasOMEMOSupport (_converse, jid) {
+        /* Checks whether the contact advertises any OMEMO-compatible devices. */
         return new Promise((resolve, reject) => {
             getDevicesForContact(_converse, jid).then((devices) => {
                 resolve(devices.length > 0)
@@ -57,10 +62,13 @@
             return !_.isNil(window.libsignal);
         },
 
+        dependencies: ["converse-chatview"],
+
         overrides: {
+
             ChatBoxView:  {
                 events: {
-                    'click .toggle-omemo': 'toggleOMEMO',
+                    'click .toggle-omemo': 'toggleOMEMO'
                 },
 
                 toggleOMEMO (ev) {
@@ -68,7 +76,7 @@
                     ev.preventDefault();
                 },
 
-                addOMEMOToolbarButton (options) {
+                addOMEMOToolbarButton () {
                     const { _converse } = this.__super__,
                           { __ } = _converse;
                     Promise.all([
@@ -83,12 +91,6 @@
                                 tpl_toolbar_omemo({'__': __}));
                         }
                     }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
-                },
-
-                renderToolbar (toolbar, options) {
-                    const result = this.__super__.renderToolbar.apply(this, arguments);
-                    this.addOMEMOToolbarButton(options);
-                    return result;
                 }
             }
         },
@@ -101,31 +103,51 @@
 
             _converse.api.promises.add(['OMEMOInitialized']);
 
+            function generateBundle () {
+                return new Promise((resolve, reject) => {
+                    libsignal.KeyHelper.generateIdentityKeyPair().then((identity_keypair) => {
+                        const data = {
+                            'device_id': libsignal.KeyHelper.generateRegistrationId(),
+                            'pubkey': identity_keypair.pubKey,
+                            'privkey': identity_keypair.privKey,
+                            'prekeys': {}
+                        };
+                        const signed_prekey_id = '0';
+                        libsignal.KeyHelper.generateSignedPreKey(identity_keypair, signed_prekey_id)
+                            .then((signed_prekey) => {
+                                data['signed_prekey'] = signed_prekey;
+                                const key_promises = _.map(_.range(0, 100), (id) => libsignal.KeyHelper.generatePreKey(id));
+                                Promise.all(key_promises).then((keys) => {
+                                    data['prekeys'] = keys;
+                                    resolve(data)
+                                });
+                            }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
+                    });
+                }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
+            }
 
-            _converse.OMEMOSession = Backbone.Model.extend({
-                initialize () {
-                    this.keyhelper = window.libsignal.KeyHelper;
-                },
+
+            _converse.OMEMOStore = Backbone.Model.extend({
 
                 fetchSession () {
-                    return new Promise((resolve, reject) => {
-                        this.fetch({
-                            'success': () => {
-                                if (!_converse.omemo_session.get('registration_id')) {
-                                    this.keyhelper.generateIdentityKeyPair().then((keypair) => {
-                                        _converse.omemo_session.set({
-                                            'registration_id': this.keyhelper.generateRegistrationId(),
-                                            'pub_key': keypair.pubKey,
-                                            'priv_key': keypair.privKey
-                                        });
+                    if (_.isUndefined(this._setup_promise)) {
+                        this._setup_promise = new Promise((resolve, reject) => {
+                            this.fetch({
+                                'success': () => {
+                                    if (!_converse.omemo_store.get('device_id')) {
+                                        generateBundle()
+                                            .then((data) => {
+                                                _converse.omemo_store.save(data);
+                                                resolve();
+                                            }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
+                                    } else {
                                         resolve();
-                                    });
-                                } else {
-                                    resolve();
+                                    }
                                 }
-                            }
+                            });
                         });
-                    });
+                    }
+                    return this._setup_promise;
                 }
             });
 
@@ -199,11 +221,29 @@
 
 
             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();
+                const store = _converse.omemo_store,
+                      signed_prekey = store.get('signed_prekey');
+                return new Promise((resolve, reject) => {
+                    const stanza = $iq({
+                        'from': _converse.bare_jid,
+                        'type': 'set'
+                    }).c('pubsub', {'xmlns': Strophe.NS.PUBSUB})
+                        .c('publish', {'node': `${Strophe.NS.OMEMO_BUNDLES}:${store.get('device_id')}`})
+                            .c('item')
+                                .c('bundle', {'xmlns': Strophe.NS.OMEMO})
+                                    .c('signedPreKeyPublic', {'signedPreKeyId': signed_prekey.keyId})
+                                        .t(u.arrayBuffer2Base64(signed_prekey.keyPair.pubKey)).up()
+                                    .c('signedPreKeySignature').up()
+                                    .c('identityKey').up()
+                                    .c('prekeys');
+                    _.forEach(
+                        store.get('prekeys'),
+                        (prekey) => {
+                            stanza.c('preKeyPublic', {'preKeyId': prekey.keyId})
+                                .t(u.arrayBuffer2Base64(prekey.keyPair.pubKey)).up();
+                        });
+                    _converse.connection.sendIQ(stanza, resolve, reject, _converse.IQ_TIMEOUT);
+                });
             }
 
             function fetchDeviceLists () {
@@ -215,13 +255,15 @@
                  * Also, deduplicate devices if necessary.
                  */
                 return new Promise((resolve, reject) => {
-                    let own_devicelist = _converse.devicelists.get(_converse.bare_jid);
-                    if (_.isNil(own_devicelist)) {
-                        own_devicelist = _converse.devicelists.create({'jid': _converse.bare_jid});
-                    }
-                    own_devicelist.fetchDevices().then(resolve).catch(reject);
-                    // TODO: if our own device is not onthe list, add it.
-                    // TODO: deduplicate
+                    fetchDeviceLists().then(() => {
+                        let own_devicelist = _converse.devicelists.get(_converse.bare_jid);
+                        if (_.isNil(own_devicelist)) {
+                            own_devicelist = _converse.devicelists.create({'jid': _converse.bare_jid});
+                        }
+                        own_devicelist.fetchDevices().then(resolve).catch(reject);
+                        // TODO: if our own device is not onthe list, add it.
+                        // TODO: deduplicate
+                    });
                 });
             }
 
@@ -252,13 +294,21 @@
                 }, null, 'message', 'headline', null, _converse.bare_jid);
             }
 
+            function restoreOMEMOSession () {
+                _converse.omemo_store = new _converse.OMEMOStore();
+                _converse.omemo_store.browserStorage =  new Backbone.BrowserStorage.session(
+                    b64_sha1(`converse.omemosession-${_converse.bare_jid}`)
+                );
+                return _converse.omemo_store.fetchSession()
+            }
+
             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())
+                restoreOMEMOSession()
+                    .then(() => publishBundle())
                     .then(() => updateOwnDeviceList())
                     .then(() => _converse.emit('OMEMOInitialized'))
                     .catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
@@ -270,15 +320,10 @@
                     b64_sha1(`converse.devicelists-${_converse.bare_jid}`)
                 );
 
-                _converse.omemo_session = new _converse.OMEMOSession();
-                _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));
+                initOMEMO();
             }
 
+            _converse.api.listen.on('renderToolbar', (view) => view.addOMEMOToolbarButton());
             _converse.api.listen.on('statusInitialized', onStatusInitialized);
             _converse.api.listen.on('connected', registerPEPPushHandler);
             _converse.api.listen.on('afterTearDown', () => _converse.devices.reset());

+ 9 - 7
src/utils/core.js

@@ -823,14 +823,11 @@
         return text.replace(_converse.geouri_regex, replacement);
     };
 
-    u.getSelectValues = function(select) {
-        var result = [];
-        var options = select && select.options;
-        var opt;
-
+    u.getSelectValues = function (select) {
+        const result = [];
+        const options = select && select.options;
         for (var i=0, iLen=options.length; i<iLen; i++) {
-            opt = options[i];
-
+            const opt = options[i];
             if (opt.selected) {
                 result.push(opt.value || opt.text);
             }
@@ -838,5 +835,10 @@
         return result;
     };
 
+    u.arrayBuffer2Base64 = function (ab) {
+        return new window.Uint8Array(ab)
+            .reduce((data, byte) => data + String.fromCharCode(byte), '')
+    };
+
     return u;
 }));

+ 1 - 0
tests/runner.js

@@ -47,6 +47,7 @@ var specs = [
     "spec/ping",
     "spec/xmppstatus",
     "spec/mam",
+    "spec/omemo",
     "spec/controlbox",
     "spec/roster",
     "spec/chatbox",