diff --git a/spec/unit/rust-crypto/rust-crypto.spec.ts b/spec/unit/rust-crypto/rust-crypto.spec.ts index c22e1bd62..7b0c2ab7d 100644 --- a/spec/unit/rust-crypto/rust-crypto.spec.ts +++ b/spec/unit/rust-crypto/rust-crypto.spec.ts @@ -2318,6 +2318,43 @@ describe("RustCrypto", () => { expect(dehydratedDeviceIsDeleted).toBeTruthy(); }); }); + + describe("disableKeyStorage", () => { + it("should disable key storage", async () => { + const secretStorage = { + getDefaultKeyId: jest.fn().mockResolvedValue("bloop"), + setDefaultKeyId: jest.fn(), + store: jest.fn(), + } as unknown as ServerSideSecretStorage; + + fetchMock.get("path:/_matrix/client/v3/room_keys/version", testData.SIGNED_BACKUP_DATA); + + let backupIsDeleted = false; + fetchMock.delete("path:/_matrix/client/v3/room_keys/version/1", () => { + backupIsDeleted = true; + return {}; + }); + + let dehydratedDeviceIsDeleted = false; + fetchMock.delete("path:/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device", () => { + dehydratedDeviceIsDeleted = true; + return { device_id: "ADEVICEID" }; + }); + + const rustCrypto = await makeTestRustCrypto(makeMatrixHttpApi(), undefined, undefined, secretStorage); + await rustCrypto.disableKeyStorage(); + + expect(secretStorage.store).toHaveBeenCalledWith("m.cross_signing.master", null); + expect(secretStorage.store).toHaveBeenCalledWith("m.cross_signing.self_signing", null); + expect(secretStorage.store).toHaveBeenCalledWith("m.cross_signing.user_signing", null); + expect(secretStorage.store).toHaveBeenCalledWith("m.megolm_backup.v1", null); + expect(secretStorage.store).toHaveBeenCalledWith("m.secret_storage.key.bloop", null); + expect(secretStorage.setDefaultKeyId).toHaveBeenCalledWith(null); + + expect(backupIsDeleted).toBeTruthy(); + expect(dehydratedDeviceIsDeleted).toBeTruthy(); + }); + }); }); /** Build a MatrixHttpApi instance */ diff --git a/src/crypto-api/index.ts b/src/crypto-api/index.ts index e84648f94..a52df709e 100644 --- a/src/crypto-api/index.ts +++ b/src/crypto-api/index.ts @@ -629,6 +629,16 @@ export interface CryptoApi { */ resetKeyBackup(): Promise; + /** + * Disables server-side key storage and deletes server-side backups. + * * Deletes the current key backup version, if any (but not any previous versions). + * * Disables 4S, deleting the info for the default key, the default key pointer itself and any + * known 4S data (cross-signing keys and the megolm key backup key). + * * Deletes any dehydrated devices. + * * Sets the "m.org.matrix.custom.backup_disabled" account data flag to indicate that the user has disabled backups. + */ + disableKeyStorage(): Promise; + /** * Deletes the given key backup. * diff --git a/src/rust-crypto/rust-crypto.ts b/src/rust-crypto/rust-crypto.ts index 6da07785c..998455f36 100644 --- a/src/rust-crypto/rust-crypto.ts +++ b/src/rust-crypto/rust-crypto.ts @@ -1276,6 +1276,24 @@ export class RustCrypto extends TypedEventEmitter { + // Get the key backup version we're using + const info = await this.getKeyBackupInfo(); + if (info?.version) { + await this.deleteKeyBackupVersion(info.version); + } else { + logger.error("Can't delete key backup version: no version available"); + } + + // also turn off 4S, since this is also storing keys on the server. + await this.deleteSecretStorage(); + + await this.dehydratedDeviceManager.delete(); + } + /** * Signs the given object with the current device and current identity (if available). * As defined in {@link https://spec.matrix.org/v1.8/appendices/#signing-json | Signing JSON}. @@ -1447,17 +1465,7 @@ export class RustCrypto extends TypedEventEmitter { + // Remove the stored secrets in the secret storage + await this.secretStorage.store("m.cross_signing.master", null); + await this.secretStorage.store("m.cross_signing.self_signing", null); + await this.secretStorage.store("m.cross_signing.user_signing", null); + await this.secretStorage.store("m.megolm_backup.v1", null); + + // Remove the recovery key + const defaultKeyId = await this.secretStorage.getDefaultKeyId(); + if (defaultKeyId) await this.secretStorage.store(`m.secret_storage.key.${defaultKeyId}`, null); + // Disable the recovery key and the secret storage + await this.secretStorage.setDefaultKeyId(null); + } + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // SyncCryptoCallbacks implementation