瀏覽代碼

Refactor the OMEMOStore to not duplicate prekeys

Before these changes, prekeys were stored in two places, one place that
converse-omemo accessed and one that libsignal accessed and when
libsignal deleted a prekey the other store wasn't updated.

Now we let the methods called by libsignal store/remove prekeys (and the
signed_prekey) in the same place as used by the code in converse-omemo.
JC Brand 6 年之前
父節點
當前提交
ea5144c7e5
共有 3 個文件被更改,包括 164 次插入121 次删除
  1. 84 61
      dist/converse.js
  2. 2 2
      spec/omemo.js
  3. 78 58
      src/converse-omemo.js

+ 84 - 61
dist/converse.js

@@ -74300,7 +74300,10 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
                 'tag': aes_data.tag
                 'tag': aes_data.tag
               }));
               }));
             }).then(plaintext => {
             }).then(plaintext => {
-              // TODO remove newly used key before republishing
+              // TODO the prekey should now have been removed.
+              // Double-check that this is the case and then
+              // generate a new key to replace it, before
+              // republishing.
               _converse.omemo_store.publishBundle();
               _converse.omemo_store.publishBundle();
 
 
               return _.extend(attrs, {
               return _.extend(attrs, {
@@ -74647,54 +74650,81 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
           }
           }
         },
         },
 
 
-        loadPreKey(keyId) {
-          let res = this.get('25519KeypreKey' + keyId);
+        getPreKeys() {
+          return this.get('prekeys') || {};
+        },
+
+        loadPreKey(key_id) {
+          const res = this.getPreKeys()[key_id];
 
 
           if (res) {
           if (res) {
-            res = {
+            return Promise.resolve({
               'privKey': u.base64ToArrayBuffer(res.privKey),
               'privKey': u.base64ToArrayBuffer(res.privKey),
               'pubKey': u.base64ToArrayBuffer(res.pubKey)
               'pubKey': u.base64ToArrayBuffer(res.pubKey)
-            };
+            });
           }
           }
 
 
-          return Promise.resolve(res);
+          return Promise.resolve();
         },
         },
 
 
-        storePreKey(keyId, keyPair) {
-          this.save('25519KeypreKey' + keyId, {
-            'privKey': u.arrayBufferToBase64(keyPair.privKey),
-            'pubKey': u.arrayBufferToBase64(keyPair.pubKey)
-          });
+        storePreKey(key_id, key_pair) {
+          const prekey = {};
+          prekey[key_id] = {
+            'pubKey': u.arrayBufferToBase64(key_pair.pubKey),
+            'privKey': u.arrayBufferToBase64(key_pair.privKey)
+          };
+          this.save('prekeys', _.extend(this.getPreKeys(), prekey));
           return Promise.resolve();
           return Promise.resolve();
         },
         },
 
 
-        removePreKey(keyId) {
-          return Promise.resolve(this.unset('25519KeypreKey' + keyId));
+        removePreKey(key_id) {
+          this.save('prekeys', _.omit(this.getPreKeys(), key_id));
+          return Promise.resolve();
         },
         },
 
 
         loadSignedPreKey(keyId) {
         loadSignedPreKey(keyId) {
-          let res = this.get('25519KeysignedKey' + keyId);
+          const res = this.get('signed_prekey');
 
 
           if (res) {
           if (res) {
-            res = {
+            return Promise.resolve({
               'privKey': u.base64ToArrayBuffer(res.privKey),
               'privKey': u.base64ToArrayBuffer(res.privKey),
               'pubKey': u.base64ToArrayBuffer(res.pubKey)
               'pubKey': u.base64ToArrayBuffer(res.pubKey)
-            };
+            });
           }
           }
 
 
-          return Promise.resolve(res);
+          return Promise.resolve();
         },
         },
 
 
-        storeSignedPreKey(keyId, keyPair) {
-          this.save('25519KeysignedKey' + keyId, {
-            'privKey': u.arrayBufferToBase64(keyPair.privKey),
-            'pubKey': u.arrayBufferToBase64(keyPair.pubKey)
+        storeSignedPreKey(spk) {
+          if (typeof spk !== "object") {
+            // XXX: We've changed the signature of this method from the
+            // example given in InMemorySignalProtocolStore.
+            // Should be fine because the libsignal code doesn't
+            // actually call this method.
+            throw new Error("storeSignedPreKey: expected an object");
+          }
+
+          this.save('signed_prekey', {
+            'id': spk.keyId,
+            'privKey': u.arrayBufferToBase64(spk.keyPair.privKey),
+            'pubKey': u.arrayBufferToBase64(spk.keyPair.pubKey),
+            // XXX: The InMemorySignalProtocolStore does not pass
+            // in or store the signature, but we need it when we
+            // publish out bundle and this method isn't called from
+            // within libsignal code, so we modify it to also store
+            // the signature.
+            'signature': u.arrayBufferToBase64(spk.signature)
           });
           });
           return Promise.resolve();
           return Promise.resolve();
         },
         },
 
 
-        removeSignedPreKey(keyId) {
-          return Promise.resolve(this.unset('25519KeysignedKey' + keyId));
+        removeSignedPreKey(key_id) {
+          if (this.get('signed_prekey')['id'] === key_id) {
+            this.unset('signed_prekey');
+            this.save();
+          }
+
+          return Promise.resolve();
         },
         },
 
 
         loadSession(identifier) {
         loadSession(identifier) {
@@ -74738,12 +74768,12 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
           }).c('item').c('bundle', {
           }).c('item').c('bundle', {
             'xmlns': Strophe.NS.OMEMO
             'xmlns': Strophe.NS.OMEMO
           }).c('signedPreKeyPublic', {
           }).c('signedPreKeyPublic', {
-            'signedPreKeyId': signed_prekey.keyId
-          }).t(signed_prekey.keyPair.pubKey).up().c('signedPreKeySignature').t(signed_prekey.signature).up().c('identityKey').t(this.get('identity_keypair').pubKey).up().c('prekeys');
+            'signedPreKeyId': signed_prekey.id
+          }).t(signed_prekey.pubKey).up().c('signedPreKeySignature').t(signed_prekey.signature).up().c('identityKey').t(this.get('identity_keypair').pubKey).up().c('prekeys');
 
 
-          _.forEach(this.get('prekeys').slice(0, _converse.NUM_PREKEYS), prekey => stanza.c('preKeyPublic', {
-            'preKeyId': prekey.keyId
-          }).t(prekey.keyPair.pubKey).up());
+          _.forEach(this.get('prekeys'), (prekey, id) => stanza.c('preKeyPublic', {
+            'preKeyId': id
+          }).t(prekey.pubKey).up());
 
 
           return _converse.api.sendIQ(stanza);
           return _converse.api.sendIQ(stanza);
         },
         },
