Skip to content

Commit

Permalink
feat(crypto api): Add CryptoApi#resetEncryption
Browse files Browse the repository at this point in the history
  • Loading branch information
florianduros committed Jan 15, 2025
1 parent 5babcaf commit 6ea0cd1
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 0 deletions.
67 changes: 67 additions & 0 deletions spec/unit/rust-crypto/rust-crypto.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import {
EventShieldReason,
ImportRoomKeysOpts,
KeyBackupCheck,
KeyBackupInfo,
VerificationRequest,
} from "../../../src/crypto-api";
import * as testData from "../../test-utils/test-data";
Expand All @@ -72,6 +73,7 @@ import { Curve25519AuthData } from "../../../src/crypto-api/keybackup";
import encryptAESSecretStorageItem from "../../../src/utils/encryptAESSecretStorageItem.ts";
import { CryptoStore, SecretStorePrivateKeys } from "../../../src/crypto/store/base";
import { CryptoEvent } from "../../../src/crypto-api/index.ts";
import { RustBackupManager } from "../../../src/rust-crypto/backup.ts";

const TEST_USER = "@alice:example.com";
const TEST_DEVICE_ID = "TEST_DEVICE";
Expand Down Expand Up @@ -1879,6 +1881,71 @@ describe("RustCrypto", () => {
);
});
});

