1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-11-25 05:23:13 +03:00

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
This commit is contained in:
Richard van der Hoff
2023-08-30 10:30:31 +01:00
committed by GitHub
parent dec4650d3d
commit f406ffd3dd
4 changed files with 76 additions and 3 deletions

View File

@@ -150,6 +150,44 @@ describe("RustCrypto", () => {
await expect(rustCrypto.getCrossSigningKeyId()).resolves.toBe(null); 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<ServerSideSecretStorage>;
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<ServerSideSecretStorage>;
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 () => { it("bootstrapCrossSigning delegates to CrossSigningIdentity", async () => {
const rustCrypto = await makeTestRustCrypto(); const rustCrypto = await makeTestRustCrypto();
const mockCrossSigningIdentity = { const mockCrossSigningIdentity = {

View File

@@ -170,6 +170,8 @@ export interface CryptoApi {
* return true. * return true.
* *
* @returns True if cross-signing is ready to be used on this device * @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<boolean>; isCrossSigningReady(): Promise<boolean>;
@@ -234,7 +236,10 @@ export interface CryptoApi {
/** /**
* Get the status of our cross-signing keys. * 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<CrossSigningStatus>; getCrossSigningStatus(): Promise<CrossSigningStatus>;

View File

@@ -51,3 +51,17 @@ export class KeySignatureUploadError extends Error {
super(message); 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");
}
}

View File

@@ -62,6 +62,7 @@ import { TypedEventEmitter } from "../models/typed-event-emitter";
import { RustBackupCryptoEventMap, RustBackupCryptoEvents, RustBackupManager } from "./backup"; import { RustBackupCryptoEventMap, RustBackupCryptoEvents, RustBackupManager } from "./backup";
import { TypedReEmitter } from "../ReEmitter"; import { TypedReEmitter } from "../ReEmitter";
import { randomString } from "../randomstring"; 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"]; 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<RustCryptoEvents, RustCryptoEv
); );
} }
/**
* Return the OlmMachine only if {@link RustCrypto#stop} has not been called.
*
* This allows us to better handle race conditions where the client is stopped before or during a crypto API call.
*
* @throws ClientStoppedError if {@link RustCrypto#stop} has been called.
*/
private getOlmMachineOrThrow(): RustSdkCryptoJs.OlmMachine {
if (this.stopped) {
throw new ClientStoppedError();
}
return this.olmMachine;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// CryptoBackend implementation // CryptoBackend implementation
@@ -635,16 +650,17 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
* Implementation of {@link CryptoApi#getCrossSigningStatus} * Implementation of {@link CryptoApi#getCrossSigningStatus}
*/ */
public async getCrossSigningStatus(): Promise<CrossSigningStatus> { public async getCrossSigningStatus(): Promise<CrossSigningStatus> {
const userIdentity: RustSdkCryptoJs.OwnUserIdentity | null = await this.olmMachine.getIdentity( const userIdentity: RustSdkCryptoJs.OwnUserIdentity | null = await this.getOlmMachineOrThrow().getIdentity(
new RustSdkCryptoJs.UserId(this.userId), new RustSdkCryptoJs.UserId(this.userId),
); );
const publicKeysOnDevice = const publicKeysOnDevice =
Boolean(userIdentity?.masterKey) && Boolean(userIdentity?.masterKey) &&
Boolean(userIdentity?.selfSigningKey) && Boolean(userIdentity?.selfSigningKey) &&
Boolean(userIdentity?.userSigningKey); Boolean(userIdentity?.userSigningKey);
const privateKeysInSecretStorage = await secretStorageContainsCrossSigningKeys(this.secretStorage); const privateKeysInSecretStorage = await secretStorageContainsCrossSigningKeys(this.secretStorage);
const crossSigningStatus: RustSdkCryptoJs.CrossSigningStatus | null = const crossSigningStatus: RustSdkCryptoJs.CrossSigningStatus | null =
await this.olmMachine.crossSigningStatus(); await this.getOlmMachineOrThrow().crossSigningStatus();
return { return {
publicKeysOnDevice, publicKeysOnDevice,