@@ -74755,52 +74785,45 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
            * public/private Key pair. The Device ID is a randomly
            * public/private Key pair. The Device ID is a randomly
            * generated integer between 1 and 2^31 - 1.
            * generated integer between 1 and 2^31 - 1.
            */
            */
-          const data = {
-            'device_id': generateDeviceID()
-          };
+          const bundle = {};
           return libsignal.KeyHelper.generateIdentityKeyPair().then(identity_keypair => {
           return libsignal.KeyHelper.generateIdentityKeyPair().then(identity_keypair => {
-            const identity_key = u.arrayBufferToBase64(identity_keypair.pubKey);
-            data['identity_keypair'] = {
-              'privKey': u.arrayBufferToBase64(identity_keypair.privKey),
-              'pubKey': identity_key
-            };
-            data['identity_key'] = identity_key;
-            return libsignal.KeyHelper.generateSignedPreKey(identity_keypair, 1);
+            const identity_key = u.arrayBufferToBase64(identity_keypair.pubKey),
+                  device_id = generateDeviceID();
+            bundle['identity_key'] = identity_key;
+            bundle['device_id'] = device_id;
+            this.save({
+              'device_id': device_id,
+              'identity_keypair': {
+                'privKey': u.arrayBufferToBase64(identity_keypair.privKey),
+                'pubKey': identity_key
+              },
+              'identity_key': identity_key
+            });
+            return libsignal.KeyHelper.generateSignedPreKey(identity_keypair, 0);
           }).then(signed_prekey => {
           }).then(signed_prekey => {
-            _converse.omemo_store.storeSignedPreKey(signed_prekey.keyId, signed_prekey.keyPair);
+            _converse.omemo_store.storeSignedPreKey(signed_prekey);
 
 
-            data['signed_prekey'] = {
-              'keyId': signed_prekey.keyId,
-              'keyPair': {
-                'privKey': u.arrayBufferToBase64(signed_prekey.keyPair.privKey),
-                'pubKey': u.arrayBufferToBase64(signed_prekey.keyPair.pubKey)
-              },
+            bundle['signed_prekey'] = {
+              'id': signed_prekey.keyId,
+              'public_key': u.arrayBufferToBase64(signed_prekey.keyPair.privKey),
               'signature': u.arrayBufferToBase64(signed_prekey.signature)
               'signature': u.arrayBufferToBase64(signed_prekey.signature)
             };
             };
             return Promise.all(_.map(_.range(0, _converse.NUM_PREKEYS), id => libsignal.KeyHelper.generatePreKey(id)));
             return Promise.all(_.map(_.range(0, _converse.NUM_PREKEYS), id => libsignal.KeyHelper.generatePreKey(id)));
           }).then(keys => {
           }).then(keys => {
             _.forEach(keys, k => _converse.omemo_store.storePreKey(k.keyId, k.keyPair));
             _.forEach(keys, k => _converse.omemo_store.storePreKey(k.keyId, k.keyPair));
 
 
-            const marshalled_keys = _.map(keys, k => {
-              return {
-                'keyId': k.keyId,
-                'keyPair': {
-                  'pubKey': u.arrayBufferToBase64(k.keyPair.pubKey),
-                  'privKey': u.arrayBufferToBase64(k.keyPair.privKey)
-                }
-              };
-            });
-
-            data['prekeys'] = marshalled_keys;
-            this.save(data); // Save the bundle to the device
-
             const devicelist = _converse.devicelists.get(_converse.bare_jid),
             const devicelist = _converse.devicelists.get(_converse.bare_jid),
                   device = devicelist.devices.create({
                   device = devicelist.devices.create({
-              'id': data.device_id,
+              'id': bundle.device_id,
               'jid': _converse.bare_jid
               'jid': _converse.bare_jid
-            });
+            }),
+                  marshalled_keys = _.map(keys, k => ({
+              'id': k.keyId,
+              'key': u.arrayBufferToBase64(k.keyPair.pubKey)
+            }));
 
 
-            device.save('bundle', data);
+            bundle['prekeys'] = marshalled_keys;
+            device.save('bundle', bundle);
           });
           });
         },
         },
 
 

