Skip to content

Commit 414ac9d

Browse files
hughnsrobintown
andauthored
Don't share full key history for RTC per-participant encryption (#4406)
* Don't share full key history for RTC per-participant encryption Also record stats for how many keys have been sent/received and age of those received * Update src/matrixrtc/MatrixRTCSession.ts Co-authored-by: Robin <[email protected]> * Add comment about why we track total age of events --------- Co-authored-by: Robin <[email protected]>
1 parent 30058a4 commit 414ac9d

File tree

3 files changed

+187
-30
lines changed

3 files changed

+187
-30
lines changed

spec/unit/matrixrtc/MatrixRTCSession.spec.ts

Lines changed: 120 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -594,35 +594,48 @@ describe("MatrixRTCSession", () => {
594594
});
595595

596596
it("sends keys when joining", async () => {
597-
const eventSentPromise = new Promise((resolve) => {
598-
sendEventMock.mockImplementation(resolve);
599-
});
597+
jest.useFakeTimers();
598+
try {
599+
const eventSentPromise = new Promise((resolve) => {
600+
sendEventMock.mockImplementation(resolve);
601+
});
600602

601-
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
603+
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
602604

603-
await eventSentPromise;
605+
await eventSentPromise;
604606

605-
expect(sendEventMock).toHaveBeenCalledWith(expect.stringMatching(".*"), "io.element.call.encryption_keys", {
606-
call_id: "",
607-
device_id: "AAAAAAA",
608-
keys: [
607+
expect(sendEventMock).toHaveBeenCalledWith(
608+
expect.stringMatching(".*"),
609+
"io.element.call.encryption_keys",
609610
{
610-
index: 0,
611-
key: expect.stringMatching(".*"),
611+
call_id: "",
612+
device_id: "AAAAAAA",
613+
keys: [
614+
{
615+
index: 0,
616+
key: expect.stringMatching(".*"),
617+
},
618+
],
619+
sent_ts: Date.now(),
612620
},
613-
],
614-
});
621+
);
622+
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
623+
} finally {
624+
jest.useRealTimers();
625+
}
615626
});
616627

617628
it("does not send key if join called when already joined", () => {
618629
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
619630

620631
expect(client.sendStateEvent).toHaveBeenCalledTimes(1);
621632
expect(client.sendEvent).toHaveBeenCalledTimes(1);
633+
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
622634

623635
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
624636
expect(client.sendStateEvent).toHaveBeenCalledTimes(1);
625637
expect(client.sendEvent).toHaveBeenCalledTimes(1);
638+
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
626639
});
627640

628641
it("retries key sends", async () => {
@@ -651,6 +664,7 @@ describe("MatrixRTCSession", () => {
651664
await eventSentPromise;
652665

653666
expect(sendEventMock).toHaveBeenCalledTimes(2);
667+
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(2);
654668
} finally {
655669
jest.useRealTimers();
656670
}
@@ -684,6 +698,7 @@ describe("MatrixRTCSession", () => {
684698

685699
sess.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
686700
await keysSentPromise1;
701+
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
687702

688703
sendEventMock.mockClear();
689704
jest.advanceTimersByTime(10000);
@@ -707,6 +722,7 @@ describe("MatrixRTCSession", () => {
707722
await keysSentPromise2;
708723

709724
expect(sendEventMock).toHaveBeenCalled();
725+
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(2);
710726
} finally {
711727
jest.useRealTimers();
712728
}
@@ -747,14 +763,17 @@ describe("MatrixRTCSession", () => {
747763
key: expect.stringMatching(".*"),
748764
},
749765
],
766+
sent_ts: Date.now(),
750767
},
751768
);
769+
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
752770

753771
sendEventMock.mockClear();
754772

755773
// these should be a no-op:
756774
sess.onMembershipUpdate();
757775
expect(sendEventMock).toHaveBeenCalledTimes(0);
776+
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
758777
} finally {
759778
jest.useRealTimers();
760779
}
@@ -796,8 +815,10 @@ describe("MatrixRTCSession", () => {
796815
key: expect.stringMatching(".*"),
797816
},
798817
],
818+
sent_ts: Date.now(),
799819
},
800820
);
821+
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
801822

802823
sendEventMock.mockClear();
803824

@@ -832,8 +853,10 @@ describe("MatrixRTCSession", () => {
832853
key: expect.stringMatching(".*"),
833854
},
834855
],
856+
sent_ts: Date.now(),
835857
},
836858
);
859+
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(2);
837860
} finally {
838861
jest.useRealTimers();
839862
}
@@ -877,8 +900,10 @@ describe("MatrixRTCSession", () => {
877900
key: expect.stringMatching(".*"),
878901
},
879902
],
903+
sent_ts: Date.now(),
880904
},
881905
);
906+
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
882907

