Commit a3593dbc authored by JC Brand's avatar JC Brand

Implement and test sending of encrypted messages

updates #497
parent f40e4b4d
...@@ -73263,85 +73263,143 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ ...@@ -73263,85 +73263,143 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
const _converse = this.__super__._converse; const _converse = this.__super__._converse;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
_converse.getDevicesForContact(this.get('jid')).then(devices => { _converse.getDevicesForContact(this.get('jid')).then(devices => {
const promises = devices.map(device => device.getBundle()); Promise.all(devices.map(device => device.getBundle())).then(() => this.buildSessions(devices)).then(() => resolve(devices)).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
Promise.all(promises).then(() => {
this.buildSessions(devices).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));
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); });
},
buildSession(device) {
const _converse = this.__super__._converse;
const bundle = device.get('bundle'),
address = new libsignal.SignalProtocolAddress(device.get('jid'), device.get('id')),
sessionBuilder = new libsignal.SessionBuilder(_converse.omemo_store, address),
prekey = device.getRandomPreKey();
return sessionBuilder.processPreKey({
'registrationId': _converse.omemo_store.get('registration_id'),
'identityKey': _converse.omemo_store.get('identity_keypair'),
'signedPreKey': {
'keyId': bundle.signed_prekey.id,
// <Number>
'publicKey': u.base64ToArrayBuffer(bundle.signed_prekey.public_key),
'signature': u.base64ToArrayBuffer(bundle.signed_prekey.signature)
},
'preKey': {
'keyId': prekey.id,
// <Number>
'publicKey': u.base64ToArrayBuffer(prekey.key)
}
});
}, },
buildSessions(devices) { buildSessions(devices) {
return Promise.all(devices.map(device => this.buildSession(device)));
},
encryptMessage(plaintext) {
// The client MUST use fresh, randomly generated key/IV pairs
// with AES-128 in Galois/Counter Mode (GCM).
const TAG_LENGTH = 128,
iv = window.crypto.getRandomValues(new window.Uint8Array(16));
let key;
return window.crypto.subtle.generateKey({
'name': "AES-GCM",
'length': 256
}, true, // extractable
["encrypt", "decrypt"] // key usages
).then(result => {
key = result;
const algo = {
'name': 'AES-GCM',
'iv': iv,
'tagLength': TAG_LENGTH
};
return window.crypto.subtle.encrypt(algo, key, new TextEncoder().encode(plaintext));
}).then(ciphertext => {
return window.crypto.subtle.exportKey("jwk", key).then(key_str => {
return Promise.resolve({
'key_str': key_str,
'tag': ciphertext.slice(ciphertext.byteLength - (TAG_LENGTH + 7 >> 3)),
'iv': iv
});
});
});
},
encryptKey(plaintext, device) {
const _converse = this.__super__._converse, const _converse = this.__super__._converse,
device_id = _converse.omemo_store.get('device_id'); address = new libsignal.SignalProtocolAddress(this.get('jid'), device.get('id')),
sessionCipher = new window.libsignal.SessionCipher(_converse.omemo_store, address);
return Promise.all(_.map(devices, device => { return sessionCipher.encrypt(plaintext);
const recipient_id = device['id']; },
const address = new libsignal.SignalProtocolAddress(parseInt(recipient_id, 10), device_id);
const sessionBuilder = new libsignal.SessionBuilder(_converse.omemo_store, address);
return sessionBuilder.processPreKey({
'registrationId': _converse.omemo_store.get('registration_id'),
'identityKey': _converse.omemo_store.get('identity_keypair'),
'signedPreKey': {
'keyId': '',
// <Number>,
'publicKey': '',
// <ArrayBuffer>,
'signature': '' // <ArrayBuffer>
}, addKeysToMessageStanza(stanza, devices, payloads) {
'preKey': { for (var i in payloads) {
'keyId': '', if (Object.prototype.hasOwnProperty.call(payloads, i)) {
// <Number>, const payload = btoa(JSON.stringify(payloads[i]));
'publicKey': '' // <ArrayBuffer> 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();
} }
}); }
})); }
},
encryptMessage(message) {// TODO: return Promise.resolve(stanza);
// const { _converse } = this.__super__;
// const plaintext = message.get('message');
// const address = new libsignal.SignalProtocolAddress(recipientId, deviceId);
// return new Promise((resolve, reject) => {
// var sessionCipher = new window.libsignal.SessionCipher(_converse.omemo_store, address);
// sessionCipher.encrypt(plaintext).then((ciphertext) => {});
// });
}, },
createOMEMOMessageStanza(message, bundles) { createOMEMOMessageStanza(message, devices) {
const _converse = this.__super__._converse, const _converse = this.__super__._converse,
__ = _converse.__; __ = _converse.__;
const body = __("This is an OMEMO encrypted message which your client doesn’t seem to support. " + "Find more information on https://conversations.im/omemo"); const body = __("This is an OMEMO encrypted message which your client doesn’t seem to support. " + "Find more information on https://conversations.im/omemo"); // An encrypted header is added to the message for each device that is supposed to receive it.
// These headers simply contain the key that the payload message is encrypted with,
// and they are separately encrypted using the session corresponding to the counterpart device.
return new Promise((resolve, reject) => {
this.encryptMessage(message).then(payload => { const stanza = $msg({
const stanza = $msg({ 'from': _converse.connection.jid,
'from': _converse.connection.jid, 'to': this.get('jid'),
'to': this.get('jid'), 'type': this.get('message_type'),
'type': this.get('message_type'), 'id': message.get('msgid')
'id': message.get('msgid') }).c('body').t(body).up().c('encrypted', {
}).c('body').t(body).up().c('encrypted').t(payload).c('header').t(payload).up(); 'xmlns': Strophe.NS.OMEMO
}).c('header', {
_.forEach(bundles, bundle => { 'sid': _converse.omemo_store.get('device_id')
const prekey = bundle.prekeys[Math.random(bundle.prekeys.length)].textContent; });
stanza('key', { return this.encryptMessage(message).then(payload => {
'rid': bundle.identity_key // The 16 bytes key and the GCM authentication tag (The tag
}).t(prekey).up(); // SHOULD have at least 128 bit) are concatenated and for each
}); // TODO: set storage hint urn:xmpp:hints // intended recipient device, i.e. both own devices as well as
// devices associated with the contact, the result of this
// concatenation is encrypted using the corresponding
resolve(stanza); // long-standing SignalProtocol session.
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); // 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));
}); });
}, },
sendMessage(attrs) { sendMessage(attrs) {
const _converse = this.__super__._converse;
if (this.get('omemo_active')) { if (this.get('omemo_active')) {
const message = this.messages.create(attrs); const message = this.messages.create(attrs);
this.getBundlesAndBuildSessions().then(bundles => this.createOMEMOMessageStanza(message, bundles)).then(stanza => this.sendMessageStanza(stanza)); this.getBundlesAndBuildSessions().then(devices => this.createOMEMOMessageStanza(message, devices)).then(stanza => this.sendMessageStanza(stanza)).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
} else { } else {
return this.__super__.sendMessage.apply(this, arguments); return this.__super__.sendMessage.apply(this, arguments);
} }
...@@ -73643,6 +73701,12 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ ...@@ -73643,6 +73701,12 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
'trusted': UNDECIDED 'trusted': UNDECIDED
}, },
getRandomPreKey() {
// XXX: assumes that the bundle has already been fetched
const bundle = this.get('bundle');
return bundle.prekeys[u.getRandomInt(bundle.prekeys.length)];
},
fetchBundleFromServer() { fetchBundleFromServer() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const stanza = $iq({ const stanza = $iq({
...@@ -73670,7 +73734,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ ...@@ -73670,7 +73734,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
* this device, if the information is not at hand already. * this device, if the information is not at hand already.
*/ */
if (this.get('bundle')) { if (this.get('bundle')) {
return Promise.resolve(this.get('bundle').toJSON()); return Promise.resolve(this.get('bundle').toJSON(), this);
} else { } else {
return this.fetchBundleFromServer(); return this.fetchBundleFromServer();
} }
...@@ -82446,6 +82510,10 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ ...@@ -82446,6 +82510,10 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
return bytes.buffer; return bytes.buffer;
}; };
u.getRandomInt = function (max) {
return Math.floor(Math.random() * Math.floor(max));
};
u.getUniqueId = function () { u.getUniqueId = function () {
return 'xxxxxxxx-xxxx'.replace(/[x]/g, function (c) { return 'xxxxxxxx-xxxx'.replace(/[x]/g, function (c) {
var r = Math.random() * 16 | 0, var r = Math.random() * 16 | 0,
...@@ -82474,7 +82542,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ ...@@ -82474,7 +82542,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
// //
// This is the utilities module. // This is the utilities module.
// //
// Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com> // Copyright (c) 2013-2018, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2) // Licensed under the Mozilla Public License (MPLv2)
// //
...@@ -10,6 +10,122 @@ ...@@ -10,6 +10,122 @@
describe("The OMEMO module", function() { describe("The OMEMO module", function() {
it("enables encrypted messages to be sent",
mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {},
function (done, _converse) {
var sent_stanza;
let iq_stanza;
test_utils.createContacts(_converse, 'current', 1);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
// First, fetch own device list
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) { iq_stanza = iq.nodeTree;}
return node;
}).length;
}).then(() => {
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': '482886413b977930064a5888b92134fe'}).up()
_converse.connection._dataRecv(test_utils.createRequest(stanza));
_converse.emit('OMEMOInitialized');
// Check that device list for contact is fetched when chat is opened.
test_utils.openChatBoxFor(_converse, contact_jid);
return test_utils.waitUntil(() => {
return _.filter(
_converse.connection.IQ_stanzas,
(iq) => {
const node = iq.nodeTree.querySelector('iq[to="'+contact_jid+'"] query[node="eu.siacs.conversations.axolotl.devicelist"]');
if (node) { iq_stanza = iq.nodeTree; }
return node;
}).length;
});
}).then(() => {
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));
const devicelist = _converse.devicelists.create({'jid': contact_jid});
expect(devicelist.devices.length).toBe(1);
const view = _converse.chatboxviews.get(contact_jid);
view.model.set('omemo_active', true);
const textarea = view.el.querySelector('.chat-textarea');
textarea.value = 'This message will be encrypted';
view.keyPressed({
target: textarea,
preventDefault: _.noop,
keyCode: 13 // Enter
});
return test_utils.waitUntil(() => {
return _.filter(
_converse.connection.IQ_stanzas,
(iq) => {
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;
});
}).then(() => {
const stanza = $iq({
'from': contact_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:555"})
.c('item')
.c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
.c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t(btoa('1111')).up()
.c('signedPreKeySignature').t(btoa('2222')).up()
.c('identityKey').t(btoa('3333')).up()
.c('prekeys')
.c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1001')).up()
.c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1002')).up()
.c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1003'));
spyOn(_converse.connection, 'send').and.callFake(stanza => { sent_stanza = stanza });
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => sent_stanza);
}).then(function () {
expect(sent_stanza.toLocaleString()).toBe(
`<message from='dummy@localhost/resource' to='max.frankfurter@localhost' `+
`type='chat' id='${sent_stanza.nodeTree.getAttribute('id')}' xmlns='jabber:client'>`+
`<body>This is an OMEMO encrypted message which your client doesn’t seem to support. Find more information on https://conversations.im/omemo</body>`+
`<encrypted xmlns='eu.siacs.conversations.axolotl'>`+
`<header sid='123456789'>`+
`<key>eyJpdiI6IjEyMzQ1In0=</key>`+
`<iv>12345</iv>`+
`</header>`+
`</encrypted>`+
`</message>`);
done();
});
}));
it("will add processing hints to sent out encrypted <message> stanzas", it("will add processing hints to sent out encrypted <message> stanzas",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
...@@ -24,8 +140,8 @@ ...@@ -24,8 +140,8 @@
function (done, _converse) { function (done, _converse) {
let iq_stanza; let iq_stanza;
test_utils.createContacts(_converse, 'current'); test_utils.createContacts(_converse, 'current', 1);
const contact_jid = mock.cur_names[3].replace(/ /g,'.').toLowerCase() + '@localhost'; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.waitUntil(function () { test_utils.waitUntil(function () {
return _.filter( return _.filter(
......
...@@ -93,86 +93,143 @@ ...@@ -93,86 +93,143 @@
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
_converse.getDevicesForContact(this.get('jid')) _converse.getDevicesForContact(this.get('jid'))
.then((devices) => { .then((devices) => {
const promises = devices.map((device) => device.getBundle()); Promise.all(devices.map((device) => device.getBundle()))
Promise.all(promises).then(() => { .then(() => this.buildSessions(devices))
this.buildSessions(devices) .then(() => resolve(devices))
.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)); }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); });
},
buildSession (device) {
const { _converse } = this.__super__;
const bundle = device.get('bundle'),
address = new libsignal.SignalProtocolAddress(device.get('jid'), device.get('id')),
sessionBuilder = new libsignal.SessionBuilder(_converse.omemo_store, address),
prekey = device.getRandomPreKey();
return sessionBuilder.processPreKey({
'registrationId': _converse.omemo_store.get('registration_id'),
'identityKey': _converse.omemo_store.get('identity_keypair'),
'signedPreKey': {
'keyId': bundle.signed_prekey.id, // <Number>
'publicKey': u.base64ToArrayBuffer(bundle.signed_prekey.public_key),
'signature': u.base64ToArrayBuffer(bundle.signed_prekey.signature)
},
'preKey': {
'keyId': prekey.id, // <Number>
'publicKey': u.base64ToArrayBuffer(prekey.key),
}
})
}, },
buildSessions (devices) { buildSessions (devices) {
return Promise.all(devices.map((device) => this.buildSession(device)));
},
encryptMessage (plaintext) {
// The client MUST use fresh, randomly generated key/IV pairs
// with AES-128 in Galois/Counter Mode (GCM).
const TAG_LENGTH = 128,
iv = window.crypto.getRandomValues(new window.Uint8Array(16));
let key;
return window.crypto.subtle.generateKey({
'name': "AES-GCM",
'length': 256
},
true, // extractable
["encrypt", "decrypt"] // key usages
).then((result) => {
key = result;
const algo = {
'name': 'AES-GCM',
'iv': iv,
'tagLength': TAG_LENGTH
}
return window.crypto.subtle.encrypt(algo, key, new TextEncoder().encode(plaintext));
}).then((ciphertext) => {
return window.crypto.subtle.exportKey("jwk", key)
.then((key_str) => {
return Promise.resolve({
'key_str': key_str,
'tag': ciphertext.slice(ciphertext.byteLength - ((TAG_LENGTH + 7) >> 3)),
'iv': iv
});
});
});
},
encryptKey (plaintext, device) {
const { _converse } = this.__super__, const { _converse } = this.__super__,
device_id = _converse.omemo_store.get('device_id'); address = new libsignal.SignalProtocolAddress(this.get('jid'), device.get('id')),
sessionCipher = new window.libsignal.SessionCipher(_converse.omemo_store, address);
return Promise.all(_.map(devices, (device) => {
const recipient_id = device['id']; return sessionCipher.encrypt(plaintext);
const address = new libsignal.SignalProtocolAddress(parseInt(recipient_id, 10), device_id);
const sessionBuilder = new libsignal.SessionBuilder(_converse.omemo_store, address);
return sessionBuilder.processPreKey({
'registrationId': _converse.omemo_store.get('registration_id'),
'identityKey': _converse.omemo_store.get('identity_keypair'),
'signedPreKey': {
'keyId': '', // <Number>,
'publicKey': '', // <ArrayBuffer>,
'signature': '', // <ArrayBuffer>
},
'preKey': {
'keyId': '', // <Number>,
'publicKey': '', // <ArrayBuffer>
}
});
}));
}, },
encryptMessage (message) { addKeysToMessageStanza (stanza, devices, payloads) {
// TODO: for (var i in payloads) {
// const { _converse } = this.__super__; if (Object.prototype.hasOwnProperty.call(payloads, i)) {
// const plaintext = message.get('message'); const payload = btoa(JSON.stringify(payloads[i]))
// const address = new libsignal.SignalProtocolAddress(recipientId, deviceId); const prekey = 3 == parseInt(payloads[i].type, 10)
// return new Promise((resolve, reject) => { if (i == payloads.length-1) {
// var sessionCipher = new window.libsignal.SessionCipher(_converse.omemo_store, address); stanza.c('key', {'rid': devices.get('id') }).t(payload)
// sessionCipher.encrypt(plaintext).then((ciphertext) => {}); 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()
}
}
}
return Promise.resolve(stanza);
}, },
createOMEMOMessageStanza (message, bundles) { createOMEMOMessageStanza (message, devices) {
const { _converse } = this.__super__, { __ } = _converse; const { _converse } = this.__super__, { __ } = _converse;
const body = __("This is an OMEMO encrypted message which your client doesn’t seem to support. "+ const body = __("This is an OMEMO encrypted message which your client doesn’t seem to support. "+
"Find more information on https://conversations.im/omemo"); "Find more information on https://conversations.im/omemo");
return new Promise((resolve, reject) => {
this.encryptMessage(message).then((payload) => { // An encrypted header is added to the message for each device that is supposed to receive it.
const stanza = $msg({ // These headers simply contain the key that the payload message is encrypted with,
'from': _converse.connection.jid, // and they are separately encrypted using the session corresponding to the counterpart device.
'to': this.get('jid'), const stanza = $msg({
'type': this.get('message_type'), 'from': _converse.connection.jid,
'id': message.get('msgid') 'to': this.get('jid'),
}).c('body').t(body).up() 'type': this.get('message_type'),
.c('encrypted').t(payload) 'id': message.get('msgid')
.c('header').t(payload).up() }).c('body').t(body).up()
.c('encrypted', {'xmlns': Strophe.NS.OMEMO})
_.forEach(bundles, (bundle) => { .c('header', {'sid': _converse.omemo_store.get('device_id')});
const prekey = bundle.prekeys[Math.random(bundle.prekeys.length)].textContent;
stanza('key', {'rid': bundle.identity_key}).t(prekey).up() return this.encryptMessage(message).then((payload) => {
}); // The 16 bytes key and the GCM authentication tag (The tag
// TODO: set storage hint urn:xmpp:hints // SHOULD have at least 128 bit) are concatenated and for each
resolve(stanza); // intended recipient device, i.e. both own devices as well as
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); // devices associated with the contact, the result of this
// concatenation is encrypted using the corresponding
// 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));
}); });
}, },
sendMessage (attrs) { sendMessage (attrs) {
const { _converse } = this.__super__;
if (this.get('omemo_active')) { if (this.get('omemo_active')) {
const message = this.messages.create(attrs); const message = this.messages.create(attrs);
this.getBundlesAndBuildSessions() this.getBundlesAndBuildSessions()
.then((bundles) => this.createOMEMOMessageStanza(message, bundles)) .then((devices) => this.createOMEMOMessageStanza(message, devices))
.then((stanza) => this.sendMessageStanza(stanza)); .then((stanza) => this.sendMessageStanza(stanza))
.catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
} else { } else {
return this.__super__.sendMessage.apply(this, arguments); return this.__super__.sendMessage.apply(this, arguments);
} }
}, }
}, },
ChatBoxView: { ChatBoxView: {
...@@ -439,6 +496,12 @@ ...@@ -439,6 +496,12 @@
'trusted': UNDECIDED 'trusted': UNDECIDED
}, },
getRandomPreKey () {
// XXX: assumes that the bundle has already been fetched
const bundle = this.get('bundle');
return bundle.prekeys[u.getRandomInt(bundle.prekeys.length)];
},
fetchBundleFromServer () { fetchBundleFromServer () {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const stanza = $iq({ const stanza = $iq({
...@@ -467,7 +530,7 @@ ...@@ -467,7 +530,7 @@
* this device, if the information is not at hand already. * this device, if the information is not at hand already.
*/ */
if (this.get('bundle')) { if (this.get('bundle')) {
return Promise.resolve(this.get('bundle').toJSON()); return Promise.resolve(this.get('bundle').toJSON(), this);
} else { } else {
return this.fetchBundleFromServer(); return this.fetchBundleFromServer();
} }
......
...@@ -883,6 +883,10 @@ ...@@ -883,6 +883,10 @@
return bytes.buffer return bytes.buffer
}; };
u.getRandomInt = function (max) {
return Math.floor(Math.random() * Math.floor(max));
};
u.getUniqueId = function () { u.getUniqueId = function () {
return 'xxxxxxxx-xxxx'.replace(/[x]/g, function(c) { return 'xxxxxxxx-xxxx'.replace(/[x]/g, function(c) {
var r = Math.random() * 16 | 0, var r = Math.random() * 16 | 0,
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
// //
// This is the utilities module. // This is the utilities module.
// //
// Copyright (c) 2012-2017, Jan-Carel Brand <jc@opkode.com> // Copyright (c) 2013-2018, Jan-Carel Brand <jc@opkode.com>
// Licensed under the Mozilla Public License (MPLv2) // Licensed under the Mozilla Public License (MPLv2)
// //
/*global define, escape, Jed */ /*global define, escape, Jed */
......
...@@ -8,6 +8,22 @@ ...@@ -8,6 +8,22 @@
var $iq = converse.env.$iq; var $iq = converse.env.$iq;
window.libsignal = { window.libsignal = {
'SignalProtocolAddress': function (name, device_id) {
this.name = name;
this.deviceId = device_id;
},
'SessionCipher': function (storage, remote_address) {
this.remoteAddress = remote_address;
this.storage = storage;
this.encrypt = () => Promise.resolve({
'iv': '12345'
});
},
'SessionBuilder': function (storage, remote_address) {
this.processPreKey = function () {
return Promise.resolve();
}
},
'KeyHelper': { 'KeyHelper': {
'generateIdentityKeyPair': function () { 'generateIdentityKeyPair': function () {
return Promise.resolve({ return Promise.resolve({
......
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