Skip to content

Commit a08a273

Browse files
authored
Implement experimental encrypted state events. (#4994)
* feat: Implement experimental encrypted state events. Signed-off-by: Skye Elliot <[email protected]> * fix: Add cast from StateEvents[K] to IContent. --------- Signed-off-by: Skye Elliot <[email protected]>
1 parent dbe441d commit a08a273

File tree

17 files changed

+594
-144
lines changed

17 files changed

+594
-144
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
],
5050
"dependencies": {
5151
"@babel/runtime": "^7.12.5",
52-
"@matrix-org/matrix-sdk-crypto-wasm": "^15.2.0",
52+
"@matrix-org/matrix-sdk-crypto-wasm": "^15.3.0",
5353
"another-json": "^0.2.0",
5454
"bs58": "^6.0.0",
5555
"content-type": "^1.0.4",

spec/integ/crypto/crypto.spec.ts

Lines changed: 17 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,10 @@ import {
8888
encryptMegolmEventRawPlainText,
8989
establishOlmSession,
9090
getTestOlmAccountKeys,
91-
} from "./olm-utils";
91+
expectSendRoomKey,
92+
expectSendMegolmMessageEvent,
93+
expectEncryptedSendMessageEvent,
94+
} from "./olm-utils.ts";
9295
import { AccountDataAccumulator } from "../../test-utils/AccountDataAccumulator";
9396
import { UNSIGNED_MEMBERSHIP_FIELD } from "../../../src/@types/event";
9497
import { KnownMembership } from "../../../src/@types/membership";
@@ -104,107 +107,6 @@ afterEach(() => {
104107
jest.useRealTimers();
105108
});
106109

