Skip to content

Commit 83563c7

Browse files
authored
Implement decryption via the rust sdk (#3074)
A bunch of changes to tests, and wire up decryption.
1 parent 2fcc481 commit 83563c7

File tree

4 files changed

+131
-42
lines changed

4 files changed

+131
-42
lines changed

spec/TestClient.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,21 +115,30 @@ export class TestClient {
115115
}
116116

117117
/**
118-
* Set up expectations that the client will upload device keys.
118+
* Set up expectations that the client will upload device keys (and possibly one-time keys)
119119
*/
120120
public expectDeviceKeyUpload() {
121121
this.httpBackend
122122
.when("POST", "/keys/upload")
123123
.respond<IKeysUploadResponse, IUploadKeysRequest>(200, (_path, content) => {
124-
expect(content.one_time_keys).toBe(undefined);
125124
expect(content.device_keys).toBeTruthy();
126125

127126
logger.log(this + ": received device keys");
128127
// we expect this to happen before any one-time keys are uploaded.
129128
expect(Object.keys(this.oneTimeKeys!).length).toEqual(0);
130129

131130
this.deviceKeys = content.device_keys;
132-
return { one_time_key_counts: { signed_curve25519: 0 } };
131+
132+
// the first batch of one-time keys may be uploaded at the same time.
133+
if (content.one_time_keys) {
134+
logger.log(`${this}: received ${Object.keys(content.one_time_keys).length} one-time keys`);
135+
this.oneTimeKeys = content.one_time_keys;
136+
}
137+
return {
138+
one_time_key_counts: {
139+
signed_curve25519: Object.keys(this.oneTimeKeys!).length,
140+
},
141+
};
133142
});
134143
}
135144

spec/integ/megolm-integ.spec.ts

Lines changed: 89 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ limitations under the License.
1717

1818
import anotherjson from "another-json";
1919
import MockHttpBackend from "matrix-mock-request";
20+
import "fake-indexeddb/auto";
21+
import { IDBFactory } from "fake-indexeddb";
2022

2123
import * as testUtils from "../test-utils/test-utils";
2224
import { TestClient } from "../TestClient";
@@ -38,9 +40,17 @@ import {
3840
} from "../../src/matrix";
3941
import { IDeviceKeys } from "../../src/crypto/dehydration";
4042
import { DeviceInfo } from "../../src/crypto/deviceinfo";
43+
import { CRYPTO_BACKENDS, InitCrypto } from "../test-utils/test-utils";
4144

4245
const ROOM_ID = "!room:id";
4346

47+
afterEach(() => {
48+
// reset fake-indexeddb after each test, to make sure we don't leak connections
49+
// cf https://github.com/dumbmatter/fakeIndexedDB#wipingresetting-the-indexeddb-for-a-fresh-state
50+
// eslint-disable-next-line no-global-assign
51+
indexedDB = new IDBFactory();
52+
});
53+
4454
// start an Olm session with a given recipient
4555
async function createOlmSession(olmAccount: Olm.Account, recipientTestClient: TestClient): Promise<Olm.Session> {
4656
const keys = await recipientTestClient.awaitOneTimeKeyUpload();
@@ -341,11 +351,17 @@ async function expectSendMegolmMessage(
341351
return JSON.parse(r.plaintext);
342352
}
343353

344-
describe("megolm", () => {
354+
describe.each(Object.entries(CRYPTO_BACKENDS))("megolm (%s)", (backend: string, initCrypto: InitCrypto) => {
345355
if (!global.Olm) {
356+
// currently we use libolm to implement the crypto in the tests, so need it to be present.
346357
logger.warn("not running megolm tests: Olm not present");
347358
return;
348359
}
360+
361+
// oldBackendOnly is an alternative to `it` or `test` which will skip the test if we are running against the
362+
// Rust backend. Once we have full support in the rust sdk, it will go away.
363+
const oldBackendOnly = backend === "rust-sdk" ? test.skip : test;
364+
349365
const Olm = global.Olm;
350366

351367
let testOlmAccount = {} as unknown as Olm.Account;
@@ -410,8 +426,10 @@ describe("megolm", () => {
410426

411427
beforeEach(async () => {
412428
aliceTestClient = new TestClient("@alice:localhost", "xzcvb", "akjgkrgjs");
413-
await aliceTestClient.client.initCrypto();
429+
await initCrypto(aliceTestClient.client);
414430

431+
// create a test olm device which we will use to communicate with alice. We use libolm to implement this.
432+
await Olm.init();
415433
testOlmAccount = new Olm.Account();
416434
testOlmAccount.create();
417435
const testE2eKeys = JSON.parse(testOlmAccount.identity_keys());
@@ -424,13 +442,18 @@ describe("megolm", () => {
424442

425443
it("Alice receives a megolm message", async () => {
426444
await aliceTestClient.start();
427-
aliceTestClient.client.crypto!.deviceList.downloadKeys = () => Promise.resolve({});
445+
446+
// if we're using the old crypto impl, stub out some methods in the device manager.
447+
// TODO: replace this with intercepts of the /keys/query endpoint to make it impl agnostic.
448+
if (aliceTestClient.client.crypto) {
449+
aliceTestClient.client.crypto.deviceList.downloadKeys = () => Promise.resolve({});
450+
aliceTestClient.client.crypto.deviceList.getUserByIdentityKey = () => "@bob:xyz";
451+
}
452+
428453
const p2pSession = await createOlmSession(testOlmAccount, aliceTestClient);
429454
const groupSession = new Olm.OutboundGroupSession();
430455
groupSession.create();
431456

432-
aliceTestClient.client.crypto!.deviceList.getUserByIdentityKey = () => "@bob:xyz";
433-
434457
// make the room_key event
435458
const roomKeyEncrypted = encryptGroupSessionKey({
436459
recipient: aliceTestClient,
@@ -472,16 +495,21 @@ describe("megolm", () => {
472495
expect(decryptedEvent.getContent().body).toEqual("42");
473496
});
474497

475-
it("Alice receives a megolm message before the session keys", async () => {
498+
oldBackendOnly("Alice receives a megolm message before the session keys", async () => {
476499
// https://github.com/vector-im/element-web/issues/2273
477500
await aliceTestClient.start();
478-
aliceTestClient.client.crypto!.deviceList.downloadKeys = () => Promise.resolve({});
501+
502+
// if we're using the old crypto impl, stub out some methods in the device manager.
503+
// TODO: replace this with intercepts of the /keys/query endpoint to make it impl agnostic.
504+
if (aliceTestClient.client.crypto) {
505+
aliceTestClient.client.crypto.deviceList.downloadKeys = () => Promise.resolve({});
506+
aliceTestClient.client.crypto.deviceList.getUserByIdentityKey = () => "@bob:xyz";
507+
}
508+
479509
const p2pSession = await createOlmSession(testOlmAccount, aliceTestClient);
480510
const groupSession = new Olm.OutboundGroupSession();
481511
groupSession.create();
482512

483-
aliceTestClient.client.crypto!.deviceList.getUserByIdentityKey = () => "@bob:xyz";
484-
485513
// make the room_key event, but don't send it yet
486514
const roomKeyEncrypted = encryptGroupSessionKey({
487515
recipient: aliceTestClient,
@@ -535,13 +563,18 @@ describe("megolm", () => {
535563

536564
it("Alice gets a second room_key message", async () => {
537565
await aliceTestClient.start();
538-
aliceTestClient.client.crypto!.deviceList.downloadKeys = () => Promise.resolve({});
566+
567+
// if we're using the old crypto impl, stub out some methods in the device manager.
568+
// TODO: replace this with intercepts of the /keys/query endpoint to make it impl agnostic.
569+
if (aliceTestClient.client.crypto) {
570+
aliceTestClient.client.crypto.deviceList.downloadKeys = () => Promise.resolve({});
571+
aliceTestClient.client.crypto.deviceList.getUserByIdentityKey = () => "@bob:xyz";
572+
}
573+
539574
const p2pSession = await createOlmSession(testOlmAccount, aliceTestClient);
540575
const groupSession = new Olm.OutboundGroupSession();
541576
groupSession.create();
542577

543-
aliceTestClient.client.crypto!.deviceList.getUserByIdentityKey = () => "@bob:xyz";
544-
545578
// make the room_key event
546579
const roomKeyEncrypted1 = encryptGroupSessionKey({
547580
recipient: aliceTestClient,
@@ -600,7 +633,7 @@ describe("megolm", () => {
600633
expect(event.getContent().body).toEqual("42");
601634
});
602635

603-
it("Alice sends a megolm message", async () => {
636+
oldBackendOnly("Alice sends a megolm message", async () => {
604637
aliceTestClient.expectKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} });
605638
await aliceTestClient.start();
606639
const p2pSession = await establishOlmSession(aliceTestClient, testOlmAccount);
@@ -643,7 +676,7 @@ describe("megolm", () => {
643676
]);
644677
});
645678

646-
it("We shouldn't attempt to send to blocked devices", async () => {
679+
oldBackendOnly("We shouldn't attempt to send to blocked devices", async () => {
647680
aliceTestClient.expectKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} });
648681
await aliceTestClient.start();
649682
await establishOlmSession(aliceTestClient, testOlmAccount);
@@ -687,7 +720,7 @@ describe("megolm", () => {
687720
expect(() => aliceTestClient.client.getGlobalErrorOnUnknownDevices()).toThrowError("encryption disabled");
688721
});
689722

690-
it("should permit sending to unknown devices", async () => {
723+
oldBackendOnly("should permit sending to unknown devices", async () => {
691724
expect(aliceTestClient.client.getGlobalErrorOnUnknownDevices()).toBeTruthy();
692725

693726
aliceTestClient.expectKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} });
@@ -745,7 +778,7 @@ describe("megolm", () => {
745778
);
746779
});
747780

748-
it("should disable sending to unverified devices", async () => {
781+
oldBackendOnly("should disable sending to unverified devices", async () => {
749782
aliceTestClient.expectKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} });
750783
await aliceTestClient.start();
751784
const p2pSession = await establishOlmSession(aliceTestClient, testOlmAccount);
@@ -803,7 +836,7 @@ describe("megolm", () => {
803836
});
804837
});
805838

806-
it("We should start a new megolm session when a device is blocked", async () => {
839+
oldBackendOnly("We should start a new megolm session when a device is blocked", async () => {
807840
aliceTestClient.expectKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} });
808841
await aliceTestClient.start();
809842
const p2pSession = await establishOlmSession(aliceTestClient, testOlmAccount);
@@ -861,7 +894,7 @@ describe("megolm", () => {
861894
});
862895

863896
// https://github.com/vector-im/element-web/issues/2676
864-
it("Alice should send to her other devices", async () => {
897+
oldBackendOnly("Alice should send to her other devices", async () => {
865898
// for this test, we make the testOlmAccount be another of Alice's devices.
866899
// it ought to get included in messages Alice sends.
867900
await aliceTestClient.start();
@@ -942,7 +975,7 @@ describe("megolm", () => {
942975
expect(decrypted.content?.body).toEqual("test");
943976
});
944977

945-
it("Alice should wait for device list to complete when sending a megolm message", async () => {
978+
oldBackendOnly("Alice should wait for device list to complete when sending a megolm message", async () => {
946979
aliceTestClient.expectKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} });
947980
await aliceTestClient.start();
948981
await establishOlmSession(aliceTestClient, testOlmAccount);
@@ -972,15 +1005,20 @@ describe("megolm", () => {
9721005
await Promise.all([downloadPromise, sendPromise]);
9731006
});
9741007

975-
it("Alice exports megolm keys and imports them to a new device", async () => {
1008+
oldBackendOnly("Alice exports megolm keys and imports them to a new device", async () => {
9761009
aliceTestClient.expectKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} });
9771010
await aliceTestClient.start();
978-
aliceTestClient.client.crypto!.deviceList.downloadKeys = () => Promise.resolve({});
1011+
1012+
// if we're using the old crypto impl, stub out some methods in the device manager.
1013+
// TODO: replace this with intercepts of the /keys/query endpoint to make it impl agnostic.
1014+
if (aliceTestClient.client.crypto) {
1015+
aliceTestClient.client.crypto.deviceList.downloadKeys = () => Promise.resolve({});
1016+
aliceTestClient.client.crypto.deviceList.getUserByIdentityKey = () => "@bob:xyz";
1017+
}
1018+
9791019
// establish an olm session with alice
9801020
const p2pSession = await createOlmSession(testOlmAccount, aliceTestClient);
9811021

982-
aliceTestClient.client.crypto!.deviceList.getUserByIdentityKey = () => "@bob:xyz";
983-
9841022
const groupSession = new Olm.OutboundGroupSession();
9851023
groupSession.create();
9861024

@@ -1027,11 +1065,15 @@ describe("megolm", () => {
10271065
aliceTestClient.stop();
10281066

10291067
aliceTestClient = new TestClient("@alice:localhost", "device2", "access_token2");
1030-
await aliceTestClient.client.initCrypto();
1068+
await initCrypto(aliceTestClient.client);
10311069
await aliceTestClient.client.importRoomKeys(exported);
10321070
await aliceTestClient.start();
10331071

1034-
aliceTestClient.client.crypto!.deviceList.getUserByIdentityKey = () => "@bob:xyz";
1072+
// if we're using the old crypto impl, stub out some methods in the device manager.
1073+
// TODO: replace this with intercepts of the /keys/query endpoint to make it impl agnostic.
1074+
if (aliceTestClient.client.crypto) {
1075+
aliceTestClient.client.crypto.deviceList.getUserByIdentityKey = () => "@bob:xyz";
1076+
}
10351077

10361078
const syncResponse = {
10371079
next_batch: 1,
@@ -1107,13 +1149,18 @@ describe("megolm", () => {
11071149

11081150
it("Alice can decrypt a message with falsey content", async () => {
11091151
await aliceTestClient.start();
1110-
aliceTestClient.client.crypto!.deviceList.downloadKeys = () => Promise.resolve({});
1152+
1153+
// if we're using the old crypto impl, stub out some methods in the device manager.
1154+
// TODO: replace this with intercepts of the /keys/query endpoint to make it impl agnostic.
1155+
if (aliceTestClient.client.crypto) {
1156+
aliceTestClient.client.crypto.deviceList.downloadKeys = () => Promise.resolve({});
1157+
aliceTestClient.client.crypto.deviceList.getUserByIdentityKey = () => "@bob:xyz";
1158+
}
1159+
11111160
const p2pSession = await createOlmSession(testOlmAccount, aliceTestClient);
11121161
const groupSession = new Olm.OutboundGroupSession();
11131162
groupSession.create();
11141163

1115-
aliceTestClient.client.crypto!.deviceList.getUserByIdentityKey = () => "@bob:xyz";
1116-
11171164
// make the room_key event
11181165
const roomKeyEncrypted = encryptGroupSessionKey({
11191166
recipient: aliceTestClient,
@@ -1160,14 +1207,21 @@ describe("megolm", () => {
11601207
expect(decryptedEvent.getClearContent()).toBeUndefined();
11611208
});
11621209

1163-
it("Alice receives shared history before being invited to a room by the sharer", async () => {
1210+
oldBackendOnly("Alice receives shared history before being invited to a room by the sharer", async () => {
11641211
const beccaTestClient = new TestClient("@becca:localhost", "foobar", "bazquux");
11651212
await beccaTestClient.client.initCrypto();
11661213

11671214
await aliceTestClient.start();
1168-
aliceTestClient.client.crypto!.deviceList.downloadKeys = () => Promise.resolve({});
11691215
await beccaTestClient.start();
11701216

1217+
// if we're using the old crypto impl, stub out some methods in the device manager.
1218+
// TODO: replace this with intercepts of the /keys/query endpoint to make it impl agnostic.
1219+
if (aliceTestClient.client.crypto) {
1220+
aliceTestClient.client.crypto!.deviceList.downloadKeys = () => Promise.resolve({});
1221+
aliceTestClient.client.crypto!.deviceList.getDeviceByIdentityKey = () => device;
1222+
aliceTestClient.client.crypto!.deviceList.getUserByIdentityKey = () => beccaTestClient.client.getUserId()!;
1223+
}
1224+
11711225
const beccaRoom = new Room(ROOM_ID, beccaTestClient.client, "@becca:localhost", {});
11721226
beccaTestClient.client.store.storeRoom(beccaRoom);
11731227
await beccaTestClient.client.setRoomEncryption(ROOM_ID, { algorithm: "m.megolm.v1.aes-sha2" });
@@ -1193,8 +1247,6 @@ describe("megolm", () => {
11931247
event.claimedEd25519Key = null;
11941248

11951249
const device = new DeviceInfo(beccaTestClient.client.deviceId!);
1196-
aliceTestClient.client.crypto!.deviceList.getDeviceByIdentityKey = () => device;
1197-
aliceTestClient.client.crypto!.deviceList.getUserByIdentityKey = () => beccaTestClient.client.getUserId()!;
11981250

11991251
// Create an olm session for Becca and Alice's devices
12001252
const aliceOtks = await aliceTestClient.awaitOneTimeKeyUpload();
@@ -1307,7 +1359,7 @@ describe("megolm", () => {
13071359
await beccaTestClient.stop();
13081360
});
13091361

1310-
it("Alice receives shared history before being invited to a room by someone else", async () => {
1362+
oldBackendOnly("Alice receives shared history before being invited to a room by someone else", async () => {
13111363
const beccaTestClient = new TestClient("@becca:localhost", "foobar", "bazquux");
13121364
await beccaTestClient.client.initCrypto();
13131365

@@ -1453,7 +1505,7 @@ describe("megolm", () => {
14531505
await beccaTestClient.stop();
14541506
});
14551507

1456-
it("allows sending an encrypted event as soon as room state arrives", async () => {
1508+
oldBackendOnly("allows sending an encrypted event as soon as room state arrives", async () => {
14571509
/* Empirically, clients expect to be able to send encrypted events as soon as the
14581510
* RoomStateEvent.NewMember notification is emitted, so test that works correctly.
14591511
*/
@@ -1578,7 +1630,7 @@ describe("megolm", () => {
15781630
await aliceTestClient.httpBackend.flush(membersPath, 1);
15791631
}
15801632

1581-
it("Sending an event initiates a member list sync", async () => {
1633+
oldBackendOnly("Sending an event initiates a member list sync", async () => {
15821634
// we expect a call to the /members list...
15831635
const memberListPromise = expectMembershipRequest(ROOM_ID, ["@bob:xyz"]);
15841636

@@ -1610,7 +1662,7 @@ describe("megolm", () => {
16101662
]);
16111663
});
16121664

1613-
it("loading the membership list inhibits a later load", async () => {
1665+
oldBackendOnly("loading the membership list inhibits a later load", async () => {
16141666
const room = aliceTestClient.client.getRoom(ROOM_ID)!;
16151667
await Promise.all([room.loadMembersIfNeeded(), expectMembershipRequest(ROOM_ID, ["@bob:xyz"])]);
16161668

@@ -1642,7 +1694,7 @@ describe("megolm", () => {
16421694
// TODO: there are a bunch more tests for this sort of thing in spec/unit/crypto/algorithms/megolm.spec.ts.
16431695
// They should be converted to integ tests and moved.
16441696

1645-
it("does not block decryption on an 'm.unavailable' report", async function () {
1697+
oldBackendOnly("does not block decryption on an 'm.unavailable' report", async function () {
16461698
await aliceTestClient.start();
16471699

16481700
// there may be a key downloads for alice

spec/test-utils/test-utils.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,3 +403,15 @@ export const mkPusher = (extra: Partial<IPusher> = {}): IPusher => ({
403403
pushkey: "pushpush",
404404
...extra,
405405
});
406+
407+
/**
408+
* a list of the supported crypto implementations, each with a callback to initialise that implementation
409+
* for the given client
410+
*/
411+
export const CRYPTO_BACKENDS: Record<string, InitCrypto> = {};
412+
export type InitCrypto = (_: MatrixClient) => Promise<void>;
413+
414+
CRYPTO_BACKENDS["rust-sdk"] = (client: MatrixClient) => client.initRustCrypto();
415+
if (global.Olm) {
416+
CRYPTO_BACKENDS["libolm"] = (client: MatrixClient) => client.initCrypto();
417+
}

0 commit comments

Comments
 (0)