Skip to content

Commit b0cbe22

Browse files
richvdhandybalaam
andauthored
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 <[email protected]> --------- Co-authored-by: Andy Balaam <[email protected]>
1 parent 977d032 commit b0cbe22

File tree

3 files changed

+78
-4
lines changed

3 files changed

+78
-4
lines changed

spec/unit/rust-crypto/rust-crypto.spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -854,9 +854,27 @@ describe("RustCrypto", () => {
854854
});
855855
});
856856

857+
it("getSecretStorageStatus", async () => {
858+
const mockSecretStorage = {
859+
getDefaultKeyId: jest.fn().mockResolvedValue("blah"),
860+
isStored: jest.fn().mockResolvedValue({ blah: {} }),
861+
} as unknown as Mocked<ServerSideSecretStorage>;
862+
const rustCrypto = await makeTestRustCrypto(undefined, undefined, undefined, mockSecretStorage);
863+
await expect(rustCrypto.getSecretStorageStatus()).resolves.toEqual({
864+
defaultKeyId: "blah",
865+
ready: true,
866+
secretStorageKeyValidityMap: {
867+
"m.cross_signing.master": true,
868+
"m.cross_signing.self_signing": true,
869+
"m.cross_signing.user_signing": true,
870+
},
871+
});
872+
});
873+
857874
it("isSecretStorageReady", async () => {
858875
const mockSecretStorage = {
859876
getDefaultKeyId: jest.fn().mockResolvedValue(null),
877+
isStored: jest.fn().mockResolvedValue(null),
860878
} as unknown as Mocked<ServerSideSecretStorage>;
861879
const rustCrypto = await makeTestRustCrypto(undefined, undefined, undefined, mockSecretStorage);
862880
await expect(rustCrypto.isSecretStorageReady()).resolves.toBe(false);

src/crypto-api/index.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import type { ToDeviceBatch, ToDevicePayload } from "../models/ToDeviceMessage.t
2020
import { type Room } from "../models/room.ts";
2121
import { type DeviceMap } from "../models/device.ts";
2222
import { type UIAuthCallback } from "../interactive-auth.ts";
23-
import { type PassphraseInfo, type SecretStorageKeyDescription } from "../secret-storage.ts";
23+
import { type PassphraseInfo, type SecretStorageKey, type SecretStorageKeyDescription } from "../secret-storage.ts";
2424
import { type VerificationRequest } from "./verification.ts";
2525
import {
2626
type BackupTrustInfo,
@@ -369,6 +369,11 @@ export interface CryptoApi {
369369
*/
370370
isSecretStorageReady(): Promise<boolean>;
371371

372+
/**
373+
* Inspect the status of secret storage, in more detail than {@link isSecretStorageReady}.
374+
*/
375+
getSecretStorageStatus(): Promise<SecretStorageStatus>;
376+
372377
/**
373378
* Bootstrap [secret storage](https://spec.matrix.org/v1.12/client-server-api/#storage).
374379
*
@@ -1148,6 +1153,30 @@ export interface CryptoCallbacks {
11481153
cacheSecretStorageKey?: (keyId: string, keyInfo: SecretStorageKeyDescription, key: Uint8Array) => void;
11491154
}
11501155

1156+
/**
1157+
* The result of a call to {@link CryptoApi.getSecretStorageStatus}.
1158+
*/
1159+
export interface SecretStorageStatus {
1160+
/** Whether secret storage is fully populated. The same as {@link CryptoApi.isSecretStorageReady}. */
1161+
ready: boolean;
1162+
1163+
/** The ID of the current default secret storage key. */
1164+
defaultKeyId: string | null;
1165+
1166+
/**
1167+
* For each secret that we checked whether it is correctly stored in secret storage with the default secret storage key.
1168+
*
1169+
* Note that we will only check that the key backup key is stored if key backup is currently enabled (i.e. that
1170+
* {@link CryptoApi.getActiveSessionBackupVersion} returns non-null). `m.megolm_backup.v1` will only be present in that case.
1171+
*
1172+
* (This is an object rather than a `Map` so that it JSON.stringify()s nicely, since its main purpose is to end up
1173+
* in logs.)
1174+
*/
1175+
secretStorageKeyValidityMap: {
1176+
[P in SecretStorageKey]?: boolean;
1177+
};
1178+
}
1179+
11511180
/**
11521181
* Parameter of {@link CryptoApi#bootstrapSecretStorage}
11531182
*/

src/rust-crypto/rust-crypto.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ import {
6565
type KeyBackupRestoreOpts,
6666
type KeyBackupRestoreResult,
6767
type OwnDeviceKeys,
68+
type SecretStorageStatus,
6869
type StartDehydrationOpts,
6970
UserVerificationStatus,
7071
type VerificationRequest,
@@ -78,7 +79,7 @@ import {
7879
type ServerSideSecretStorage,
7980
} from "../secret-storage.ts";
8081
import { CrossSigningIdentity } from "./CrossSigningIdentity.ts";
81-
import { secretStorageCanAccessSecrets, secretStorageContainsCrossSigningKeys } from "./secret-storage.ts";
82+
import { secretStorageContainsCrossSigningKeys } from "./secret-storage.ts";
8283
import { isVerificationEvent, RustVerificationRequest, verificationMethodIdentifierToMethod } from "./verification.ts";
8384
import { EventType, MsgType } from "../@types/event.ts";
8485
import { TypedEventEmitter } from "../models/typed-event-emitter.ts";
@@ -827,20 +828,46 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, CryptoEventH
827828
* Implementation of {@link CryptoApi#isSecretStorageReady}
828829
*/
829830
public async isSecretStorageReady(): Promise<boolean> {
831+
return (await this.getSecretStorageStatus()).ready;
832+
}
833+
834+
/**
835+
* Implementation of {@link CryptoApi#getSecretStorageStatus}
836+
*/
837+
public async getSecretStorageStatus(): Promise<SecretStorageStatus> {
830838
// make sure that the cross-signing keys are stored
831839
const secretsToCheck: SecretStorageKey[] = [
832840
"m.cross_signing.master",
833841
"m.cross_signing.user_signing",
834842
"m.cross_signing.self_signing",
835843
];
836844

837-
// if key backup is active, we also need to check that the backup decryption key is stored
845+
// If key backup is active, we also need to check that the backup decryption key is stored
838846
const keyBackupEnabled = (await this.backupManager.getActiveBackupVersion()) != null;
839847
if (keyBackupEnabled) {
840848
secretsToCheck.push("m.megolm_backup.v1");
841849
}
842850

843-
return secretStorageCanAccessSecrets(this.secretStorage, secretsToCheck);
851+
const defaultKeyId = await this.secretStorage.getDefaultKeyId();
852+
853+
const result: SecretStorageStatus = {
854+
// Assume we have all secrets until proven otherwise
855+
ready: true,
856+
defaultKeyId,
857+
secretStorageKeyValidityMap: {},
858+
};
859+
860+
for (const secretName of secretsToCheck) {
861+
// Check which keys this particular secret is encrypted with
862+
const record = (await this.secretStorage.isStored(secretName)) || {};
863+
864+
// If it's encrypted with the right key, it is valid
865+
const secretStored = !!defaultKeyId && defaultKeyId in record;
866+
result.secretStorageKeyValidityMap[secretName] = secretStored;
867+
result.ready = result.ready && secretStored;
868+
}
869+
870+
return result;
844871
}
845872

846873
/**

0 commit comments

Comments
 (0)