Skip to content

Commit 6e72b35

Browse files
authored
Fix resetEncryption to remove secrets in 4S (#4683)
* fix(crypto): `resetEncryption` remove secrets in 4S Remove the cross signing keys and the backup decryption key of the 4S when calling `resetEncryption` * test(crypto): expect secrets to be deleted in 4S when `resetEncryption` is called * test(secret storage): add test case when the secret is set at null * fix(crypto): remove default key in 4S * test(crypto): default key should be removed from 4S
1 parent 7d68753 commit 6e72b35

File tree

4 files changed

+44
-15
lines changed

4 files changed

+44
-15
lines changed

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

+8
Original file line numberDiff line numberDiff line change
@@ -2247,6 +2247,8 @@ describe("RustCrypto", () => {
22472247
setDefaultKeyId: jest.fn(),
22482248
hasKey: jest.fn().mockResolvedValue(false),
22492249
getKey: jest.fn().mockResolvedValue(null),
2250+
store: jest.fn(),
2251+
getDefaultKeyId: jest.fn().mockResolvedValue("defaultKeyId"),
22502252
} as unknown as ServerSideSecretStorage;
22512253

22522254
fetchMock.post("path:/_matrix/client/v3/keys/upload", { one_time_key_counts: {} });
@@ -2285,6 +2287,12 @@ describe("RustCrypto", () => {
22852287
const authUploadDeviceSigningKeys = jest.fn();
22862288
await rustCrypto.resetEncryption(authUploadDeviceSigningKeys);
22872289

2290+
// The secrets in 4S should be deleted
2291+
expect(secretStorage.store).toHaveBeenCalledWith("m.cross_signing.master", null);
2292+
expect(secretStorage.store).toHaveBeenCalledWith("m.cross_signing.self_signing", null);
2293+
expect(secretStorage.store).toHaveBeenCalledWith("m.cross_signing.user_signing", null);
2294+
expect(secretStorage.store).toHaveBeenCalledWith("m.megolm_backup.v1", null);
2295+
expect(secretStorage.store).toHaveBeenCalledWith("m.secret_storage.key.defaultKeyId", null);
22882296
// A new key backup should be created
22892297
expect(newKeyBackupInfo.auth_data).toBeTruthy();
22902298
// The new cross signing keys should be uploaded

spec/unit/secret-storage.spec.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -243,11 +243,16 @@ describe("ServerSideSecretStorageImpl", function () {
243243
});
244244

245245
describe("store", () => {
246-
it("should ignore keys with unknown algorithm", async function () {
247-
const accountDataAdapter = mockAccountDataClient();
246+
let secretStorage: ServerSideSecretStorage;
247+
let accountDataAdapter: Mocked<AccountDataClient>;
248+
249+
beforeEach(() => {
250+
accountDataAdapter = mockAccountDataClient();
248251
const mockCallbacks = { getSecretStorageKey: jest.fn() } as Mocked<SecretStorageCallbacks>;
249-
const secretStorage = new ServerSideSecretStorageImpl(accountDataAdapter, mockCallbacks);
252+
secretStorage = new ServerSideSecretStorageImpl(accountDataAdapter, mockCallbacks);
253+
});
250254

255+
it("should ignore keys with unknown algorithm", async function () {
251256
// stub out getAccountData to return a key with an unknown algorithm
252257
const storedKey = { algorithm: "badalg" } as SecretStorageKeyDescriptionCommon;
253258
async function mockGetAccountData<K extends keyof AccountDataEvents>(
@@ -274,6 +279,11 @@ describe("ServerSideSecretStorageImpl", function () {
274279
// eslint-disable-next-line no-console
275280
expect(console.warn).toHaveBeenCalledWith(expect.stringContaining("unknown algorithm"));
276281
});
282+
283+
it("should set the secret with an empty object when the value is null", async function () {
284+
await secretStorage.store("mySecret", null);
285+
expect(accountDataAdapter.setAccountData).toHaveBeenCalledWith("mySecret", {});
286+
});
277287
});
278288

279289
describe("setDefaultKeyId", function () {

src/rust-crypto/rust-crypto.ts

+9
Original file line numberDiff line numberDiff line change
@@ -1508,6 +1508,15 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, CryptoEventH
15081508
// Disable backup, and delete all the backups from the server
15091509
await this.backupManager.deleteAllKeyBackupVersions();
15101510

1511+
// Remove the stored secrets in the secret storage
1512+
await this.secretStorage.store("m.cross_signing.master", null);
1513+
await this.secretStorage.store("m.cross_signing.self_signing", null);
1514+
await this.secretStorage.store("m.cross_signing.user_signing", null);
1515+
await this.secretStorage.store("m.megolm_backup.v1", null);
1516+
1517+
// Remove the recovery key
1518+
const defaultKeyId = await this.secretStorage.getDefaultKeyId();
1519+
if (defaultKeyId) await this.secretStorage.store(`m.secret_storage.key.${defaultKeyId}`, null);
15111520
// Disable the recovery key and the secret storage
15121521
await this.secretStorage.setDefaultKeyId(null);
15131522

src/secret-storage.ts

+14-12
Original file line numberDiff line numberDiff line change
@@ -280,14 +280,18 @@ export interface ServerSideSecretStorage {
280280
* Store an encrypted secret on the server.
281281
*
282282
* Details of the encryption keys to be used must previously have been stored in account data
283-
* (for example, via {@link ServerSideSecretStorage#addKey}.
283+
* (for example, via {@link ServerSideSecretStorageImpl#addKey}. {@link SecretStorageCallbacks#getSecretStorageKey} will be called to obtain a secret storage
284+
* key to decrypt the secret.
285+
*
286+
* If the secret is `null`, the secret value in the account data will be set to an empty object.
287+
* This is considered as "removing" the secret.
284288
*
285289
* @param name - The name of the secret - i.e., the "event type" to be stored in the account data
286290
* @param secret - The secret contents.
287291
* @param keys - The IDs of the keys to use to encrypt the secret, or null/undefined to use the default key
288292
* (will throw if no default key is set).
289293
*/
290-
store(name: string, secret: string, keys?: string[] | null): Promise<void>;
294+
store(name: string, secret: string | null, keys?: string[] | null): Promise<void>;
291295

292296
/**
293297
* Get a secret from storage, and decrypt it.
@@ -504,17 +508,15 @@ export class ServerSideSecretStorageImpl implements ServerSideSecretStorage {
504508
}
505509

506510
/**
507-
* Store an encrypted secret on the server.
508-
*
509-
* Details of the encryption keys to be used must previously have been stored in account data
510-
* (for example, via {@link ServerSideSecretStorageImpl#addKey}. {@link SecretStorageCallbacks#getSecretStorageKey} will be called to obtain a secret storage
511-
* key to decrypt the secret.
512-
*
513-
* @param name - The name of the secret - i.e., the "event type" to be stored in the account data
514-
* @param secret - The secret contents.
515-
* @param keys - The IDs of the keys to use to encrypt the secret, or null/undefined to use the default key.
511+
* Implementation of {@link ServerSideSecretStorage#store}.
516512
*/
517-
public async store(name: SecretStorageKey, secret: string, keys?: string[] | null): Promise<void> {
513+
public async store(name: SecretStorageKey, secret: string | null, keys?: string[] | null): Promise<void> {
514+
if (secret === null) {
515+
// remove secret
516+
await this.accountDataAdapter.setAccountData(name, {});
517+
return;
518+
}
519+
518520
const encrypted: Record<string, AESEncryptedSecretStoragePayload> = {};
519521

520522
if (!keys) {

0 commit comments

Comments
 (0)