Bläddra i källkod

Test that bundles can be updated via PEP

Fix bugs in the process

udpates #497
JC Brand 7 år sedan
förälder
incheckning
d484320c09
3 ändrade filer med 204 tillägg och 57 borttagningar
  1. 146 2
      spec/omemo.js
  2. 53 53
      src/converse-omemo.js
  3. 5 2
      src/utils/core.js

+ 146 - 2
spec/omemo.js

@@ -18,7 +18,7 @@
             done();
             done();
         }));
         }));
 
 
-        it("updates the user's device list based on PEP messages",
+        it("updates device lists based on PEP messages",
             mock.initConverseWithPromises(
             mock.initConverseWithPromises(
                 null, ['rosterGroupsFetched'], {},
                 null, ['rosterGroupsFetched'], {},
                 function (done, _converse) {
                 function (done, _converse) {
@@ -131,7 +131,7 @@
                     'from': _converse.bare_jid,
                     'from': _converse.bare_jid,
                     'to': _converse.bare_jid,
                     'to': _converse.bare_jid,
                     'type': 'headline',
                     'type': 'headline',
-                    'id': 'update_03',
+                    'id': 'update_04',
                 }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
                 }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
                     .c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
                     .c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
                         .c('item')
                         .c('item')
@@ -177,6 +177,150 @@
             });
             });
         }));
         }));
 
 
