From 0cd684ef8823e7316317af20a58daf84c4fde315 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Sat, 20 Jan 2024 14:04:06 -0500 Subject: [PATCH 1/4] emit events when Rust crypto wasm tells us devices have changed --- spec/unit/rust-crypto/rust-crypto.spec.ts | 27 +++++++++++++ src/client.ts | 3 ++ src/rust-crypto/index.ts | 3 ++ src/rust-crypto/rust-crypto.ts | 48 ++++++++++++++++++++++- 4 files changed, 80 insertions(+), 1 deletion(-) diff --git a/spec/unit/rust-crypto/rust-crypto.spec.ts b/spec/unit/rust-crypto/rust-crypto.spec.ts index c9146c24677..fbab7c5aa62 100644 --- a/spec/unit/rust-crypto/rust-crypto.spec.ts +++ b/spec/unit/rust-crypto/rust-crypto.spec.ts @@ -846,6 +846,33 @@ describe("RustCrypto", () => { rustCrypto.stop(); }); + it("should emit events on device changes", async () => { + jest.useFakeTimers(); + + fetchMock.post("path:/_matrix/client/v3/keys/upload", { one_time_key_counts: {} }); + fetchMock.post("path:/_matrix/client/v3/keys/query", { + device_keys: { + [testData.TEST_USER_ID]: { + [testData.TEST_DEVICE_ID]: testData.SIGNED_TEST_DEVICE_DATA, + }, + }, + }); + + const rustCrypto = await makeTestRustCrypto(makeMatrixHttpApi(), testData.TEST_USER_ID); + const willUpdateCallback = jest.fn(); + rustCrypto.on(CryptoEvent.WillUpdateDevices, willUpdateCallback); + const devicesUpdatedCallback = jest.fn(); + rustCrypto.on(CryptoEvent.DevicesUpdated, devicesUpdatedCallback); + + rustCrypto.onSyncCompleted({}); + + // wait for the devices to be updated + await rustCrypto.getUserDeviceInfo([testData.TEST_USER_ID]); + expect(willUpdateCallback).toHaveBeenCalledWith([testData.TEST_USER_ID], false); + expect(devicesUpdatedCallback).toHaveBeenCalledWith([testData.TEST_USER_ID], false); + rustCrypto.stop(); + }); + describe("requestDeviceVerification", () => { it("throws an error if the device is unknown", async () => { const rustCrypto = await makeTestRustCrypto(); diff --git a/src/client.ts b/src/client.ts index 27e70c43520..af3ba368a98 100644 --- a/src/client.ts +++ b/src/client.ts @@ -2347,6 +2347,9 @@ export class MatrixClient extends TypedEventEmitter rustCrypto.onUserIdentityUpdated(userId), ); + await olmMachine.registerDevicesUpdatedCallback((userIds: string[]) => + rustCrypto.onDevicesUpdated(userIds), + ); // Check if there are any key backup secrets pending processing. There may be multiple secrets to process if several devices have gossiped them. // The `registerReceiveSecretCallback` function will only be triggered for new secrets. If the client is restarted before processing them, the secrets will need to be manually handled. diff --git a/src/rust-crypto/rust-crypto.ts b/src/rust-crypto/rust-crypto.ts index d63b7d02c92..87552b659b4 100644 --- a/src/rust-crypto/rust-crypto.ts +++ b/src/rust-crypto/rust-crypto.ts @@ -1408,7 +1408,7 @@ export class RustCrypto extends TypedEventEmitter { + this.emit(CryptoEvent.WillUpdateDevices, userIds, false); + this.emit(CryptoEvent.DevicesUpdated, userIds, false); + } + /** * Handles secret received from the rust secret inbox. * @@ -1795,6 +1811,9 @@ function rustEncryptionInfoToJsEncryptionInfo( type RustCryptoEvents = | CryptoEvent.VerificationRequestReceived | CryptoEvent.UserTrustStatusChanged + | CryptoEvent.KeysChanged + | CryptoEvent.WillUpdateDevices + | CryptoEvent.DevicesUpdated | RustBackupCryptoEvents; type RustCryptoEventMap = { @@ -1809,4 +1828,31 @@ type RustCryptoEventMap = { [CryptoEvent.UserTrustStatusChanged]: (userId: string, userTrustLevel: UserVerificationStatus) => void; [CryptoEvent.KeyBackupDecryptionKeyCached]: (version: string) => void; + /** + * Fires when the user's cross-signing keys have changed or cross-signing + * has been enabled/disabled. The client can use getStoredCrossSigningForUser + * with the user ID of the logged in user to check if cross-signing is + * enabled on the account. If enabled, it can test whether the current key + * is trusted using with checkUserTrust with the user ID of the logged + * in user. The checkOwnCrossSigningTrust function may be used to reconcile + * the trust in the account key. + * + * The cross-signing API is currently UNSTABLE and may change without notice. + * @experimental + */ + [CryptoEvent.KeysChanged]: (data: {}) => void; + /** + * Fires whenever the stored devices for a user will be updated + * @param users - A list of user IDs that will be updated + * @param initialFetch - If true, the store is empty (apart + * from our own device) and is being seeded. + */ + [CryptoEvent.WillUpdateDevices]: (users: string[], initialFetch: boolean) => void; + /** + * Fires whenever the stored devices for a user have changed + * @param users - A list of user IDs that were updated + * @param initialFetch - If true, the store was empty (apart + * from our own device) and has been seeded. + */ + [CryptoEvent.DevicesUpdated]: (users: string[], initialFetch: boolean) => void; } & RustBackupCryptoEventMap; From f0a340e73a2746f449699e16d67ba219f88eaf05 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Sat, 20 Jan 2024 14:12:12 -0500 Subject: [PATCH 2/4] lint --- src/rust-crypto/index.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/rust-crypto/index.ts b/src/rust-crypto/index.ts index 4bd99b161ed..0311ef28291 100644 --- a/src/rust-crypto/index.ts +++ b/src/rust-crypto/index.ts @@ -116,9 +116,7 @@ async function initOlmMachine( await olmMachine.registerUserIdentityUpdatedCallback((userId: RustSdkCryptoJs.UserId) => rustCrypto.onUserIdentityUpdated(userId), ); - await olmMachine.registerDevicesUpdatedCallback((userIds: string[]) => - rustCrypto.onDevicesUpdated(userIds), - ); + await olmMachine.registerDevicesUpdatedCallback((userIds: string[]) => rustCrypto.onDevicesUpdated(userIds)); // Check if there are any key backup secrets pending processing. There may be multiple secrets to process if several devices have gossiped them. // The `registerReceiveSecretCallback` function will only be triggered for new secrets. If the client is restarted before processing them, the secrets will need to be manually handled. From 300ae319497fdf07072178ca37e783647025bd4e Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Tue, 30 Jan 2024 17:17:14 -0500 Subject: [PATCH 3/4] add missing stub function --- spec/unit/rust-crypto/rust-crypto.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/unit/rust-crypto/rust-crypto.spec.ts b/spec/unit/rust-crypto/rust-crypto.spec.ts index 3795b065b06..b2195bcdca9 100644 --- a/spec/unit/rust-crypto/rust-crypto.spec.ts +++ b/spec/unit/rust-crypto/rust-crypto.spec.ts @@ -94,6 +94,7 @@ describe("initRustCrypto", () => { getSecretsFromInbox: jest.fn().mockResolvedValue([]), deleteSecretsFromInbox: jest.fn(), registerReceiveSecretCallback: jest.fn(), + registerDevicesUpdatedCallback: jest.fn(), outgoingRequests: jest.fn(), isBackupEnabled: jest.fn().mockResolvedValue(false), verifyBackup: jest.fn().mockResolvedValue({ trusted: jest.fn().mockReturnValue(false) }), From 36c04263ad2b347831aff2e665f7754e0e269781 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Tue, 30 Jan 2024 17:22:17 -0500 Subject: [PATCH 4/4] apply workaround for queueMicrotask --- spec/unit/rust-crypto/rust-crypto.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/rust-crypto/rust-crypto.spec.ts b/spec/unit/rust-crypto/rust-crypto.spec.ts index b2195bcdca9..75b3decc6c7 100644 --- a/spec/unit/rust-crypto/rust-crypto.spec.ts +++ b/spec/unit/rust-crypto/rust-crypto.spec.ts @@ -1134,7 +1134,7 @@ describe("RustCrypto", () => { }); it("should emit events on device changes", async () => { - jest.useFakeTimers(); + jest.useFakeTimers({ doNotFake: ["queueMicrotask"] }); fetchMock.post("path:/_matrix/client/v3/keys/upload", { one_time_key_counts: {} }); fetchMock.post("path:/_matrix/client/v3/keys/query", {