From f406ffd3dd14b6db86503fc562a399a002a9df3e Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Wed, 30 Aug 2023 10:30:31 +0100 Subject: [PATCH] `RustCrypto.getCrossSigningStatus`: check the client is not stopped (#3682) * `RustCrypto.getCrossSigningStatus`: check the client is not stopped Better error handling for the case that a call to `MatrixClient.stop` happens while the call to `getCrossSigningStatus` (or `isCrossSigningReady`) is in flight. * fix up tsdoc --- spec/unit/rust-crypto/rust-crypto.spec.ts | 38 +++++++++++++++++++++++ src/crypto-api.ts | 7 ++++- src/errors.ts | 14 +++++++++ src/rust-crypto/rust-crypto.ts | 20 ++++++++++-- 4 files changed, 76 insertions(+), 3 deletions(-) diff --git a/spec/unit/rust-crypto/rust-crypto.spec.ts b/spec/unit/rust-crypto/rust-crypto.spec.ts index 447f8ce5f7b..d8356d8768b 100644 --- a/spec/unit/rust-crypto/rust-crypto.spec.ts +++ b/spec/unit/rust-crypto/rust-crypto.spec.ts @@ -150,6 +150,44 @@ describe("RustCrypto", () => { await expect(rustCrypto.getCrossSigningKeyId()).resolves.toBe(null); }); + describe("getCrossSigningStatus", () => { + it("returns sensible values on a default client", async () => { + const secretStorage = { + isStored: jest.fn().mockResolvedValue(null), + } as unknown as Mocked; + const rustCrypto = await makeTestRustCrypto(undefined, undefined, undefined, secretStorage); + + const result = await rustCrypto.getCrossSigningStatus(); + + expect(secretStorage.isStored).toHaveBeenCalledWith("m.cross_signing.master"); + expect(result).toEqual({ + privateKeysCachedLocally: { + masterKey: false, + selfSigningKey: false, + userSigningKey: false, + }, + privateKeysInSecretStorage: false, + publicKeysOnDevice: false, + }); + }); + + it("throws if `stop` is called mid-call", async () => { + const secretStorage = { + isStored: jest.fn().mockResolvedValue(null), + } as unknown as Mocked; + const rustCrypto = await makeTestRustCrypto(undefined, undefined, undefined, secretStorage); + + // start the call off + const result = rustCrypto.getCrossSigningStatus(); + + // call `.stop` + rustCrypto.stop(); + + // getCrossSigningStatus should abort + await expect(result).rejects.toEqual(new Error("MatrixClient has been stopped")); + }); + }); + it("bootstrapCrossSigning delegates to CrossSigningIdentity", async () => { const rustCrypto = await makeTestRustCrypto(); const mockCrossSigningIdentity = { diff --git a/src/crypto-api.ts b/src/crypto-api.ts index 85e9c1dd6f3..10638d21bc7 100644 --- a/src/crypto-api.ts +++ b/src/crypto-api.ts @@ -170,6 +170,8 @@ export interface CryptoApi { * return true. * * @returns True if cross-signing is ready to be used on this device + * + * @throws May throw {@link ClientStoppedError} if the `MatrixClient` is stopped before or during the call. */ isCrossSigningReady(): Promise; @@ -234,7 +236,10 @@ export interface CryptoApi { /** * Get the status of our cross-signing keys. * - * @returns The current status of cross-signing keys: whether we have public and private keys cached locally, and whether the private keys are in secret storage. + * @returns The current status of cross-signing keys: whether we have public and private keys cached locally, and + * whether the private keys are in secret storage. + * + * @throws May throw {@link ClientStoppedError} if the `MatrixClient` is stopped before or during the call. */ getCrossSigningStatus(): Promise; diff --git a/src/errors.ts b/src/errors.ts index 9d2465131f7..bbdf4735f2b 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -51,3 +51,17 @@ export class KeySignatureUploadError extends Error { super(message); } } + +/** + * It is invalid to call most methods once {@link MatrixClient#stopClient} has been called. + * + * This error will be thrown if you attempt to do so. + * + * {@link MatrixClient#stopClient} itself is an exception to this: it may safely be called multiple times on the same + * instance. + */ +export class ClientStoppedError extends Error { + public constructor() { + super("MatrixClient has been stopped"); + } +} diff --git a/src/rust-crypto/rust-crypto.ts b/src/rust-crypto/rust-crypto.ts index 9a90c9db1b5..3db3878ec55 100644 --- a/src/rust-crypto/rust-crypto.ts +++ b/src/rust-crypto/rust-crypto.ts @@ -62,6 +62,7 @@ import { TypedEventEmitter } from "../models/typed-event-emitter"; import { RustBackupCryptoEventMap, RustBackupCryptoEvents, RustBackupManager } from "./backup"; import { TypedReEmitter } from "../ReEmitter"; import { randomString } from "../randomstring"; +import { ClientStoppedError } from "../errors"; const ALL_VERIFICATION_METHODS = ["m.sas.v1", "m.qr_code.scan.v1", "m.qr_code.show.v1", "m.reciprocate.v1"]; @@ -138,6 +139,20 @@ export class RustCrypto extends TypedEventEmitter { - const userIdentity: RustSdkCryptoJs.OwnUserIdentity | null = await this.olmMachine.getIdentity( + const userIdentity: RustSdkCryptoJs.OwnUserIdentity | null = await this.getOlmMachineOrThrow().getIdentity( new RustSdkCryptoJs.UserId(this.userId), ); + const publicKeysOnDevice = Boolean(userIdentity?.masterKey) && Boolean(userIdentity?.selfSigningKey) && Boolean(userIdentity?.userSigningKey); const privateKeysInSecretStorage = await secretStorageContainsCrossSigningKeys(this.secretStorage); const crossSigningStatus: RustSdkCryptoJs.CrossSigningStatus | null = - await this.olmMachine.crossSigningStatus(); + await this.getOlmMachineOrThrow().crossSigningStatus(); return { publicKeysOnDevice,