883908
sendEventMock.mockClear();
884909

@@ -913,8 +938,10 @@ describe("MatrixRTCSession", () => {
913938
key: expect.stringMatching(".*"),
914939
},
915940
],
941+
sent_ts: Date.now(),
916942
},
917943
);
944+
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(2);
918945
} finally {
919946
jest.useRealTimers();
920947
}
@@ -946,6 +973,8 @@ describe("MatrixRTCSession", () => {
946973
sess.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
947974
const firstKeysPayload = await keysSentPromise1;
948975
expect(firstKeysPayload.keys).toHaveLength(1);
976+
expect(firstKeysPayload.keys[0].index).toEqual(0);
977+
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
949978

950979
sendEventMock.mockClear();
951980

@@ -962,8 +991,10 @@ describe("MatrixRTCSession", () => {
962991

963992
const secondKeysPayload = await keysSentPromise2;
964993

965-
expect(secondKeysPayload.keys).toHaveLength(2);
994+
expect(secondKeysPayload.keys).toHaveLength(1);
995+
expect(secondKeysPayload.keys[0].index).toEqual(1);
966996
expect(onMyEncryptionKeyChanged).toHaveBeenCalledTimes(2);
997+
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(2);
967998
} finally {
968999
jest.useRealTimers();
9691000
}
@@ -984,6 +1015,7 @@ describe("MatrixRTCSession", () => {
9841015
await keysSentPromise1;
9851016

9861017
sendEventMock.mockClear();
1018+
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
9871019

9881020
const onMembershipsChanged = jest.fn();
9891021
sess.on(MatrixRTCSessionEvent.MembershipsChanged, onMembershipsChanged);
@@ -1002,6 +1034,7 @@ describe("MatrixRTCSession", () => {
10021034
});
10031035

10041036
expect(sendEventMock).not.toHaveBeenCalled();
1037+
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
10051038
} finally {
10061039
jest.useRealTimers();
10071040
}
@@ -1167,6 +1200,7 @@ describe("MatrixRTCSession", () => {
11671200
const bobKeys = sess.getKeysForParticipant("@bob:example.org", "bobsphone")!;
11681201
expect(bobKeys).toHaveLength(1);
11691202
expect(bobKeys[0]).toEqual(Buffer.from("this is the key", "utf-8"));
1203+
expect(sess!.statistics.counters.roomEventEncryptionKeysReceived).toEqual(1);
11701204
});
11711205

11721206
it("collects keys at non-zero indices", () => {
@@ -1195,6 +1229,7 @@ describe("MatrixRTCSession", () => {
11951229
expect(bobKeys[2]).toBeFalsy();
11961230
expect(bobKeys[3]).toBeFalsy();
11971231
expect(bobKeys[4]).toEqual(Buffer.from("this is the key", "utf-8"));
1232+
expect(sess!.statistics.counters.roomEventEncryptionKeysReceived).toEqual(1);
11981233
});
11991234

12001235
it("collects keys by merging", () => {
@@ -1219,6 +1254,7 @@ describe("MatrixRTCSession", () => {
12191254
let bobKeys = sess.getKeysForParticipant("@bob:example.org", "bobsphone")!;
12201255
expect(bobKeys).toHaveLength(1);
12211256
expect(bobKeys[0]).toEqual(Buffer.from("this is the key", "utf-8"));
1257+
expect(sess!.statistics.counters.roomEventEncryptionKeysReceived).toEqual(1);
12221258

12231259
sess.onCallEncryption({
12241260
getType: jest.fn().mockReturnValue("io.element.call.encryption_keys"),
@@ -1239,6 +1275,7 @@ describe("MatrixRTCSession", () => {
12391275
bobKeys = sess.getKeysForParticipant("@bob:example.org", "bobsphone")!;
12401276
expect(bobKeys).toHaveLength(5);
12411277
expect(bobKeys[4]).toEqual(Buffer.from("this is the key", "utf-8"));
1278+
expect(sess!.statistics.counters.roomEventEncryptionKeysReceived).toEqual(2);
12421279
});
12431280

12441281
it("ignores older keys at same index", () => {
@@ -1279,6 +1316,7 @@ describe("MatrixRTCSession", () => {
12791316
const bobKeys = sess.getKeysForParticipant("@bob:example.org", "bobsphone")!;
12801317
expect(bobKeys).toHaveLength(1);
12811318
expect(bobKeys[0]).toEqual(Buffer.from("newer key", "utf-8"));
1319+
expect(sess!.statistics.counters.roomEventEncryptionKeysReceived).toEqual(2);
12821320
});
12831321

12841322
it("key timestamps are treated as monotonic", () => {
@@ -1342,5 +1380,73 @@ describe("MatrixRTCSession", () => {
13421380

13431381
const myKeys = sess.getKeysForParticipant(client.getUserId()!, client.getDeviceId()!)!;
13441382
expect(myKeys).toBeFalsy();
1383+
expect(sess!.statistics.counters.roomEventEncryptionKeysReceived).toEqual(0);
1384+
});
1385+
1386+
it("tracks total age statistics for collected keys", () => {
1387+
jest.useFakeTimers();
1388+
try {
1389+
const mockRoom = makeMockRoom([membershipTemplate]);
1390+
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
1391+
1392+
// defaults to getTs()
1393+
jest.setSystemTime(1000);
1394+
sess.onCallEncryption({
1395+
getType: jest.fn().mockReturnValue("io.element.call.encryption_keys"),
1396+
getContent: jest.fn().mockReturnValue({
1397+
device_id: "bobsphone",
1398+
call_id: "",
1399+
keys: [
1400+
{
1401+
index: 0,
1402+
key: "dGhpcyBpcyB0aGUga2V5",
1403+
},
1404+
],
1405+
}),
1406+
getSender: jest.fn().mockReturnValue("@bob:example.org"),
1407+
getTs: jest.fn().mockReturnValue(0),
1408+
} as unknown as MatrixEvent);
1409+
expect(sess!.statistics.totals.roomEventEncryptionKeysReceivedTotalAge).toEqual(1000);
1410+
1411+
jest.setSystemTime(2000);
1412+
sess.onCallEncryption({
1413+
getType: jest.fn().mockReturnValue("io.element.call.encryption_keys"),
1414+
getContent: jest.fn().mockReturnValue({
1415+
device_id: "bobsphone",
1416+
call_id: "",
1417+
keys: [
1418+
{
1419+
index: 0,
1420+
key: "dGhpcyBpcyB0aGUga2V5",
1421+
},
1422+
],
1423+
sent_ts: 0,
1424+
}),
1425+
getSender: jest.fn().mockReturnValue("@bob:example.org"),
1426+
getTs: jest.fn().mockReturnValue(Date.now()),
1427+
} as unknown as MatrixEvent);
1428+
expect(sess!.statistics.totals.roomEventEncryptionKeysReceivedTotalAge).toEqual(3000);
1429+
1430+
jest.setSystemTime(3000);
1431+
sess.onCallEncryption({
1432+
getType: jest.fn().mockReturnValue("io.element.call.encryption_keys"),
1433+
getContent: jest.fn().mockReturnValue({
1434+
device_id: "bobsphone",
1435+
call_id: "",
1436+
keys: [
1437+
{
1438+
index: 0,
1439+
key: "dGhpcyBpcyB0aGUga2V5",
1440+
},
1441+
],
1442+
sent_ts: 1000,
1443+
}),
1444+
getSender: jest.fn().mockReturnValue("@bob:example.org"),
1445+
getTs: jest.fn().mockReturnValue(Date.now()),
1446+
} as unknown as MatrixEvent);
1447+
expect(sess!.statistics.totals.roomEventEncryptionKeysReceivedTotalAge).toEqual(5000);
1448+
} finally {
1449+
jest.useRealTimers();
1450+
}
13451451
});
13461452
});

0 commit comments

Comments
 (0)