From 87c9753ed37e238d374978df06c3832261f47988 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 22 Mar 2023 15:50:59 +0000 Subject: [PATCH] Element-R: Add support for `/discardsession` Fixes https://github.com/vector-im/element-web/issues/24431 --- spec/integ/crypto.spec.ts | 59 ++++++++++++++++++++++++++++---- src/client.ts | 7 ++-- src/crypto-api.ts | 13 +++++++ src/crypto/index.ts | 3 +- src/rust-crypto/RoomEncryptor.ts | 10 ++++++ src/rust-crypto/rust-crypto.ts | 4 +++ 6 files changed, 86 insertions(+), 10 deletions(-) diff --git a/spec/integ/crypto.spec.ts b/spec/integ/crypto.spec.ts index 362cfc3a300..db47fbbe595 100644 --- a/spec/integ/crypto.spec.ts +++ b/spec/integ/crypto.spec.ts @@ -342,6 +342,11 @@ async function expectSendRoomKey( resolve(onSendRoomKey(content)); return {}; }, + { + // append to the list of intercepts on this path (since we have some tests that call + // this function multiple times) + overwriteRoutes: false, + }, ); }); } @@ -360,12 +365,20 @@ async function expectSendMegolmMessage( inboundGroupSessionPromise: Promise, ): Promise> { const encryptedMessageContent = await new Promise((resolve) => { - fetchMock.putOnce(new RegExp("/send/m.room.encrypted/"), (url: string, opts: RequestInit): MockResponse => { - resolve(JSON.parse(opts.body as string)); - return { - event_id: "$event_id", - }; - }); + fetchMock.putOnce( + new RegExp("/send/m.room.encrypted/"), + (url: string, opts: RequestInit): MockResponse => { + resolve(JSON.parse(opts.body as string)); + return { + event_id: "$event_id", + }; + }, + { + // append to the list of intercepts on this path (since we have some tests that call + // this function multiple times) + overwriteRoutes: false, + }, + ); }); // In some of the tests, the room key is sent *after* the actual event, so we may need to wait for it now. @@ -808,6 +821,40 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string, ]); }); + it("We should start a new megolm session after forceDiscardSession", async () => { + aliceClient.setGlobalErrorOnUnknownDevices(false); + expectAliceKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} }); + await startClientAndAwaitFirstSync(); + + // Alice shares a room with Bob + syncResponder.sendOrQueueSyncResponse(getSyncResponse(["@bob:xyz"])); + await syncPromise(aliceClient); + + // Once we send the message, Alice will check Bob's device list (twice, because reasons) ... + expectAliceKeyQuery(getTestKeysQueryResponse("@bob:xyz")); + expectAliceKeyQuery(getTestKeysQueryResponse("@bob:xyz")); + + // ... and claim one of his OTKs ... + expectAliceKeyClaim(getTestKeysClaimResponse("@bob:xyz")); + + // ... and send an m.room_key message + const inboundGroupSessionPromise = expectSendRoomKey("@bob:xyz", testOlmAccount); + + // Send the first message, and check we can decrypt it. + await Promise.all([ + aliceClient.sendTextMessage(ROOM_ID, "test"), + expectSendMegolmMessage(inboundGroupSessionPromise), + ]); + + // Finally the interesting part: discard the session. + aliceClient.forceDiscardSession(ROOM_ID); + + // Now when we send the next message, we should get a *new* megolm session. + const inboundGroupSessionPromise2 = expectSendRoomKey("@bob:xyz", testOlmAccount); + const p2 = expectSendMegolmMessage(inboundGroupSessionPromise2); + await Promise.all([aliceClient.sendTextMessage(ROOM_ID, "test2"), p2]); + }); + oldBackendOnly("Alice sends a megolm message", async () => { // TODO: do something about this for the rust backend. // Currently it fails because we don't respect the default GlobalErrorOnUnknownDevices and diff --git a/src/client.ts b/src/client.ts index 132be95507a..4c1c537b438 100644 --- a/src/client.ts +++ b/src/client.ts @@ -3122,13 +3122,14 @@ export class MatrixClient extends TypedEventEmitter; + /** * Get a list containing all of the room keys * diff --git a/src/crypto/index.ts b/src/crypto/index.ts index f38c8766654..d3c6f153a16 100644 --- a/src/crypto/index.ts +++ b/src/crypto/index.ts @@ -2488,13 +2488,14 @@ export class Crypto extends TypedEventEmitter { const alg = this.roomEncryptors.get(roomId); if (alg === undefined) throw new Error("Room not encrypted"); if (alg.forceDiscardSession === undefined) { throw new Error("Room encryption algorithm doesn't support session discarding"); } alg.forceDiscardSession(); + return Promise.resolve(); } /** diff --git a/src/rust-crypto/RoomEncryptor.ts b/src/rust-crypto/RoomEncryptor.ts index 381ea61786f..1649a69e722 100644 --- a/src/rust-crypto/RoomEncryptor.ts +++ b/src/rust-crypto/RoomEncryptor.ts @@ -116,6 +116,16 @@ export class RoomEncryptor { } } + /** + * Discard any existing group session for this room + */ + public async forceDiscardSession(): Promise { + const r = await this.olmMachine.invalidateGroupSession(new RoomId(this.room.roomId)); + if (r) { + this.prefixedLogger.info("Discarded existing group session"); + } + } + /** * Encrypt an event for this room * diff --git a/src/rust-crypto/rust-crypto.ts b/src/rust-crypto/rust-crypto.ts index 48775fb1c9b..4a0b1f895f3 100644 --- a/src/rust-crypto/rust-crypto.ts +++ b/src/rust-crypto/rust-crypto.ts @@ -169,6 +169,10 @@ export class RustCrypto implements CryptoBackend { } } + public forceDiscardSession(roomId: string): Promise { + return this.roomEncryptors[roomId]?.forceDiscardSession(); + } + public async exportRoomKeys(): Promise { // TODO return [];