+ 2 - 2
spec/omemo.js

@@ -682,7 +682,7 @@
                             `<publish node="eu.siacs.conversations.axolotl.bundles:123456789">`+
                             `<publish node="eu.siacs.conversations.axolotl.bundles:123456789">`+
                                 `<item>`+
                                 `<item>`+
                                     `<bundle xmlns="eu.siacs.conversations.axolotl">`+
                                     `<bundle xmlns="eu.siacs.conversations.axolotl">`+
-                                        `<signedPreKeyPublic signedPreKeyId="1">${btoa('1234')}</signedPreKeyPublic>`+
+                                        `<signedPreKeyPublic signedPreKeyId="0">${btoa('1234')}</signedPreKeyPublic>`+
                                             `<signedPreKeySignature>${btoa('11112222333344445555')}</signedPreKeySignature>`+
                                             `<signedPreKeySignature>${btoa('11112222333344445555')}</signedPreKeySignature>`+
                                             `<identityKey>${btoa('1234')}</identityKey>`+
                                             `<identityKey>${btoa('1234')}</identityKey>`+
                                         `<prekeys>`+
                                         `<prekeys>`+
@@ -796,7 +796,7 @@
                 const signed_prekeys = iq_stanza.querySelectorAll('signedPreKeyPublic');
                 const signed_prekeys = iq_stanza.querySelectorAll('signedPreKeyPublic');
                 expect(signed_prekeys.length).toBe(1);
                 expect(signed_prekeys.length).toBe(1);
                 const signed_prekey = signed_prekeys[0];
                 const signed_prekey = signed_prekeys[0];