107-
/**
108-
* Expect that the client shares keys with the given recipient
109-
*
110-
* Waits for an HTTP request to send the encrypted m.room_key to-device message; decrypts it and uses it
111-
* to establish an Olm InboundGroupSession.
112-
*
113-
* @param recipientUserID - the user id of the expected recipient
114-
*
115-
* @param recipientOlmAccount - Olm.Account for the recipient
116-
*
117-
* @param recipientOlmSession - an Olm.Session for the recipient, which must already have exchanged pre-key
118-
* messages with the sender. Alternatively, null, in which case we will expect a pre-key message.
119-
*
120-
* @returns the established inbound group session
121-
*/
122-
async function expectSendRoomKey(
123-
recipientUserID: string,
124-
recipientOlmAccount: Olm.Account,
125-
recipientOlmSession: Olm.Session | null = null,
126-
): Promise<Olm.InboundGroupSession> {
127-
const testRecipientKey = JSON.parse(recipientOlmAccount.identity_keys())["curve25519"];
128-
129-
function onSendRoomKey(content: any): Olm.InboundGroupSession {
130-
const m = content.messages[recipientUserID].DEVICE_ID;
131-
const ct = m.ciphertext[testRecipientKey];
132-
133-
if (!recipientOlmSession) {
134-
expect(ct.type).toEqual(0); // pre-key message
135-
recipientOlmSession = new Olm.Session();
136-
recipientOlmSession.create_inbound(recipientOlmAccount, ct.body);
137-
} else {
138-
expect(ct.type).toEqual(1); // regular message
139-
}
140-
141-
const decrypted = JSON.parse(recipientOlmSession.decrypt(ct.type, ct.body));
142-
expect(decrypted.type).toEqual("m.room_key");
143-
const inboundGroupSession = new Olm.InboundGroupSession();
144-
inboundGroupSession.create(decrypted.content.session_key);
145-
return inboundGroupSession;
146-
}
147-
return await new Promise<Olm.InboundGroupSession>((resolve) => {
148-
fetchMock.putOnce(
149-
new RegExp("/sendToDevice/m.room.encrypted/"),
150-
(url: string, opts: RequestInit): FetchMock.MockResponse => {
151-
const content = JSON.parse(opts.body as string);
152-
resolve(onSendRoomKey(content));
153-
return {};
154-
},
155-
{
156-
// append to the list of intercepts on this path (since we have some tests that call
157-
// this function multiple times)
158-
overwriteRoutes: false,
159-
},
160-
);
161-
});
162-
}
163-
164-
/**
165-
* Return the event received on rooms/{roomId}/send/m.room.encrypted endpoint.
166-
* See https://spec.matrix.org/latest/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid
167-
* @returns the content of the encrypted event
168-
*/
169-
function expectEncryptedSendMessage() {
170-
return new Promise<IContent>((resolve) => {
171-
fetchMock.putOnce(
172-
new RegExp("/send/m.room.encrypted/"),
173-
(url, request) => {
174-
const content = JSON.parse(request.body as string);
175-
resolve(content);
176-
return { event_id: "$event_id" };
177-
},
178-
// append to the list of intercepts on this path (since we have some tests that call
179-
// this function multiple times)
180-
{ overwriteRoutes: false },
181-
);
182-
});
183-
}
184-
185-
/**
186-
* Expect that the client sends an encrypted event
187-
*
188-
* Waits for an HTTP request to send an encrypted message in the test room.
189-
*
190-
* @param inboundGroupSessionPromise - a promise for an Olm InboundGroupSession, which will
191-
* be used to decrypt the event. We will wait for this to resolve once the HTTP request has been processed.
192-
*
193-
* @returns The content of the successfully-decrypted event
194-
*/
195-
async function expectSendMegolmMessage(
196-
inboundGroupSessionPromise: Promise<Olm.InboundGroupSession>,
197-
): Promise<Partial<IEvent>> {
198-
const encryptedMessageContent = await expectEncryptedSendMessage();
199-
200-
// In some of the tests, the room key is sent *after* the actual event, so we may need to wait for it now.
201-
const inboundGroupSession = await inboundGroupSessionPromise;
202-
203-
const r: any = inboundGroupSession.decrypt(encryptedMessageContent!.ciphertext);
204-
logger.log("Decrypted received megolm message", r);
205-
return JSON.parse(r.plaintext);
206-
}
207-
208110
describe("crypto", () => {
209111
let testOlmAccount = {} as unknown as Olm.Account;
210112
let testSenderKey = "";
@@ -991,7 +893,7 @@ describe("crypto", () => {
991893
// Finally, send the message, and expect to get an `m.room.encrypted` event that we can decrypt.
992894
await Promise.all([
993895
aliceClient.sendTextMessage(ROOM_ID, "test"),
994-
expectSendMegolmMessage(inboundGroupSessionPromise),
896+
expectSendMegolmMessageEvent(inboundGroupSessionPromise),
995897
]);
996898
});
997899

@@ -1018,15 +920,15 @@ describe("crypto", () => {
1018920
// Send the first message, and check we can decrypt it.
1019921
await Promise.all([
1020922
aliceClient.sendTextMessage(ROOM_ID, "test"),
1021-
expectSendMegolmMessage(inboundGroupSessionPromise),
923+
expectSendMegolmMessageEvent(inboundGroupSessionPromise),
1022924
]);
1023925

1024926
// Finally the interesting part: discard the session.
1025927
aliceClient.getCrypto()!.forceDiscardSession(ROOM_ID);
1026928

1027929
// Now when we send the next message, we should get a *new* megolm session.
1028930
const inboundGroupSessionPromise2 = expectSendRoomKey("@bob:xyz", testOlmAccount);
1029-
const p2 = expectSendMegolmMessage(inboundGroupSessionPromise2);
931+
const p2 = expectSendMegolmMessageEvent(inboundGroupSessionPromise2);
1030932
await Promise.all([aliceClient.sendTextMessage(ROOM_ID, "test2"), p2]);
1031933
});
1032934

@@ -1037,7 +939,7 @@ describe("crypto", () => {
1037939
*/
1038940
async function sendEncryptedMessage(): Promise<IContent> {
1039941
const [encryptedMessage] = await Promise.all([
1040-
expectEncryptedSendMessage(),
942+
expectEncryptedSendMessageEvent(),
1041943
aliceClient.sendTextMessage(ROOM_ID, "test"),
1042944
]);
1043945
return encryptedMessage;
@@ -1159,7 +1061,7 @@ describe("crypto", () => {
11591061
let [, , encryptedMessage] = await Promise.all([
11601062
aliceClient.sendTextMessage(ROOM_ID, "test"),
11611063
expectSendRoomKey("@bob:xyz", testOlmAccount, p2pSession),
1162-
expectEncryptedSendMessage(),
1064+
expectEncryptedSendMessageEvent(),
11631065
]);
11641066

11651067
// Check that the session id exists
@@ -1187,7 +1089,7 @@ describe("crypto", () => {
11871089
[, , encryptedMessage] = await Promise.all([
11881090
aliceClient.sendTextMessage(ROOM_ID, "test"),
11891091
expectSendRoomKey("@bob:xyz", testOlmAccount, p2pSession),
1190-
expectEncryptedSendMessage(),
1092+
expectEncryptedSendMessageEvent(),
11911093
]);
11921094

11931095
// Check that the new session id exists
@@ -1385,7 +1287,7 @@ describe("crypto", () => {
13851287
const inboundGroupSessionPromise = expectSendRoomKey("@bob:xyz", testOlmAccount, p2pSession);
13861288

13871289
// and finally the megolm message
1388-
const megolmMessagePromise = expectSendMegolmMessage(inboundGroupSessionPromise);
1290+
const megolmMessagePromise = expectSendMegolmMessageEvent(inboundGroupSessionPromise);
13891291

13901292
// kick it off
13911293
const sendPromise = aliceClient.sendTextMessage(ROOM_ID, "test");
@@ -1408,7 +1310,7 @@ describe("crypto", () => {
14081310
const inboundGroupSessionPromise = expectSendRoomKey("@bob:xyz", testOlmAccount, p2pSession);
14091311

14101312
// and finally the megolm message
1411-
const megolmMessagePromise = expectSendMegolmMessage(inboundGroupSessionPromise);
1313+
const megolmMessagePromise = expectSendMegolmMessageEvent(inboundGroupSessionPromise);
14121314

14131315
// kick it off
14141316
const sendPromise = aliceClient.sendTextMessage(ROOM_ID, "test");
@@ -2300,7 +2202,7 @@ describe("crypto", () => {
23002202
await syncPromise(client1);
23012203

23022204
// Send a message, and expect to get an `m.room.encrypted` event.
2303-
await Promise.all([client1.sendTextMessage(ROOM_ID, "test"), expectEncryptedSendMessage()]);
2205+
await Promise.all([client1.sendTextMessage(ROOM_ID, "test"), expectEncryptedSendMessageEvent()]);
23042206

23052207
// We now replace the client, and allow the new one to resync, *without* the encryption event.
23062208
client2 = await replaceClient(client1);
@@ -2321,7 +2223,7 @@ describe("crypto", () => {
23212223
// Send a message, and expect to get an `m.room.encrypted` event.
23222224
const [, msg1Content] = await Promise.all([
23232225
client1.sendTextMessage(ROOM_ID, "test1"),
2324-
expectEncryptedSendMessage(),
2226+
expectEncryptedSendMessageEvent(),
23252227
]);
23262228

23272229
// Replace the state with one which bumps the rotation period. This should be ignored, though it's not
@@ -2340,12 +2242,12 @@ describe("crypto", () => {
23402242
// use a different one.
23412243
const [, msg2Content] = await Promise.all([
23422244
client1.sendTextMessage(ROOM_ID, "test2"),
2343-
expectEncryptedSendMessage(),
2245+
expectEncryptedSendMessageEvent(),
23442246
]);
23452247
expect(msg2Content.session_id).toEqual(msg1Content.session_id);
23462248
const [, msg3Content] = await Promise.all([
23472249
client1.sendTextMessage(ROOM_ID, "test3"),
2348-
expectEncryptedSendMessage(),
2250+
expectEncryptedSendMessageEvent(),
23492251
]);
23502252
expect(msg3Content.session_id).not.toEqual(msg1Content.session_id);
23512253
});
@@ -2357,7 +2259,7 @@ describe("crypto", () => {
23572259
await syncPromise(client1);
23582260

23592261
// Send a message, and expect to get an `m.room.encrypted` event.
2360-
await Promise.all([client1.sendTextMessage(ROOM_ID, "test1"), expectEncryptedSendMessage()]);
2262+
await Promise.all([client1.sendTextMessage(ROOM_ID, "test1"), expectEncryptedSendMessageEvent()]);
23612263

23622264
// We now replace the client, and allow the new one to resync with a *different* encryption event.
23632265
client2 = await replaceClient(client1);

0 commit comments

Comments
 (0)