Skip to content

Add disableKeyStorage() to crypto API #4742

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions spec/unit/rust-crypto/rust-crypto.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
10 changes: 10 additions & 0 deletions src/crypto-api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,16 @@ export interface CryptoApi {
*/
resetKeyBackup(): Promise<void>;

/**
* 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<void>;

/**
* Deletes the given key backup.
*
Expand Down
48 changes: 37 additions & 11 deletions src/rust-crypto/rust-crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1276,6 +1276,24 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, CryptoEventH
this.checkKeyBackupAndEnable();
}

/**
* Implementation of {@link CryptoApi#disableKeyStorage}.
*/
public async disableKeyStorage(): Promise<void> {
// 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}.
Expand Down Expand Up @@ -1447,17 +1465,7 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, CryptoEventH
// Disable backup, and delete all the backups from the server
await this.backupManager.deleteAllKeyBackupVersions();

// 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);
this.deleteSecretStorage();

// Reset the cross-signing keys
await this.crossSigningIdentity.bootstrapCrossSigning({
Expand All @@ -1471,6 +1479,24 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, CryptoEventH
this.logger.debug("resetEncryption: ended");
}

/**
* Removes the secret storage key, default key pointer and all (known) secret storage data
* from the user's account data
*/
private async deleteSecretStorage(): Promise<void> {
// 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
Expand Down