Add code to generate and publish our bundle and update the test
updates #497
This commit is contained in:
parent
6042c233bc
commit
09eb1731b5
@ -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, _));
|
||||
}));
|
||||
});
|
||||
|
||||
|
@ -387,6 +387,7 @@
|
||||
this.addSpoilerButton(options);
|
||||
this.addFileUploadButton();
|
||||
this.insertEmojiPicker();
|
||||
_converse.emit('renderToolbar', this);
|
||||
return this;
|
||||
},
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
}));
|
||||
|
@ -47,6 +47,7 @@ var specs = [
|
||||
"spec/ping",
|
||||
"spec/xmppstatus",
|
||||
"spec/mam",
|
||||
"spec/omemo",
|
||||
"spec/controlbox",
|
||||
"spec/roster",
|
||||
"spec/chatbox",
|
||||
|
Loading…
Reference in New Issue
Block a user