diff --git a/spec/unit/rust-crypto/rust-crypto.spec.ts b/spec/unit/rust-crypto/rust-crypto.spec.ts index e9c77ef4e7..3c83cb1d7a 100644 --- a/spec/unit/rust-crypto/rust-crypto.spec.ts +++ b/spec/unit/rust-crypto/rust-crypto.spec.ts @@ -854,9 +854,27 @@ describe("RustCrypto", () => { }); }); + it("getSecretStorageStatus", async () => { + const mockSecretStorage = { + getDefaultKeyId: jest.fn().mockResolvedValue("blah"), + isStored: jest.fn().mockResolvedValue({ blah: {} }), + } as unknown as Mocked; + const rustCrypto = await makeTestRustCrypto(undefined, undefined, undefined, mockSecretStorage); + await expect(rustCrypto.getSecretStorageStatus()).resolves.toEqual({ + defaultKeyId: "blah", + ready: true, + secretStorageKeyValidityMap: { + "m.cross_signing.master": true, + "m.cross_signing.self_signing": true, + "m.cross_signing.user_signing": true, + }, + }); + }); + it("isSecretStorageReady", async () => { const mockSecretStorage = { getDefaultKeyId: jest.fn().mockResolvedValue(null), + isStored: jest.fn().mockResolvedValue(null), } as unknown as Mocked; const rustCrypto = await makeTestRustCrypto(undefined, undefined, undefined, mockSecretStorage); await expect(rustCrypto.isSecretStorageReady()).resolves.toBe(false); diff --git a/src/crypto-api/index.ts b/src/crypto-api/index.ts index ff55c43963..976e6e46e2 100644 --- a/src/crypto-api/index.ts +++ b/src/crypto-api/index.ts @@ -20,7 +20,7 @@ import type { ToDeviceBatch, ToDevicePayload } from "../models/ToDeviceMessage.t import { type Room } from "../models/room.ts"; import { type DeviceMap } from "../models/device.ts"; import { type UIAuthCallback } from "../interactive-auth.ts"; -import { type PassphraseInfo, type SecretStorageKeyDescription } from "../secret-storage.ts"; +import { type PassphraseInfo, type SecretStorageKey, type SecretStorageKeyDescription } from "../secret-storage.ts"; import { type VerificationRequest } from "./verification.ts"; import { type BackupTrustInfo, @@ -369,6 +369,11 @@ export interface CryptoApi { */ isSecretStorageReady(): Promise; + /** + * Inspect the status of secret storage, in more detail than {@link isSecretStorageReady}. + */ + getSecretStorageStatus(): Promise; + /** * Bootstrap [secret storage](https://spec.matrix.org/v1.12/client-server-api/#storage). * @@ -1148,6 +1153,30 @@ export interface CryptoCallbacks { cacheSecretStorageKey?: (keyId: string, keyInfo: SecretStorageKeyDescription, key: Uint8Array) => void; } +/** + * The result of a call to {@link CryptoApi.getSecretStorageStatus}. + */ +export interface SecretStorageStatus { + /** Whether secret storage is fully populated. The same as {@link CryptoApi.isSecretStorageReady}. */ + ready: boolean; + + /** The ID of the current default secret storage key. */ + defaultKeyId: string | null; + + /** + * For each secret that we checked whether it is correctly stored in secret storage with the default secret storage key. + * + * Note that we will only check that the key backup key is stored if key backup is currently enabled (i.e. that + * {@link CryptoApi.getActiveSessionBackupVersion} returns non-null). `m.megolm_backup.v1` will only be present in that case. + * + * (This is an object rather than a `Map` so that it JSON.stringify()s nicely, since its main purpose is to end up + * in logs.) + */ + secretStorageKeyValidityMap: { + [P in SecretStorageKey]?: boolean; + }; +} + /** * Parameter of {@link CryptoApi#bootstrapSecretStorage} */ diff --git a/src/rust-crypto/rust-crypto.ts b/src/rust-crypto/rust-crypto.ts index 638bf37d1b..dbb0abf7c3 100644 --- a/src/rust-crypto/rust-crypto.ts +++ b/src/rust-crypto/rust-crypto.ts @@ -65,6 +65,7 @@ import { type KeyBackupRestoreOpts, type KeyBackupRestoreResult, type OwnDeviceKeys, + type SecretStorageStatus, type StartDehydrationOpts, UserVerificationStatus, type VerificationRequest, @@ -78,7 +79,7 @@ import { type ServerSideSecretStorage, } from "../secret-storage.ts"; import { CrossSigningIdentity } from "./CrossSigningIdentity.ts"; -import { secretStorageCanAccessSecrets, secretStorageContainsCrossSigningKeys } from "./secret-storage.ts"; +import { secretStorageContainsCrossSigningKeys } from "./secret-storage.ts"; import { isVerificationEvent, RustVerificationRequest, verificationMethodIdentifierToMethod } from "./verification.ts"; import { EventType, MsgType } from "../@types/event.ts"; import { TypedEventEmitter } from "../models/typed-event-emitter.ts"; @@ -827,6 +828,13 @@ export class RustCrypto extends TypedEventEmitter { + return (await this.getSecretStorageStatus()).ready; + } + + /** + * Implementation of {@link CryptoApi#getSecretStorageStatus} + */ + public async getSecretStorageStatus(): Promise { // make sure that the cross-signing keys are stored const secretsToCheck: SecretStorageKey[] = [ "m.cross_signing.master", @@ -834,13 +842,32 @@ export class RustCrypto extends TypedEventEmitter