From da898e86d89a8411e398f991734a922879a211de Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Fri, 17 Jan 2025 18:47:45 -0500 Subject: [PATCH 1/4] Schedule dehydration on reload --- src/MatrixClientPeg.ts | 11 ++++++++++- test/unit-tests/MatrixClientPeg-test.ts | 7 +++++++ .../components/structures/MatrixChat-test.tsx | 2 ++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/MatrixClientPeg.ts b/src/MatrixClientPeg.ts index ab952c56334..b338f1ec22e 100644 --- a/src/MatrixClientPeg.ts +++ b/src/MatrixClientPeg.ts @@ -340,7 +340,16 @@ class MatrixClientPegClass implements IMatrixClientPeg { setDeviceIsolationMode(this.matrixClient, SettingsStore.getValue("feature_exclude_insecure_devices")); - // TODO: device dehydration and whathaveyou + // Start dehydration. This code is only for the case where the client + // gets restarted, so we only do this if we already have the dehydration + // key cached, and we don't have to try to rehydrate a device. If this + // is a new login, we will start dehydration after Secret Storage is + // unlocked. + const crypto = this.matrixClient.getCrypto(); + if (await crypto?.isDehydrationSupported()) { + await crypto!.startDehydration({ onlyIfKeyCached: true, rehydrate: false }); + } + return; } diff --git a/test/unit-tests/MatrixClientPeg-test.ts b/test/unit-tests/MatrixClientPeg-test.ts index c46edad55cf..4fb17ad702c 100644 --- a/test/unit-tests/MatrixClientPeg-test.ts +++ b/test/unit-tests/MatrixClientPeg-test.ts @@ -80,10 +80,17 @@ describe("MatrixClientPeg", () => { it("should initialise the rust crypto library by default", async () => { const mockInitRustCrypto = jest.spyOn(testPeg.safeGet(), "initRustCrypto").mockResolvedValue(undefined); + const mockStartDehydration = jest.fn(); + jest.spyOn(testPeg.safeGet(), "getCrypto").mockReturnValue({ + isDehydrationSupported: jest.fn().mockResolvedValue(true), + startDehydration: mockStartDehydration, + setDeviceIsolationMode: jest.fn(), + } as any); const cryptoStoreKey = new Uint8Array([1, 2, 3, 4]); await testPeg.start({ rustCryptoStoreKey: cryptoStoreKey }); expect(mockInitRustCrypto).toHaveBeenCalledWith({ storageKey: cryptoStoreKey }); + expect(mockStartDehydration).toHaveBeenCalledWith({ onlyIfKeyCached: true, rehydrate: false }); }); it("Should migrate existing login", async () => { diff --git a/test/unit-tests/components/structures/MatrixChat-test.tsx b/test/unit-tests/components/structures/MatrixChat-test.tsx index a29834d51f7..66397a7275a 100644 --- a/test/unit-tests/components/structures/MatrixChat-test.tsx +++ b/test/unit-tests/components/structures/MatrixChat-test.tsx @@ -129,6 +129,7 @@ describe("", () => { getCrypto: jest.fn().mockReturnValue({ getVerificationRequestsToDeviceInProgress: jest.fn().mockReturnValue([]), isCrossSigningReady: jest.fn().mockReturnValue(false), + isDehydrationSupported: jest.fn().mockReturnValue(false), getUserDeviceInfo: jest.fn().mockReturnValue(new Map()), getUserVerificationStatus: jest.fn().mockResolvedValue(new UserVerificationStatus(false, false, false)), getVersion: jest.fn().mockReturnValue("1"), @@ -1006,6 +1007,7 @@ describe("", () => { resetKeyBackup: jest.fn(), isEncryptionEnabledInRoom: jest.fn().mockResolvedValue(false), checkKeyBackupAndEnable: jest.fn().mockResolvedValue(null), + isDehydrationSupported: jest.fn().mockReturnValue(false), }; loginClient.getCrypto.mockReturnValue(mockCrypto as any); }); From a4ae0b2631356652d97f5c1643fc926481a0fb4b Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Wed, 29 Jan 2025 00:46:02 -0500 Subject: [PATCH 2/4] fix test and use the right function to check dehydration is enabled --- src/MatrixClientPeg.ts | 12 ++++++++++-- src/utils/device/dehydration.ts | 2 +- test/unit-tests/MatrixClientPeg-test.ts | 3 +++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/MatrixClientPeg.ts b/src/MatrixClientPeg.ts index b338f1ec22e..5e9bc06666f 100644 --- a/src/MatrixClientPeg.ts +++ b/src/MatrixClientPeg.ts @@ -41,6 +41,7 @@ import PlatformPeg from "./PlatformPeg"; import { formatList } from "./utils/FormattingUtils"; import SdkConfig from "./SdkConfig"; import { setDeviceIsolationMode } from "./settings/controllers/DeviceIsolationModeController.ts"; +import { deviceDehydrationEnabled } from "./utils/device/dehydration"; export interface IMatrixClientCreds { homeserverUrl: string; @@ -346,8 +347,15 @@ class MatrixClientPegClass implements IMatrixClientPeg { // is a new login, we will start dehydration after Secret Storage is // unlocked. const crypto = this.matrixClient.getCrypto(); - if (await crypto?.isDehydrationSupported()) { - await crypto!.startDehydration({ onlyIfKeyCached: true, rehydrate: false }); + if (await deviceDehydrationEnabled(crypto)) { + try { + await crypto!.startDehydration({ onlyIfKeyCached: true, rehydrate: false }); + } catch (e) { + // We may get an error dehydrating, such as if cross-signing and + // SSSS are not set up yet. Just log the error and continue. + // If SSSS gets set up later, we will re-try dehydration. + console.log("Error starting device dehydration", e); + } } return; diff --git a/src/utils/device/dehydration.ts b/src/utils/device/dehydration.ts index d87d43e13a7..e2dc5a173cb 100644 --- a/src/utils/device/dehydration.ts +++ b/src/utils/device/dehydration.ts @@ -21,7 +21,7 @@ import { MatrixClientPeg } from "../../MatrixClientPeg"; * * Dehydration can currently only be enabled by setting a flag in the .well-known file. */ -async function deviceDehydrationEnabled(crypto: CryptoApi | undefined): Promise { +export async function deviceDehydrationEnabled(crypto: CryptoApi | undefined): Promise { if (!crypto) { return false; } diff --git a/test/unit-tests/MatrixClientPeg-test.ts b/test/unit-tests/MatrixClientPeg-test.ts index 4fb17ad702c..6f4786ff42f 100644 --- a/test/unit-tests/MatrixClientPeg-test.ts +++ b/test/unit-tests/MatrixClientPeg-test.ts @@ -86,6 +86,9 @@ describe("MatrixClientPeg", () => { startDehydration: mockStartDehydration, setDeviceIsolationMode: jest.fn(), } as any); + jest.spyOn(testPeg.safeGet(), "waitForClientWellKnown").mockResolvedValue({ + "org.matrix.msc3814": true, + } as any); const cryptoStoreKey = new Uint8Array([1, 2, 3, 4]); await testPeg.start({ rustCryptoStoreKey: cryptoStoreKey }); From 1e7a2031a84a0b527bc26d060647660426ef0140 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Wed, 29 Jan 2025 13:19:10 -0500 Subject: [PATCH 3/4] use dehydration helper function when scheduling dehydration on restart --- src/MatrixClientPeg.ts | 19 ++++++++----------- .../security/CreateSecretStorageDialog.tsx | 2 +- src/utils/device/dehydration.ts | 12 +++++++----- test/unit-tests/MatrixClientPeg-test.ts | 11 +++++++++++ 4 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/MatrixClientPeg.ts b/src/MatrixClientPeg.ts index 5e9bc06666f..e14bfdcf302 100644 --- a/src/MatrixClientPeg.ts +++ b/src/MatrixClientPeg.ts @@ -41,7 +41,7 @@ import PlatformPeg from "./PlatformPeg"; import { formatList } from "./utils/FormattingUtils"; import SdkConfig from "./SdkConfig"; import { setDeviceIsolationMode } from "./settings/controllers/DeviceIsolationModeController.ts"; -import { deviceDehydrationEnabled } from "./utils/device/dehydration"; +import { initialiseDehydration } from "./utils/device/dehydration"; export interface IMatrixClientCreds { homeserverUrl: string; @@ -346,16 +346,13 @@ class MatrixClientPegClass implements IMatrixClientPeg { // key cached, and we don't have to try to rehydrate a device. If this // is a new login, we will start dehydration after Secret Storage is // unlocked. - const crypto = this.matrixClient.getCrypto(); - if (await deviceDehydrationEnabled(crypto)) { - try { - await crypto!.startDehydration({ onlyIfKeyCached: true, rehydrate: false }); - } catch (e) { - // We may get an error dehydrating, such as if cross-signing and - // SSSS are not set up yet. Just log the error and continue. - // If SSSS gets set up later, we will re-try dehydration. - console.log("Error starting device dehydration", e); - } + try { + await initialiseDehydration({ onlyIfKeyCached: true, rehydrate: false }, this.matrixClient); + } catch (e) { + // We may get an error dehydrating, such as if cross-signing and + // SSSS are not set up yet. Just log the error and continue. + // If SSSS gets set up later, we will re-try dehydration. + console.log("Error starting device dehydration", e); } return; diff --git a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx index 235f73fc8e6..7f93a03339c 100644 --- a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx +++ b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx @@ -332,7 +332,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent { +async function deviceDehydrationEnabled(crypto: CryptoApi | undefined): Promise { if (!crypto) { return false; } @@ -40,10 +41,11 @@ export async function deviceDehydrationEnabled(crypto: CryptoApi | undefined): P * @param createNewKey: force a new dehydration key to be created, even if one * already exists. This is used when we reset secret storage. */ -export async function initialiseDehydration(createNewKey: boolean = false): Promise { - const crypto = MatrixClientPeg.safeGet().getCrypto(); +export async function initialiseDehydration(opts: StartDehydrationOpts = {}, client?: MatrixClient): Promise { + client = client || MatrixClientPeg.safeGet(); + const crypto = client.getCrypto(); if (await deviceDehydrationEnabled(crypto)) { logger.log("Device dehydration enabled"); - await crypto!.startDehydration(createNewKey); + await crypto!.startDehydration(opts); } } diff --git a/test/unit-tests/MatrixClientPeg-test.ts b/test/unit-tests/MatrixClientPeg-test.ts index 6f4786ff42f..51e9406f360 100644 --- a/test/unit-tests/MatrixClientPeg-test.ts +++ b/test/unit-tests/MatrixClientPeg-test.ts @@ -80,6 +80,14 @@ describe("MatrixClientPeg", () => { it("should initialise the rust crypto library by default", async () => { const mockInitRustCrypto = jest.spyOn(testPeg.safeGet(), "initRustCrypto").mockResolvedValue(undefined); + + const cryptoStoreKey = new Uint8Array([1, 2, 3, 4]); + await testPeg.start({ rustCryptoStoreKey: cryptoStoreKey }); + expect(mockInitRustCrypto).toHaveBeenCalledWith({ storageKey: cryptoStoreKey }); + }); + + it("should try to start dehydration if dehydration is enabled", async () => { + const mockInitRustCrypto = jest.spyOn(testPeg.safeGet(), "initRustCrypto").mockResolvedValue(undefined); const mockStartDehydration = jest.fn(); jest.spyOn(testPeg.safeGet(), "getCrypto").mockReturnValue({ isDehydrationSupported: jest.fn().mockResolvedValue(true), @@ -87,6 +95,9 @@ describe("MatrixClientPeg", () => { setDeviceIsolationMode: jest.fn(), } as any); jest.spyOn(testPeg.safeGet(), "waitForClientWellKnown").mockResolvedValue({ + "m.homeserver": { + base_url: "http://example.com", + }, "org.matrix.msc3814": true, } as any); From 30ff3c180c65765a7d7b839456c1ed48934ed043 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Wed, 29 Jan 2025 15:21:15 -0500 Subject: [PATCH 4/4] fix test by passing in client object --- src/utils/device/dehydration.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/device/dehydration.ts b/src/utils/device/dehydration.ts index 6f216508c7f..8ad4901b8c2 100644 --- a/src/utils/device/dehydration.ts +++ b/src/utils/device/dehydration.ts @@ -22,14 +22,14 @@ import { MatrixClientPeg } from "../../MatrixClientPeg"; * * Dehydration can currently only be enabled by setting a flag in the .well-known file. */ -async function deviceDehydrationEnabled(crypto: CryptoApi | undefined): Promise { +async function deviceDehydrationEnabled(client: MatrixClient, crypto: CryptoApi | undefined): Promise { if (!crypto) { return false; } if (!(await crypto.isDehydrationSupported())) { return false; } - const wellknown = await MatrixClientPeg.safeGet().waitForClientWellKnown(); + const wellknown = await client.waitForClientWellKnown(); return !!wellknown?.["org.matrix.msc3814"]; } @@ -44,7 +44,7 @@ async function deviceDehydrationEnabled(crypto: CryptoApi | undefined): Promise< export async function initialiseDehydration(opts: StartDehydrationOpts = {}, client?: MatrixClient): Promise { client = client || MatrixClientPeg.safeGet(); const crypto = client.getCrypto(); - if (await deviceDehydrationEnabled(crypto)) { + if (await deviceDehydrationEnabled(client, crypto)) { logger.log("Device dehydration enabled"); await crypto!.startDehydration(opts); }