Skip to content

Commit

Permalink
Rust crypto: ensure we persist the key backup version (#3770)
Browse files Browse the repository at this point in the history
  • Loading branch information
BillCarsonFr authored Oct 4, 2023
1 parent 10b6c24 commit 95baccf
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 11 deletions.
28 changes: 23 additions & 5 deletions spec/integ/crypto/megolm-backup.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,15 +163,31 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("megolm-keys backup (%s)", (backe
},
};

fetchMock.get(
"express:/_matrix/client/v3/room_keys/keys/:room_id/:session_id",
testData.CURVE25519_KEY_BACKUP_DATA,
);
fetchMock.get("express:/_matrix/client/v3/room_keys/keys/:room_id/:session_id", (url, request) => {
// check that the version is correct
const version = new URLSearchParams(new URL(url).search).get("version");
if (version == "1") {
return testData.CURVE25519_KEY_BACKUP_DATA;
} else {
return {
status: 403,
body: {
current_version: "1",
errcode: "M_WRONG_ROOM_KEYS_VERSION",
error: "Wrong backup version.",
},
};
}
});

fetchMock.get("path:/_matrix/client/v3/room_keys/version", testData.SIGNED_BACKUP_DATA);

aliceClient = await initTestClient();
const aliceCrypto = aliceClient.getCrypto()!;
await aliceCrypto.storeSessionBackupPrivateKey(Buffer.from(testData.BACKUP_DECRYPTION_KEY_BASE64, "base64"));
await aliceCrypto.storeSessionBackupPrivateKey(
Buffer.from(testData.BACKUP_DECRYPTION_KEY_BASE64, "base64"),
testData.SIGNED_BACKUP_DATA.version!,
);

// start after saving the private key
await aliceClient.startClient();
Expand Down Expand Up @@ -645,6 +661,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("megolm-keys backup (%s)", (backe
await aliceClient.startClient();
await aliceCrypto.storeSessionBackupPrivateKey(
Buffer.from(testData.BACKUP_DECRYPTION_KEY_BASE64, "base64"),
testData.SIGNED_BACKUP_DATA.version!,
);

const result = await aliceCrypto.isKeyBackupTrusted(testData.SIGNED_BACKUP_DATA);
Expand All @@ -658,6 +675,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("megolm-keys backup (%s)", (backe
await aliceClient.startClient();
await aliceCrypto.storeSessionBackupPrivateKey(
Buffer.from(testData.BACKUP_DECRYPTION_KEY_BASE64, "base64"),
testData.SIGNED_BACKUP_DATA.version!,
);

const backup: KeyBackupInfo = JSON.parse(JSON.stringify(testData.SIGNED_BACKUP_DATA));
Expand Down
16 changes: 16 additions & 0 deletions spec/unit/crypto/backup.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,22 @@ describe("MegolmBackup", function () {
);
});

test("fail if given backup has no version", async () => {
const client = makeTestClient(cryptoStore);
await client.initCrypto();
const data = {
algorithm: olmlib.MEGOLM_BACKUP_ALGORITHM,
auth_data: {
public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo",
},
};
const key = Uint8Array.from([1, 2, 3, 4, 5, 6, 7, 8]);
await client.getCrypto()!.storeSessionBackupPrivateKey(key, "1");
await expect(client.restoreKeyBackupWithCache(undefined, undefined, data)).rejects.toThrow(
"Backup version must be defined",
);
});

it("automatically calls the key back up", function () {
const groupSession = new Olm.OutboundGroupSession();
groupSession.create();
Expand Down
15 changes: 14 additions & 1 deletion spec/unit/rust-crypto/rust-crypto.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -783,10 +783,23 @@ describe("RustCrypto", () => {
it("can save and restore a key", async () => {
const key = "testtesttesttesttesttesttesttest";
const rustCrypto = await makeTestRustCrypto();
await rustCrypto.storeSessionBackupPrivateKey(new TextEncoder().encode(key));
await rustCrypto.storeSessionBackupPrivateKey(
new TextEncoder().encode(key),
testData.SIGNED_BACKUP_DATA.version!,
);
const fetched = await rustCrypto.getSessionBackupPrivateKey();
expect(new TextDecoder().decode(fetched!)).toEqual(key);
});

it("fails to save a key if version not provided", async () => {
const key = "testtesttesttesttesttesttesttest";
const rustCrypto = await makeTestRustCrypto();
await expect(() => rustCrypto.storeSessionBackupPrivateKey(new TextEncoder().encode(key))).rejects.toThrow(
"storeSessionBackupPrivateKey: version is required",
);
const fetched = await rustCrypto.getSessionBackupPrivateKey();
expect(fetched).toBeNull();
});
});

describe("getActiveSessionBackupVersion", () => {
Expand Down
6 changes: 5 additions & 1 deletion src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3864,6 +3864,10 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
throw new Error("End-to-end encryption disabled");
}

if (!backupInfo.version) {
throw new Error("Backup version must be defined");
}

let totalKeyCount = 0;
let keys: IMegolmSessionData[] = [];

Expand All @@ -3881,7 +3885,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
// Cache the key, if possible.
// This is async.
this.cryptoBackend
.storeSessionBackupPrivateKey(privKey)
.storeSessionBackupPrivateKey(privKey, backupInfo.version)
.catch((e) => {
logger.warn("Error caching session backup key:", e);
})
Expand Down
14 changes: 14 additions & 0 deletions src/crypto-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,11 +372,25 @@ export interface CryptoApi {
* Store the backup decryption key.
*
* This should be called if the client has received the key from another device via secret sharing (gossiping).
* It is the responsability of the caller to check that the decryption key is valid for the current backup version.
*
* @param key - the backup decryption key
*
* @deprecated prefer the variant with a `version` parameter.
*/
storeSessionBackupPrivateKey(key: Uint8Array): Promise<void>;

/**
* Store the backup decryption key.
*
* This should be called if the client has received the key from another device via secret sharing (gossiping).
* It is the responsability of the caller to check that the decryption key is valid for the given backup version.
*
* @param key - the backup decryption key
* @param version - the backup version corresponding to this decryption key
*/
storeSessionBackupPrivateKey(key: Uint8Array, version: string): Promise<void>;

/**
* Get the current status of key backup.
*
Expand Down
2 changes: 1 addition & 1 deletion src/crypto/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1317,7 +1317,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
* @param key - the private key
* @returns a promise so you can catch failures
*/
public async storeSessionBackupPrivateKey(key: ArrayLike<number>): Promise<void> {
public async storeSessionBackupPrivateKey(key: ArrayLike<number>, version?: string): Promise<void> {
if (!(key instanceof Uint8Array)) {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
throw new Error(`storeSessionBackupPrivateKey expects Uint8Array, got ${key}`);
Expand Down
13 changes: 10 additions & 3 deletions src/rust-crypto/rust-crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1005,12 +1005,19 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
* Implementation of {@link CryptoApi#storeSessionBackupPrivateKey}.
*
* @param key - the backup decryption key
* @param version - the backup version for this key.
*/
public async storeSessionBackupPrivateKey(key: Uint8Array): Promise<void> {
public async storeSessionBackupPrivateKey(key: Uint8Array, version?: string): Promise<void> {
const base64Key = encodeBase64(key);

// TODO get version from backupManager
await this.olmMachine.saveBackupDecryptionKey(RustSdkCryptoJs.BackupDecryptionKey.fromBase64(base64Key), "");
if (!version) {
throw new Error("storeSessionBackupPrivateKey: version is required");
}

await this.olmMachine.saveBackupDecryptionKey(
RustSdkCryptoJs.BackupDecryptionKey.fromBase64(base64Key),
version,
);
}

/**
Expand Down

0 comments on commit 95baccf

Please sign in to comment.