Commit dd0c0b6c authored by JC Brand's avatar JC Brand

Add a new `active` flag for OMEMO devices.

Instead of deleting devices that are not returned in the device list,
set an `active` flag, so that we don't remove the trust setting.

Set deactivated devices to `active` when a receive an OMEMO message from
it.

Also, set omemo_supported to true when we've succesfully decrypted a
message.
parent c32ecb7e
This diff is collapsed.
...@@ -701,8 +701,11 @@ ...@@ -701,8 +701,11 @@
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(_converse.devicelists.length).toBe(2); expect(_converse.devicelists.length).toBe(2);
expect(devices.length).toBe(2); expect(devices.length).toBe(3);
expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('4223,4224'); expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('1234,4223,4224');
expect(devices.get('1234').get('active')).toBe(false);
expect(devices.get('4223').get('active')).toBe(true);
expect(devices.get('4224').get('active')).toBe(true);
// Check that own devicelist gets updated // Check that own devicelist gets updated
stanza = $msg({ stanza = $msg({
...@@ -723,6 +726,9 @@ ...@@ -723,6 +726,9 @@
devices = _converse.devicelists.get(_converse.bare_jid).devices; devices = _converse.devicelists.get(_converse.bare_jid).devices;
expect(devices.length).toBe(3); expect(devices.length).toBe(3);
expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('123456789,555,777'); expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('123456789,555,777');
expect(devices.get('123456789').get('active')).toBe(true);
expect(devices.get('555').get('active')).toBe(true);
expect(devices.get('777').get('active')).toBe(true);
_converse.connection.IQ_stanzas = []; _converse.connection.IQ_stanzas = [];
...@@ -769,8 +775,12 @@ ...@@ -769,8 +775,12 @@
devices = _converse.devicelists.get(_converse.bare_jid).devices; devices = _converse.devicelists.get(_converse.bare_jid).devices;
// The device id for this device (123456789) was also generated and added to the list, // The device id for this device (123456789) was also generated and added to the list,
// which is why we have 2 devices now. // which is why we have 2 devices now.
expect(devices.length).toBe(2); expect(devices.length).toBe(4);
expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('123456789,444'); expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('123456789,444,555,777');
expect(devices.get('123456789').get('active')).toBe(true);
expect(devices.get('444').get('active')).toBe(true);
expect(devices.get('555').get('active')).toBe(false);
expect(devices.get('777').get('active')).toBe(false);
done(); done();
})); }));
......
...@@ -154,7 +154,7 @@ converse.plugins.add('converse-omemo', { ...@@ -154,7 +154,7 @@ converse.plugins.add('converse-omemo', {
initialize () { initialize () {
const { _converse } = this.__super__; const { _converse } = this.__super__;
const jid = this.model.get('jid'); const jid = this.model.get('jid');
this.devicelist = _converse.devicelists.get(jid) || _converse.devicelists.create({'jid': jid}); this.devicelist = _converse.devicelists.getDeviceList(jid);
this.devicelist.devices.on('change:bundle', this.render, this); this.devicelist.devices.on('change:bundle', this.render, this);
this.devicelist.devices.on('change:trusted', this.render, this); this.devicelist.devices.on('change:trusted', this.render, this);
this.devicelist.devices.on('remove', this.render, this); this.devicelist.devices.on('remove', this.render, this);
...@@ -229,6 +229,24 @@ converse.plugins.add('converse-omemo', { ...@@ -229,6 +229,24 @@ converse.plugins.add('converse-omemo', {
_converse.log(`${e.name} ${e.message}`, Strophe.LogLevel.ERROR); _converse.log(`${e.name} ${e.message}`, Strophe.LogLevel.ERROR);
}, },
async handleDecryptedWhisperMessage (encrypted, key_and_tag) {
const { _converse } = this.__super__,
devicelist = _converse.devicelists.getDeviceList(this.get('jid'));
this.save('omemo_supported', true);
let device = devicelist.get(encrypted.device_id);
if (!device) {
device = devicelist.devices.create({'id': encrypted.device_id, 'jid': this.get('jid')});
}
if (encrypted.payload) {
const key = key_and_tag.slice(0, 16),
tag = key_and_tag.slice(16);
const result = await this.decryptMessage(_.extend(encrypted, {'key': key, 'tag': tag}));
device.save('active', true);
return result;
}
},
decrypt (attrs) { decrypt (attrs) {
const { _converse } = this.__super__, const { _converse } = this.__super__,
session_cipher = this.getSessionCipher(attrs.from, parseInt(attrs.encrypted.device_id, 10)); session_cipher = this.getSessionCipher(attrs.from, parseInt(attrs.encrypted.device_id, 10));
...@@ -237,14 +255,8 @@ converse.plugins.add('converse-omemo', { ...@@ -237,14 +255,8 @@ converse.plugins.add('converse-omemo', {
if (attrs.encrypted.prekey === true) { if (attrs.encrypted.prekey === true) {
let plaintext; let plaintext;
return session_cipher.decryptPreKeyWhisperMessage(u.base64ToArrayBuffer(attrs.encrypted.key), 'binary') return session_cipher.decryptPreKeyWhisperMessage(u.base64ToArrayBuffer(attrs.encrypted.key), 'binary')
.then(key_and_tag => { .then(key_and_tag => this.handleDecryptedWhisperMessage(attrs.encrypted, key_and_tag))
if (attrs.encrypted.payload) { .then(pt => {
const key = key_and_tag.slice(0, 16),
tag = key_and_tag.slice(16);
return this.decryptMessage(_.extend(attrs.encrypted, {'key': key, 'tag': tag}));
}
return Promise.resolve();
}).then(pt => {
plaintext = pt; plaintext = pt;
return _converse.omemo_store.generateMissingPreKeys(); return _converse.omemo_store.generateMissingPreKeys();
}).then(() => _converse.omemo_store.publishBundle()) }).then(() => _converse.omemo_store.publishBundle())
...@@ -260,11 +272,8 @@ converse.plugins.add('converse-omemo', { ...@@ -260,11 +272,8 @@ converse.plugins.add('converse-omemo', {
}); });
} else { } else {
return session_cipher.decryptWhisperMessage(u.base64ToArrayBuffer(attrs.encrypted.key), 'binary') return session_cipher.decryptWhisperMessage(u.base64ToArrayBuffer(attrs.encrypted.key), 'binary')
.then(key_and_tag => { .then(key_and_tag => this.handleDecryptedWhisperMessage(attrs.encrypted, key_and_tag))
const key = key_and_tag.slice(0, 16), .then(plaintext => _.extend(attrs, {'plaintext': plaintext}))
tag = key_and_tag.slice(16);
return this.decryptMessage(_.extend(attrs.encrypted, {'key': key, 'tag': tag}));
}).then(plaintext => _.extend(attrs, {'plaintext': plaintext}))
.catch(e => { .catch(e => {
this.reportDecryptionError(e); this.reportDecryptionError(e);
return attrs; return attrs;
...@@ -625,7 +634,7 @@ converse.plugins.add('converse-omemo', { ...@@ -625,7 +634,7 @@ converse.plugins.add('converse-omemo', {
// concatenation is encrypted using the corresponding // concatenation is encrypted using the corresponding
// long-standing SignalProtocol session. // long-standing SignalProtocol session.
const promises = devices const promises = devices
.filter(device => device.get('trusted') != UNTRUSTED) .filter(device => (device.get('trusted') != UNTRUSTED && device.get('active')))
.map(device => chatbox.encryptKey(obj.key_and_tag, device)); .map(device => chatbox.encryptKey(obj.key_and_tag, device));
return Promise.all(promises) return Promise.all(promises)
...@@ -890,7 +899,8 @@ converse.plugins.add('converse-omemo', { ...@@ -890,7 +899,8 @@ converse.plugins.add('converse-omemo', {
_converse.Device = Backbone.Model.extend({ _converse.Device = Backbone.Model.extend({
defaults: { defaults: {
'trusted': UNDECIDED 'trusted': UNDECIDED,
'active': true
}, },
getRandomPreKey () { getRandomPreKey () {
...@@ -939,6 +949,11 @@ converse.plugins.add('converse-omemo', { ...@@ -939,6 +949,11 @@ converse.plugins.add('converse-omemo', {
model: _converse.Device, model: _converse.Device,
}); });
/**
* @class
* @namespace _converse.DeviceList
* @memberOf _converse
*/
_converse.DeviceList = Backbone.Model.extend({ _converse.DeviceList = Backbone.Model.extend({
idAttribute: 'jid', idAttribute: 'jid',
...@@ -1016,7 +1031,7 @@ converse.plugins.add('converse-omemo', { ...@@ -1016,7 +1031,7 @@ converse.plugins.add('converse-omemo', {
publishDevices () { publishDevices () {
const item = $build('item').c('list', {'xmlns': Strophe.NS.OMEMO}) const item = $build('item').c('list', {'xmlns': Strophe.NS.OMEMO})
this.devices.each(d => item.c('device', {'id': d.get('id')}).up()); this.devices.filter(d => d.get('active')).forEach(d => item.c('device', {'id': d.get('id')}).up());
const options = {'pubsub#access_model': 'open'}; const options = {'pubsub#access_model': 'open'};
return _converse.api.pubsub.publish(null, Strophe.NS.OMEMO_DEVICELIST, item, options, false); return _converse.api.pubsub.publish(null, Strophe.NS.OMEMO_DEVICELIST, item, options, false);
}, },
...@@ -1030,8 +1045,23 @@ converse.plugins.add('converse-omemo', { ...@@ -1030,8 +1045,23 @@ converse.plugins.add('converse-omemo', {
} }
}); });
/**
* @class
* @namespace _converse.DeviceLists
* @memberOf _converse
*/
_converse.DeviceLists = Backbone.Collection.extend({ _converse.DeviceLists = Backbone.Collection.extend({
model: _converse.DeviceList, model: _converse.DeviceList,
/**
* Returns the {@link _converse.DeviceList} for a particular JID.
* The device list will be created if it doesn't exist already.
* @private
* @method _converse.DeviceLists#getDeviceList
* @param { String } jid - The Jabber ID for which the device list will be returned.
*/
getDeviceList (jid) {
return this.get(jid) || this.create({'jid': jid});
}
}); });
...@@ -1056,7 +1086,7 @@ converse.plugins.add('converse-omemo', { ...@@ -1056,7 +1086,7 @@ converse.plugins.add('converse-omemo', {
const device_id = items_el.getAttribute('node').split(':')[1], const device_id = items_el.getAttribute('node').split(':')[1],
jid = stanza.getAttribute('from'), jid = stanza.getAttribute('from'),
bundle_el = sizzle(`item > bundle`, items_el).pop(), bundle_el = sizzle(`item > bundle`, items_el).pop(),
devicelist = _converse.devicelists.get(jid) || _converse.devicelists.create({'jid': jid}), devicelist = _converse.devicelists.getDeviceList(jid),
device = devicelist.devices.get(device_id) || devicelist.devices.create({'id': device_id, 'jid': jid}); device = devicelist.devices.get(device_id) || devicelist.devices.create({'id': device_id, 'jid': jid});
device.save({'bundle': parseBundle(bundle_el)}); device.save({'bundle': parseBundle(bundle_el)});
} }
...@@ -1071,27 +1101,28 @@ converse.plugins.add('converse-omemo', { ...@@ -1071,27 +1101,28 @@ converse.plugins.add('converse-omemo', {
(device) => device.getAttribute('id') (device) => device.getAttribute('id')
); );
const jid = stanza.getAttribute('from'), const jid = stanza.getAttribute('from'),
devicelist = _converse.devicelists.get(jid) || _converse.devicelists.create({'jid': jid}), devicelist = _converse.devicelists.getDeviceList(jid),
devices = devicelist.devices, devices = devicelist.devices,
removed_ids = _.difference(devices.pluck('id'), device_ids); removed_ids = _.difference(devices.pluck('id'), device_ids);
_.forEach(removed_ids, (id) => { _.forEach(removed_ids, (id) => {
if (jid === _converse.bare_jid && id === _converse.omemo_store.get('device_id')) { if (jid === _converse.bare_jid && id === _converse.omemo_store.get('device_id')) {
// We don't remove the current device return // We don't set the current device as inactive
return
} }
devices.get(id).destroy(); devices.get(id).save('active', false);
}); });
_.forEach(device_ids, (device_id) => { _.forEach(device_ids, (device_id) => {
if (!devices.get(device_id)) { const device = devices.get(device_id);
if (device) {
device.save('active', true);
} else {
devices.create({'id': device_id, 'jid': jid}) devices.create({'id': device_id, 'jid': jid})
} }
}); });
if (Strophe.getBareJidFromJid(jid) === _converse.bare_jid) { if (u.isSameBareJID(jid, _converse.bare_jid)) {
// Make sure our own device is on the list (i.e. if it was // Make sure our own device is on the list
// removed, add it again. // (i.e. if it was removed, add it again).
_converse.devicelists.get(_converse.bare_jid).publishCurrentDevice(device_ids); devicelist.publishCurrentDevice(device_ids);
} }
} }
......
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