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

Add CryptoApi.getSecretStorageStatus (#5054)

* Add `CryptoApi.getSecretStorageStatus`

`isSecretStorageReady` is a bit of a blunt instrument: it's hard to see from
logs *why* the secret storage isn't ready.

Add a new method which returns a bit more data.

* Update src/rust-crypto/rust-crypto.ts

Co-authored-by: Andy Balaam <andy.balaam@matrix.org>

---------

Co-authored-by: Andy Balaam <andy.balaam@matrix.org>
This commit is contained in:
Richard van der Hoff
2025-10-23 13:04:28 +02:00
committed by GitHub
parent 977d0322da
commit b0cbe22f64
3 changed files with 78 additions and 4 deletions

View File

@@ -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<ServerSideSecretStorage>;
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<ServerSideSecretStorage>;
const rustCrypto = await makeTestRustCrypto(undefined, undefined, undefined, mockSecretStorage);
await expect(rustCrypto.isSecretStorageReady()).resolves.toBe(false);

View File

@@ -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<boolean>;
/**
* Inspect the status of secret storage, in more detail than {@link isSecretStorageReady}.
*/
getSecretStorageStatus(): Promise<SecretStorageStatus>;
/**
* 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}
*/

View File

@@ -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<RustCryptoEvents, CryptoEventH
* Implementation of {@link CryptoApi#isSecretStorageReady}
*/
public async isSecretStorageReady(): Promise<boolean> {
return (await this.getSecretStorageStatus()).ready;
}
/**
* Implementation of {@link CryptoApi#getSecretStorageStatus}
*/
public async getSecretStorageStatus(): Promise<SecretStorageStatus> {
// 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<RustCryptoEvents, CryptoEventH
"m.cross_signing.self_signing",
];
// if key backup is active, we also need to check that the backup decryption key is stored
// If key backup is active, we also need to check that the backup decryption key is stored
const keyBackupEnabled = (await this.backupManager.getActiveBackupVersion()) != null;
if (keyBackupEnabled) {
secretsToCheck.push("m.megolm_backup.v1");
}
return secretStorageCanAccessSecrets(this.secretStorage, secretsToCheck);
const defaultKeyId = await this.secretStorage.getDefaultKeyId();
const result: SecretStorageStatus = {
// Assume we have all secrets until proven otherwise
ready: true,
defaultKeyId,
secretStorageKeyValidityMap: {},
};
for (const secretName of secretsToCheck) {
// Check which keys this particular secret is encrypted with
const record = (await this.secretStorage.isStored(secretName)) || {};
// If it's encrypted with the right key, it is valid
const secretStored = !!defaultKeyId && defaultKeyId in record;
result.secretStorageKeyValidityMap[secretName] = secretStored;
result.ready = result.ready && secretStored;
}
return result;
}
/**