From e774e9d1afab3b8e791f812c88711e931a4f7f50 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Sat, 28 Jul 2018 16:36:23 +0200 Subject: [PATCH] Test that own devices (from other clients) get included when sending out encrypted messages. updates #497 --- spec/omemo.js | 54 +++++++++++++++++++++++++++++++++++++++---- src/converse-omemo.js | 51 +++++++++++++++++++++++++--------------- tests/mock.js | 4 +++- 3 files changed, 86 insertions(+), 23 deletions(-) diff --git a/spec/omemo.js b/spec/omemo.js index 9fb8420ab..994820a50 100644 --- a/spec/omemo.js +++ b/spec/omemo.js @@ -10,7 +10,7 @@ describe("The OMEMO module", function() { - it("enables encrypted messages to be sent", + it("enables encrypted messages to be sent and received", mock.initConverseWithPromises( null, ['rosterGroupsFetched'], {}, function (done, _converse) { @@ -83,7 +83,9 @@ return _.filter( _converse.connection.IQ_stanzas, (iq) => { - const node = iq.nodeTree.querySelector('iq[to="'+contact_jid+'"] items[node="eu.siacs.conversations.axolotl.bundles:555"]'); + const node = iq.nodeTree.querySelector( + 'iq[to="'+contact_jid+'"] items[node="eu.siacs.conversations.axolotl.bundles:555"]' + ); if (node) { iq_stanza = iq.nodeTree; } return node; }).length; @@ -106,6 +108,36 @@ .c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1001')).up() .c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1002')).up() .c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1003')); + _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+'"] items[node="eu.siacs.conversations.axolotl.bundles:482886413b977930064a5888b92134fe"]' + ); + if (node) { iq_stanza = iq.nodeTree; } + return node; + }).length; + }); + }).then(() => { + const stanza = $iq({ + 'from': _converse.bare_jid, + 'id': iq_stanza.getAttribute('id'), + 'to': _converse.bare_jid, + 'type': 'result', + }).c('pubsub', { + 'xmlns': 'http://jabber.org/protocol/pubsub' + }).c('items', {'node': "eu.siacs.conversations.axolotl.bundles:482886413b977930064a5888b92134fe"}) + .c('item') + .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'}) + .c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t(btoa('100000')).up() + .c('signedPreKeySignature').t(btoa('200000')).up() + .c('identityKey').t(btoa('300000')).up() + .c('prekeys') + .c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1991')).up() + .c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1992')).up() + .c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1993')); spyOn(_converse.connection, 'send').and.callFake(stanza => { sent_stanza = stanza }); _converse.connection._dataRecv(test_utils.createRequest(stanza)); @@ -117,11 +149,25 @@ `This is an OMEMO encrypted message which your client doesn’t seem to support. Find more information on https://conversations.im/omemo`+ ``+ `
`+ - `eyJpdiI6IjEyMzQ1In0=`+ - `12345`+ + `eyJ0eXBlIjoxLCJib2R5IjoiYzFwaDNSNzNYNyIsInJlZ2lzdHJhdGlvbklkIjoiMTMzNyJ9`+ + `eyJ0eXBlIjoxLCJib2R5IjoiYzFwaDNSNzNYNyIsInJlZ2lzdHJhdGlvbklkIjoiMTMzNyJ9`+ + `${sent_stanza.nodeTree.querySelector('iv').textContent}`+ `
`+ `
`+ ``); + + // Test reception of an encrypted message + const stanza = $msg({ + 'from': contact_jid, + 'to': _converse.connection.jid, + 'type': 'chat', + 'id': 'qwerty' + }).c('body').t('This is a fallback message').up() + .c('encrypted', {'xmlns': Strophe.NS.OMEMO}) + .c('header', {'sid': '555'}) + .c('key', {'rid': _converse.omemo_store.get('device_id')}).t('c1ph3R73X7').up() + .c('iv').t('1234'); + _converse.connection._dataRecv(test_utils.createRequest(stanza)); done(); }); })); diff --git a/src/converse-omemo.js b/src/converse-omemo.js index 7c246bb30..d822ed4b4 100644 --- a/src/converse-omemo.js +++ b/src/converse-omemo.js @@ -92,8 +92,13 @@ const { _converse } = this.__super__; return new Promise((resolve, reject) => { _converse.getDevicesForContact(this.get('jid')) - .then((devices) => { - Promise.all(devices.map((device) => device.getBundle())) + .then((their_devices) => { + const device_id = _converse.omemo_store.get('device_id'), + devicelist = _converse.devicelists.get(_converse.bare_jid), + own_devices = devicelist.devices.filter(device => device.get('id') !== device_id), + devices = _.concat(own_devices, their_devices.models); + + Promise.all(devices.map(device => device.getBundle())) .then(() => this.buildSessions(devices)) .then(() => resolve(devices)) .catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); @@ -165,22 +170,27 @@ address = new libsignal.SignalProtocolAddress(this.get('jid'), device.get('id')), sessionCipher = new window.libsignal.SessionCipher(_converse.omemo_store, address); - return sessionCipher.encrypt(plaintext); + return new Promise((resolve, reject) => { + sessionCipher.encrypt(plaintext) + .then(payload => resolve({'payload': payload, 'device': device})) + .catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); + }); }, - addKeysToMessageStanza (stanza, devices, payloads) { - for (var i in payloads) { - if (Object.prototype.hasOwnProperty.call(payloads, i)) { - const payload = btoa(JSON.stringify(payloads[i])) - const prekey = 3 == parseInt(payloads[i].type, 10) - if (i == payloads.length-1) { - stanza.c('key', {'rid': devices.get('id') }).t(payload) - if (prekey) { - stanza.attrs({'prekey': prekey}); - } - stanza.up().c('iv').t(payloads[0].iv).up().up() - } else { - stanza.c('key', {prekey: prekey, rid: devices.get('id') }).t(payload).up() + addKeysToMessageStanza (stanza, dicts, iv) { + for (var i in dicts) { + if (Object.prototype.hasOwnProperty.call(dicts, i)) { + const payload = dicts[i].payload, + device = dicts[i].device, + prekey = 3 == parseInt(payload.type, 10); + + stanza.c('key', {'rid': device.get('id') }).t(btoa(JSON.stringify(dicts[i].payload))); + if (prekey) { + stanza.attrs({'prekey': prekey}); + } + stanza.up(); + if (i == dicts.length-1) { + stanza.c('iv').t(iv).up().up() } } } @@ -213,8 +223,13 @@ // long-standing SignalProtocol session. // TODO: need to include own devices here as well (and filter out distrusted devices) - const promises = devices.map(device => this.encryptKey(payload.key_str+payload.tag, device)); - return Promise.all(promises).then((payloads) => this.addKeysToMessageStanza(stanza, devices, payloads)); + const promises = devices + .filter(device => device.get('trusted') != UNTRUSTED) + .map(device => this.encryptKey(payload.key_str+payload.tag, device)); + + return Promise.all(promises) + .then((dicts) => this.addKeysToMessageStanza(stanza, dicts, payload.iv)) + .catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); }); }, diff --git a/tests/mock.js b/tests/mock.js index b58d91ad2..5f8c5682a 100644 --- a/tests/mock.js +++ b/tests/mock.js @@ -16,7 +16,9 @@ this.remoteAddress = remote_address; this.storage = storage; this.encrypt = () => Promise.resolve({ - 'iv': '12345' + 'type': 1, + 'body': 'c1ph3R73X7', + 'registrationId': '1337' }); }, 'SessionBuilder': function (storage, remote_address) {