You've already forked matrix-js-sdk
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:
@ -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);
|
||||
}
|
||||
});
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
25
src/rust-crypto/backup.ts
Normal 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;
|
||||
}
|
||||
}
|
@ -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
|
||||
|
Reference in New Issue
Block a user