Commit 7c43d043 authored by JC Brand's avatar JC Brand

Refactor OMEMO.

- Add hooks to the stanza parsers so that plugins can do additional parsing.
- Change ChatBox instance methods to functions and use them for stanza parsing.
- Move encrypt and decrypt messages to `converse.env.omemo`

Apparently, when receving a 1:1 carbon message, a device was wrongly created
for the contact's device list, instead of our own.
parent fce337e3
...@@ -7307,9 +7307,9 @@ ...@@ -7307,9 +7307,9 @@
}, },
"dependencies": { "dependencies": {
"dot-prop": { "dot-prop": {
"version": "5.2.0", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
"integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==",
"dev": true, "dev": true,
"requires": { "requires": {
"is-obj": "^2.0.0" "is-obj": "^2.0.0"
...@@ -11651,9 +11651,9 @@ ...@@ -11651,9 +11651,9 @@
} }
}, },
"git-url-parse": { "git-url-parse": {
"version": "11.1.3", "version": "11.2.0",
"resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-11.1.3.tgz", "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-11.2.0.tgz",
"integrity": "sha512-GPsfwticcu52WQ+eHp0IYkAyaOASgYdtsQDIt4rUp6GbiNt1P9ddrh3O0kQB0eD4UJZszVqNT3+9Zwcg40fywA==", "integrity": "sha512-KPoHZg8v+plarZvto4ruIzzJLFQoRx+sUs5DQSr07By9IBKguVd+e6jwrFR6/TP6xrCJlNV1tPqLO1aREc7O2g==",
"dev": true, "dev": true,
"requires": { "requires": {
"git-up": "^4.0.0" "git-up": "^4.0.0"
...@@ -14712,9 +14712,9 @@ ...@@ -14712,9 +14712,9 @@
} }
}, },
"node-fetch": { "node-fetch": {
"version": "2.6.0", "version": "2.6.1",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
"integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==",
"dev": true "dev": true
}, },
"node-fetch-npm": { "node-fetch-npm": {
...@@ -21278,9 +21278,9 @@ ...@@ -21278,9 +21278,9 @@
} }
}, },
"rxjs": { "rxjs": {
"version": "6.6.2", "version": "6.6.3",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.2.tgz", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz",
"integrity": "sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg==", "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"tslib": "^1.9.0" "tslib": "^1.9.0"
......
/*global mock, converse */ /*global mock, converse */
const { $iq, $pres, $msg, _, Strophe } = converse.env; const { $iq, $pres, $msg, _, omemo, Strophe } = converse.env;
const u = converse.env.utils; const u = converse.env.utils;
async function deviceListFetched (_converse, jid) { async function deviceListFetched (_converse, jid) {
...@@ -78,15 +78,12 @@ describe("The OMEMO module", function() { ...@@ -78,15 +78,12 @@ describe("The OMEMO module", function() {
const message = 'This message will be encrypted' const message = 'This message will be encrypted'
await mock.waitForRoster(_converse, 'current', 1); await mock.waitForRoster(_converse, 'current', 1);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const payload = await omemo.encryptMessage(message);
const view = await mock.openChatBoxFor(_converse, contact_jid); const result = await omemo.decryptMessage(payload);
const payload = await view.model.encryptMessage(message);
const result = await view.model.decryptMessage(payload);
expect(result).toBe(message); expect(result).toBe(message);
done(); done();
})); }));
it("enables encrypted messages to be sent and received", it("enables encrypted messages to be sent and received",
mock.initConverse( mock.initConverse(
['rosterGroupsFetched', 'chatBoxesFetched'], {}, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
...@@ -182,10 +179,9 @@ describe("The OMEMO module", function() { ...@@ -182,10 +179,9 @@ describe("The OMEMO module", function() {
`</message>`); `</message>`);
// Test reception of an encrypted message // Test reception of an encrypted message
let obj = await view.model.encryptMessage('This is an encrypted message from the contact') let obj = await omemo.encryptMessage('This is an encrypted message from the contact')
// XXX: Normally the key will be encrypted via libsignal. // XXX: Normally the key will be encrypted via libsignal.
// However, we're mocking libsignal in the tests, so we include // However, we're mocking libsignal in the tests, so we include it as plaintext in the message.
// it as plaintext in the message.
stanza = $msg({ stanza = $msg({
'from': contact_jid, 'from': contact_jid,
'to': _converse.connection.jid, 'to': _converse.connection.jid,
...@@ -205,7 +201,7 @@ describe("The OMEMO module", function() { ...@@ -205,7 +201,7 @@ describe("The OMEMO module", function() {
.toBe('This is an encrypted message from the contact'); .toBe('This is an encrypted message from the contact');
// #1193 Check for a received message without <body> tag // #1193 Check for a received message without <body> tag
obj = await view.model.encryptMessage('Another received encrypted message without fallback') obj = await omemo.encryptMessage('Another received encrypted message without fallback')
stanza = $msg({ stanza = $msg({
'from': contact_jid, 'from': contact_jid,
'to': _converse.connection.jid, 'to': _converse.connection.jid,
...@@ -383,6 +379,9 @@ describe("The OMEMO module", function() { ...@@ -383,6 +379,9 @@ describe("The OMEMO module", function() {
await u.waitUntil(() => initializedOMEMO(_converse)); await u.waitUntil(() => initializedOMEMO(_converse));
await mock.openChatBoxFor(_converse, contact_jid); await mock.openChatBoxFor(_converse, contact_jid);
let iq_stanza = await u.waitUntil(() => deviceListFetched(_converse, contact_jid)); let iq_stanza = await u.waitUntil(() => deviceListFetched(_converse, contact_jid));
const my_devicelist = _converse.devicelists.get({'jid': _converse.bare_jid});
expect(my_devicelist.devices.length).toBe(2);
const stanza = $iq({ const stanza = $iq({
'from': contact_jid, 'from': contact_jid,
'id': iq_stanza.getAttribute('id'), 'id': iq_stanza.getAttribute('id'),
...@@ -395,14 +394,15 @@ describe("The OMEMO module", function() { ...@@ -395,14 +394,15 @@ describe("The OMEMO module", function() {
.c('device', {'id': '555'}); .c('device', {'id': '555'});
_converse.connection._dataRecv(mock.createRequest(stanza)); _converse.connection._dataRecv(mock.createRequest(stanza));
await u.waitUntil(() => _converse.omemo_store); await u.waitUntil(() => _converse.omemo_store);
const devicelist = _converse.devicelists.get({'jid': contact_jid});
await u.waitUntil(() => devicelist.devices.length === 1); const contact_devicelist = _converse.devicelists.get({'jid': contact_jid});
await u.waitUntil(() => contact_devicelist.devices.length === 1);
const view = _converse.chatboxviews.get(contact_jid); const view = _converse.chatboxviews.get(contact_jid);
view.model.set('omemo_active', true); view.model.set('omemo_active', true);
// Test reception of an encrypted carbon message // Test reception of an encrypted carbon message
const obj = await view.model.encryptMessage('This is an encrypted carbon message from another device of mine') const obj = await omemo.encryptMessage('This is an encrypted carbon message from another device of mine')
const carbon = u.toStanza(` const carbon = u.toStanza(`
<message xmlns="jabber:client" to="romeo@montague.lit/orchard" from="romeo@montague.lit" type="chat"> <message xmlns="jabber:client" to="romeo@montague.lit/orchard" from="romeo@montague.lit" type="chat">
<sent xmlns="urn:xmpp:carbons:2"> <sent xmlns="urn:xmpp:carbons:2">
...@@ -440,10 +440,14 @@ describe("The OMEMO module", function() { ...@@ -440,10 +440,14 @@ describe("The OMEMO module", function() {
expect(view.el.querySelector('.chat-msg__text').textContent.trim()) expect(view.el.querySelector('.chat-msg__text').textContent.trim())
.toBe('This is an encrypted carbon message from another device of mine'); .toBe('This is an encrypted carbon message from another device of mine');
expect(devicelist.devices.length).toBe(2); expect(contact_devicelist.devices.length).toBe(1);
expect(devicelist.devices.at(0).get('id')).toBe('555');
expect(devicelist.devices.at(1).get('id')).toBe('988349631'); // Check that the new device id has been added to my devices
expect(devicelist.devices.get('988349631').get('active')).toBe(true); expect(my_devicelist.devices.length).toBe(3);
expect(my_devicelist.devices.at(0).get('id')).toBe('482886413b977930064a5888b92134fe');
expect(my_devicelist.devices.at(1).get('id')).toBe('123456789');
expect(my_devicelist.devices.at(2).get('id')).toBe('988349631');
expect(my_devicelist.devices.get('988349631').get('active')).toBe(true);
const textarea = view.el.querySelector('.chat-textarea'); const textarea = view.el.querySelector('.chat-textarea');
textarea.value = 'This is an encrypted message from this device'; textarea.value = 'This is an encrypted message from this device';
...@@ -601,7 +605,7 @@ describe("The OMEMO module", function() { ...@@ -601,7 +605,7 @@ describe("The OMEMO module", function() {
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit'; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
await u.waitUntil(() => initializedOMEMO(_converse)); await u.waitUntil(() => initializedOMEMO(_converse));
const obj = await _converse.ChatBox.prototype.encryptMessage('This is an encrypted message from the contact'); const obj = await omemo.encryptMessage('This is an encrypted message from the contact');
// XXX: Normally the key will be encrypted via libsignal. // XXX: Normally the key will be encrypted via libsignal.
// However, we're mocking libsignal in the tests, so we include // However, we're mocking libsignal in the tests, so we include
// it as plaintext in the message. // it as plaintext in the message.
...@@ -631,8 +635,8 @@ describe("The OMEMO module", function() { ...@@ -631,8 +635,8 @@ describe("The OMEMO module", function() {
return generateMissingPreKeys.apply(_converse.omemo_store, arguments); return generateMissingPreKeys.apply(_converse.omemo_store, arguments);
}); });
_converse.connection._dataRecv(mock.createRequest(stanza)); _converse.connection._dataRecv(mock.createRequest(stanza));
let iq_stanza = await u.waitUntil(() => _converse.chatboxviews.get(contact_jid));
iq_stanza = await deviceListFetched(_converse, contact_jid); let iq_stanza = await deviceListFetched(_converse, contact_jid);
stanza = $iq({ stanza = $iq({
'from': contact_jid, 'from': contact_jid,
'id': iq_stanza.getAttribute('id'), 'id': iq_stanza.getAttribute('id'),
...@@ -688,7 +692,6 @@ describe("The OMEMO module", function() { ...@@ -688,7 +692,6 @@ describe("The OMEMO module", function() {
done(); done();
})); }));
it("updates device lists based on PEP messages", it("updates device lists based on PEP messages",
mock.initConverse( mock.initConverse(
['rosterGroupsFetched'], {'allow_non_roster_messaging': true}, ['rosterGroupsFetched'], {'allow_non_roster_messaging': true},
......
This diff is collapsed.
...@@ -457,8 +457,6 @@ converse.plugins.add('converse-chat', { ...@@ -457,8 +457,6 @@ converse.plugins.add('converse-chat', {
attrs.stanza && log.error(attrs.stanza); attrs.stanza && log.error(attrs.stanza);
return log.error(attrs.message); return log.error(attrs.message);
} }
// TODO: move to OMEMO
attrs = attrs.encrypted ? await this.decrypt(attrs) : attrs;
const message = this.getDuplicateMessage(attrs); const message = this.getDuplicateMessage(attrs);
if (message) { if (message) {
this.updateMessage(message, attrs); this.updateMessage(message, attrs);
...@@ -1215,7 +1213,7 @@ converse.plugins.add('converse-chat', { ...@@ -1215,7 +1213,7 @@ converse.plugins.add('converse-chat', {
} }
const has_body = !!sizzle(`body, encrypted[xmlns="${Strophe.NS.OMEMO}"]`, stanza).length; const has_body = !!sizzle(`body, encrypted[xmlns="${Strophe.NS.OMEMO}"]`, stanza).length;
const chatbox = await api.chats.get(attrs.contact_jid, {'nickname': attrs.nick }, has_body); const chatbox = await api.chats.get(attrs.contact_jid, {'nickname': attrs.nick }, has_body);
chatbox && await chatbox.queueMessage(attrs); await chatbox?.queueMessage(attrs);
/** /**
* Triggered when a message stanza is been received and processed. * Triggered when a message stanza is been received and processed.
* @event _converse#message * @event _converse#message
......
...@@ -1993,8 +1993,6 @@ converse.plugins.add('converse-muc', { ...@@ -1993,8 +1993,6 @@ converse.plugins.add('converse-muc', {
attrs.stanza && log.error(attrs.stanza); attrs.stanza && log.error(attrs.stanza);
return log.error(attrs.message); return log.error(attrs.message);
} }
// TODO: move to OMEMO
attrs = attrs.encrypted ? await this.decrypt(attrs) : attrs;
const message = this.getDuplicateMessage(attrs); const message = this.getDuplicateMessage(attrs);
if (message) { if (message) {
return this.updateMessage(message, attrs); return this.updateMessage(message, attrs);
......
...@@ -362,7 +362,6 @@ const st = { ...@@ -362,7 +362,6 @@ const st = {
}, {}); }, {});
}, },
/** /**
* Parses a passed in message stanza and returns an object of attributes. * Parses a passed in message stanza and returns an object of attributes.
* @method st#parseMessage * @method st#parseMessage
...@@ -418,7 +417,6 @@ const st = { ...@@ -418,7 +417,6 @@ const st = {
); );
} }
const is_headline = st.isHeadline(stanza); const is_headline = st.isHeadline(stanza);
const is_server_message = st.isServerMessage(stanza); const is_server_message = st.isServerMessage(stanza);
let contact, contact_jid; let contact, contact_jid;
...@@ -534,7 +532,12 @@ const st = { ...@@ -534,7 +532,12 @@ const st = {
// We prefer to use one of the XEP-0359 unique and stable stanza IDs // We prefer to use one of the XEP-0359 unique and stable stanza IDs
// as the Model id, to avoid duplicates. // as the Model id, to avoid duplicates.
attrs['id'] = attrs['origin_id'] || attrs[`stanza_id ${(attrs.from)}`] || u.getUniqueId(); attrs['id'] = attrs['origin_id'] || attrs[`stanza_id ${(attrs.from)}`] || u.getUniqueId();
return attrs;
/**
* *Hook* which allows plugins to add additional parsing
* @event _converse#parseMessage
*/
return api.hook('parseMessage', attrs);
}, },
/** /**
...@@ -678,7 +681,11 @@ const st = { ...@@ -678,7 +681,11 @@ const st = {
} }
// We prefer to use one of the XEP-0359 unique and stable stanza IDs as the Model id, to avoid duplicates. // We prefer to use one of the XEP-0359 unique and stable stanza IDs as the Model id, to avoid duplicates.
attrs['id'] = attrs['origin_id'] || attrs[`stanza_id ${(attrs.from_muc || attrs.from)}`] || u.getUniqueId(); attrs['id'] = attrs['origin_id'] || attrs[`stanza_id ${(attrs.from_muc || attrs.from)}`] || u.getUniqueId();
return attrs; /**
* *Hook* which allows plugins to add additional parsing
* @event _converse#parseMUCMessage
*/
return api.hook('parseMUCMessage', attrs);
}, },
/** /**
......
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