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

updates #497
This commit is contained in:
JC Brand 2018-05-12 23:26:14 +02:00
parent 6042c233bc
commit 09eb1731b5
5 changed files with 168 additions and 79 deletions

View File

@ -15,7 +15,25 @@
}); });
}, },
'generateRegistrationId': function () { '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'], {}, null, ['rosterGroupsFetched'], {},
function (done, _converse) { function (done, _converse) {
let devicelist_iq, let iq_stanza;
disco_info_iq;
test_utils.createContacts(_converse, 'current'); test_utils.createContacts(_converse, 'current');
const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost'; const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.waitUntil(function () { test_utils.waitUntil(function () {
return _.filter(_converse.connection.IQ_stanzas, function (iq) { 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"]'); 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; return node;
}).length > 0; }).length > 0;
}, 1000).then(function () { }, 1000).then(function () {
@ -52,32 +69,57 @@
'type': 'result', 'type': 'result',
'from': 'dummy@localhost', 'from': 'dummy@localhost',
'to': 'dummy@localhost/resource', '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('query', {'xmlns': 'http://jabber.org/protocol/disco#info'})
.c('identity', { .c('identity', {
'category': 'pubsub', 'category': 'pubsub',
'type': 'pep'}); 'type': 'pep'});
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _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 test_utils.waitUntil(() => {
return _.filter( return _.filter(
_converse.connection.IQ_stanzas, _converse.connection.IQ_stanzas,
(iq) => { (iq) => {
const node = iq.nodeTree.querySelector('iq[to="'+_converse.bare_jid+'"] query[node="eu.siacs.conversations.axolotl.devicelist"]'); const node = iq.nodeTree.querySelector('iq[to="'+_converse.bare_jid+'"] query[node="eu.siacs.conversations.axolotl.devicelist"]');
if (node) { if (node) { iq_stanza = iq.nodeTree;}
devicelist_iq = iq;
}
return node; return node;
}).length; }).length;
}); });
}).then(function () { }).then(function () {
expect(devicelist_iq.toLocaleString()).toBe( expect(iq_stanza.outerHTML).toBe(
"<iq type='get' from='dummy@localhost' to='dummy@localhost' xmlns='jabber:client' id='"+devicelist_iq.nodeTree.getAttribute('id')+"'>"+ '<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' "+ '<query xmlns="http://jabber.org/protocol/disco#items" '+
"node='eu.siacs.conversations.axolotl.devicelist'/>"+ 'node="eu.siacs.conversations.axolotl.devicelist"/>'+
"</iq>"); '</iq>');
const stanza = $iq({ const stanza = $iq({
'from': contact_jid, 'from': contact_jid,
'id': devicelist_iq.nodeTree.getAttribute('id'), 'id': iq_stanza.getAttribute('id'),
'to': _converse.bare_jid, 'to': _converse.bare_jid,
'type': 'result', 'type': 'result',
}).c('query', { }).c('query', {
@ -97,20 +139,18 @@
_converse.connection.IQ_stanzas, _converse.connection.IQ_stanzas,
(iq) => { (iq) => {
const node = iq.nodeTree.querySelector('iq[to="'+contact_jid+'"] query[node="eu.siacs.conversations.axolotl.devicelist"]'); const node = iq.nodeTree.querySelector('iq[to="'+contact_jid+'"] query[node="eu.siacs.conversations.axolotl.devicelist"]');
if (node) { if (node) { iq_stanza = iq.nodeTree; }
devicelist_iq = iq;
}
return node; return node;
}).length;}); }).length;});
}).then(function () { }).then(function () {
expect(devicelist_iq.toLocaleString()).toBe( expect(iq_stanza.outerHTML).toBe(
"<iq type='get' from='dummy@localhost' to='"+contact_jid+"' xmlns='jabber:client' id='"+devicelist_iq.nodeTree.getAttribute('id')+"'>"+ '<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' "+ '<query xmlns="http://jabber.org/protocol/disco#items" '+
"node='eu.siacs.conversations.axolotl.devicelist'/>"+ 'node="eu.siacs.conversations.axolotl.devicelist"/>'+
"</iq>"); '</iq>');
const stanza = $iq({ const stanza = $iq({
'from': contact_jid, 'from': contact_jid,
'id': devicelist_iq.nodeTree.getAttribute('id'), 'id': iq_stanza.getAttribute('id'),
'to': _converse.bare_jid, 'to': _converse.bare_jid,
'type': 'result', 'type': 'result',
}).c('query', { }).c('query', {
@ -139,7 +179,7 @@
toolbar.querySelector('.toggle-omemo').click(); toolbar.querySelector('.toggle-omemo').click();
expect(view.toggleOMEMO).toHaveBeenCalled(); expect(view.toggleOMEMO).toHaveBeenCalled();
done(); done();
}); }).catch(_.partial(console.error, _));
})); }));
}); });

View File

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

View File

