1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-07-30 04:23:07 +03:00

Deprecate MatrixClient.{prepare,create}KeyBackupVersion in favour of new CryptoApi.resetKeyBackup API (#3689)

* new resetKeyBackup API

* add delete backup version test

* code review

* code review
This commit is contained in:
Valere
2023-09-04 22:00:28 +02:00
committed by GitHub
parent 5ddd453699
commit c65e329101
7 changed files with 317 additions and 22 deletions

View File

@ -40,6 +40,7 @@ import { logger } from "../../../src/logger";
import {
Category,
createClient,
CryptoEvent,
IClaimOTKsResult,
IContent,
IDownloadKeyResult,
@ -61,9 +62,13 @@ import { ISyncResponder, SyncResponder } from "../../test-utils/SyncResponder";
import { escapeRegExp } from "../../../src/utils";
import { downloadDeviceToJsDevice } from "../../../src/rust-crypto/device-converter";
import { flushPromises } from "../../test-utils/flushPromises";
import { mockInitialApiRequests, mockSetupCrossSigningRequests } from "../../test-utils/mockEndpoints";
import {
mockInitialApiRequests,
mockSetupCrossSigningRequests,
mockSetupMegolmBackupRequests,
} from "../../test-utils/mockEndpoints";
import { AddSecretStorageKeyOpts, SECRET_STORAGE_ALGORITHM_V1_AES } from "../../../src/secret-storage";
import { CryptoCallbacks } from "../../../src/crypto-api";
import { CryptoCallbacks, KeyBackupInfo } from "../../../src/crypto-api";
import { E2EKeyResponder } from "../../test-utils/E2EKeyResponder";
afterEach(() => {
@ -2197,11 +2202,9 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
"express:/_matrix/client/v3/user/:userId/account_data/:type(m.secret_storage.*)",
(url: string, options: RequestInit) => {
const content = JSON.parse(options.body as string);
if (content.key) {
resolve(content.key);
}
return {};
},
{ overwriteRoutes: true },
@ -2228,6 +2231,74 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
});
}
function awaitMegolmBackupKeyUpload(): Promise<Record<string, {}>> {
return new Promise((resolve) => {
// Called when the megolm backup key is uploaded
fetchMock.put(
`express:/_matrix/client/v3/user/:userId/account_data/m.megolm_backup.v1`,
(url: string, options: RequestInit) => {
const content = JSON.parse(options.body as string);
resolve(content.encrypted);
return {};
},
{ overwriteRoutes: true },
);
});
}
/**
* Add all mocks needed to set up cross-signing, key backup, 4S and then
* configure the account to have recovery.
*
* @param backupVersion - The version of the created backup
*/
async function bootstrapSecurity(backupVersion: string): Promise<void> {
mockSetupCrossSigningRequests();
mockSetupMegolmBackupRequests(backupVersion);
// promise which will resolve when a `KeyBackupStatus` event is emitted with `enabled: true`
const backupStatusUpdate = new Promise<void>((resolve) => {
aliceClient.on(CryptoEvent.KeyBackupStatus, (enabled) => {
if (enabled) {
resolve();
}
});
});
const setupPromises = [
awaitCrossSigningKeyUpload("master"),
awaitCrossSigningKeyUpload("user_signing"),
awaitCrossSigningKeyUpload("self_signing"),
awaitMegolmBackupKeyUpload(),
];
// Before setting up secret-storage, bootstrap cross-signing, so that the client has cross-signing keys.
await aliceClient.getCrypto()!.bootstrapCrossSigning({});
// Now, when we bootstrap secret-storage, the cross-signing keys should be uploaded.
const bootstrapPromise = aliceClient.getCrypto()!.bootstrapSecretStorage({
setupNewSecretStorage: true,
createSecretStorageKey,
setupNewKeyBackup: true,
});
// Wait for the key to be uploaded in the account data
const secretStorageKey = await awaitSecretStorageKeyStoredInAccountData();
// Return the newly created key in the sync response
sendSyncResponse(secretStorageKey);
// Wait for the cross signing keys to be uploaded
await Promise.all(setupPromises);
// wait for bootstrapSecretStorage to finished
await bootstrapPromise;
// Finally ensure backup is working
await aliceClient.getCrypto()!.checkKeyBackupAndEnable();
await backupStatusUpdate;
}
/**
* Send in the sync response the provided `secretStorageKey` into the account_data field
* The key is set for the `m.secret_storage.default_key` and `m.secret_storage.key.${secretStorageKey}` events
@ -2385,6 +2456,95 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
expect(userSigningKey[secretStorageKey]).toBeDefined();
expect(selfSigningKey[secretStorageKey]).toBeDefined();
});
oldBackendOnly("should create a new megolm backup", async () => {
const backupVersion = "abc";
await bootstrapSecurity(backupVersion);
// Expect a backup to be available and used
const activeBackup = await aliceClient.getCrypto()!.getActiveSessionBackupVersion();
expect(activeBackup).toStrictEqual(backupVersion);
});
oldBackendOnly("Reset key backup should create a new backup and update 4S", async () => {
// First set up 4S and key backup
const backupVersion = "1";
await bootstrapSecurity(backupVersion);
const currentVersion = await aliceClient.getCrypto()!.getActiveSessionBackupVersion();
const currentBackupKey = await aliceClient.getCrypto()!.getSessionBackupPrivateKey();
// we will call reset backup, it should delete the existing one, then setup a new one
// Let's mock for that
// Mock delete and replace the GET to return 404 as soon as called
const awaitDeleteCalled = new Promise<void>((resolve) => {
fetchMock.delete(
"express:/_matrix/client/v3/room_keys/version/:version",
(url: string, options: RequestInit) => {
fetchMock.get(
"path:/_matrix/client/v3/room_keys/version",
{
status: 404,
body: { errcode: "M_NOT_FOUND", error: "Account data not found." },
},
{ overwriteRoutes: true },
);
resolve();
return {};
},
{ overwriteRoutes: true },
);
});
const newVersion = "2";
fetchMock.post(
"path:/_matrix/client/v3/room_keys/version",
(url, request) => {
const backupData: KeyBackupInfo = JSON.parse(request.body?.toString() ?? "{}");
backupData.version = newVersion;
backupData.count = 0;
backupData.etag = "zer";
// update get call with new version
fetchMock.get("path:/_matrix/client/v3/room_keys/version", backupData, {
overwriteRoutes: true,
});
return {
version: backupVersion,
};
},
{ overwriteRoutes: true },
);
const newBackupStatusUpdate = new Promise<void>((resolve) => {
aliceClient.on(CryptoEvent.KeyBackupStatus, (enabled) => {
if (enabled) {
resolve();
}
});
});
const newBackupUploadPromise = awaitMegolmBackupKeyUpload();
await aliceClient.getCrypto()!.resetKeyBackup();
await awaitDeleteCalled;
await newBackupStatusUpdate;
await newBackupUploadPromise;
const nextVersion = await aliceClient.getCrypto()!.getActiveSessionBackupVersion();
const nextKey = await aliceClient.getCrypto()!.getSessionBackupPrivateKey();
expect(nextVersion).toBeDefined();
expect(nextVersion).not.toEqual(currentVersion);
expect(nextKey).not.toEqual(currentBackupKey);
// Test deletion of the backup
await aliceClient.getCrypto()!.deleteKeyBackupVersion(nextVersion!);
await aliceClient.getCrypto()!.checkKeyBackupAndEnable();
// XXX Legacy crypto does not update 4S when deleting backup; should ensure that rust implem does it.
expect(await aliceClient.getCrypto()!.getActiveSessionBackupVersion()).toBeNull();
});
});
describe("Incoming verification in a DM", () => {