Browse Source

Fix generation of OMEMO prekeys

JC Brand 1 year ago
parent
commit
0641eb82f9

+ 4 - 4
src/plugins/omemo/index.js

@@ -1,6 +1,9 @@
 /**
  * @copyright The Converse.js contributors
  * @license Mozilla Public License (MPLv2)
+ *
+ * @module plugins-omemo-index
+ * @typedef {Window & globalThis & {libsignal: any} } WindowWithLibsignal
  */
 import './fingerprints.js';
 import './profile.js';
@@ -39,14 +42,11 @@ Strophe.addNamespace('OMEMO_VERIFICATION', Strophe.NS.OMEMO + '.verification');
 Strophe.addNamespace('OMEMO_WHITELISTED', Strophe.NS.OMEMO + '.whitelisted');
 Strophe.addNamespace('OMEMO_BUNDLES', Strophe.NS.OMEMO + '.bundles');
 
-/**
- * @typedef {Window & globalThis & {libsignal: any} } window
- */
 
 converse.plugins.add('converse-omemo', {
     enabled (_converse) {
         return (
-            /** @type window */(window).libsignal &&
+            /** @type WindowWithLibsignal */(window).libsignal &&
             _converse.config.get('trusted') &&
             !api.settings.get('clear_cache_on_logout') &&
             !_converse.api.settings.get('blacklisted_plugins').includes('converse-omemo')

+ 21 - 7
src/plugins/omemo/store.js

@@ -1,4 +1,6 @@
-/* global libsignal */
+/**
+ * @typedef {module:plugins-omemo-index.WindowWithLibsignal} WindowWithLibsignal
+ */
 import range from 'lodash-es/range';
 import { Model } from '@converse/skeletor';
 import { generateDeviceID } from './utils.js';
@@ -53,6 +55,7 @@ class OMEMOStore extends Model {
         if (identifier === null || identifier === undefined) {
             throw new Error("Can't save identity_key for invalid identifier");
         }
+        const { libsignal } = /** @type WindowWithLibsignal */(window);
         const address = new libsignal.SignalProtocolAddress.fromString(identifier);
         const existing = this.get('identity_key' + address.getName());
         const b64_idkey = u.arrayBufferToBase64(identity_key);
@@ -97,7 +100,7 @@ class OMEMOStore extends Model {
         return Promise.resolve();
     }
 
-    loadSignedPreKey (keyId) { // eslint-disable-line no-unused-vars
+    loadSignedPreKey (_keyId) {
         const res = this.get('signed_prekey');
         if (res) {
             return Promise.resolve({
@@ -184,6 +187,9 @@ class OMEMOStore extends Model {
     }
 
     async generateMissingPreKeys () {
+        const { libsignal } = /** @type WindowWithLibsignal */(window);
+        const { KeyHelper } = libsignal;
+
         const prekeyIds = Object.keys(this.getPreKeys());
         const missing_keys = range(0, _converse.NUM_PREKEYS)
             .map(id => id.toString())
@@ -193,15 +199,20 @@ class OMEMOStore extends Model {
             log.warn('No missing prekeys to generate for our own device');
             return Promise.resolve();
         }
+
         const keys = await Promise.all(
-            missing_keys.map(id => libsignal.KeyHelper.generatePreKey(parseInt(id, 10)))
+            missing_keys.map(id => KeyHelper.generatePreKey(parseInt(id, 10)))
         );
         keys.forEach(k => this.storePreKey(k.keyId, k.keyPair));
-        const marshalled_keys = Object.keys(this.getPreKeys()).map(k => ({
-            'id': k.keyId,
-            'key': u.arrayBufferToBase64(k.pubKey)
+
+        const prekeys = this.getPreKeys();
+        const marshalled_keys = Object.keys(prekeys).map((id) => ({
+            id,
+            key: prekeys[id].pubKey
         }));
-        const devicelist = await api.omemo.devicelists.get(_converse.bare_jid);
+
+        const bare_jid = _converse.session.get('bare_jid');
+        const devicelist = await api.omemo.devicelists.get(bare_jid);
         const device = devicelist.devices.get(this.get('device_id'));
         const bundle = await device.getBundle();
         device.save('bundle', Object.assign(bundle, { 'prekeys': marshalled_keys }));
@@ -219,6 +230,7 @@ class OMEMOStore extends Model {
      */
     async generatePreKeys () {
         const amount = _converse.NUM_PREKEYS;
+        const { libsignal } = /** @type WindowWithLibsignal */(window);
         const { KeyHelper } = libsignal;
         const keys = await Promise.all(
             range(0, amount).map(id => KeyHelper.generatePreKey(id))
@@ -241,6 +253,8 @@ class OMEMOStore extends Model {
      * even if we're offline at that time.
      */
     async generateBundle () {
+        const { libsignal } = /** @type WindowWithLibsignal */(window);
+
         // The first thing that needs to happen if a client wants to
         // start using OMEMO is they need to generate an IdentityKey
         // and a Device ID.

+ 17 - 1
src/plugins/omemo/tests/omemo.js

@@ -228,11 +228,13 @@ describe("The OMEMO module", function() {
     it("will create a new device based on a received carbon message",
             mock.initConverse(['chatBoxesFetched'], {}, async function (_converse) {
 
+
         await mock.waitUntilDiscoConfirmed(_converse, _converse.bare_jid, [], [Strophe.NS.SID]);
         await mock.waitForRoster(_converse, 'current', 1);
         const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@montague.lit';
         await u.waitUntil(() => mock.initializedOMEMO(_converse));
         await mock.openChatBoxFor(_converse, contact_jid);
+
         let iq_stanza = await u.waitUntil(() => mock.deviceListFetched(_converse, contact_jid));
         const my_devicelist = _converse.devicelists.get({'jid': _converse.bare_jid});
         expect(my_devicelist.devices.length).toBe(2);
@@ -250,6 +252,8 @@ describe("The OMEMO module", function() {
         _converse.api.connection.get()._dataRecv(mock.createRequest(stanza));
         await u.waitUntil(() => _converse.omemo_store);
 
+        const { omemo_store } = _converse;
+
         const contact_devicelist = _converse.devicelists.get({'jid': contact_jid});
         await u.waitUntil(() => contact_devicelist.devices.length === 1);
 
@@ -275,7 +279,7 @@ describe("The OMEMO module", function() {
                         <origin-id xmlns="urn:xmpp:sid:0" id="87141781-61d6-4eb3-9a31-429935a61b76"/>
                         <encrypted xmlns="eu.siacs.conversations.axolotl">
                             <header sid="988349631">
-                                <key rid="${_converse.omemo_store.get('device_id')}"
+                                <key rid="${omemo_store.get('device_id')}"
                                      prekey="true">${u.arrayBufferToBase64(obj.key_and_tag)}</key>
                                 <iv>${obj.iv}</iv>
                             </header>
@@ -291,6 +295,14 @@ describe("The OMEMO module", function() {
         _converse.api.connection.get().IQ_stanzas = [];
         _converse.api.connection.get()._dataRecv(mock.createRequest(carbon));
 
+        // Remove one pre-key to exercise code that generates new ones.
+        const prekeys = omemo_store.getPreKeys();
+        omemo_store.removePreKey(Object.keys(prekeys)[9]);
+
+        let prekey_ids = Object.keys(omemo_store.getPreKeys());
+        expect(prekey_ids.length).toBe(99);
+        expect(prekey_ids.includes('9')).toBe(false);
+
         // The message received is a prekey message, so missing prekeys are
         // generated and a new bundle published.
         iq_stanza = await u.waitUntil(() => mock.bundleHasBeenPublished(_converse));
@@ -331,6 +343,10 @@ describe("The OMEMO module", function() {
                     `<items node="eu.siacs.conversations.axolotl.bundles:988349631"/>`+
                 `</pubsub>`+
             `</iq>`);
+
+        prekey_ids = Object.keys(omemo_store.getPreKeys());
+        expect(prekey_ids.length).toBe(100);
+        expect(prekey_ids.includes('9')).toBe(true);
     }));
 
     it("can receive a PreKeySignalMessage",

+ 9 - 4
src/plugins/omemo/utils.js

@@ -1,4 +1,6 @@
-/* global libsignal */
+/**
+ * @typedef {module:plugins-omemo-index.WindowWithLibsignal} WindowWithLibsignal
+ */
 import tplAudio from 'templates/audio.js';
 import tplFile from 'templates/file.js';
 import tplImage from 'templates/image.js';
@@ -449,11 +451,9 @@ export function addKeysToMessageStanza (stanza, dicts, iv) {
                 stanza.attrs({ 'prekey': prekey });
             }
             stanza.up();
-            if (i == dicts.length - 1) {
-                stanza.c('iv').t(iv).up().up();
-            }
         }
     }
+    stanza.c('iv').t(iv).up().up();
     return Promise.resolve(stanza);
 }
 
@@ -497,6 +497,8 @@ export async function getDevicesForContact (jid) {
 }
 
 export async function generateDeviceID () {
+    const { libsignal } = /** @type WindowWithLibsignal */(window);
+
     /* Generates a device ID, making sure that it's unique */
     const devicelist = await api.omemo.devicelists.get(_converse.bare_jid, true);
     const existing_ids = devicelist.devices.pluck('id');
@@ -516,6 +518,7 @@ export async function generateDeviceID () {
 }
 
 async function buildSession (device) {
+    const { libsignal } = /** @type WindowWithLibsignal */(window);
     const address = new libsignal.SignalProtocolAddress(device.get('jid'), device.get('id'));
     const sessionBuilder = new libsignal.SessionBuilder(_converse.omemo_store, address);
     const prekey = device.getRandomPreKey();
@@ -541,6 +544,8 @@ export async function getSession (device) {
         log.error(`Could not build an OMEMO session for device ${device.get('id')} because we don't have its bundle`);
         return null;
     }
+
+    const { libsignal } = /** @type WindowWithLibsignal */(window);
     const address = new libsignal.SignalProtocolAddress(device.get('jid'), device.get('id'));
     const session = await _converse.omemo_store.loadSession(address.toString());
     if (session) {