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

Add CryptoApi.getActiveSessionBackupVersion() (#3555)

* stub backupmanager

* Implement `CryptoApi.getActiveSessionBackupVersion`

* Revert unnecessary change

we can do this later, once we have better test coverage

* more test coverage

---------

Co-authored-by: Richard van der Hoff <richard@matrix.org>
This commit is contained in:
Valere
2023-07-28 10:04:20 +02:00
committed by GitHub
parent 83d447adfe
commit 6d28154dcd
7 changed files with 141 additions and 6 deletions

View File

@ -18,7 +18,7 @@ import fetchMock from "fetch-mock-jest";
import "fake-indexeddb/auto";
import { IKeyBackupSession } from "../../../src/crypto/keybackup";
import { createClient, ICreateClientOpts, IEvent, MatrixClient } from "../../../src";
import { createClient, CryptoEvent, ICreateClientOpts, IEvent, MatrixClient } from "../../../src";
import { SyncResponder } from "../../test-utils/SyncResponder";
import { E2EKeyReceiver } from "../../test-utils/E2EKeyReceiver";
import { E2EKeyResponder } from "../../test-utils/E2EKeyResponder";
@ -151,13 +151,9 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("megolm-keys backup (%s)", (backe
// start after saving the private key
await aliceClient.startClient();
// Persuade alice to fetch the device list. Completing the initial sync will make the device list download
// outdated device lists (of which our own user will be one).
syncResponder.sendOrQueueSyncResponse({});
await jest.advanceTimersByTimeAsync(10); // DeviceList has a sleep(5) which we need to make happen
// tell Alice to trust the dummy device that signed the backup, and re-check the backup.
// XXX: should we automatically re-check after a device becomes verified?
await waitForDeviceList();
await aliceCrypto.setDeviceVerified(testData.TEST_USER_ID, testData.TEST_DEVICE_ID);
await aliceClient.checkKeyBackup();
@ -170,4 +166,72 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("megolm-keys backup (%s)", (backe
await awaitDecryption(event, { waitOnDecryptionFailure: true });
expect(event.getContent()).toEqual("testytest");
});
oldBackendOnly("getActiveSessionBackupVersion() should give correct result", async function () {
// 404 means that there is no active backup
fetchMock.get("express:/_matrix/client/v3/room_keys/version", 404);
aliceClient = await initTestClient();
const aliceCrypto = aliceClient.getCrypto()!;
await aliceClient.startClient();
// tell Alice to trust the dummy device that signed the backup
await waitForDeviceList();
await aliceCrypto.setDeviceVerified(testData.TEST_USER_ID, testData.TEST_DEVICE_ID);
await aliceClient.checkKeyBackup();
// At this point there is no backup
let backupStatus: string | null;
backupStatus = await aliceCrypto.getActiveSessionBackupVersion();
expect(backupStatus).toBeNull();
// Serve a backup with no trusted signature
const unsignedBackup = JSON.parse(JSON.stringify(testData.SIGNED_BACKUP_DATA));
delete unsignedBackup.auth_data.signatures;
fetchMock.get("express:/_matrix/client/v3/room_keys/version", unsignedBackup, {
overwriteRoutes: true,
});
const checked = await aliceClient.checkKeyBackup();
expect(checked?.backupInfo?.version).toStrictEqual(unsignedBackup.version);
expect(checked?.trustInfo?.usable).toBeFalsy();
backupStatus = await aliceCrypto.getActiveSessionBackupVersion();
expect(backupStatus).toBeNull();
// Add a valid signature to the backup
fetchMock.get("express:/_matrix/client/v3/room_keys/version", testData.SIGNED_BACKUP_DATA, {
overwriteRoutes: true,
});
// check that signaling is working
const backupPromise = new Promise<void>((resolve, reject) => {
aliceClient.on(CryptoEvent.KeyBackupStatus, (enabled) => {
if (enabled) {
resolve();
}
});
});
const validCheck = await aliceClient.checkKeyBackup();
expect(validCheck?.trustInfo?.usable).toStrictEqual(true);
await backupPromise;
backupStatus = await aliceCrypto.getActiveSessionBackupVersion();
expect(backupStatus).toStrictEqual(testData.SIGNED_BACKUP_DATA.version);
});
/** make sure that the client knows about the dummy device */
async function waitForDeviceList(): Promise<void> {
// Completing the initial sync will make the device list download outdated device lists (of which our own
// user will be one).
syncResponder.sendOrQueueSyncResponse({});
// DeviceList has a sleep(5) which we need to make happen
await jest.advanceTimersByTimeAsync(10);
// The client should now know about the dummy device
const devices = await aliceClient.getCrypto()!.getUserDeviceInfo([TEST_USER_ID]);
expect(devices.get(TEST_USER_ID)!.keys()).toContain(TEST_DEVICE_ID);
}
});

View File

@ -569,6 +569,13 @@ describe("RustCrypto", () => {
expect(new TextDecoder().decode(fetched!)).toEqual(key);
});
});
describe("getActiveSessionBackupVersion", () => {
it("returns null", async () => {
const rustCrypto = await makeTestRustCrypto();
expect(await rustCrypto.getActiveSessionBackupVersion()).toBeNull();
});
});
});
/** build a basic RustCrypto instance for testing

View File

@ -3294,6 +3294,12 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* @returns true if the client is configured to back up keys to
* the server, otherwise false. If we haven't completed a successful check
* of key backup status yet, returns null.
*
* @deprecated Prefer direct access to {@link CryptoApi.getActiveSessionBackupVersion}:
*
* ```javascript
* let enabled = (await client.getCrypto().getActiveSessionBackupVersion()) !== null;
* ```
*/
public getKeyBackupEnabled(): boolean | null {
if (!this.crypto) {
@ -3436,6 +3442,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
// If we're currently backing up to this backup... stop.
// (We start using it automatically in createKeyBackupVersion
// so this is symmetrical).
// TODO: convert this to use crypto.getActiveSessionBackupVersion. And actually check the version.
if (this.crypto.backupManager.version) {
this.crypto.backupManager.disableKeyBackup();
}

View File

@ -325,6 +325,13 @@ export interface CryptoApi {
* @param key - the backup decryption key
*/
storeSessionBackupPrivateKey(key: Uint8Array): Promise<void>;
/**
* Get the current status of key backup.
*
* @returns If automatic key backups are enabled, the `version` of the active backup. Otherwise, `null`.
*/
getActiveSessionBackupVersion(): Promise<string | null>;
}
/**

View File

@ -1280,6 +1280,18 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
});
}
/**
* Get the current status of key backup.
*
* Implementation of {@link CryptoApi.getActiveSessionBackupVersion}.
*/
public async getActiveSessionBackupVersion(): Promise<string | null> {
if (this.backupManager.getKeyBackupEnabled()) {
return this.backupManager.version ?? null;
}
return null;
}
/**
* Checks that a given cross-signing private key matches a given public key.
* This can be used by the getCrossSigningKey callback to verify that the

25
src/rust-crypto/backup.ts Normal file
View File

@ -0,0 +1,25 @@
/*
Copyright 2023 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
export class RustBackupManager {
/**
* Get the backup version we are currently backing up to, if any
*/
public async getActiveBackupVersion(): Promise<string | null> {
// TODO stub
return null;
}
}

View File

@ -56,6 +56,7 @@ import { RustVerificationRequest, verificationMethodIdentifierToMethod } from ".
import { EventType } from "../@types/event";
import { CryptoEvent } from "../crypto";
import { TypedEventEmitter } from "../models/typed-event-emitter";
import { RustBackupManager } from "./backup";
const ALL_VERIFICATION_METHODS = ["m.sas.v1", "m.qr_code.scan.v1", "m.qr_code.show.v1", "m.reciprocate.v1"];
@ -82,6 +83,8 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
private outgoingRequestProcessor: OutgoingRequestProcessor;
private crossSigningIdentity: CrossSigningIdentity;
public readonly backupManager: RustBackupManager;
public constructor(
/** The `OlmMachine` from the underlying rust crypto sdk. */
private readonly olmMachine: RustSdkCryptoJs.OlmMachine,
@ -109,6 +112,7 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
this.outgoingRequestProcessor = new OutgoingRequestProcessor(olmMachine, http);
this.keyClaimManager = new KeyClaimManager(olmMachine, this.outgoingRequestProcessor);
this.eventDecryptor = new EventDecryptor(olmMachine);
this.backupManager = new RustBackupManager();
// Fire if the cross signing keys are imported from the secret storage
const onCrossSigningKeysImport = (): void => {
@ -780,6 +784,15 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
await this.olmMachine.saveBackupDecryptionKey(RustSdkCryptoJs.BackupDecryptionKey.fromBase64(base64Key), "");
}
/**
* Get the current status of key backup.
*
* Implementation of {@link CryptoApi#getActiveSessionBackupVersion}.
*/
public async getActiveSessionBackupVersion(): Promise<string | null> {
return await this.backupManager.getActiveBackupVersion();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// SyncCryptoCallbacks implementation