describe("resetEncryption", () => {
fetchMock.post("path:/_matrix/client/v3/keys/upload", { one_time_key_counts: {} });
fetchMock.post("path:/_matrix/client/v3/keys/signatures/upload", {});

let secretStorage: ServerSideSecretStorage;
beforeEach(() => {
secretStorage = {
setDefaultKeyId: jest.fn(),
hasKey: jest.fn().mockResolvedValue(false),
getKey: jest.fn().mockResolvedValue(null),
} as unknown as ServerSideSecretStorage;
});

it("key backup should stay disabled after reset", async () => {

Check failure on line 1898 in spec/unit/rust-crypto/rust-crypto.spec.ts

View workflow job for this annotation

GitHub Actions / Jest [unit] (Node lts/*)

RustCrypto › resetEncryption › key backup should stay disabled after reset

thrown: "Exceeded timeout of 5000 ms for a test. Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout." at it (spec/unit/rust-crypto/rust-crypto.spec.ts:1898:9) at describe (spec/unit/rust-crypto/rust-crypto.spec.ts:1885:5) at Object.describe (spec/unit/rust-crypto/rust-crypto.spec.ts:471:1)

Check failure on line 1898 in spec/unit/rust-crypto/rust-crypto.spec.ts

View workflow job for this annotation

GitHub Actions / Jest [unit] (Node 22)

RustCrypto › resetEncryption › key backup should stay disabled after reset

thrown: "Exceeded timeout of 5000 ms for a test. Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout." at it (spec/unit/rust-crypto/rust-crypto.spec.ts:1898:9) at describe (spec/unit/rust-crypto/rust-crypto.spec.ts:1885:5) at Object.describe (spec/unit/rust-crypto/rust-crypto.spec.ts:471:1)
fetchMock.get("path:/_matrix/client/v3/room_keys/version", { errcode: "M_NOT_FOUND" });

const rustCrypto = await makeTestRustCrypto(makeMatrixHttpApi(), undefined, undefined, secretStorage);

const authUploadDeviceSigningKeys = jest.fn();
await rustCrypto.resetEncryption(authUploadDeviceSigningKeys);

expect(secretStorage.setDefaultKeyId).toHaveBeenCalledWith(null);
expect(await rustCrypto.getActiveSessionBackupVersion()).toBeNull();
expect(authUploadDeviceSigningKeys).toHaveBeenCalledWith(expect.any(Function));
});

it("key backup should be re-enabled after reset", async () => {

Check failure on line 1911 in spec/unit/rust-crypto/rust-crypto.spec.ts

View workflow job for this annotation

GitHub Actions / Jest [unit] (Node lts/*)

RustCrypto › resetEncryption › key backup should be re-enabled after reset

thrown: "Exceeded timeout of 5000 ms for a test. Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout." at it (spec/unit/rust-crypto/rust-crypto.spec.ts:1911:9) at describe (spec/unit/rust-crypto/rust-crypto.spec.ts:1885:5) at Object.describe (spec/unit/rust-crypto/rust-crypto.spec.ts:471:1)

Check failure on line 1911 in spec/unit/rust-crypto/rust-crypto.spec.ts

View workflow job for this annotation

GitHub Actions / Jest [unit] (Node 22)

RustCrypto › resetEncryption › key backup should be re-enabled after reset

thrown: "Exceeded timeout of 5000 ms for a test. Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout." at it (spec/unit/rust-crypto/rust-crypto.spec.ts:1911:9) at describe (spec/unit/rust-crypto/rust-crypto.spec.ts:1885:5) at Object.describe (spec/unit/rust-crypto/rust-crypto.spec.ts:471:1)
fetchMock.get("path:/_matrix/client/v3/room_keys/version", testData.SIGNED_BACKUP_DATA);
// When we will delete the key backup
fetchMock.delete("path:/_matrix/client/v3/room_keys/version/1", {});

jest.spyOn(RustBackupManager.prototype, "isKeyBackupTrusted").mockResolvedValue({
trusted: true,
matchesDecryptionKey: true,
});

const rustCrypto = await makeTestRustCrypto(makeMatrixHttpApi(), undefined, undefined, secretStorage);
// We have a key backup
expect(await rustCrypto.getActiveSessionBackupVersion()).not.toBeNull();

let nbCalls = 0;
fetchMock.get(
"path:/_matrix/client/v3/room_keys/version",
() => {
// First call is when we check if the key backup is enabled
// Second call is when we get the next backup after we deleted the first key backup
return ++nbCalls === 1 ? testData.SIGNED_BACKUP_DATA : {};
},
{ overwriteRoutes: true },
);

let content!: KeyBackupInfo;
fetchMock.post("path:/_matrix/client/v3/room_keys/version", (res, options) => {
content = JSON.parse(options.body as string);
return { version: "2" };
});

const authUploadDeviceSigningKeys = jest.fn();
await rustCrypto.resetEncryption(authUploadDeviceSigningKeys);

expect(content.auth_data).toBeTruthy();
expect(authUploadDeviceSigningKeys).toHaveBeenCalledWith(expect.any(Function));
});
});
});

/** Build a MatrixHttpApi instance */
Expand Down
13 changes: 13 additions & 0 deletions src/crypto-api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,19 @@ export interface CryptoApi {
payload: ToDevicePayload,
): Promise<ToDeviceBatch>;

/**
* Reset the encryption of the account by going through the following steps:
* - Disable backing up room keys and delete any existing backup.
* - Remove the default secret storage key from the account data (ie: the recovery key).
* - Reset the cross-signing keys.
* - Re-enable backing up room keys if enabled before.
*
* @param authUploadDeviceSigningKeys - Callback to authenticate the upload of device signing keys.
* Used when resetting the cross signing keys.
* See {@link BootstrapCrossSigningOpts#authUploadDeviceSigningKeys}.
*/
resetEncryption(authUploadDeviceSigningKeys: UIAuthCallback<void>): Promise<void>;

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Device/User verification
Expand Down
7 changes: 7 additions & 0 deletions src/crypto/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4340,6 +4340,13 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
): Promise<KeyBackupRestoreResult> {
throw new Error("Not implemented");
}

/**
* Stub function -- resetEncryption is not implemented here, so throw error
*/
public resetEncryption(): Promise<void> {
throw new Error("Not implemented");
}
}

/**
Expand Down
25 changes: 25 additions & 0 deletions src/rust-crypto/rust-crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ import { PerSessionKeyBackupDownloader } from "./PerSessionKeyBackupDownloader.t
import { DehydratedDeviceManager } from "./DehydratedDeviceManager.ts";
import { VerificationMethod } from "../types.ts";
import { keyFromAuthData } from "../common-crypto/key-passphrase.ts";
import { UIAuthCallback } from "../interactive-auth.ts";

const ALL_VERIFICATION_METHODS = [
VerificationMethod.Sas,
Expand Down Expand Up @@ -1472,6 +1473,30 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, CryptoEventH
return batch;
}

/**
* Implementation of {@link CryptoApi#resetEncryption}.
*/
public async resetEncryption(authUploadDeviceSigningKeys: UIAuthCallback<void>): Promise<void> {
const backupEnabled = (await this.backupManager.getActiveBackupVersion()) !== null;

// Delete all the backup
await this.backupManager.deleteAllKeyBackupVersions();

// Disable the recovery key and the secret storage
await this.secretStorage.setDefaultKeyId(null);

// Reset the cross-signing keys
await this.crossSigningIdentity.bootstrapCrossSigning({
setupNewCrossSigning: true,
authUploadDeviceSigningKeys,
});

// If key backup was enabled, we create a new backup
if (backupEnabled) {
await this.resetKeyBackup();
}
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// SyncCryptoCallbacks implementation
Expand Down

0 comments on commit 6ea0cd1

Please sign in to comment.