-                expect(signed_prekey.getAttribute('signedPreKeyId')).toBe('1')
+                expect(signed_prekey.getAttribute('signedPreKeyId')).toBe('0')
                 expect(iq_stanza.querySelectorAll('signedPreKeySignature').length).toBe(1);
                 expect(iq_stanza.querySelectorAll('signedPreKeySignature').length).toBe(1);
                 expect(iq_stanza.querySelectorAll('identityKey').length).toBe(1);
                 expect(iq_stanza.querySelectorAll('identityKey').length).toBe(1);
 
 

+ 78 - 58
src/converse-omemo.js

@@ -260,7 +260,10 @@
                                 const aes_data = this.getKeyAndTag(u.arrayBufferToString(key_and_tag));
                                 const aes_data = this.getKeyAndTag(u.arrayBufferToString(key_and_tag));
                                 return this.decryptMessage(_.extend(attrs.encrypted, {'key': aes_data.key, 'tag': aes_data.tag}));
                                 return this.decryptMessage(_.extend(attrs.encrypted, {'key': aes_data.key, 'tag': aes_data.tag}));
                             }).then(plaintext => {
                             }).then(plaintext => {
-                                // TODO remove newly used key before republishing
+                                // TODO the prekey should now have been removed.
+                                // Double-check that this is the case and then
+                                // generate a new key to replace it, before
+                                // republishing.
                                 _converse.omemo_store.publishBundle()
                                 _converse.omemo_store.publishBundle()
                                 return _.extend(attrs, {'plaintext': plaintext});
                                 return _.extend(attrs, {'plaintext': plaintext});
                             }).catch((e) => {
                             }).catch((e) => {
@@ -586,50 +589,75 @@
                     }
                     }
                 },
                 },
 
 
-                loadPreKey (keyId) {
-                    let res = this.get('25519KeypreKey'+keyId);
+                getPreKeys () {
+                    return this.get('prekeys') || {};
+                },
+
+                loadPreKey (key_id) {
+                    const res = this.getPreKeys()[key_id];
                     if (res) {
                     if (res) {
-                        res = {
+                        return Promise.resolve({
                             'privKey': u.base64ToArrayBuffer(res.privKey),
                             'privKey': u.base64ToArrayBuffer(res.privKey),
                             'pubKey': u.base64ToArrayBuffer(res.pubKey)
                             'pubKey': u.base64ToArrayBuffer(res.pubKey)
-                        };
+                        });
                     }
                     }
-                    return Promise.resolve(res);
+                    return Promise.resolve();
                 },
                 },
 
 
-                storePreKey (keyId, keyPair) {
-                    this.save('25519KeypreKey'+keyId, {
-                        'privKey': u.arrayBufferToBase64(keyPair.privKey),
-                        'pubKey': u.arrayBufferToBase64(keyPair.pubKey)
-                    });
+                storePreKey (key_id, key_pair) {
+                    const prekey = {};
+                    prekey[key_id] = {
+                        'pubKey': u.arrayBufferToBase64(key_pair.pubKey),
+                        'privKey': u.arrayBufferToBase64(key_pair.privKey)
+                    }
+                    this.save('prekeys', _.extend(this.getPreKeys(), prekey));
                     return Promise.resolve();
                     return Promise.resolve();
                 },
                 },
 
 
-                removePreKey (keyId) {
-                    return Promise.resolve(this.unset('25519KeypreKey'+keyId));
+                removePreKey (key_id) {
+                    this.save('prekeys', _.omit(this.getPreKeys(), key_id));
+                    return Promise.resolve();
                 },
                 },
 
 
                 loadSignedPreKey (keyId) {
                 loadSignedPreKey (keyId) {
-                    let res = this.get('25519KeysignedKey'+keyId);
+                    const res = this.get('signed_prekey');
                     if (res) {
                     if (res) {
-                        res = {
+                        return Promise.resolve({
                             'privKey': u.base64ToArrayBuffer(res.privKey),
                             'privKey': u.base64ToArrayBuffer(res.privKey),
                             'pubKey': u.base64ToArrayBuffer(res.pubKey)
                             'pubKey': u.base64ToArrayBuffer(res.pubKey)
-                        };
+                        });
                     }
                     }
-                    return Promise.resolve(res);
+                    return Promise.resolve();
                 },
                 },
 
 
-                storeSignedPreKey (keyId, keyPair) {
-                    this.save('25519KeysignedKey'+keyId, {
-                        'privKey': u.arrayBufferToBase64(keyPair.privKey),
-                        'pubKey': u.arrayBufferToBase64(keyPair.pubKey)
+                storeSignedPreKey (spk) {
+                    if (typeof spk !== "object") {
+                        // XXX: We've changed the signature of this method from the
+                        // example given in InMemorySignalProtocolStore.
+                        // Should be fine because the libsignal code doesn't
+                        // actually call this method.
+                        throw new Error("storeSignedPreKey: expected an object");
+                    }
+                    this.save('signed_prekey', {
+                        'id': spk.keyId,
+                        'privKey': u.arrayBufferToBase64(spk.keyPair.privKey),
+                        'pubKey': u.arrayBufferToBase64(spk.keyPair.pubKey),
+                        // XXX: The InMemorySignalProtocolStore does not pass
+                        // in or store the signature, but we need it when we
+                        // publish out bundle and this method isn't called from
+                        // within libsignal code, so we modify it to also store
+                        // the signature.
+                        'signature': u.arrayBufferToBase64(spk.signature)
                     });
                     });
                     return Promise.resolve();
                     return Promise.resolve();
                 },
                 },
 
 
-                removeSignedPreKey (keyId) {
-                    return Promise.resolve(this.unset('25519KeysignedKey'+keyId));
+                removeSignedPreKey (key_id) {
+                    if (this.get('signed_prekey')['id'] === key_id) {
+                        this.unset('signed_prekey');
+                        this.save();
+                    }
+                    return Promise.resolve();
                 },
                 },
 
 
                 loadSession (identifier) {
                 loadSession (identifier) {
@@ -665,14 +693,14 @@
                         .c('publish', {'node': `${Strophe.NS.OMEMO_BUNDLES}:${this.get('device_id')}`})
                         .c('publish', {'node': `${Strophe.NS.OMEMO_BUNDLES}:${this.get('device_id')}`})
                             .c('item')
                             .c('item')
                                 .c('bundle', {'xmlns': Strophe.NS.OMEMO})
                                 .c('bundle', {'xmlns': Strophe.NS.OMEMO})
-                                    .c('signedPreKeyPublic', {'signedPreKeyId': signed_prekey.keyId})
-                                        .t(signed_prekey.keyPair.pubKey).up()
+                                    .c('signedPreKeyPublic', {'signedPreKeyId': signed_prekey.id})
+                                        .t(signed_prekey.pubKey).up()
                                     .c('signedPreKeySignature').t(signed_prekey.signature).up()
                                     .c('signedPreKeySignature').t(signed_prekey.signature).up()
                                     .c('identityKey').t(this.get('identity_keypair').pubKey).up()
                                     .c('identityKey').t(this.get('identity_keypair').pubKey).up()
                                     .c('prekeys');
                                     .c('prekeys');
                     _.forEach(
                     _.forEach(
-                        this.get('prekeys').slice(0, _converse.NUM_PREKEYS),
-                        (prekey) => stanza.c('preKeyPublic', {'preKeyId': prekey.keyId}).t(prekey.keyPair.pubKey).up()
+                        this.get('prekeys'),
+                        (prekey, id) => stanza.c('preKeyPublic', {'preKeyId': id}).t(prekey.pubKey).up()
                     );
                     );
                     return _converse.api.sendIQ(stanza);
                     return _converse.api.sendIQ(stanza);
                 },
                 },
@@ -684,46 +712,38 @@
                      * public/private Key pair. The Device ID is a randomly
                      * public/private Key pair. The Device ID is a randomly
                      * generated integer between 1 and 2^31 - 1.
                      * generated integer between 1 and 2^31 - 1.
                      */
                      */