@ -4,6 +4,8 @@
// Copyright (c) 2013-2018, the Converse.js developers // Copyright (c) 2013-2018, the Converse.js developers
// Licensed under the Mozilla Public License (MPLv2) // Licensed under the Mozilla Public License (MPLv2)
/* global libsignal */
(function (root, factory) { (function (root, factory) {
define([ define([
"converse-core", "converse-core",
@ -12,11 +14,13 @@
}(this, function (converse, tpl_toolbar_omemo) { }(this, function (converse, tpl_toolbar_omemo) {
const { Backbone, Promise, Strophe, sizzle, $iq, _, b64_sha1 } = converse.env; 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', "eu.siacs.conversations.axolotl");
Strophe.addNamespace('OMEMO_DEVICELIST', Strophe.NS.OMEMO+".devicelist"); Strophe.addNamespace('OMEMO_DEVICELIST', Strophe.NS.OMEMO+".devicelist");
Strophe.addNamespace('OMEMO_VERIFICATION', Strophe.NS.OMEMO+".verification"); Strophe.addNamespace('OMEMO_VERIFICATION', Strophe.NS.OMEMO+".verification");
Strophe.addNamespace('OMEMO_WHITELISTED', Strophe.NS.OMEMO+".whitelisted"); Strophe.addNamespace('OMEMO_WHITELISTED', Strophe.NS.OMEMO+".whitelisted");
Strophe.addNamespace('OMEMO_BUNDLES', Strophe.NS.OMEMO+".bundles");
const UNDECIDED = 0; const UNDECIDED = 0;
const TRUSTED = 1; const TRUSTED = 1;
@ -37,6 +41,7 @@
} }
function contactHasOMEMOSupport (_converse, jid) { function contactHasOMEMOSupport (_converse, jid) {
/* Checks whether the contact advertises any OMEMO-compatible devices. */
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
getDevicesForContact(_converse, jid).then((devices) => { getDevicesForContact(_converse, jid).then((devices) => {
resolve(devices.length > 0) resolve(devices.length > 0)
@ -57,10 +62,13 @@
return !_.isNil(window.libsignal); return !_.isNil(window.libsignal);
}, },
dependencies: ["converse-chatview"],
overrides: { overrides: {
ChatBoxView: { ChatBoxView: {
events: { events: {
'click .toggle-omemo': 'toggleOMEMO', 'click .toggle-omemo': 'toggleOMEMO'
}, },
toggleOMEMO (ev) { toggleOMEMO (ev) {
@ -68,7 +76,7 @@
ev.preventDefault(); ev.preventDefault();
}, },
addOMEMOToolbarButton (options) { addOMEMOToolbarButton () {
const { _converse } = this.__super__, const { _converse } = this.__super__,
{ __ } = _converse; { __ } = _converse;
Promise.all([ Promise.all([
@ -83,12 +91,6 @@
tpl_toolbar_omemo({'__': __})); tpl_toolbar_omemo({'__': __}));
} }
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); }).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']); _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 () { _converse.OMEMOStore = Backbone.Model.extend({
this.keyhelper = window.libsignal.KeyHelper;
},
fetchSession () { fetchSession () {
return new Promise((resolve, reject) => { if (_.isUndefined(this._setup_promise)) {
this.fetch({ this._setup_promise = new Promise((resolve, reject) => {
'success': () => { this.fetch({
if (!_converse.omemo_session.get('registration_id')) { 'success': () => {
this.keyhelper.generateIdentityKeyPair().then((keypair) => { if (!_converse.omemo_store.get('device_id')) {
_converse.omemo_session.set({ generateBundle()
'registration_id': this.keyhelper.generateRegistrationId(), .then((data) => {
'pub_key': keypair.pubKey, _converse.omemo_store.save(data);
'priv_key': keypair.privKey resolve();
}); }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
} else {
resolve(); resolve();
}); }
} else {
resolve();
} }
} });
}); });
}); }
return this._setup_promise;
} }
}); });
@ -199,11 +221,29 @@
function publishBundle () { function publishBundle () {
// TODO: publish bundle information (public key and pre keys) const store = _converse.omemo_store,
// Keep the used device id consistant. You have to republish signed_prekey = store.get('signed_prekey');
// this because you don't know if the server was restarted or might have return new Promise((resolve, reject) => {
// otherwise lost the information. const stanza = $iq({
return Promise.resolve(); '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 () { function fetchDeviceLists () {
@ -215,13 +255,15 @@
* Also, deduplicate devices if necessary. * Also, deduplicate devices if necessary.
*/ */
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let own_devicelist = _converse.devicelists.get(_converse.bare_jid); fetchDeviceLists().then(() => {
if (_.isNil(own_devicelist)) { let own_devicelist = _converse.devicelists.get(_converse.bare_jid);
own_devicelist = _converse.devicelists.create({'jid': _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. own_devicelist.fetchDevices().then(resolve).catch(reject);
// TODO: deduplicate // TODO: if our own device is not onthe list, add it.
// TODO: deduplicate
});
}); });
} }
@ -252,13 +294,21 @@
}, null, 'message', 'headline', null, _converse.bare_jid); }, 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 () { function initOMEMO () {
/* Publish our bundle and then fetch our own device list. /* Publish our bundle and then fetch our own device list.
* If our device list does not contain this device's id, publish the * 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. * device list with the id added. Also deduplicate device ids in the list.
*/ */
publishBundle() restoreOMEMOSession()
.then(() => fetchDeviceLists()) .then(() => publishBundle())
.then(() => updateOwnDeviceList()) .then(() => updateOwnDeviceList())
.then(() => _converse.emit('OMEMOInitialized')) .then(() => _converse.emit('OMEMOInitialized'))
.catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); .catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
@ -270,15 +320,10 @@
b64_sha1(`converse.devicelists-${_converse.bare_jid}`) b64_sha1(`converse.devicelists-${_converse.bare_jid}`)
); );
_converse.omemo_session = new _converse.OMEMOSession(); initOMEMO();
_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('renderToolbar', (view) => view.addOMEMOToolbarButton());
_converse.api.listen.on('statusInitialized', onStatusInitialized); _converse.api.listen.on('statusInitialized', onStatusInitialized);
_converse.api.listen.on('connected', registerPEPPushHandler); _converse.api.listen.on('connected', registerPEPPushHandler);
_converse.api.listen.on('afterTearDown', () => _converse.devices.reset()); _converse.api.listen.on('afterTearDown', () => _converse.devices.reset());

View File

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

View File

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