+        it("updates device bundles based on PEP messages",
+            mock.initConverseWithPromises(
+                null, ['rosterGroupsFetched'], {},
+                function (done, _converse) {
+
+            let iq_stanza;
+            test_utils.createContacts(_converse, 'current');
+            const contact_jid = mock.cur_names[3].replace(/ /g,'.').toLowerCase() + '@localhost';
+
+            test_utils.waitUntil(function () {
+                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) { iq_stanza = iq.nodeTree;}
+                        return node;
+                    }).length;
+            }).then(function () {
+                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': iq_stanza.getAttribute('id'),
+                    'to': _converse.bare_jid,
+                    'type': 'result',
+                }).c('query', {
+                    'xmlns': 'http://jabber.org/protocol/disco#items',
+                    'node': 'eu.siacs.conversations.axolotl.devicelist'
+                }).c('device', {'id': '555'}).up()
+                _converse.connection._dataRecv(test_utils.createRequest(stanza));
+
+                expect(_converse.devicelists.length).toBe(1);
+                return test_utils.waitUntil(() => _converse.devicelists);
+            }).then(function () {
+                // We simply emit, to avoid doing all the setup work
+                expect(_converse.devicelists.length).toBe(1);
+                let devicelist = _converse.devicelists.get(_converse.bare_jid);
+                expect(devicelist.devices.length).toBe(2);
+                expect(devicelist.devices.at(0).get('id')).toBe('555');
+                expect(devicelist.devices.at(1).get('id')).toBe('123456789');
+                _converse.emit('OMEMOInitialized');
+
+                let stanza = $msg({
+                    'from': contact_jid,
+                    'to': _converse.bare_jid,
+                    'type': 'headline',
+                    'id': 'update_01',
+                }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
+                    .c('items', {'node': 'eu.siacs.conversations.axolotl.bundles:555'})
+                        .c('item')
+                            .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
+                                .c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t('1111').up()
+                                .c('signedPreKeySignature').t('2222').up()
+                                .c('identityKey').t('3333').up()
+                                .c('prekeys')
+                                    .c('preKeyPublic', {'preKeyId': '1001'}).up()
+                                    .c('preKeyPublic', {'preKeyId': '1002'}).up()
+                                    .c('preKeyPublic', {'preKeyId': '1003'});
+                _converse.connection._dataRecv(test_utils.createRequest(stanza));
+
+                expect(_converse.devicelists.length).toBe(2);
+                devicelist = _converse.devicelists.get(contact_jid);
+                expect(devicelist.devices.length).toBe(1);
+                let device = devicelist.devices.at(0);
+                expect(device.get('bundle').identity_key).toBe(3333);
+                expect(device.get('bundle').signed_prekey.public_key).toBe('1111');
+                expect(device.get('bundle').signed_prekey.id).toBe(4223);
+                expect(device.get('bundle').signed_prekey.signature).toBe('2222');
+                expect(device.get('bundle').prekeys.length).toBe(3);
+                expect(device.get('bundle').prekeys[0].id).toBe(1001);
+                expect(device.get('bundle').prekeys[1].id).toBe(1002);
+                expect(device.get('bundle').prekeys[2].id).toBe(1003);
+
+                stanza = $msg({
+                    'from': contact_jid,
+                    'to': _converse.bare_jid,
+                    'type': 'headline',
+                    'id': 'update_02',
+                }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
+                    .c('items', {'node': 'eu.siacs.conversations.axolotl.bundles:555'})
+                        .c('item')
+                            .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
+                                .c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t('5555').up()
+                                .c('signedPreKeySignature').t('6666').up()
+                                .c('identityKey').t('7777').up()
+                                .c('prekeys')
+                                    .c('preKeyPublic', {'preKeyId': '2001'}).up()
+                                    .c('preKeyPublic', {'preKeyId': '2002'}).up()
+                                    .c('preKeyPublic', {'preKeyId': '2003'});
+                _converse.connection._dataRecv(test_utils.createRequest(stanza));
+
+                expect(_converse.devicelists.length).toBe(2);
+                devicelist = _converse.devicelists.get(contact_jid);
+                expect(devicelist.devices.length).toBe(1);
+                device = devicelist.devices.at(0);
+                expect(device.get('bundle').identity_key).toBe(7777);
+                expect(device.get('bundle').signed_prekey.public_key).toBe('5555');
+                expect(device.get('bundle').signed_prekey.id).toBe(4223);
+                expect(device.get('bundle').signed_prekey.signature).toBe('6666');
+                expect(device.get('bundle').prekeys.length).toBe(3);
+                expect(device.get('bundle').prekeys[0].id).toBe(2001);
+                expect(device.get('bundle').prekeys[1].id).toBe(2002);
+                expect(device.get('bundle').prekeys[2].id).toBe(2003);
+
+                stanza = $msg({
+                    'from': _converse.bare_jid,
+                    'to': _converse.bare_jid,
+                    'type': 'headline',
+                    'id': 'update_03',
+                }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
+                    .c('items', {'node': 'eu.siacs.conversations.axolotl.bundles:123456789'})
+                        .c('item')
+                            .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
+                                .c('signedPreKeyPublic', {'signedPreKeyId': '9999'}).t('8888').up()
+                                .c('signedPreKeySignature').t('3333').up()
+                                .c('identityKey').t('1111').up()
+                                .c('prekeys')
+                                    .c('preKeyPublic', {'preKeyId': '3001'}).up()
+                                    .c('preKeyPublic', {'preKeyId': '3002'}).up()
+                                    .c('preKeyPublic', {'preKeyId': '3003'});
+                _converse.connection._dataRecv(test_utils.createRequest(stanza));
+
+                expect(_converse.devicelists.length).toBe(2);
+                devicelist = _converse.devicelists.get(_converse.bare_jid);
+                expect(devicelist.devices.length).toBe(2);
+                expect(devicelist.devices.at(0).get('id')).toBe('555');
+                expect(devicelist.devices.at(1).get('id')).toBe('123456789');
+                device = devicelist.devices.at(1);
+                expect(device.get('bundle').identity_key).toBe(1111);
+                expect(device.get('bundle').signed_prekey.public_key).toBe('8888');
+                expect(device.get('bundle').signed_prekey.id).toBe(9999);
+                expect(device.get('bundle').signed_prekey.signature).toBe('3333');
+                expect(device.get('bundle').prekeys.length).toBe(3);
+                expect(device.get('bundle').prekeys[0].id).toBe(3001);
+                expect(device.get('bundle').prekeys[1].id).toBe(3002);
+                expect(device.get('bundle').prekeys[2].id).toBe(3003);
+                done();
+            });
+        }));
+
         it("adds a toolbar button for starting an encrypted chat session",
         it("adds a toolbar button for starting an encrypted chat session",
             mock.initConverseWithPromises(
             mock.initConverseWithPromises(
                 null, ['rosterGroupsFetched'], {},
                 null, ['rosterGroupsFetched'], {},

+ 53 - 53
src/converse-omemo.js

@@ -54,23 +54,23 @@
          * and return a map.
          * and return a map.
          */
          */
         const signed_prekey_public_el = bundle_el.querySelector('signedPreKeyPublic'),
         const signed_prekey_public_el = bundle_el.querySelector('signedPreKeyPublic'),
-                signed_prekey_signature_el = bundle_el.querySelector('signedPreKeySignature'),
-                identity_key_el = bundle_el.querySelector('identityKey');
+              signed_prekey_signature_el = bundle_el.querySelector('signedPreKeySignature'),
+              identity_key_el = bundle_el.querySelector('identityKey');
 
 
         const prekeys = _.map(
         const prekeys = _.map(
-            sizzle(`> prekeys > preKeyPublic`, bundle_el),
+            sizzle(`prekeys > preKeyPublic`, bundle_el),
             (el) => {
             (el) => {
                 return {
                 return {
-                    'id': parseInt(el.getAttribute('keyId'), 10),
-                    'key': u.base64ToArrayBuffer(el.textContent)
+                    'id': parseInt(el.getAttribute('preKeyId'), 10),
+                    'key': el.textContent
                 }
                 }
             });
             });
         return {
         return {
-            'identity_key': bundle_el.querySelector('> identityKey').textContent,
+            'identity_key': parseInt(bundle_el.querySelector('identityKey').textContent, 10),
             'signed_prekey': {
             'signed_prekey': {
                 'id': parseInt(signed_prekey_public_el.getAttribute('signedPreKeyId'), 10),
                 'id': parseInt(signed_prekey_public_el.getAttribute('signedPreKeyId'), 10),
-                'public_key': u.base64ToArrayBuffer(signed_prekey_public_el.textContent),
-                'signature': u.base64ToArrayBuffer(signed_prekey_signature_el.textContent)
+                'public_key': signed_prekey_public_el.textContent,
+                'signature': signed_prekey_signature_el.textContent
             },
             },
             'prekeys': prekeys
             'prekeys': prekeys
         }
         }
@@ -89,49 +89,15 @@
 
 
             ChatBox: {
             ChatBox: {
 
 
-                parseBundleFromIQ (device_id, stanza) {
-                    const publish_el = sizzle(`items[node="${Strophe.NS.OMEMO_BUNDLES}:${device_id}"]`, stanza).pop();
-                    const bundle_el = sizzle(`bundle[xmlns="${Strophe.NS.OMEMO}"]`, publish_el).pop();
-                    return parseBundle(bundle_el);
-                },
-
-                fetchBundle (device_id) {
-                    const { _converse } = this.__super__,
-                          device = _converse.devicelists.get(this.get('jid')).devices.get(device_id);
-
-                    if (device.get('bundle')) {
-                        return Promise.resolve(device.get('bundle').toJSON());
-                    } else {
-                        return new Promise((resolve, reject) => {
-                            const stanza = $iq({
-                                'type': 'get',
-                                'from': _converse.bare_jid,
-                                'to': this.get('jid')
-                            }).c('pubsub', {'xmlns': Strophe.NS.PUBSUB})
-                                .c('items', {'xmlns': `${Strophe.NS.OMEMO_BUNDLES}:${device_id}`});
-                            _converse.connection.sendIQ(
-                                stanza,
-                                (iq) => {
-                                    const bundle = this.parseBundleFromIQ(iq);
-                                    bundle.device_id = device_id;
-                                    resolve(bundle);
-                                },
-                                reject,
-                                _converse.IQ_TIMEOUT
-                            );
-                        });
-                    }
-                },
-
-                fetchBundlesAndBuildSessions () {
+                getBundlesAndBuildSessions () {
                     const { _converse } = this.__super__;
                     const { _converse } = this.__super__;
                     return new Promise((resolve, reject) => {
                     return new Promise((resolve, reject) => {
                         getDevicesForContact(this.get('jid'))
                         getDevicesForContact(this.get('jid'))
                             .then((devices) => {
                             .then((devices) => {
-                                const bundle_promises = _.map(devices, (device_id) => this.fetchBundle(device_id));
-                                Promise.all(bundle_promises).then(() => {
+                                const promises = _.map(devices, (device) => device.getBundle());
+                                Promise.all(promises).then(() => {
                                     this.buildSessions(devices)
                                     this.buildSessions(devices)
-                                        .then(() => resolve(bundles))
+                                        .then(() => resolve(devices))
                                         .catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
                                         .catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
 
 
                                 }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
                                 }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
@@ -145,7 +111,7 @@
 
 
                     return Promise.all(_.map(devices, (device) => {
                     return Promise.all(_.map(devices, (device) => {
                         const recipient_id = device['id'];
                         const recipient_id = device['id'];
-                        const address = new libsignal.SignalProtocolAddress(recipient_id, device_id);
+                        const address = new libsignal.SignalProtocolAddress(parseInt(recipient_id, 10), device_id);
                         const sessionBuilder = new libsignal.SessionBuilder(_converse.omemo_store, address);
                         const sessionBuilder = new libsignal.SessionBuilder(_converse.omemo_store, address);
                         return sessionBuilder.processPreKey({
                         return sessionBuilder.processPreKey({
                             'registrationId': _converse.omemo_store.get('registration_id'),
                             'registrationId': _converse.omemo_store.get('registration_id'),
@@ -201,7 +167,7 @@
 
 
                 createMessageStanza (message) {
                 createMessageStanza (message) {
                     if (this.get('omemo_active')) {
                     if (this.get('omemo_active')) {
-                        return this.fetchBundlesAndBuildSessions()
+                        return this.getBundlesAndBuildSessions()
                             .then((bundles) => this.createOMEMOMessageStanza(message, bundles));
                             .then((bundles) => this.createOMEMOMessageStanza(message, bundles));
                     } else {
                     } else {
                         return Promise.resolve(this.__super__.createMessageStanza.apply(this, arguments));
                         return Promise.resolve(this.__super__.createMessageStanza.apply(this, arguments));
@@ -449,6 +415,39 @@
                 defaults: {
                 defaults: {
                     'active': true,
                     'active': true,
                     'trusted': UNDECIDED
                     'trusted': UNDECIDED
+                },
+
+                fetchBundleFromServer () {
+                    return new Promise((resolve, reject) => {
+                        const stanza = $iq({
+                            'type': 'get',
+                            'from': _converse.bare_jid,
+                            'to': this.get('jid')
+                        }).c('pubsub', {'xmlns': Strophe.NS.PUBSUB})
+                            .c('items', {'xmlns': `${Strophe.NS.OMEMO_BUNDLES}:${this.get('id')}`});
+                        _converse.connection.sendIQ(
+                            stanza,
+                            (iq) => {
+                                const publish_el = sizzle(`items[node="${Strophe.NS.OMEMO_BUNDLES}:${this.get('id')}"]`, stanza).pop();
+                                const bundle_el = sizzle(`bundle[xmlns="${Strophe.NS.OMEMO}"]`, publish_el).pop();
+                                this.save(parseBundle(bundle_el));
+                                resolve();
+                            },
+                            reject,
+                            _converse.IQ_TIMEOUT
+                        );
+                    });
+                },
+
+                getBundle () {
+                    /* Fetch and save the bundle information associated with
+                     * this device, if the information is not at hand already.
+                     */
+                    if (this.get('bundle')) {
+                        return Promise.resolve(this.get('bundle').toJSON());
+                    } else {
+                        return this.fetchBundleFromServer();
+                    }
                 }
                 }
             });
             });
 
 
@@ -600,14 +599,15 @@
 
 
 
 
             function updateBundleFromStanza (stanza) {
             function updateBundleFromStanza (stanza) {
-                const items_el = sizzle(`items[node="${Strophe.NS.OMEMO_BUNDLES}"]`, stanza).pop();
-                if (!items_el) {
+                const items_el = sizzle(`items`, stanza).pop();
+                if (!items_el || !items_el.getAttribute('node').startsWith(Strophe.NS.OMEMO_BUNDLES)) {
                     return;
                     return;
                 }
                 }
                 const device_id = items_el.getAttribute('node').split(':')[1],
                 const device_id = items_el.getAttribute('node').split(':')[1],
-                      from = stanza.getAttribute('from'),
-                      bundle_el = sizzle(`item list[xmlns="${Strophe.NS.OMEMO}"] bundle`, items_el).pop(),
-                      device = _converse.devicelists.get(from).devices.get(device_id);
+                      jid = stanza.getAttribute('from'),
+                      bundle_el = sizzle(`item > bundle`, items_el).pop(),
+                      devicelist = _converse.devicelists.get(jid) || _converse.devicelists.create({'jid': jid}),
+                      device = devicelist.devices.get(device_id) || devicelist.devices.create({'id': device_id});
                 device.save({'bundle': parseBundle(bundle_el)});
                 device.save({'bundle': parseBundle(bundle_el)});
             }
             }
 
 

+ 5 - 2
src/utils/core.js

@@ -849,8 +849,11 @@
         const binary_string =  window.atob(b64),
         const binary_string =  window.atob(b64),
               len = binary_string.length,
               len = binary_string.length,
               bytes = new Uint8Array(len);
               bytes = new Uint8Array(len);
-        _.forEach(_.range(0, len), (i) => bytes.push(binary_string.charCodeAt(i))); // eslint-disable-line lodash/prefer-map
-        return bytes.buffer;
+
+        for (let i = 0; i < len; i++) {
+            bytes[i] = binary_string.charCodeAt(i)
+        }
+        return bytes.buffer
     };
     };
 
 
     return u;
     return u;