From 63abd00ca7104cf9b37f673e42712711a4db26e2 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Fri, 12 May 2023 13:21:52 +0100 Subject: [PATCH] Element-R: Stub out `isCrossSigningReady` and `isSecretStorageReady` (#3354) * Stub implementation of `isCrossSigningReady` * Stub implementation of `isSecretStorageReady` * add tests to meet quality gate * factor out common * Remove accidentally-added file --- spec/unit/matrix-client.spec.ts | 42 +++++++++++++++ spec/unit/rust-crypto/rust-crypto.spec.ts | 62 +++++++++++------------ src/client.ts | 12 ++--- src/crypto-api.ts | 29 +++++++++++ src/rust-crypto/rust-crypto.ts | 14 +++++ 5 files changed, 122 insertions(+), 37 deletions(-) diff --git a/spec/unit/matrix-client.spec.ts b/spec/unit/matrix-client.spec.ts index 635cd2ee5..3b5231d55 100644 --- a/spec/unit/matrix-client.spec.ts +++ b/spec/unit/matrix-client.spec.ts @@ -70,6 +70,7 @@ import { SyncState } from "../../src/sync"; import * as featureUtils from "../../src/feature"; import { StubStore } from "../../src/store/stub"; import { SecretStorageKeyDescriptionAesV1, ServerSideSecretStorageImpl } from "../../src/secret-storage"; +import { CryptoBackend } from "../../src/common-crypto/CryptoBackend"; jest.useFakeTimers(); @@ -2750,6 +2751,47 @@ describe("MatrixClient", function () { }); }); + // these wrappers are deprecated, but we need coverage of them to pass the quality gate + describe("Crypto wrappers", () => { + describe("exception if no crypto", () => { + it("isCrossSigningReady", () => { + expect(() => client.isCrossSigningReady()).toThrow("End-to-end encryption disabled"); + }); + + it("isSecretStorageReady", () => { + expect(() => client.isSecretStorageReady()).toThrow("End-to-end encryption disabled"); + }); + }); + + describe("defer to crypto backend", () => { + let mockCryptoBackend: Mocked; + + beforeEach(() => { + mockCryptoBackend = { + isCrossSigningReady: jest.fn(), + isSecretStorageReady: jest.fn(), + stop: jest.fn().mockResolvedValue(undefined), + } as unknown as Mocked; + client["cryptoBackend"] = mockCryptoBackend; + }); + + it("isCrossSigningReady", async () => { + const testResult = "test"; + mockCryptoBackend.isCrossSigningReady.mockResolvedValue(testResult as unknown as boolean); + expect(await client.isCrossSigningReady()).toBe(testResult); + expect(mockCryptoBackend.isCrossSigningReady).toHaveBeenCalledTimes(1); + }); + + it("isSecretStorageReady", async () => { + client["cryptoBackend"] = mockCryptoBackend; + const testResult = "test"; + mockCryptoBackend.isSecretStorageReady.mockResolvedValue(testResult as unknown as boolean); + expect(await client.isSecretStorageReady()).toBe(testResult); + expect(mockCryptoBackend.isSecretStorageReady).toHaveBeenCalledTimes(1); + }); + }); + }); + describe("paginateEventTimeline()", () => { describe("notifications timeline", () => { const unsafeNotification = { diff --git a/spec/unit/rust-crypto/rust-crypto.spec.ts b/spec/unit/rust-crypto/rust-crypto.spec.ts index f6bc05ade..397b4df03 100644 --- a/spec/unit/rust-crypto/rust-crypto.spec.ts +++ b/spec/unit/rust-crypto/rust-crypto.spec.ts @@ -22,7 +22,7 @@ import { Mocked } from "jest-mock"; import { RustCrypto } from "../../../src/rust-crypto/rust-crypto"; import { initRustCrypto } from "../../../src/rust-crypto"; -import { IToDeviceEvent, MatrixClient, MatrixHttpApi } from "../../../src"; +import { IHttpOpts, IToDeviceEvent, MatrixClient, MatrixHttpApi } from "../../../src"; import { mkEvent } from "../../test-utils/test-utils"; import { CryptoBackend } from "../../../src/common-crypto/CryptoBackend"; import { IEventDecryptionResult } from "../../../src/@types/crypto"; @@ -36,21 +36,15 @@ afterEach(() => { indexedDB = new IDBFactory(); }); -describe("RustCrypto", () => { - const TEST_USER = "@alice:example.com"; - const TEST_DEVICE_ID = "TEST_DEVICE"; +const TEST_USER = "@alice:example.com"; +const TEST_DEVICE_ID = "TEST_DEVICE"; +describe("RustCrypto", () => { describe(".exportRoomKeys", () => { let rustCrypto: RustCrypto; beforeEach(async () => { - const mockHttpApi = {} as MatrixClient["http"]; - rustCrypto = (await initRustCrypto( - mockHttpApi, - TEST_USER, - TEST_DEVICE_ID, - {} as ServerSideSecretStorage, - )) as RustCrypto; + rustCrypto = await makeTestRustCrypto(); }); it("should return a list", async () => { @@ -63,13 +57,7 @@ describe("RustCrypto", () => { let rustCrypto: RustCrypto; beforeEach(async () => { - const mockHttpApi = {} as MatrixClient["http"]; - rustCrypto = (await initRustCrypto( - mockHttpApi, - TEST_USER, - TEST_DEVICE_ID, - {} as ServerSideSecretStorage, - )) as RustCrypto; + rustCrypto = await makeTestRustCrypto(); }); it("should pass through unencrypted to-device messages", async () => { @@ -105,6 +93,16 @@ describe("RustCrypto", () => { }); }); + it("isCrossSigningReady", async () => { + const rustCrypto = await makeTestRustCrypto(); + await expect(rustCrypto.isCrossSigningReady()).resolves.toBe(false); + }); + + it("isSecretStorageReady", async () => { + const rustCrypto = await makeTestRustCrypto(); + await expect(rustCrypto.isSecretStorageReady()).resolves.toBe(false); + }); + describe("outgoing requests", () => { /** the RustCrypto implementation under test */ let rustCrypto: RustCrypto; @@ -223,13 +221,7 @@ describe("RustCrypto", () => { let rustCrypto: RustCrypto; beforeEach(async () => { - const mockHttpApi = {} as MatrixClient["http"]; - rustCrypto = (await initRustCrypto( - mockHttpApi, - TEST_USER, - TEST_DEVICE_ID, - {} as ServerSideSecretStorage, - )) as RustCrypto; + rustCrypto = await makeTestRustCrypto(); }); it("should handle unencrypted events", () => { @@ -257,12 +249,7 @@ describe("RustCrypto", () => { let rustCrypto: RustCrypto; beforeEach(async () => { - rustCrypto = await initRustCrypto( - {} as MatrixClient["http"], - TEST_USER, - TEST_DEVICE_ID, - {} as ServerSideSecretStorage, - ); + rustCrypto = await makeTestRustCrypto(); }); it("should be true by default", () => { @@ -315,3 +302,16 @@ describe("RustCrypto", () => { }); }); }); + +/** build a basic RustCrypto instance for testing + * + * just provides default arguments for initRustCrypto() + */ +async function makeTestRustCrypto( + http: MatrixHttpApi = {} as MatrixClient["http"], + userId: string = TEST_USER, + deviceId: string = TEST_DEVICE_ID, + secretStorage: ServerSideSecretStorage = {} as ServerSideSecretStorage, +): Promise { + return await initRustCrypto(http, userId, deviceId, secretStorage); +} diff --git a/src/client.ts b/src/client.ts index 016f7ffc7..216f34d6b 100644 --- a/src/client.ts +++ b/src/client.ts @@ -2731,12 +2731,13 @@ export class MatrixClient extends TypedEventEmitter { - if (!this.crypto) { + if (!this.cryptoBackend) { throw new Error("End-to-end encryption disabled"); } - return this.crypto.isCrossSigningReady(); + return this.cryptoBackend.isCrossSigningReady(); } /** @@ -2843,15 +2844,14 @@ export class MatrixClient extends TypedEventEmitter { - if (!this.crypto) { + if (!this.cryptoBackend) { throw new Error("End-to-end encryption disabled"); } - return this.crypto.isSecretStorageReady(); + return this.cryptoBackend.isSecretStorageReady(); } /** diff --git a/src/crypto-api.ts b/src/crypto-api.ts index 22d91cc12..e303d0583 100644 --- a/src/crypto-api.ts +++ b/src/crypto-api.ts @@ -122,6 +122,35 @@ export interface CryptoApi { * @returns Verification status of the device, or `null` if the device is not known */ getDeviceVerificationStatus(userId: string, deviceId: string): Promise; + + /** + * Checks whether cross signing: + * - is enabled on this account and trusted by this device + * - has private keys either cached locally or stored in secret storage + * + * If this function returns false, bootstrapCrossSigning() can be used + * to fix things such that it returns true. That is to say, after + * bootstrapCrossSigning() completes successfully, this function should + * return true. + * + * @returns True if cross-signing is ready to be used on this device + */ + isCrossSigningReady(): Promise; + + /** + * Checks whether secret storage: + * - is enabled on this account + * - is storing cross-signing private keys + * - is storing session backup key (if enabled) + * + * If this function returns false, bootstrapSecretStorage() can be used + * to fix things such that it returns true. That is to say, after + * bootstrapSecretStorage() completes successfully, this function should + * return true. + * + * @returns True if secret storage is ready to be used on this device + */ + isSecretStorageReady(): Promise; } /** diff --git a/src/rust-crypto/rust-crypto.ts b/src/rust-crypto/rust-crypto.ts index 9f7ef7468..3f7b6f52e 100644 --- a/src/rust-crypto/rust-crypto.ts +++ b/src/rust-crypto/rust-crypto.ts @@ -317,6 +317,20 @@ export class RustCrypto implements CryptoBackend { }); } + /** + * Implementation of {@link CryptoApi#isCrossSigningReady} + */ + public async isCrossSigningReady(): Promise { + return false; + } + + /** + * Implementation of {@link CryptoApi#isSecretStorageReady} + */ + public async isSecretStorageReady(): Promise { + return false; + } + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // SyncCryptoCallbacks implementation