diff --git a/spec/integ/crypto/crypto.spec.ts b/spec/integ/crypto/crypto.spec.ts index f725cb63f66..d7cc0896956 100644 --- a/spec/integ/crypto/crypto.spec.ts +++ b/spec/integ/crypto/crypto.spec.ts @@ -157,7 +157,7 @@ async function expectSendRoomKey( /** * Return the event received on rooms/{roomId}/send/m.room.encrypted endpoint. * See https://spec.matrix.org/latest/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid - * @returns the content of event (no decryption) + * @returns the content of the encrypted event */ function expectEncryptedSendMessage() { return new Promise((resolve) => { @@ -938,6 +938,122 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string, }); }); + describe("Session should rotate according to encryption settings", () => { + /** + * Send a message to bob and get the encrypted message + * @returns {Promise} The encrypted message + */ + async function sendEncryptedMessage(): Promise { + const [encryptedMessage] = await Promise.all([ + expectEncryptedSendMessage(), + aliceClient.sendTextMessage(ROOM_ID, "test"), + ]); + return encryptedMessage; + } + + afterEach(() => { + jest.useRealTimers(); + }); + + newBackendOnly("should rotate the session after 2 messages", async () => { + expectAliceKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} }); + await startClientAndAwaitFirstSync(); + const p2pSession = await establishOlmSession(aliceClient, keyReceiver, syncResponder, testOlmAccount); + + const syncResponse = getSyncResponse(["@bob:xyz"]); + // Every 2 messages in the room, the session should be rotated + syncResponse.rooms[Category.Join][ROOM_ID].state.events[0].content = { + algorithm: "m.megolm.v1.aes-sha2", + rotation_period_msgs: 2, + }; + + // Tell alice we share a room with bob + syncResponder.sendOrQueueSyncResponse(syncResponse); + await syncPromise(aliceClient); + + // Force alice to download bob keys + expectAliceKeyQuery(getTestKeysQueryResponse("@bob:xyz")); + + // Send a message to bob and get the encrypted message + const [encryptedMessage] = await Promise.all([ + sendEncryptedMessage(), + expectSendRoomKey("@bob:xyz", testOlmAccount, p2pSession), + ]); + + // Check that the session id exists + const sessionId = encryptedMessage.session_id; + expect(sessionId).toBeDefined(); + + // Send a second message to bob and get the current message + const secondEncryptedMessage = await sendEncryptedMessage(); + + // Check that the same session id is shared between the two messages + const secondSessionId = secondEncryptedMessage.session_id; + expect(secondSessionId).toBe(sessionId); + + // The session should be rotated, we are expecting the room key to be sent + const [thirdEncryptedMessage] = await Promise.all([ + sendEncryptedMessage(), + expectSendRoomKey("@bob:xyz", testOlmAccount, p2pSession), + ]); + + // The session is rotated every 2 messages, we should have a new session id + const thirdSessionId = thirdEncryptedMessage.session_id; + expect(thirdSessionId).not.toBe(sessionId); + }); + + newBackendOnly("should rotate the session after 1h", async () => { + expectAliceKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} }); + await startClientAndAwaitFirstSync(); + const p2pSession = await establishOlmSession(aliceClient, keyReceiver, syncResponder, testOlmAccount); + + // We need to fake the timers to advance the time + jest.useFakeTimers(); + + const syncResponse = getSyncResponse(["@bob:xyz"]); + + // The minimum rotation period is 1h + // https://github.com/matrix-org/matrix-rust-sdk/blob/f75b2cd1d0981db42751dadb08c826740af1018e/crates/matrix-sdk-crypto/src/olm/group_sessions/outbound.rs#L410-L415 + const oneHourInMs = 60 * 60 * 1000; + + // Every 1h the session should be rotated + syncResponse.rooms[Category.Join][ROOM_ID].state.events[0].content = { + algorithm: "m.megolm.v1.aes-sha2", + rotation_period_ms: oneHourInMs, + }; + + // Tell alice we share a room with bob + syncResponder.sendOrQueueSyncResponse(syncResponse); + await syncPromise(aliceClient); + + // Force alice to download bob keys + expectAliceKeyQuery(getTestKeysQueryResponse("@bob:xyz")); + + // Send a message to bob and get the encrypted message + const [encryptedMessage] = await Promise.all([ + sendEncryptedMessage(), + expectSendRoomKey("@bob:xyz", testOlmAccount, p2pSession), + ]); + + // Check that the session id exists + const sessionId = encryptedMessage.session_id; + expect(sessionId).toBeDefined(); + + // Advance the time by 1h + jest.advanceTimersByTime(oneHourInMs); + + // Send a second message to bob and get the encrypted message + const [secondEncryptedMessage] = await Promise.all([ + sendEncryptedMessage(), + expectSendRoomKey("@bob:xyz", testOlmAccount, p2pSession), + ]); + + // The session should be rotated + const secondSessionId = secondEncryptedMessage.session_id; + expect(secondSessionId).not.toBe(sessionId); + }); + }); + newBackendOnly("should rotate the session when the history visibility changes", async () => { expectAliceKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} }); await startClientAndAwaitFirstSync(); diff --git a/src/rust-crypto/RoomEncryptor.ts b/src/rust-crypto/RoomEncryptor.ts index fc10c6a1338..0e4d1a2dda4 100644 --- a/src/rust-crypto/RoomEncryptor.ts +++ b/src/rust-crypto/RoomEncryptor.ts @@ -15,6 +15,7 @@ limitations under the License. */ import { + EncryptionAlgorithm, EncryptionSettings, OlmMachine, RoomId, @@ -110,10 +111,22 @@ export class RoomEncryptor { this.prefixedLogger.debug("Sessions for users are ready; now sharing room key"); const rustEncryptionSettings = new EncryptionSettings(); - /* FIXME rotation, etc */ - rustEncryptionSettings.historyVisibility = toRustHistoryVisibility(this.room.getHistoryVisibility()); + // We only support megolm + rustEncryptionSettings.algorithm = EncryptionAlgorithm.MegolmV1AesSha2; + + // We need to convert the rotation period from milliseconds to microseconds + // See https://spec.matrix.org/v1.8/client-server-api/#mroomencryption and + // https://matrix-org.github.io/matrix-rust-sdk-crypto-wasm/classes/EncryptionSettings.html#rotationPeriod + if (typeof this.encryptionSettings.rotation_period_ms === "number") { + rustEncryptionSettings.rotationPeriod = BigInt(this.encryptionSettings.rotation_period_ms * 1000); + } + + if (typeof this.encryptionSettings.rotation_period_msgs === "number") { + rustEncryptionSettings.rotationPeriodMessages = BigInt(this.encryptionSettings.rotation_period_msgs); + } + const shareMessages = await this.olmMachine.shareRoomKey( new RoomId(this.room.roomId), userList,