Commit d484320c authored by JC Brand's avatar JC Brand

Test that bundles can be updated via PEP

Fix bugs in the process

udpates #497
parent ddd0ef8e
...@@ -18,7 +18,7 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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'], {},
......
...@@ -54,23 +54,23 @@ ...@@ -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'), signed_prekey_signature_el = bundle_el.querySelector('signedPreKeySignature'),
identity_key_el = bundle_el.querySelector('identityKey'); 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), 'id': parseInt(el.getAttribute('preKeyId'), 10),
'key': u.base64ToArrayBuffer(el.textContent) '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), 'public_key': signed_prekey_public_el.textContent,
'signature': u.base64ToArrayBuffer(signed_prekey_signature_el.textContent) 'signature': signed_prekey_signature_el.textContent
}, },
'prekeys': prekeys 'prekeys': prekeys
} }
...@@ -89,49 +89,15 @@ ...@@ -89,49 +89,15 @@
ChatBox: { ChatBox: {
parseBundleFromIQ (device_id, stanza) { getBundlesAndBuildSessions () {
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 () {
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)); const promises = _.map(devices, (device) => device.getBundle());
Promise.all(bundle_promises).then(() => { 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 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -600,14 +599,15 @@
function updateBundleFromStanza (stanza) { function updateBundleFromStanza (stanza) {
const items_el = sizzle(`items[node="${Strophe.NS.OMEMO_BUNDLES}"]`, stanza).pop(); const items_el = sizzle(`items`, stanza).pop();
if (!items_el) { 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'), jid = stanza.getAttribute('from'),
bundle_el = sizzle(`item list[xmlns="${Strophe.NS.OMEMO}"] bundle`, items_el).pop(), bundle_el = sizzle(`item > bundle`, items_el).pop(),
device = _converse.devicelists.get(from).devices.get(device_id); 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)});
} }
......
...@@ -849,8 +849,11 @@ ...@@ -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;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment