index : matrix-js-sdk

My fork of matrix-js-sdk

diff options
context:
space:
mode:
authorBruno Windels <[email protected]>2020-06-05 13:25:00 +0000
committerGitHub <[email protected]>2020-06-05 13:25:00 +0000
commit013fbb87a789027f01a7585082d8eb692b072a9f (patch)
tree94faa74cd4d3813dedf22ef847c4a027610ca53a
parent0a790b2ae3341c59b7221fe69298b9096b9c0dc4 (diff)
parent73764d23dca31088c59f14fe3a9cfe919b410932 (diff)
downloadmatrix-js-sdk-013fbb87a789027f01a7585082d8eb692b072a9f.tar.gz
Merge pull request #1398 from matrix-org/bwindels/backupformatbug
Bring back backup key format migration
-rw-r--r--spec/unit/crypto/secrets.spec.js131
-rw-r--r--src/client.js14
-rw-r--r--src/crypto/index.js45
3 files changed, 182 insertions, 8 deletions
diff --git a/spec/unit/crypto/secrets.spec.js b/spec/unit/crypto/secrets.spec.js
index 74552a2e..d60ca1ba 100644
--- a/spec/unit/crypto/secrets.spec.js
+++ b/spec/unit/crypto/secrets.spec.js
@@ -20,6 +20,7 @@ import {SECRET_STORAGE_ALGORITHM_V1_AES} from "../../../src/crypto/SecretStorage
import {MatrixEvent} from "../../../src/models/event";
import {TestClient} from '../../TestClient';
import {makeTestClients} from './verification/util';
+import {encryptAES} from "../../../src/crypto/aes";
import * as utils from "../../../src/utils";
@@ -527,5 +528,135 @@ describe("Secrets", function() {
expect(alice.checkSecretStorageKey(secretStorageKeys.key_id, keyInfo))
.toBeTruthy();
});
+ it("fixes backup keys in the wrong format", async function() {
+ let crossSigningKeys = {
+ master: XSK,
+ user_signing: USK,
+ self_signing: SSK,
+ };
+ const secretStorageKeys = {
+ key_id: SSSSKey,
+ };
+ const alice = await makeTestClient(
+ {userId: "@alice:example.com", deviceId: "Osborne2"},
+ {
+ cryptoCallbacks: {
+ getCrossSigningKey: t => crossSigningKeys[t],
+ saveCrossSigningKeys: k => crossSigningKeys = k,
+ getSecretStorageKey: ({keys}, name) => {
+ for (const keyId of Object.keys(keys)) {
+ if (secretStorageKeys[keyId]) {
+ return [keyId, secretStorageKeys[keyId]];
+ }
+ }
+ },
+ },
+ },
+ );
+ alice.store.storeAccountDataEvents([
+ new MatrixEvent({
+ type: "m.secret_storage.default_key",
+ content: {
+ key: "key_id",
+ },
+ }),
+ new MatrixEvent({
+ type: "m.secret_storage.key.key_id",
+ content: {
+ algorithm: "m.secret_storage.v1.aes-hmac-sha2",
+ passphrase: {
+ algorithm: "m.pbkdf2",
+ iterations: 500000,
+ salt: "GbkvwKHVMveo1zGVSb2GMMdCinG2npJK",
+ },
+ },
+ }),
+ new MatrixEvent({
+ type: "m.cross_signing.master",
+ content: {
+ encrypted: {
+ key_id: {ciphertext: "bla", mac: "bla", iv: "bla"},
+ },
+ },
+ }),
+ new MatrixEvent({
+ type: "m.cross_signing.self_signing",
+ content: {
+ encrypted: {
+ key_id: {ciphertext: "bla", mac: "bla", iv: "bla"},
+ },
+ },
+ }),
+ new MatrixEvent({
+ type: "m.cross_signing.user_signing",
+ content: {
+ encrypted: {
+ key_id: {ciphertext: "bla", mac: "bla", iv: "bla"},
+ },
+ },
+ }),
+ new MatrixEvent({
+ type: "m.megolm_backup.v1",
+ content: {
+ encrypted: {
+ key_id: await encryptAES(
+ "123,45,6,7,89,1,234,56,78,90,12,34,5,67,8,90",
+ secretStorageKeys.key_id, "m.megolm_backup.v1",
+ ),
+ },
+ },
+ }),
+ ]);
+ alice._crypto._deviceList.storeCrossSigningForUser("@alice:example.com", {
+ keys: {
+ master: {
+ user_id: "@alice:example.com",
+ usage: ["master"],
+ keys: {
+ [`ed25519:${XSPubKey}`]: XSPubKey,
+ },
+ },
+ self_signing: sign({
+ user_id: "@alice:example.com",
+ usage: ["self_signing"],
+ keys: {
+ [`ed25519:${SSPubKey}`]: SSPubKey,
+ },
+ }, XSK, "@alice:example.com"),
+ user_signing: sign({
+ user_id: "@alice:example.com",
+ usage: ["user_signing"],
+ keys: {
+ [`ed25519:${USPubKey}`]: USPubKey,
+ },
+ }, XSK, "@alice:example.com"),
+ },
+ });
+ alice.getKeyBackupVersion = async () => {
+ return {
+ version: "1",
+ algorithm: "m.megolm_backup.v1.curve25519-aes-sha2",
+ auth_data: sign({
+ public_key: "pxEXhg+4vdMf/kFwP4bVawFWdb0EmytL3eFJx++zQ0A",
+ }, XSK, "@alice:example.com"),
+ };
+ };
+ alice.setAccountData = async function(name, data) {
+ const event = new MatrixEvent({
+ type: name,
+ content: data,
+ });
+ alice.store.storeAccountDataEvents([event]);
+ this.emit("accountData", event);
+ };
+
+ await alice.bootstrapSecretStorage();
+
+ const backupKey = alice.getAccountData("m.megolm_backup.v1")
+ .getContent();
+ expect(backupKey.encrypted).toHaveProperty("key_id");
+ expect(await alice.getSecret("m.megolm_backup.v1"))
+ .toEqual("ey0GB1kB6jhOWgwiBUMIWg==");
+ });
});
});
diff --git a/src/client.js b/src/client.js
index d390441e..fc73b41f 100644
--- a/src/client.js
+++ b/src/client.js
@@ -47,7 +47,7 @@ import * as olmlib from "./crypto/olmlib";
import {ReEmitter} from './ReEmitter';
import {RoomList} from './crypto/RoomList';
import {logger} from './logger';
-import {Crypto, isCryptoAvailable} from './crypto';
+import {Crypto, isCryptoAvailable, fixBackupKey} from './crypto';
import {decodeRecoveryKey} from './crypto/recoverykey';
import {keyFromAuthData} from './crypto/key_passphrase';
import {randomString} from './randomstring';
@@ -1835,7 +1835,17 @@ MatrixClient.prototype.restoreKeyBackupWithPassword = async function(
MatrixClient.prototype.restoreKeyBackupWithSecretStorage = async function(
backupInfo, targetRoomId, targetSessionId, opts,
) {
- const privKey = decodeBase64(await this.getSecret("m.megolm_backup.v1"));
+ const storedKey = await this.getSecret("m.megolm_backup.v1");
+
+ // ensure that the key is in the right format. If not, fix the key and
+ // store the fixed version
+ const fixedKey = fixBackupKey(storedKey);
+ if (fixedKey) {
+ const [keyId] = await this._crypto.getSecretStorageKey();
+ await this.storeSecret("m.megolm_backup.v1", fixedKey, [keyId]);
+ }
+
+ const privKey = decodeBase64(fixedKey || storedKey);
return this._restoreKeyBackup(
privKey, targetRoomId, targetSessionId, backupInfo, opts,
);
diff --git a/src/crypto/index.js b/src/crypto/index.js
index 06cc2add..ef9f492d 100644
--- a/src/crypto/index.js
+++ b/src/crypto/index.js
@@ -712,8 +712,17 @@ Crypto.prototype.bootstrapSecretStorage = async function({
const sessionBackupKey = await this.getSecret('m.megolm_backup.v1');
if (sessionBackupKey) {
logger.info("Got session backup key from secret storage: caching");
- const decodedBackupKey =
- new Uint8Array(olmlib.decodeBase64(sessionBackupKey));
+ // fix up the backup key if it's in the wrong format, and replace
+ // in secret storage
+ const fixedBackupKey = fixBackupKey(sessionBackupKey);
+ if (fixedBackupKey) {
+ await this.storeSecret(
+ "m.megolm_backup.v1", fixedBackupKey, [newKeyId || oldKeyId],
+ );
+ }
+ const decodedBackupKey = new Uint8Array(olmlib.decodeBase64(
+ fixedBackupKey || sessionBackupKey,
+ ));
await this.storeSessionBackupPrivateKey(decodedBackupKey);
}
} finally {
@@ -731,6 +740,26 @@ Crypto.prototype.bootstrapSecretStorage = async function({
logger.log("Secure Secret Storage ready");
};
+/**
+ * Fix up the backup key, that may be in the wrong format due to a bug in a
+ * migration step. Some backup keys were stored as a comma-separated list of
+ * integers, rather than a base64-encoded byte array. If this function is
+ * passed a string that looks like a list of integers rather than a base64
+ * string, it will attempt to convert it to the right format.
+ *
+ * @param {string} key the key to check
+ * @returns {null | string} If the key is in the wrong format, then the fixed
+ * key will be returned. Otherwise null will be returned.
+ *
+ */
+export function fixBackupKey(key) {
+ if (typeof key !== "string" || key.indexOf(",") < 0) {
+ return null;
+ }
+ const fixedKey = Uint8Array.from(key.split(","), x => parseInt(x));
+ return olmlib.encodeBase64(fixedKey);
+}
+
Crypto.prototype.addSecretStorageKey = function(algorithm, opts, keyID) {
return this._secretStorage.addKey(algorithm, opts, keyID);
};
@@ -800,7 +829,7 @@ Crypto.prototype.checkSecretStoragePrivateKey = function(privateKey, expectedPub
* @returns {Promise} the key, if any, or null
*/
Crypto.prototype.getSessionBackupPrivateKey = async function() {
- const key = await new Promise((resolve) => {
+ let key = await new Promise((resolve) => {
this._cryptoStore.doTxn(
'readonly',
[IndexedDBCryptoStore.STORE_ACCOUNT],
@@ -814,13 +843,17 @@ Crypto.prototype.getSessionBackupPrivateKey = async function() {
);
});
+ // make sure we have a Uint8Array, rather than a string
+ if (key && typeof key === "string") {
+ key = new Uint8Array(olmlib.decodeBase64(fixBackupKey(key) || key));
+ await this.storeSessionBackupPrivateKey(key);
+ }
if (key && key.ciphertext) {
const pickleKey = Buffer.from(this._olmDevice._pickleKey);
const decrypted = await decryptAES(key, pickleKey, "m.megolm_backup.v1");
- return olmlib.decodeBase64(decrypted);
- } else {
- return key;
+ key = olmlib.decodeBase64(decrypted);
}
+ return key;
};
/**