diff --git a/dist/converse.js b/dist/converse.js index b875fff85..daccda218 100644 --- a/dist/converse.js +++ b/dist/converse.js @@ -56274,8 +56274,9 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins _converse.log(`${e.name} ${e.message}`, Strophe.LogLevel.ERROR); }, - async handleDecryptedWhisperMessage(encrypted, key_and_tag) { + async handleDecryptedWhisperMessage(attrs, key_and_tag) { const _converse = this.__super__._converse, + encrypted = attrs.encrypted, devicelist = _converse.devicelists.getDeviceList(this.get('jid')); this.save('omemo_supported', true); @@ -56284,7 +56285,7 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins if (!device) { device = devicelist.devices.create({ 'id': encrypted.device_id, - 'jid': this.get('jid') + 'jid': attrs.from }); } @@ -56306,7 +56307,7 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins if (attrs.encrypted.prekey === true) { let plaintext; - return session_cipher.decryptPreKeyWhisperMessage(u.base64ToArrayBuffer(attrs.encrypted.key), 'binary').then(key_and_tag => this.handleDecryptedWhisperMessage(attrs.encrypted, key_and_tag)).then(pt => { + return session_cipher.decryptPreKeyWhisperMessage(u.base64ToArrayBuffer(attrs.encrypted.key), 'binary').then(key_and_tag => this.handleDecryptedWhisperMessage(attrs, key_and_tag)).then(pt => { plaintext = pt; return _converse.omemo_store.generateMissingPreKeys(); }).then(() => _converse.omemo_store.publishBundle()).then(() => { @@ -56324,7 +56325,7 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins return attrs; }); } else { - return session_cipher.decryptWhisperMessage(u.base64ToArrayBuffer(attrs.encrypted.key), 'binary').then(key_and_tag => this.handleDecryptedWhisperMessage(attrs.encrypted, key_and_tag)).then(plaintext => _.extend(attrs, { + return session_cipher.decryptWhisperMessage(u.base64ToArrayBuffer(attrs.encrypted.key), 'binary').then(key_and_tag => this.handleDecryptedWhisperMessage(attrs, key_and_tag)).then(plaintext => _.extend(attrs, { 'plaintext': plaintext })).catch(e => { this.reportDecryptionError(e); @@ -57006,7 +57007,11 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins resolve(); } }, - 'error': () => { + 'error': (model, resp) => { + _converse.log("Could not fetch OMEMO session from cache, we'll generate a new one.", Strophe.LogLevel.WARN); + + _converse.log(resp, Strophe.LogLevel.WARN); + this.generateBundle().then(resolve).catch(reject); } }); @@ -65275,9 +65280,9 @@ _converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins.add('converse-dis const stanza = await _converse.api.disco.info(this.get('jid'), null); this.onInfo(stanza); } catch (iq) { - this.waitUntilFeaturesDiscovered.resolve(this); - _converse.log(iq, Strophe.LogLevel.ERROR); + + this.waitUntilFeaturesDiscovered.resolve(this); } }, diff --git a/spec/omemo.js b/spec/omemo.js index 908b86abb..e1347534f 100644 --- a/spec/omemo.js +++ b/spec/omemo.js @@ -31,7 +31,7 @@ function bundleFetched (_converse, jid, device_id) { return _.filter( _converse.connection.IQ_stanzas, - (iq) => iq.nodeTree.querySelector(`iq[to="${jid}"] items[node="eu.siacs.conversations.axolotl.bundles:${device_id}"]`) + iq => iq.nodeTree.querySelector(`iq[to="${jid}"] items[node="eu.siacs.conversations.axolotl.bundles:${device_id}"]`) ).pop(); } @@ -381,6 +381,98 @@ done(); })); + it("will create a new device based on a received carbon message", + mock.initConverse( + null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, + async function (done, _converse) { + + await test_utils.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, [], [Strophe.NS.SID]); + + let sent_stanza; + test_utils.createContacts(_converse, 'current', 1); + _converse.api.trigger('rosterContactsFetched'); + const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; + await test_utils.waitUntil(() => initializedOMEMO(_converse)); + await test_utils.openChatBoxFor(_converse, contact_jid); + let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid)); + const stanza = $iq({ + 'from': contact_jid, + 'id': iq_stanza.nodeTree.getAttribute('id'), + 'to': _converse.connection.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': '555'}); + _converse.connection._dataRecv(test_utils.createRequest(stanza)); + await test_utils.waitUntil(() => _converse.omemo_store); + const devicelist = _converse.devicelists.get({'jid': contact_jid}); + await test_utils.waitUntil(() => devicelist.devices.length === 1); + + const view = _converse.chatboxviews.get(contact_jid); + view.model.set('omemo_active', true); + + // 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 carbon = u.toStanza(` + + + + + + + + + + + +
+ ${u.arrayBufferToBase64(obj.key_and_tag)} + ${obj.iv} +
+ ${obj.payload} +
+ + +
+
+
+
+ `); + _converse.connection._dataRecv(test_utils.createRequest(carbon)); + await new Promise(resolve => view.once('messageInserted', resolve)); + expect(view.model.messages.length).toBe(1); + expect(view.el.querySelector('.chat-msg__body').textContent.trim()) + .toBe('This is an encrypted carbon message from another device of mine'); + + expect(devicelist.devices.length).toBe(2); + expect(devicelist.devices.at(0).get('id')).toBe('555'); + expect(devicelist.devices.at(1).get('id')).toBe('988349631'); + expect(devicelist.devices.get('988349631').get('active')).toBe(true); + + const textarea = view.el.querySelector('.chat-textarea'); + textarea.value = 'This is an encrypted message from this device'; + view.keyPressed({ + target: textarea, + preventDefault: _.noop, + keyCode: 13 // Enter + }); + iq_stanza = await test_utils.waitUntil(() => bundleFetched(_converse, _converse.bare_jid, '988349631')); + expect(iq_stanza.toLocaleString()).toBe( + ``+ + ``+ + ``+ + ``+ + ``); + done(); + })); + it("gracefully handles auth errors when trying to send encrypted groupchat messages", mock.initConverse( null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, diff --git a/src/converse-omemo.js b/src/converse-omemo.js index ff80f088c..a7c33cb69 100644 --- a/src/converse-omemo.js +++ b/src/converse-omemo.js @@ -229,14 +229,15 @@ converse.plugins.add('converse-omemo', { _converse.log(`${e.name} ${e.message}`, Strophe.LogLevel.ERROR); }, - async handleDecryptedWhisperMessage (encrypted, key_and_tag) { + async handleDecryptedWhisperMessage (attrs, key_and_tag) { const { _converse } = this.__super__, + encrypted = attrs.encrypted, 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')}); + device = devicelist.devices.create({'id': encrypted.device_id, 'jid': attrs.from}); } if (encrypted.payload) { const key = key_and_tag.slice(0, 16), @@ -255,7 +256,7 @@ converse.plugins.add('converse-omemo', { if (attrs.encrypted.prekey === true) { let plaintext; return session_cipher.decryptPreKeyWhisperMessage(u.base64ToArrayBuffer(attrs.encrypted.key), 'binary') - .then(key_and_tag => this.handleDecryptedWhisperMessage(attrs.encrypted, key_and_tag)) + .then(key_and_tag => this.handleDecryptedWhisperMessage(attrs, key_and_tag)) .then(pt => { plaintext = pt; return _converse.omemo_store.generateMissingPreKeys(); @@ -272,7 +273,7 @@ converse.plugins.add('converse-omemo', { }); } else { return session_cipher.decryptWhisperMessage(u.base64ToArrayBuffer(attrs.encrypted.key), 'binary') - .then(key_and_tag => this.handleDecryptedWhisperMessage(attrs.encrypted, key_and_tag)) + .then(key_and_tag => this.handleDecryptedWhisperMessage(attrs, key_and_tag)) .then(plaintext => _.extend(attrs, {'plaintext': plaintext})) .catch(e => { this.reportDecryptionError(e); @@ -886,7 +887,12 @@ converse.plugins.add('converse-omemo', { resolve(); } }, - 'error': () => { + 'error': (model, resp) => { + _converse.log( + "Could not fetch OMEMO session from cache, we'll generate a new one.", + Strophe.LogLevel.WARN + ); + _converse.log(resp, Strophe.LogLevel.WARN); this.generateBundle().then(resolve).catch(reject); } }); @@ -926,8 +932,8 @@ converse.plugins.add('converse-omemo', { throw new IQError("Could not fetch bundle", iq); } const publish_el = sizzle(`items[node="${Strophe.NS.OMEMO_BUNDLES}:${this.get('id')}"]`, iq).pop(), - bundle_el = sizzle(`bundle[xmlns="${Strophe.NS.OMEMO}"]`, publish_el).pop(), - bundle = parseBundle(bundle_el); + bundle_el = sizzle(`bundle[xmlns="${Strophe.NS.OMEMO}"]`, publish_el).pop(), + bundle = parseBundle(bundle_el); this.save('bundle', bundle); return bundle; }, diff --git a/src/headless/converse-disco.js b/src/headless/converse-disco.js index 3eaaf869f..07610a7ef 100644 --- a/src/headless/converse-disco.js +++ b/src/headless/converse-disco.js @@ -141,9 +141,9 @@ converse.plugins.add('converse-disco', { try { const stanza = await _converse.api.disco.info(this.get('jid'), null); this.onInfo(stanza); - } catch(iq) { - this.waitUntilFeaturesDiscovered.resolve(this); + } catch (iq) { _converse.log(iq, Strophe.LogLevel.ERROR); + this.waitUntilFeaturesDiscovered.resolve(this); } },