diff --git a/CHANGES.md b/CHANGES.md
index 44176df27..c14fc6392 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -2,8 +2,10 @@
## 9.0.0 (Unreleased)
+- Use more specific types for form fields based on XEP-0122
- Fix trimming of chats in overlayed view mode
- #2647: Singleton mode doesn't work
+- OMEMO bugfix: Always create device session based on real JID.
- Emit a `change` event when a configuration setting changes
- 3 New configuration settings:
@@ -18,7 +20,6 @@ Three config settings have been obsoleted:
- show_images_inline
- muc_show_ogp_unfurls
-- Use more specific types for form fields based on XEP-0122
### Breaking Changes
diff --git a/karma.conf.js b/karma.conf.js
index 40701141f..a2d59f99c 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -93,6 +93,7 @@ module.exports = function(config) {
{ pattern: "src/plugins/notifications/tests/notification.js", type: 'module' },
{ pattern: "src/plugins/omemo/tests/media-sharing.js", type: 'module' },
{ pattern: "src/plugins/omemo/tests/omemo.js", type: 'module' },
+ { pattern: "src/plugins/omemo/tests/muc.js", type: 'module' },
{ pattern: "src/plugins/push/tests/push.js", type: 'module' },
{ pattern: "src/plugins/register/tests/register.js", type: 'module' },
{ pattern: "src/plugins/rootview/tests/root.js", type: 'module' },
diff --git a/src/plugins/omemo/tests/muc.js b/src/plugins/omemo/tests/muc.js
new file mode 100644
index 000000000..4b258fd66
--- /dev/null
+++ b/src/plugins/omemo/tests/muc.js
@@ -0,0 +1,478 @@
+/*global mock, converse */
+
+const { $iq, $msg, $pres, Strophe, omemo } = converse.env;
+const u = converse.env.utils;
+
+describe("The OMEMO module", function() {
+
+ it("enables encrypted groupchat messages to be sent and received",
+ mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {
+
+ // MEMO encryption works only in members only conferences
+ // that are non-anonymous.
+ const features = [
+ 'http://jabber.org/protocol/muc',
+ 'jabber:iq:register',
+ 'muc_passwordprotected',
+ 'muc_hidden',
+ 'muc_temporary',
+ 'muc_membersonly',
+ 'muc_unmoderated',
+ 'muc_nonanonymous'
+ ];
+ const muc_jid = 'lounge@montague.lit';
+ await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo', features);
+ const view = _converse.chatboxviews.get('lounge@montague.lit');
+ await u.waitUntil(() => mock.initializedOMEMO(_converse));
+
+ const toolbar = await u.waitUntil(() => view.querySelector('.chat-toolbar'));
+ const el = await u.waitUntil(() => toolbar.querySelector('.toggle-omemo'));
+ el.click();
+ expect(view.model.get('omemo_active')).toBe(true);
+
+ // newguy enters the room
+ const contact_jid = 'newguy@montague.lit';
+ let stanza = $pres({
+ 'to': 'romeo@montague.lit/orchard',
+ 'from': 'lounge@montague.lit/newguy'
+ })
+ .c('x', {xmlns: Strophe.NS.MUC_USER})
+ .c('item', {
+ 'affiliation': 'none',
+ 'jid': 'newguy@montague.lit/_converse.js-290929789',
+ 'role': 'participant'
+ }).tree();
+ _converse.connection._dataRecv(mock.createRequest(stanza));
+
+ // Wait for Converse to fetch newguy's device list
+ let iq_stanza = await u.waitUntil(() => mock.deviceListFetched(_converse, contact_jid));
+ expect(Strophe.serialize(iq_stanza)).toBe(
+ ``+
+ ``+
+ ``+
+ ``+
+ ``);
+
+ // The server returns his device list
+ 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.devicelist"})
+ .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
+ .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
+ .c('device', {'id': '4e30f35051b7b8b42abe083742187228'}).up()
+ _converse.connection._dataRecv(mock.createRequest(stanza));
+ await u.waitUntil(() => _converse.omemo_store);
+ expect(_converse.devicelists.length).toBe(2);
+
+ await u.waitUntil(() => mock.deviceListFetched(_converse, contact_jid));
+ const devicelist = _converse.devicelists.get(contact_jid);
+ expect(devicelist.devices.length).toBe(1);
+ expect(devicelist.devices.at(0).get('id')).toBe('4e30f35051b7b8b42abe083742187228');
+ expect(view.model.get('omemo_active')).toBe(true);
+
+ const icon = toolbar.querySelector('.toggle-omemo converse-icon');
+ expect(u.hasClass('fa-unlock', icon)).toBe(false);
+ expect(u.hasClass('fa-lock', icon)).toBe(true);
+
+ const textarea = view.querySelector('.chat-textarea');
+ textarea.value = 'This message will be encrypted';
+ const message_form = view.querySelector('converse-muc-message-form');
+ message_form.onKeyDown({
+ target: textarea,
+ preventDefault: function preventDefault () {},
+ keyCode: 13 // Enter
+ });
+ iq_stanza = await u.waitUntil(() => mock.bundleFetched(_converse, contact_jid, '4e30f35051b7b8b42abe083742187228'), 1000);
+ console.log("Bundle fetched 4e30f35051b7b8b42abe083742187228");
+ 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:4e30f35051b7b8b42abe083742187228"})
+ .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'));
+ _converse.connection._dataRecv(mock.createRequest(stanza));
+
+ iq_stanza = await u.waitUntil(() => mock.bundleFetched(_converse, _converse.bare_jid, '482886413b977930064a5888b92134fe'), 1000);
+ console.log("Bundle fetched 482886413b977930064a5888b92134fe");
+ stanza = $iq({
+ 'from': _converse.bare_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:482886413b977930064a5888b92134fe"})
+ .c('item')
+ .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
+ .c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t(btoa('100000')).up()
+ .c('signedPreKeySignature').t(btoa('200000')).up()
+ .c('identityKey').t(btoa('300000')).up()
+ .c('prekeys')
+ .c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1991')).up()
+ .c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1992')).up()
+ .c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1993'));
+
+ spyOn(_converse.connection, 'send');
+ _converse.connection._dataRecv(mock.createRequest(stanza));
+ await u.waitUntil(() => _converse.connection.send.calls.count(), 1000);
+ const sent_stanza = _converse.connection.send.calls.all()[0].args[0];
+
+ expect(Strophe.serialize(sent_stanza)).toBe(
+ ``+
+ `This is an OMEMO encrypted message which your client doesn’t seem to support. Find more information on https://conversations.im/omemo`+
+ ``+
+ ``+
+ `YzFwaDNSNzNYNw==`+
+ `YzFwaDNSNzNYNw==`+
+ `${sent_stanza.querySelector("iv").textContent}`+
+ ``+
+ `${sent_stanza.querySelector("payload").textContent}`+
+ ``+
+ ``+
+ ``+
+ ``);
+
+ // Test reception of an encrypted message
+ const obj = await omemo.encryptMessage('This is an encrypted message from the contact')
+ // XXX: Normally the key will be encrypted via libsignal.
+ // However, we're mocking libsignal in the tests, so we include it as plaintext in the message.
+ stanza = $msg({
+ 'from': `${muc_jid}/newguy`,
+ 'to': _converse.connection.jid,
+ 'type': 'groupchat',
+ 'id': _converse.connection.getUniqueId()
+ }).c('body').t('This is a fallback message').up()
+ .c('encrypted', {'xmlns': Strophe.NS.OMEMO})
+ .c('header', {'sid': '555'})
+ .c('key', {'rid': _converse.omemo_store.get('device_id')}).t(u.arrayBufferToBase64(obj.key_and_tag)).up()
+ .c('iv').t(obj.iv)
+ .up().up()
+ .c('payload').t(obj.payload);
+ _converse.connection._dataRecv(mock.createRequest(stanza));
+ await new Promise(resolve => view.model.messages.once('rendered', resolve));
+ expect(view.model.messages.length).toBe(2);
+ expect(view.querySelectorAll('.chat-msg__body')[1].textContent.trim())
+ .toBe('This is an encrypted message from the contact');
+
+ expect(_converse.devicelists.length).toBe(2);
+ expect(_converse.devicelists.at(0).get('jid')).toBe(_converse.bare_jid);
+ expect(_converse.devicelists.at(1).get('jid')).toBe(contact_jid);
+ }));
+
+ it("gracefully handles auth errors when trying to send encrypted groupchat messages",
+ mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {
+
+ // MEMO encryption works only in members only conferences
+ // that are non-anonymous.
+ const features = [
+ 'http://jabber.org/protocol/muc',
+ 'jabber:iq:register',
+ 'muc_passwordprotected',
+ 'muc_hidden',
+ 'muc_temporary',
+ 'muc_membersonly',
+ 'muc_unmoderated',
+ 'muc_nonanonymous'
+ ];
+ await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo', features);
+ const view = _converse.chatboxviews.get('lounge@montague.lit');
+ await u.waitUntil(() => mock.initializedOMEMO(_converse));
+
+ const contact_jid = 'newguy@montague.lit';
+ let stanza = $pres({
+ 'to': 'romeo@montague.lit/orchard',
+ 'from': 'lounge@montague.lit/newguy'
+ })
+ .c('x', {xmlns: Strophe.NS.MUC_USER})
+ .c('item', {
+ 'affiliation': 'none',
+ 'jid': 'newguy@montague.lit/_converse.js-290929789',
+ 'role': 'participant'
+ }).tree();
+ _converse.connection._dataRecv(mock.createRequest(stanza));
+
+ const toolbar = await u.waitUntil(() => view.querySelector('.chat-toolbar'));
+ const toggle = await u.waitUntil(() => toolbar.querySelector('.toggle-omemo'));
+ toggle.click();
+ expect(view.model.get('omemo_active')).toBe(true);
+ expect(view.model.get('omemo_supported')).toBe(true);
+
+ const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
+ textarea.value = 'This message will be encrypted';
+ const message_form = view.querySelector('converse-muc-message-form');
+ message_form.onKeyDown({
+ target: textarea,
+ preventDefault: function preventDefault () {},
+ keyCode: 13 // Enter
+ });
+ let iq_stanza = await u.waitUntil(() => mock.deviceListFetched(_converse, contact_jid));
+ expect(Strophe.serialize(iq_stanza)).toBe(
+ ``+
+ ``+
+ ``+
+ ``+
+ ``);
+
+ 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.devicelist"})
+ .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
+ .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
+ .c('device', {'id': '4e30f35051b7b8b42abe083742187228'}).up()
+
+ _converse.connection._dataRecv(mock.createRequest(stanza));
+ await u.waitUntil(() => _converse.omemo_store);
+ expect(_converse.devicelists.length).toBe(2);
+
+ const devicelist = _converse.devicelists.get(contact_jid);
+ await u.waitUntil(() => mock.deviceListFetched(_converse, contact_jid));
+ expect(devicelist.devices.length).toBe(1);
+ expect(devicelist.devices.at(0).get('id')).toBe('4e30f35051b7b8b42abe083742187228');
+
+ iq_stanza = await u.waitUntil(() => mock.bundleFetched(_converse, _converse.bare_jid, '482886413b977930064a5888b92134fe'));
+ stanza = $iq({
+ 'from': _converse.bare_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:482886413b977930064a5888b92134fe"})
+ .c('item')
+ .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
+ .c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t(btoa('100000')).up()
+ .c('signedPreKeySignature').t(btoa('200000')).up()
+ .c('identityKey').t(btoa('300000')).up()
+ .c('prekeys')
+ .c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1991')).up()
+ .c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1992')).up()
+ .c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1993'));
+ iq_stanza = await u.waitUntil(() => mock.bundleFetched(_converse, contact_jid, '4e30f35051b7b8b42abe083742187228'));
+
+ /*
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ */
+ 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:4e30f35051b7b8b42abe083742187228"}).up().up()
+ .c('error', {'code': '401', 'type': 'auth'})
+ .c('presence-subscription-required', {'xmlns':"http://jabber.org/protocol/pubsub#errors" }).up()
+ .c('not-authorized', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
+ _converse.connection._dataRecv(mock.createRequest(stanza));
+
+ await u.waitUntil(() => document.querySelectorAll('.alert-danger').length, 2000);
+ const header = document.querySelector('.alert-danger .modal-title');
+ expect(header.textContent).toBe("Error");
+ expect(u.ancestor(header, '.modal-content').querySelector('.modal-body p').textContent.trim())
+ .toBe("Sorry, we're unable to send an encrypted message because newguy@montague.lit requires you "+
+ "to be subscribed to their presence in order to see their OMEMO information");
+
+ expect(view.model.get('omemo_supported')).toBe(false);
+ expect(view.querySelector('.chat-textarea').value).toBe('This message will be encrypted');
+ }));
+
+
+ it("adds a toolbar button for starting an encrypted groupchat session",
+ mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {
+
+ await mock.waitForRoster(_converse, 'current', 0);
+ await mock.waitUntilDiscoConfirmed(
+ _converse, _converse.bare_jid,
+ [{'category': 'pubsub', 'type': 'pep'}],
+ ['http://jabber.org/protocol/pubsub#publish-options']
+ );
+
+ // MEMO encryption works only in members-only conferences that are non-anonymous.
+ const features = [
+ 'http://jabber.org/protocol/muc',
+ 'jabber:iq:register',
+ 'muc_passwordprotected',
+ 'muc_hidden',
+ 'muc_temporary',
+ 'muc_membersonly',
+ 'muc_unmoderated',
+ 'muc_nonanonymous'
+ ];
+ await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo', features);
+ const view = _converse.chatboxviews.get('lounge@montague.lit');
+ await u.waitUntil(() => mock.initializedOMEMO(_converse));
+
+ const toolbar = await u.waitUntil(() => view.querySelector('.chat-toolbar'));
+ let toggle = await u.waitUntil(() => toolbar.querySelector('.toggle-omemo'));
+ expect(view.model.get('omemo_active')).toBe(undefined);
+ expect(view.model.get('omemo_supported')).toBe(true);
+ await u.waitUntil(() => toggle.dataset.disabled === "false");
+
+ let icon = toolbar.querySelector('.toggle-omemo converse-icon');
+ expect(u.hasClass('fa-unlock', icon)).toBe(true);
+ expect(u.hasClass('fa-lock', icon)).toBe(false);
+
+ toggle.click();
+ toggle = toolbar.querySelector('.toggle-omemo');
+ expect(toggle.dataset.disabled).toBe("false");
+ expect(view.model.get('omemo_active')).toBe(true);
+ expect(view.model.get('omemo_supported')).toBe(true);
+
+ await u.waitUntil(() => !u.hasClass('fa-unlock', toolbar.querySelector('.toggle-omemo converse-icon')));
+ expect(u.hasClass('fa-lock', toolbar.querySelector('.toggle-omemo converse-icon'))).toBe(true);
+
+ let contact_jid = 'newguy@montague.lit';
+ let stanza = $pres({
+ to: 'romeo@montague.lit/orchard',
+ from: 'lounge@montague.lit/newguy'
+ })
+ .c('x', {xmlns: Strophe.NS.MUC_USER})
+ .c('item', {
+ 'affiliation': 'none',
+ 'jid': 'newguy@montague.lit/_converse.js-290929789',
+ 'role': 'participant'
+ }).tree();
+ _converse.connection._dataRecv(mock.createRequest(stanza));
+
+ let iq_stanza = await u.waitUntil(() => mock.deviceListFetched(_converse, contact_jid));
+ expect(Strophe.serialize(iq_stanza)).toBe(
+ ``+
+ ``+
+ ``+
+ ``+
+ ``);
+
+ 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.devicelist"})
+ .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
+ .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
+ .c('device', {'id': '4e30f35051b7b8b42abe083742187228'}).up()
+ .c('device', {'id': 'ae890ac52d0df67ed7cfdf51b644e901'});
+ _converse.connection._dataRecv(mock.createRequest(stanza));
+ await u.waitUntil(() => _converse.omemo_store);
+ expect(_converse.devicelists.length).toBe(2);
+
+ await u.waitUntil(() => mock.deviceListFetched(_converse, contact_jid));
+ const devicelist = _converse.devicelists.get(contact_jid);
+ expect(devicelist.devices.length).toBe(2);
+ expect(devicelist.devices.at(0).get('id')).toBe('4e30f35051b7b8b42abe083742187228');
+ expect(devicelist.devices.at(1).get('id')).toBe('ae890ac52d0df67ed7cfdf51b644e901');
+
+ expect(view.model.get('omemo_active')).toBe(true);
+ toggle = toolbar.querySelector('.toggle-omemo');
+ expect(toggle === null).toBe(false);
+ expect(toggle.dataset.disabled).toBe("false");
+ expect(view.model.get('omemo_supported')).toBe(true);
+
+ await u.waitUntil(() => !u.hasClass('fa-unlock', toolbar.querySelector('.toggle-omemo converse-icon')));
+ expect(u.hasClass('fa-lock', toolbar.querySelector('.toggle-omemo converse-icon'))).toBe(true);
+
+ // Test that the button gets disabled when the room becomes
+ // anonymous or semi-anonymous
+ view.model.features.save({'nonanonymous': false, 'semianonymous': true});
+ await u.waitUntil(() => !view.model.get('omemo_supported'));
+ await u.waitUntil(() => view.querySelector('.toggle-omemo').dataset.disabled === "true");
+
+ view.model.features.save({'nonanonymous': true, 'semianonymous': false});
+ await u.waitUntil(() => view.model.get('omemo_supported'));
+ await u.waitUntil(() => view.querySelector('.toggle-omemo') !== null);
+ expect(u.hasClass('fa-unlock', toolbar.querySelector('.toggle-omemo converse-icon'))).toBe(true);
+ expect(u.hasClass('fa-lock', toolbar.querySelector('.toggle-omemo converse-icon'))).toBe(false);
+ expect(view.querySelector('.toggle-omemo').dataset.disabled).toBe("false");
+
+ // Test that the button gets disabled when the room becomes open
+ view.model.features.save({'membersonly': false, 'open': true});
+ await u.waitUntil(() => !view.model.get('omemo_supported'));
+ await u.waitUntil(() => view.querySelector('.toggle-omemo').dataset.disabled === "true");
+
+ view.model.features.save({'membersonly': true, 'open': false});
+ await u.waitUntil(() => view.model.get('omemo_supported'));
+ await u.waitUntil(() => view.querySelector('.toggle-omemo').dataset.disabled === "false");
+
+ expect(u.hasClass('fa-unlock', view.querySelector('.toggle-omemo converse-icon'))).toBe(true);
+ expect(u.hasClass('fa-lock', view.querySelector('.toggle-omemo converse-icon'))).toBe(false);
+
+ expect(view.model.get('omemo_supported')).toBe(true);
+ expect(view.model.get('omemo_active')).toBe(false);
+
+ view.querySelector('.toggle-omemo').click();
+ expect(view.model.get('omemo_active')).toBe(true);
+
+ // Someone enters the room who doesn't have OMEMO support, while we
+ // have OMEMO activated...
+ contact_jid = 'oldguy@montague.lit';
+ stanza = $pres({
+ to: 'romeo@montague.lit/orchard',
+ from: 'lounge@montague.lit/oldguy'
+ })
+ .c('x', {xmlns: Strophe.NS.MUC_USER})
+ .c('item', {
+ 'affiliation': 'none',
+ 'jid': `${contact_jid}/_converse.js-290929788`,
+ 'role': 'participant'
+ }).tree();
+ _converse.connection._dataRecv(mock.createRequest(stanza));
+ iq_stanza = await u.waitUntil(() => mock.deviceListFetched(_converse, contact_jid));
+ expect(Strophe.serialize(iq_stanza)).toBe(
+ ``+
+ ``+
+ ``+
+ ``+
+ ``);
+
+ stanza = $iq({
+ 'from': contact_jid,
+ 'id': iq_stanza.getAttribute('id'),
+ 'to': _converse.bare_jid,
+ 'type': 'error'
+ }).c('error', {'type': 'cancel'})
+ .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
+ _converse.connection._dataRecv(mock.createRequest(stanza));
+
+ await u.waitUntil(() => !view.model.get('omemo_supported'));
+ await u.waitUntil(() => view.querySelector('.chat-error .chat-info__message')?.textContent.trim() ===
+ "oldguy doesn't appear to have a client that supports OMEMO. "+
+ "Encrypted chat will no longer be possible in this grouchat."
+ );
+
+ await u.waitUntil(() => toolbar.querySelector('.toggle-omemo').dataset.disabled === "true");
+ icon = view.querySelector('.toggle-omemo converse-icon');
+ expect(u.hasClass('fa-unlock', icon)).toBe(true);
+ expect(u.hasClass('fa-lock', icon)).toBe(false);
+ expect(toolbar.querySelector('.toggle-omemo').title).toBe('This groupchat needs to be members-only and non-anonymous in order to support OMEMO encrypted messages');
+ }));
+});
diff --git a/src/plugins/omemo/tests/omemo.js b/src/plugins/omemo/tests/omemo.js
index 7ab8d3998..e25ace1bc 100644
--- a/src/plugins/omemo/tests/omemo.js
+++ b/src/plugins/omemo/tests/omemo.js
@@ -1,6 +1,6 @@
/*global mock, converse */
-const { $iq, $pres, $msg, omemo, Strophe } = converse.env;
+const { $iq, $msg, omemo, Strophe } = converse.env;
const u = converse.env.utils;
describe("The OMEMO module", function() {
@@ -152,152 +152,6 @@ describe("The OMEMO module", function() {
.toBe('Another received encrypted message without fallback');
}));
- it("enables encrypted groupchat messages to be sent and received",
- mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {
-
- // MEMO encryption works only in members only conferences
- // that are non-anonymous.
- const features = [
- 'http://jabber.org/protocol/muc',
- 'jabber:iq:register',
- 'muc_passwordprotected',
- 'muc_hidden',
- 'muc_temporary',
- 'muc_membersonly',
- 'muc_unmoderated',
- 'muc_nonanonymous'
- ];
- await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo', features);
- const view = _converse.chatboxviews.get('lounge@montague.lit');
- await u.waitUntil(() => mock.initializedOMEMO(_converse));
-
- const toolbar = await u.waitUntil(() => view.querySelector('.chat-toolbar'));
- const el = await u.waitUntil(() => toolbar.querySelector('.toggle-omemo'));
- el.click();
- expect(view.model.get('omemo_active')).toBe(true);
-
- // newguy enters the room
- const contact_jid = 'newguy@montague.lit';
- let stanza = $pres({
- 'to': 'romeo@montague.lit/orchard',
- 'from': 'lounge@montague.lit/newguy'
- })
- .c('x', {xmlns: Strophe.NS.MUC_USER})
- .c('item', {
- 'affiliation': 'none',
- 'jid': 'newguy@montague.lit/_converse.js-290929789',
- 'role': 'participant'
- }).tree();
- _converse.connection._dataRecv(mock.createRequest(stanza));
-
- // Wait for Converse to fetch newguy's device list
- let iq_stanza = await u.waitUntil(() => mock.deviceListFetched(_converse, contact_jid));
- expect(Strophe.serialize(iq_stanza)).toBe(
- ``+
- ``+
- ``+
- ``+
- ``);
-
- // The server returns his device list
- 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.devicelist"})
- .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
- .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
- .c('device', {'id': '4e30f35051b7b8b42abe083742187228'}).up()
- _converse.connection._dataRecv(mock.createRequest(stanza));
- await u.waitUntil(() => _converse.omemo_store);
- expect(_converse.devicelists.length).toBe(2);
-
- await u.waitUntil(() => mock.deviceListFetched(_converse, contact_jid));
- const devicelist = _converse.devicelists.get(contact_jid);
- expect(devicelist.devices.length).toBe(1);
- expect(devicelist.devices.at(0).get('id')).toBe('4e30f35051b7b8b42abe083742187228');
- expect(view.model.get('omemo_active')).toBe(true);
-
- const icon = toolbar.querySelector('.toggle-omemo converse-icon');
- expect(u.hasClass('fa-unlock', icon)).toBe(false);
- expect(u.hasClass('fa-lock', icon)).toBe(true);
-
- const textarea = view.querySelector('.chat-textarea');
- textarea.value = 'This message will be encrypted';
- const message_form = view.querySelector('converse-muc-message-form');
- message_form.onKeyDown({
- target: textarea,
- preventDefault: function preventDefault () {},
- keyCode: 13 // Enter
- });
- iq_stanza = await u.waitUntil(() => mock.bundleFetched(_converse, contact_jid, '4e30f35051b7b8b42abe083742187228'), 1000);
- console.log("Bundle fetched 4e30f35051b7b8b42abe083742187228");
- 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:4e30f35051b7b8b42abe083742187228"})
- .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'));
- _converse.connection._dataRecv(mock.createRequest(stanza));
-
- iq_stanza = await u.waitUntil(() => mock.bundleFetched(_converse, _converse.bare_jid, '482886413b977930064a5888b92134fe'), 1000);
- console.log("Bundle fetched 482886413b977930064a5888b92134fe");
- stanza = $iq({
- 'from': _converse.bare_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:482886413b977930064a5888b92134fe"})
- .c('item')
- .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
- .c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t(btoa('100000')).up()
- .c('signedPreKeySignature').t(btoa('200000')).up()
- .c('identityKey').t(btoa('300000')).up()
- .c('prekeys')
- .c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1991')).up()
- .c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1992')).up()
- .c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1993'));
-
- spyOn(_converse.connection, 'send');
- _converse.connection._dataRecv(mock.createRequest(stanza));
- await u.waitUntil(() => _converse.connection.send.calls.count(), 1000);
- const sent_stanza = _converse.connection.send.calls.all()[0].args[0];
-
- expect(Strophe.serialize(sent_stanza)).toBe(
- ``+
- `This is an OMEMO encrypted message which your client doesn’t seem to support. Find more information on https://conversations.im/omemo`+
- ``+
- ``+
- `YzFwaDNSNzNYNw==`+
- `YzFwaDNSNzNYNw==`+
- `${sent_stanza.querySelector("iv").textContent}`+
- ``+
- `${sent_stanza.querySelector("payload").textContent}`+
- ``+
- ``+
- ``+
- ``);
- }));
-
it("will create a new device based on a received carbon message",
mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {
@@ -406,133 +260,6 @@ describe("The OMEMO module", function() {
``);
}));
- it("gracefully handles auth errors when trying to send encrypted groupchat messages",
- mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {
-
- // MEMO encryption works only in members only conferences
- // that are non-anonymous.
- const features = [
- 'http://jabber.org/protocol/muc',
- 'jabber:iq:register',
- 'muc_passwordprotected',
- 'muc_hidden',
- 'muc_temporary',
- 'muc_membersonly',
- 'muc_unmoderated',
- 'muc_nonanonymous'
- ];
- await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo', features);
- const view = _converse.chatboxviews.get('lounge@montague.lit');
- await u.waitUntil(() => mock.initializedOMEMO(_converse));
-
- const contact_jid = 'newguy@montague.lit';
- let stanza = $pres({
- 'to': 'romeo@montague.lit/orchard',
- 'from': 'lounge@montague.lit/newguy'
- })
- .c('x', {xmlns: Strophe.NS.MUC_USER})
- .c('item', {
- 'affiliation': 'none',
- 'jid': 'newguy@montague.lit/_converse.js-290929789',
- 'role': 'participant'
- }).tree();
- _converse.connection._dataRecv(mock.createRequest(stanza));
-
- const toolbar = await u.waitUntil(() => view.querySelector('.chat-toolbar'));
- const toggle = await u.waitUntil(() => toolbar.querySelector('.toggle-omemo'));
- toggle.click();
- expect(view.model.get('omemo_active')).toBe(true);
- expect(view.model.get('omemo_supported')).toBe(true);
-
- const textarea = await u.waitUntil(() => view.querySelector('.chat-textarea'));
- textarea.value = 'This message will be encrypted';
- const message_form = view.querySelector('converse-muc-message-form');
- message_form.onKeyDown({
- target: textarea,
- preventDefault: function preventDefault () {},
- keyCode: 13 // Enter
- });
- let iq_stanza = await u.waitUntil(() => mock.deviceListFetched(_converse, contact_jid));
- expect(Strophe.serialize(iq_stanza)).toBe(
- ``+
- ``+
- ``+
- ``+
- ``);
-
- 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.devicelist"})
- .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
- .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
- .c('device', {'id': '4e30f35051b7b8b42abe083742187228'}).up()
-
- _converse.connection._dataRecv(mock.createRequest(stanza));
- await u.waitUntil(() => _converse.omemo_store);
- expect(_converse.devicelists.length).toBe(2);
-
- const devicelist = _converse.devicelists.get(contact_jid);
- await u.waitUntil(() => mock.deviceListFetched(_converse, contact_jid));
- expect(devicelist.devices.length).toBe(1);
- expect(devicelist.devices.at(0).get('id')).toBe('4e30f35051b7b8b42abe083742187228');
-
- iq_stanza = await u.waitUntil(() => mock.bundleFetched(_converse, _converse.bare_jid, '482886413b977930064a5888b92134fe'));
- stanza = $iq({
- 'from': _converse.bare_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:482886413b977930064a5888b92134fe"})
- .c('item')
- .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
- .c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t(btoa('100000')).up()
- .c('signedPreKeySignature').t(btoa('200000')).up()
- .c('identityKey').t(btoa('300000')).up()
- .c('prekeys')
- .c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1991')).up()
- .c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1992')).up()
- .c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1993'));
- iq_stanza = await u.waitUntil(() => mock.bundleFetched(_converse, contact_jid, '4e30f35051b7b8b42abe083742187228'));
-
- /*
- *
- *
- *
- *
- *
- *
- *
- *
- */
- 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:4e30f35051b7b8b42abe083742187228"}).up().up()
- .c('error', {'code': '401', 'type': 'auth'})
- .c('presence-subscription-required', {'xmlns':"http://jabber.org/protocol/pubsub#errors" }).up()
- .c('not-authorized', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
- _converse.connection._dataRecv(mock.createRequest(stanza));
-
- await u.waitUntil(() => document.querySelectorAll('.alert-danger').length, 2000);
- const header = document.querySelector('.alert-danger .modal-title');
- expect(header.textContent).toBe("Error");
- expect(u.ancestor(header, '.modal-content').querySelector('.modal-body p').textContent.trim())
- .toBe("Sorry, we're unable to send an encrypted message because newguy@montague.lit requires you "+
- "to be subscribed to their presence in order to see their OMEMO information");
-
- expect(view.model.get('omemo_supported')).toBe(false);
- expect(view.querySelector('.chat-textarea').value).toBe('This message will be encrypted');
- }));
-
it("can receive a PreKeySignalMessage",
mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {
@@ -1178,177 +905,6 @@ describe("The OMEMO module", function() {
expect(u.hasClass('fa-unlock', icon)).toBe(true);
}));
- it("adds a toolbar button for starting an encrypted groupchat session",
- mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {
-
- await mock.waitForRoster(_converse, 'current', 0);
- await mock.waitUntilDiscoConfirmed(
- _converse, _converse.bare_jid,
- [{'category': 'pubsub', 'type': 'pep'}],
- ['http://jabber.org/protocol/pubsub#publish-options']
- );
-
- // MEMO encryption works only in members-only conferences that are non-anonymous.
- const features = [
- 'http://jabber.org/protocol/muc',
- 'jabber:iq:register',
- 'muc_passwordprotected',
- 'muc_hidden',
- 'muc_temporary',
- 'muc_membersonly',
- 'muc_unmoderated',
- 'muc_nonanonymous'
- ];
- await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo', features);
- const view = _converse.chatboxviews.get('lounge@montague.lit');
- await u.waitUntil(() => mock.initializedOMEMO(_converse));
-
- const toolbar = await u.waitUntil(() => view.querySelector('.chat-toolbar'));
- let toggle = await u.waitUntil(() => toolbar.querySelector('.toggle-omemo'));
- expect(view.model.get('omemo_active')).toBe(undefined);
- expect(view.model.get('omemo_supported')).toBe(true);
- await u.waitUntil(() => toggle.dataset.disabled === "false");
-
- let icon = toolbar.querySelector('.toggle-omemo converse-icon');
- expect(u.hasClass('fa-unlock', icon)).toBe(true);
- expect(u.hasClass('fa-lock', icon)).toBe(false);
-
- toggle.click();
- toggle = toolbar.querySelector('.toggle-omemo');
- expect(toggle.dataset.disabled).toBe("false");
- expect(view.model.get('omemo_active')).toBe(true);
- expect(view.model.get('omemo_supported')).toBe(true);
-
- await u.waitUntil(() => !u.hasClass('fa-unlock', toolbar.querySelector('.toggle-omemo converse-icon')));
- expect(u.hasClass('fa-lock', toolbar.querySelector('.toggle-omemo converse-icon'))).toBe(true);
-
- let contact_jid = 'newguy@montague.lit';
- let stanza = $pres({
- to: 'romeo@montague.lit/orchard',
- from: 'lounge@montague.lit/newguy'
- })
- .c('x', {xmlns: Strophe.NS.MUC_USER})
- .c('item', {
- 'affiliation': 'none',
- 'jid': 'newguy@montague.lit/_converse.js-290929789',
- 'role': 'participant'
- }).tree();
- _converse.connection._dataRecv(mock.createRequest(stanza));
-
- let iq_stanza = await u.waitUntil(() => mock.deviceListFetched(_converse, contact_jid));
- expect(Strophe.serialize(iq_stanza)).toBe(
- ``+
- ``+
- ``+
- ``+
- ``);
-
- 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.devicelist"})
- .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
- .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
- .c('device', {'id': '4e30f35051b7b8b42abe083742187228'}).up()
- .c('device', {'id': 'ae890ac52d0df67ed7cfdf51b644e901'});
- _converse.connection._dataRecv(mock.createRequest(stanza));
- await u.waitUntil(() => _converse.omemo_store);
- expect(_converse.devicelists.length).toBe(2);
-
- await u.waitUntil(() => mock.deviceListFetched(_converse, contact_jid));
- const devicelist = _converse.devicelists.get(contact_jid);
- expect(devicelist.devices.length).toBe(2);
- expect(devicelist.devices.at(0).get('id')).toBe('4e30f35051b7b8b42abe083742187228');
- expect(devicelist.devices.at(1).get('id')).toBe('ae890ac52d0df67ed7cfdf51b644e901');
-
- expect(view.model.get('omemo_active')).toBe(true);
- toggle = toolbar.querySelector('.toggle-omemo');
- expect(toggle === null).toBe(false);
- expect(toggle.dataset.disabled).toBe("false");
- expect(view.model.get('omemo_supported')).toBe(true);
-
- await u.waitUntil(() => !u.hasClass('fa-unlock', toolbar.querySelector('.toggle-omemo converse-icon')));
- expect(u.hasClass('fa-lock', toolbar.querySelector('.toggle-omemo converse-icon'))).toBe(true);
-
- // Test that the button gets disabled when the room becomes
- // anonymous or semi-anonymous
- view.model.features.save({'nonanonymous': false, 'semianonymous': true});
- await u.waitUntil(() => !view.model.get('omemo_supported'));
- await u.waitUntil(() => view.querySelector('.toggle-omemo').dataset.disabled === "true");
-
- view.model.features.save({'nonanonymous': true, 'semianonymous': false});
- await u.waitUntil(() => view.model.get('omemo_supported'));
- await u.waitUntil(() => view.querySelector('.toggle-omemo') !== null);
- expect(u.hasClass('fa-unlock', toolbar.querySelector('.toggle-omemo converse-icon'))).toBe(true);
- expect(u.hasClass('fa-lock', toolbar.querySelector('.toggle-omemo converse-icon'))).toBe(false);
- expect(view.querySelector('.toggle-omemo').dataset.disabled).toBe("false");
-
- // Test that the button gets disabled when the room becomes open
- view.model.features.save({'membersonly': false, 'open': true});
- await u.waitUntil(() => !view.model.get('omemo_supported'));
- await u.waitUntil(() => view.querySelector('.toggle-omemo').dataset.disabled === "true");
-
- view.model.features.save({'membersonly': true, 'open': false});
- await u.waitUntil(() => view.model.get('omemo_supported'));
- await u.waitUntil(() => view.querySelector('.toggle-omemo').dataset.disabled === "false");
-
- expect(u.hasClass('fa-unlock', view.querySelector('.toggle-omemo converse-icon'))).toBe(true);
- expect(u.hasClass('fa-lock', view.querySelector('.toggle-omemo converse-icon'))).toBe(false);
-
- expect(view.model.get('omemo_supported')).toBe(true);
- expect(view.model.get('omemo_active')).toBe(false);
-
- view.querySelector('.toggle-omemo').click();
- expect(view.model.get('omemo_active')).toBe(true);
-
- // Someone enters the room who doesn't have OMEMO support, while we
- // have OMEMO activated...
- contact_jid = 'oldguy@montague.lit';
- stanza = $pres({
- to: 'romeo@montague.lit/orchard',
- from: 'lounge@montague.lit/oldguy'
- })
- .c('x', {xmlns: Strophe.NS.MUC_USER})
- .c('item', {
- 'affiliation': 'none',
- 'jid': `${contact_jid}/_converse.js-290929788`,
- 'role': 'participant'
- }).tree();
- _converse.connection._dataRecv(mock.createRequest(stanza));
- iq_stanza = await u.waitUntil(() => mock.deviceListFetched(_converse, contact_jid));
- expect(Strophe.serialize(iq_stanza)).toBe(
- ``+
- ``+
- ``+
- ``+
- ``);
-
- stanza = $iq({
- 'from': contact_jid,
- 'id': iq_stanza.getAttribute('id'),
- 'to': _converse.bare_jid,
- 'type': 'error'
- }).c('error', {'type': 'cancel'})
- .c('item-not-found', {'xmlns': "urn:ietf:params:xml:ns:xmpp-stanzas"});
- _converse.connection._dataRecv(mock.createRequest(stanza));
-
- await u.waitUntil(() => !view.model.get('omemo_supported'));
- await u.waitUntil(() => view.querySelector('.chat-error .chat-info__message')?.textContent.trim() ===
- "oldguy doesn't appear to have a client that supports OMEMO. "+
- "Encrypted chat will no longer be possible in this grouchat."
- );
-
- await u.waitUntil(() => toolbar.querySelector('.toggle-omemo').dataset.disabled === "true");
- icon = view.querySelector('.toggle-omemo converse-icon');
- expect(u.hasClass('fa-unlock', icon)).toBe(true);
- expect(u.hasClass('fa-lock', icon)).toBe(false);
- expect(toolbar.querySelector('.toggle-omemo').title).toBe('This groupchat needs to be members-only and non-anonymous in order to support OMEMO encrypted messages');
- }));
-
-
it("shows OMEMO device fingerprints in the user details modal",
mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {
diff --git a/src/plugins/omemo/utils.js b/src/plugins/omemo/utils.js
index a000753d5..727532753 100644
--- a/src/plugins/omemo/utils.js
+++ b/src/plugins/omemo/utils.js
@@ -251,14 +251,30 @@ export function getSessionCipher (jid, id) {
return new window.libsignal.SessionCipher(_converse.omemo_store, address);
}
+function getJIDForDecryption (attrs) {
+ const from_jid = attrs.from_muc ? attrs.from_real_jid : attrs.from;
+ if (!from_jid) {
+ Object.assign(attrs, {
+ 'error_text': __("Sorry, could not decrypt a received OMEMO message because we don't have the XMPP address for that user."),
+ 'error_type': 'Decryption',
+ 'is_ephemeral': false,
+ 'is_error': true,
+ 'type': 'error'
+ });
+ throw new Error("Could not find JID to decrypt OMEMO message for");
+ }
+ return from_jid;
+}
+
async function handleDecryptedWhisperMessage (attrs, key_and_tag) {
- const encrypted = attrs.encrypted;
- const devicelist = _converse.devicelists.getDeviceList(attrs.from);
+ const from_jid = getJIDForDecryption(attrs);
+ const devicelist = _converse.devicelists.getDeviceList(from_jid);
await devicelist._devices_promise;
+ const encrypted = attrs.encrypted;
let device = devicelist.get(encrypted.device_id);
if (!device) {
- device = await devicelist.devices.create({ 'id': encrypted.device_id, 'jid': attrs.from }, { 'promise': true });
+ device = await devicelist.devices.create({ 'id': encrypted.device_id, 'jid': from_jid }, { 'promise': true });
}
if (encrypted.payload) {
const key = key_and_tag.slice(0, 16);
@@ -285,7 +301,8 @@ function getDecryptionErrorAttributes (e) {
}
async function decryptPrekeyWhisperMessage (attrs) {
- const session_cipher = getSessionCipher(attrs.from, parseInt(attrs.encrypted.device_id, 10));
+ const from_jid = getJIDForDecryption(attrs);
+ const session_cipher = getSessionCipher(from_jid, parseInt(attrs.encrypted.device_id, 10));
const key = base64ToArrayBuffer(attrs.encrypted.key);
let key_and_tag;
try {
@@ -332,16 +349,7 @@ async function decryptPrekeyWhisperMessage (attrs) {
}
async function decryptWhisperMessage (attrs) {
- const from_jid = attrs.from_muc ? attrs.from_real_jid : attrs.from;
- if (!from_jid) {
- Object.assign(attrs, {
- 'error_text': __("Sorry, could not decrypt a received OMEMO message because we don't have the XMPP address for that user."),
- 'error_type': 'Decryption',
- 'is_ephemeral': false,
- 'is_error': true,
- 'type': 'error'
- });
- }
+ const from_jid = getJIDForDecryption(attrs);
const session_cipher = getSessionCipher(from_jid, parseInt(attrs.encrypted.device_id, 10));
const key = base64ToArrayBuffer(attrs.encrypted.key);
try {
@@ -432,9 +440,6 @@ export function generateDeviceID () {
}
async function buildSession (device) {
- // TODO: check device-get('jid') versus the 'from' attribute which is used
- // to build a session when receiving an encrypted message in a MUC.
- // https://github.com/conversejs/converse.js/issues/1481#issuecomment-509183431
const address = new libsignal.SignalProtocolAddress(device.get('jid'), device.get('id'));
const sessionBuilder = new libsignal.SessionBuilder(_converse.omemo_store, address);
const prekey = device.getRandomPreKey();