-                    const data = {
-                        'device_id': generateDeviceID()
-                    };
+                    const bundle = {};
                     return libsignal.KeyHelper.generateIdentityKeyPair()
                     return libsignal.KeyHelper.generateIdentityKeyPair()
                         .then(identity_keypair => {
                         .then(identity_keypair => {
-                            const identity_key = u.arrayBufferToBase64(identity_keypair.pubKey);
-                            data['identity_keypair'] = {
-                                'privKey': u.arrayBufferToBase64(identity_keypair.privKey),
-                                'pubKey': identity_key
-                            };
-                            data['identity_key'] = identity_key;
-                            return libsignal.KeyHelper.generateSignedPreKey(identity_keypair, 1);
-                        }).then(signed_prekey => {
-                            _converse.omemo_store.storeSignedPreKey(signed_prekey.keyId, signed_prekey.keyPair);
-                            data['signed_prekey'] = {
-                                'keyId': signed_prekey.keyId,
-                                'keyPair': {
-                                    'privKey': u.arrayBufferToBase64(signed_prekey.keyPair.privKey),
-                                    'pubKey': u.arrayBufferToBase64(signed_prekey.keyPair.pubKey)
+                            const identity_key = u.arrayBufferToBase64(identity_keypair.pubKey),
+                                  device_id = generateDeviceID();
+
+                            bundle['identity_key'] = identity_key;
+                            bundle['device_id'] = device_id;
+                            this.save({
+                                'device_id': device_id,
+                                'identity_keypair': {
+                                    'privKey': u.arrayBufferToBase64(identity_keypair.privKey),
+                                    'pubKey': identity_key
                                 },
                                 },
+                                'identity_key': identity_key
+                            });
+                            return libsignal.KeyHelper.generateSignedPreKey(identity_keypair, 0);
+                        }).then(signed_prekey => {
+                            _converse.omemo_store.storeSignedPreKey(signed_prekey);
+                            bundle['signed_prekey'] = {
+                                'id': signed_prekey.keyId,
+                                'public_key': u.arrayBufferToBase64(signed_prekey.keyPair.privKey),
                                 'signature': u.arrayBufferToBase64(signed_prekey.signature)
                                 'signature': u.arrayBufferToBase64(signed_prekey.signature)
                             }
                             }
                             return Promise.all(_.map(_.range(0, _converse.NUM_PREKEYS), id => libsignal.KeyHelper.generatePreKey(id)));
                             return Promise.all(_.map(_.range(0, _converse.NUM_PREKEYS), id => libsignal.KeyHelper.generatePreKey(id)));
                         }).then(keys => {
                         }).then(keys => {
                             _.forEach(keys, k => _converse.omemo_store.storePreKey(k.keyId, k.keyPair));
                             _.forEach(keys, k => _converse.omemo_store.storePreKey(k.keyId, k.keyPair));
-                            const marshalled_keys = _.map(keys, k => {
-                                return {
-                                    'keyId': k.keyId,
-                                    'keyPair': {
-                                        'pubKey': u.arrayBufferToBase64(k.keyPair.pubKey),
-                                        'privKey': u.arrayBufferToBase64(k.keyPair.privKey)
-                                    }
-                                }
-                            });
-                            data['prekeys'] = marshalled_keys;
-                            this.save(data)
-                            // Save the bundle to the device
                             const devicelist = _converse.devicelists.get(_converse.bare_jid),
                             const devicelist = _converse.devicelists.get(_converse.bare_jid),
-                                  device = devicelist.devices.create({'id': data.device_id, 'jid': _converse.bare_jid});
-                            device.save('bundle', data);
+                                  device = devicelist.devices.create({'id': bundle.device_id, 'jid': _converse.bare_jid}),
+                                  marshalled_keys = _.map(keys, k => ({'id': k.keyId, 'key': u.arrayBufferToBase64(k.keyPair.pubKey)}));
+                            bundle['prekeys'] = marshalled_keys;
+                            device.save('bundle', bundle);
                         });
                         });
                 },
                 },