Commit 15a4bcd1 authored by JC Brand's avatar JC Brand

Add method to generate missing prekeys

When receiving a PreKeySignalMessage, then a prekey has been chosen and
should now be removed from the list of available prekeys in the bundle,
so that a different device doesn't choose it as well.

AFAICT, libsignal removes the prekey, so it's then up to us to
regenerate it and republish our bundle.

updates #497
parent 3d015c78
This diff is collapsed.
...@@ -240,32 +240,30 @@ ...@@ -240,32 +240,30 @@
decrypt (attrs) { decrypt (attrs) {
const { _converse } = this.__super__, const { _converse } = this.__super__,
devicelist = _converse.devicelists.get(attrs.from), address = new libsignal.SignalProtocolAddress(attrs.from, parseInt(attrs.encrypted.device_id, 10)),
device = devicelist.devices.get(attrs.encrypted.device_id),
address = new libsignal.SignalProtocolAddress(
attrs.from,
parseInt(attrs.encrypted.device_id, 10)
),
session_cipher = new window.libsignal.SessionCipher(_converse.omemo_store, address), session_cipher = new window.libsignal.SessionCipher(_converse.omemo_store, address),
libsignal_payload = JSON.parse(atob(attrs.encrypted.key)); libsignal_payload = JSON.parse(atob(attrs.encrypted.key));
// https://xmpp.org/extensions/xep-0384.html#usecases-receiving
if (attrs.encrypted.prekey === 'true') { if (attrs.encrypted.prekey === 'true') {
// If this is the case, a new session is built from this received element. The client let plaintext;
// SHOULD then republish their bundle information, replacing the used PreKey, such
// that it won't be used again by a different client. If the client already has a session
// with the sender's device, it MUST replace this session with the newly built session.
// The client MUST delete the private key belonging to the PreKey after use.
return session_cipher.decryptPreKeyWhisperMessage(libsignal_payload.body, 'binary') return session_cipher.decryptPreKeyWhisperMessage(libsignal_payload.body, 'binary')
.then(key_and_tag => { .then(key_and_tag => {
if (attrs.encrypted.payload) {
const aes_data = this.getKeyAndTag(u.arrayBufferToString(key_and_tag)); const aes_data = this.getKeyAndTag(u.arrayBufferToString(key_and_tag));
return this.decryptMessage(_.extend(attrs.encrypted, {'key': aes_data.key, 'tag': aes_data.tag})); return this.decryptMessage(_.extend(attrs.encrypted, {'key': aes_data.key, 'tag': aes_data.tag}));
}).then(plaintext => { }
// TODO the prekey should now have been removed. return Promise.resolve();
// Double-check that this is the case and then }).then(pt => {
// generate a new key to replace it, before plaintext = pt;
// republishing. return _converse.omemo_store.generateMissingPreKeys();
_converse.omemo_store.publishBundle() }).then(() => _converse.omemo_store.publishBundle())
.then(() => {
if (plaintext) {
return _.extend(attrs, {'plaintext': plaintext}); return _.extend(attrs, {'plaintext': plaintext});
} else {
return _.extend(attrs, {'is_only_key': true});
}
}).catch((e) => { }).catch((e) => {
this.reportDecryptionError(e); this.reportDecryptionError(e);
return attrs; return attrs;
...@@ -446,6 +444,13 @@ ...@@ -446,6 +444,13 @@
'click .toggle-omemo': 'toggleOMEMO' 'click .toggle-omemo': 'toggleOMEMO'
}, },
showMessage (message) {
// We don't show a message if it's only keying material
if (!message.get('is_only_key')) {
return this.__super__.showMessage.apply(this, arguments);
}
},
renderOMEMOToolbarButton () { renderOMEMOToolbarButton () {
const { _converse } = this.__super__, const { _converse } = this.__super__,
{ __ } = _converse; { __ } = _converse;
...@@ -497,6 +502,10 @@ ...@@ -497,6 +502,10 @@
.then(devices => Promise.all(devices.map(d => generateFingerprint(d)))) .then(devices => Promise.all(devices.map(d => generateFingerprint(d))))
} }
_converse.getDeviceForContact = function (jid, device_id) {
return _converse.getDevicesForContact(jid).then(devices => devices.get(device_id));
}
_converse.getDevicesForContact = function (jid) { _converse.getDevicesForContact = function (jid) {
let devicelist; let devicelist;
return _converse.api.waitUntil('OMEMOInitialized') return _converse.api.waitUntil('OMEMOInitialized')
...@@ -705,6 +714,26 @@ ...@@ -705,6 +714,26 @@
return _converse.api.sendIQ(stanza); return _converse.api.sendIQ(stanza);
}, },
generateMissingPreKeys () {
const current_keys = this.getPreKeys(),
missing_keys = _.difference(_.invokeMap(_.range(0, _converse.NUM_PREKEYS), Number.prototype.toString), _.keys(current_keys));
if (missing_keys.length < 1) {
_converse.log("No missing prekeys to generate for our own device", Strophe.LogLevel.WARN);
return Promise.resolve();
}
return Promise.all(_.map(missing_keys, id => libsignal.KeyHelper.generatePreKey(parseInt(id, 10))))
.then(keys => {
_.forEach(keys, k => this.storePreKey(k.keyId, k.keyPair));
const marshalled_keys = _.map(this.getPreKeys(), k => ({'id': k.keyId, 'key': u.arrayBufferToBase64(k.pubKey)})),
devicelist = _converse.devicelists.get(_converse.bare_jid),
device = devicelist.devices.get(this.get('device_id'));
return device.getBundle()
.then(bundle => device.save('bundle', _.extend(bundle, {'prekeys': marshalled_keys})));
});
},
generateBundle () { generateBundle () {
/* The first thing that needs to happen if a client wants to /* The first thing that needs to happen if a client wants to
* start using OMEMO is they need to generate an IdentityKey * start using OMEMO is they need to generate an IdentityKey
......
...@@ -21,6 +21,11 @@ ...@@ -21,6 +21,11 @@
'body': 'c1ph3R73X7', 'body': 'c1ph3R73X7',
'registrationId': '1337' 'registrationId': '1337'
}); });
this.decryptPreKeyWhisperMessage = (key_and_tag) => {
// TODO: remove the prekey
return Promise.resolve(u.stringToArrayBuffer(key_and_tag));
};
this.decryptWhisperMessage = (key_and_tag) => { this.decryptWhisperMessage = (key_and_tag) => {
return Promise.resolve(u.stringToArrayBuffer(key_and_tag)); return Promise.resolve(u.stringToArrayBuffer(key_and_tag));
} }
......
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