You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-25 05:23:13 +03:00
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 <robin@robin.town> * Add comment about why we track total age of events --------- Co-authored-by: Robin <robin@robin.town>
This commit is contained in:
@@ -594,6 +594,8 @@ describe("MatrixRTCSession", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("sends keys when joining", async () => {
|
it("sends keys when joining", async () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
try {
|
||||||
const eventSentPromise = new Promise((resolve) => {
|
const eventSentPromise = new Promise((resolve) => {
|
||||||
sendEventMock.mockImplementation(resolve);
|
sendEventMock.mockImplementation(resolve);
|
||||||
});
|
});
|
||||||
@@ -602,7 +604,10 @@ describe("MatrixRTCSession", () => {
|
|||||||
|
|
||||||
await eventSentPromise;
|
await eventSentPromise;
|
||||||
|
|
||||||
expect(sendEventMock).toHaveBeenCalledWith(expect.stringMatching(".*"), "io.element.call.encryption_keys", {
|
expect(sendEventMock).toHaveBeenCalledWith(
|
||||||
|
expect.stringMatching(".*"),
|
||||||
|
"io.element.call.encryption_keys",
|
||||||
|
{
|
||||||
call_id: "",
|
call_id: "",
|
||||||
device_id: "AAAAAAA",
|
device_id: "AAAAAAA",
|
||||||
keys: [
|
keys: [
|
||||||
@@ -611,7 +616,13 @@ describe("MatrixRTCSession", () => {
|
|||||||
key: expect.stringMatching(".*"),
|
key: expect.stringMatching(".*"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
sent_ts: Date.now(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
|
||||||
|
} finally {
|
||||||
|
jest.useRealTimers();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not send key if join called when already joined", () => {
|
it("does not send key if join called when already joined", () => {
|
||||||
@@ -619,10 +630,12 @@ describe("MatrixRTCSession", () => {
|
|||||||
|
|
||||||
expect(client.sendStateEvent).toHaveBeenCalledTimes(1);
|
expect(client.sendStateEvent).toHaveBeenCalledTimes(1);
|
||||||
expect(client.sendEvent).toHaveBeenCalledTimes(1);
|
expect(client.sendEvent).toHaveBeenCalledTimes(1);
|
||||||
|
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
|
||||||
|
|
||||||
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
|
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
|
||||||
expect(client.sendStateEvent).toHaveBeenCalledTimes(1);
|
expect(client.sendStateEvent).toHaveBeenCalledTimes(1);
|
||||||
expect(client.sendEvent).toHaveBeenCalledTimes(1);
|
expect(client.sendEvent).toHaveBeenCalledTimes(1);
|
||||||
|
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("retries key sends", async () => {
|
it("retries key sends", async () => {
|
||||||
@@ -651,6 +664,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
await eventSentPromise;
|
await eventSentPromise;
|
||||||
|
|
||||||
expect(sendEventMock).toHaveBeenCalledTimes(2);
|
expect(sendEventMock).toHaveBeenCalledTimes(2);
|
||||||
|
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(2);
|
||||||
} finally {
|
} finally {
|
||||||
jest.useRealTimers();
|
jest.useRealTimers();
|
||||||
}
|
}
|
||||||
@@ -684,6 +698,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
|
|
||||||
sess.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
|
sess.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
|
||||||
await keysSentPromise1;
|
await keysSentPromise1;
|
||||||
|
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
|
||||||
|
|
||||||
sendEventMock.mockClear();
|
sendEventMock.mockClear();
|
||||||
jest.advanceTimersByTime(10000);
|
jest.advanceTimersByTime(10000);
|
||||||
@@ -707,6 +722,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
await keysSentPromise2;
|
await keysSentPromise2;
|
||||||
|
|
||||||
expect(sendEventMock).toHaveBeenCalled();
|
expect(sendEventMock).toHaveBeenCalled();
|
||||||
|
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(2);
|
||||||
} finally {
|
} finally {
|
||||||
jest.useRealTimers();
|
jest.useRealTimers();
|
||||||
}
|
}
|
||||||
@@ -747,14 +763,17 @@ describe("MatrixRTCSession", () => {
|
|||||||
key: expect.stringMatching(".*"),
|
key: expect.stringMatching(".*"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
sent_ts: Date.now(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
|
||||||
|
|
||||||
sendEventMock.mockClear();
|
sendEventMock.mockClear();
|
||||||
|
|
||||||
// these should be a no-op:
|
// these should be a no-op:
|
||||||
sess.onMembershipUpdate();
|
sess.onMembershipUpdate();
|
||||||
expect(sendEventMock).toHaveBeenCalledTimes(0);
|
expect(sendEventMock).toHaveBeenCalledTimes(0);
|
||||||
|
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
|
||||||
} finally {
|
} finally {
|
||||||
jest.useRealTimers();
|
jest.useRealTimers();
|
||||||
}
|
}
|
||||||
@@ -796,8 +815,10 @@ describe("MatrixRTCSession", () => {
|
|||||||
key: expect.stringMatching(".*"),
|
key: expect.stringMatching(".*"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
sent_ts: Date.now(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
|
||||||
|
|
||||||
sendEventMock.mockClear();
|
sendEventMock.mockClear();
|
||||||
|
|
||||||
@@ -832,8 +853,10 @@ describe("MatrixRTCSession", () => {
|
|||||||
key: expect.stringMatching(".*"),
|
key: expect.stringMatching(".*"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
sent_ts: Date.now(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(2);
|
||||||
} finally {
|
} finally {
|
||||||
jest.useRealTimers();
|
jest.useRealTimers();
|
||||||
}
|
}
|
||||||
@@ -877,8 +900,10 @@ describe("MatrixRTCSession", () => {
|
|||||||
key: expect.stringMatching(".*"),
|
key: expect.stringMatching(".*"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
sent_ts: Date.now(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
|
||||||
|
|
||||||
sendEventMock.mockClear();
|
sendEventMock.mockClear();
|
||||||
|
|
||||||
@@ -913,8 +938,10 @@ describe("MatrixRTCSession", () => {
|
|||||||
key: expect.stringMatching(".*"),
|
key: expect.stringMatching(".*"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
sent_ts: Date.now(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(2);
|
||||||
} finally {
|
} finally {
|
||||||
jest.useRealTimers();
|
jest.useRealTimers();
|
||||||
}
|
}
|
||||||
@@ -946,6 +973,8 @@ describe("MatrixRTCSession", () => {
|
|||||||
sess.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
|
sess.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
|
||||||
const firstKeysPayload = await keysSentPromise1;
|
const firstKeysPayload = await keysSentPromise1;
|
||||||
expect(firstKeysPayload.keys).toHaveLength(1);
|
expect(firstKeysPayload.keys).toHaveLength(1);
|
||||||
|
expect(firstKeysPayload.keys[0].index).toEqual(0);
|
||||||
|
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
|
||||||
|
|
||||||
sendEventMock.mockClear();
|
sendEventMock.mockClear();
|
||||||
|
|
||||||
@@ -962,8 +991,10 @@ describe("MatrixRTCSession", () => {
|
|||||||
|
|
||||||
const secondKeysPayload = await keysSentPromise2;
|
const secondKeysPayload = await keysSentPromise2;
|
||||||
|
|
||||||
expect(secondKeysPayload.keys).toHaveLength(2);
|
expect(secondKeysPayload.keys).toHaveLength(1);
|
||||||
|
expect(secondKeysPayload.keys[0].index).toEqual(1);
|
||||||
expect(onMyEncryptionKeyChanged).toHaveBeenCalledTimes(2);
|
expect(onMyEncryptionKeyChanged).toHaveBeenCalledTimes(2);
|
||||||
|
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(2);
|
||||||
} finally {
|
} finally {
|
||||||
jest.useRealTimers();
|
jest.useRealTimers();
|
||||||
}
|
}
|
||||||
@@ -984,6 +1015,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
await keysSentPromise1;
|
await keysSentPromise1;
|
||||||
|
|
||||||
sendEventMock.mockClear();
|
sendEventMock.mockClear();
|
||||||
|
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
|
||||||
|
|
||||||
const onMembershipsChanged = jest.fn();
|
const onMembershipsChanged = jest.fn();
|
||||||
sess.on(MatrixRTCSessionEvent.MembershipsChanged, onMembershipsChanged);
|
sess.on(MatrixRTCSessionEvent.MembershipsChanged, onMembershipsChanged);
|
||||||
@@ -1002,6 +1034,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(sendEventMock).not.toHaveBeenCalled();
|
expect(sendEventMock).not.toHaveBeenCalled();
|
||||||
|
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
|
||||||
} finally {
|
} finally {
|
||||||
jest.useRealTimers();
|
jest.useRealTimers();
|
||||||
}
|
}
|
||||||
@@ -1167,6 +1200,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
const bobKeys = sess.getKeysForParticipant("@bob:example.org", "bobsphone")!;
|
const bobKeys = sess.getKeysForParticipant("@bob:example.org", "bobsphone")!;
|
||||||
expect(bobKeys).toHaveLength(1);
|
expect(bobKeys).toHaveLength(1);
|
||||||
expect(bobKeys[0]).toEqual(Buffer.from("this is the key", "utf-8"));
|
expect(bobKeys[0]).toEqual(Buffer.from("this is the key", "utf-8"));
|
||||||
|
expect(sess!.statistics.counters.roomEventEncryptionKeysReceived).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("collects keys at non-zero indices", () => {
|
it("collects keys at non-zero indices", () => {
|
||||||
@@ -1195,6 +1229,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
expect(bobKeys[2]).toBeFalsy();
|
expect(bobKeys[2]).toBeFalsy();
|
||||||
expect(bobKeys[3]).toBeFalsy();
|
expect(bobKeys[3]).toBeFalsy();
|
||||||
expect(bobKeys[4]).toEqual(Buffer.from("this is the key", "utf-8"));
|
expect(bobKeys[4]).toEqual(Buffer.from("this is the key", "utf-8"));
|
||||||
|
expect(sess!.statistics.counters.roomEventEncryptionKeysReceived).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("collects keys by merging", () => {
|
it("collects keys by merging", () => {
|
||||||
@@ -1219,6 +1254,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
let bobKeys = sess.getKeysForParticipant("@bob:example.org", "bobsphone")!;
|
let bobKeys = sess.getKeysForParticipant("@bob:example.org", "bobsphone")!;
|
||||||
expect(bobKeys).toHaveLength(1);
|
expect(bobKeys).toHaveLength(1);
|
||||||
expect(bobKeys[0]).toEqual(Buffer.from("this is the key", "utf-8"));
|
expect(bobKeys[0]).toEqual(Buffer.from("this is the key", "utf-8"));
|
||||||
|
expect(sess!.statistics.counters.roomEventEncryptionKeysReceived).toEqual(1);
|
||||||
|
|
||||||
sess.onCallEncryption({
|
sess.onCallEncryption({
|
||||||
getType: jest.fn().mockReturnValue("io.element.call.encryption_keys"),
|
getType: jest.fn().mockReturnValue("io.element.call.encryption_keys"),
|
||||||
@@ -1239,6 +1275,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
bobKeys = sess.getKeysForParticipant("@bob:example.org", "bobsphone")!;
|
bobKeys = sess.getKeysForParticipant("@bob:example.org", "bobsphone")!;
|
||||||
expect(bobKeys).toHaveLength(5);
|
expect(bobKeys).toHaveLength(5);
|
||||||
expect(bobKeys[4]).toEqual(Buffer.from("this is the key", "utf-8"));
|
expect(bobKeys[4]).toEqual(Buffer.from("this is the key", "utf-8"));
|
||||||
|
expect(sess!.statistics.counters.roomEventEncryptionKeysReceived).toEqual(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("ignores older keys at same index", () => {
|
it("ignores older keys at same index", () => {
|
||||||
@@ -1279,6 +1316,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
const bobKeys = sess.getKeysForParticipant("@bob:example.org", "bobsphone")!;
|
const bobKeys = sess.getKeysForParticipant("@bob:example.org", "bobsphone")!;
|
||||||
expect(bobKeys).toHaveLength(1);
|
expect(bobKeys).toHaveLength(1);
|
||||||
expect(bobKeys[0]).toEqual(Buffer.from("newer key", "utf-8"));
|
expect(bobKeys[0]).toEqual(Buffer.from("newer key", "utf-8"));
|
||||||
|
expect(sess!.statistics.counters.roomEventEncryptionKeysReceived).toEqual(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("key timestamps are treated as monotonic", () => {
|
it("key timestamps are treated as monotonic", () => {
|
||||||
@@ -1342,5 +1380,73 @@ describe("MatrixRTCSession", () => {
|
|||||||
|
|
||||||
const myKeys = sess.getKeysForParticipant(client.getUserId()!, client.getDeviceId()!)!;
|
const myKeys = sess.getKeysForParticipant(client.getUserId()!, client.getDeviceId()!)!;
|
||||||
expect(myKeys).toBeFalsy();
|
expect(myKeys).toBeFalsy();
|
||||||
|
expect(sess!.statistics.counters.roomEventEncryptionKeysReceived).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("tracks total age statistics for collected keys", () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
try {
|
||||||
|
const mockRoom = makeMockRoom([membershipTemplate]);
|
||||||
|
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
|
||||||
|
|
||||||
|
// defaults to getTs()
|
||||||
|
jest.setSystemTime(1000);
|
||||||
|
sess.onCallEncryption({
|
||||||
|
getType: jest.fn().mockReturnValue("io.element.call.encryption_keys"),
|
||||||
|
getContent: jest.fn().mockReturnValue({
|
||||||
|
device_id: "bobsphone",
|
||||||
|
call_id: "",
|
||||||
|
keys: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
key: "dGhpcyBpcyB0aGUga2V5",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
getSender: jest.fn().mockReturnValue("@bob:example.org"),
|
||||||
|
getTs: jest.fn().mockReturnValue(0),
|
||||||
|
} as unknown as MatrixEvent);
|
||||||
|
expect(sess!.statistics.totals.roomEventEncryptionKeysReceivedTotalAge).toEqual(1000);
|
||||||
|
|
||||||
|
jest.setSystemTime(2000);
|
||||||
|
sess.onCallEncryption({
|
||||||
|
getType: jest.fn().mockReturnValue("io.element.call.encryption_keys"),
|
||||||
|
getContent: jest.fn().mockReturnValue({
|
||||||
|
device_id: "bobsphone",
|
||||||
|
call_id: "",
|
||||||
|
keys: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
key: "dGhpcyBpcyB0aGUga2V5",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sent_ts: 0,
|
||||||
|
}),
|
||||||
|
getSender: jest.fn().mockReturnValue("@bob:example.org"),
|
||||||
|
getTs: jest.fn().mockReturnValue(Date.now()),
|
||||||
|
} as unknown as MatrixEvent);
|
||||||
|
expect(sess!.statistics.totals.roomEventEncryptionKeysReceivedTotalAge).toEqual(3000);
|
||||||
|
|
||||||
|
jest.setSystemTime(3000);
|
||||||
|
sess.onCallEncryption({
|
||||||
|
getType: jest.fn().mockReturnValue("io.element.call.encryption_keys"),
|
||||||
|
getContent: jest.fn().mockReturnValue({
|
||||||
|
device_id: "bobsphone",
|
||||||
|
call_id: "",
|
||||||
|
keys: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
key: "dGhpcyBpcyB0aGUga2V5",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sent_ts: 1000,
|
||||||
|
}),
|
||||||
|
getSender: jest.fn().mockReturnValue("@bob:example.org"),
|
||||||
|
getTs: jest.fn().mockReturnValue(Date.now()),
|
||||||
|
} as unknown as MatrixEvent);
|
||||||
|
expect(sess!.statistics.totals.roomEventEncryptionKeysReceivedTotalAge).toEqual(5000);
|
||||||
|
} finally {
|
||||||
|
jest.useRealTimers();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -144,6 +144,31 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
|||||||
// if it looks like a membership has been updated.
|
// if it looks like a membership has been updated.
|
||||||
private lastMembershipFingerprints: Set<string> | undefined;
|
private lastMembershipFingerprints: Set<string> | undefined;
|
||||||
|
|
||||||
|
private currentEncryptionKeyIndex = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The statistics for this session.
|
||||||
|
*/
|
||||||
|
public statistics = {
|
||||||
|
counters: {
|
||||||
|
/**
|
||||||
|
* The number of times we have sent a room event containing encryption keys.
|
||||||
|
*/
|
||||||
|
roomEventEncryptionKeysSent: 0,
|
||||||
|
/**
|
||||||
|
* The number of times we have received a room event containing encryption keys.
|
||||||
|
*/
|
||||||
|
roomEventEncryptionKeysReceived: 0,
|
||||||
|
},
|
||||||
|
totals: {
|
||||||
|
/**
|
||||||
|
* The total age (in milliseconds) of all room events containing encryption keys that we have received.
|
||||||
|
* We track the total age so that we can later calculate the average age of all keys received.
|
||||||
|
*/
|
||||||
|
roomEventEncryptionKeysReceivedTotalAge: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The callId (sessionId) of the call.
|
* The callId (sessionId) of the call.
|
||||||
*
|
*
|
||||||
@@ -467,10 +492,16 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
|||||||
const useKeyTimeout = setTimeout(() => {
|
const useKeyTimeout = setTimeout(() => {
|
||||||
this.setNewKeyTimeouts.delete(useKeyTimeout);
|
this.setNewKeyTimeouts.delete(useKeyTimeout);
|
||||||
logger.info(`Delayed-emitting key changed event for ${participantId} idx ${encryptionKeyIndex}`);
|
logger.info(`Delayed-emitting key changed event for ${participantId} idx ${encryptionKeyIndex}`);
|
||||||
|
if (userId === this.client.getUserId() && deviceId === this.client.getDeviceId()) {
|
||||||
|
this.currentEncryptionKeyIndex = encryptionKeyIndex;
|
||||||
|
}
|
||||||
this.emit(MatrixRTCSessionEvent.EncryptionKeyChanged, keyBin, encryptionKeyIndex, participantId);
|
this.emit(MatrixRTCSessionEvent.EncryptionKeyChanged, keyBin, encryptionKeyIndex, participantId);
|
||||||
}, USE_KEY_DELAY);
|
}, USE_KEY_DELAY);
|
||||||
this.setNewKeyTimeouts.add(useKeyTimeout);
|
this.setNewKeyTimeouts.add(useKeyTimeout);
|
||||||
} else {
|
} else {
|
||||||
|
if (userId === this.client.getUserId() && deviceId === this.client.getDeviceId()) {
|
||||||
|
this.currentEncryptionKeyIndex = encryptionKeyIndex;
|
||||||
|
}
|
||||||
this.emit(MatrixRTCSessionEvent.EncryptionKeyChanged, keyBin, encryptionKeyIndex, participantId);
|
this.emit(MatrixRTCSessionEvent.EncryptionKeyChanged, keyBin, encryptionKeyIndex, participantId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -479,8 +510,9 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
|||||||
* Generate a new sender key and add it at the next available index
|
* Generate a new sender key and add it at the next available index
|
||||||
* @param delayBeforeUse - If true, wait for a short period before setting the key for the
|
* @param delayBeforeUse - If true, wait for a short period before setting the key for the
|
||||||
* media encryptor to use. If false, set the key immediately.
|
* media encryptor to use. If false, set the key immediately.
|
||||||
|
* @returns The index of the new key
|
||||||
*/
|
*/
|
||||||
private makeNewSenderKey(delayBeforeUse = false): void {
|
private makeNewSenderKey(delayBeforeUse = false): number {
|
||||||
const userId = this.client.getUserId();
|
const userId = this.client.getUserId();
|
||||||
const deviceId = this.client.getDeviceId();
|
const deviceId = this.client.getDeviceId();
|
||||||
|
|
||||||
@@ -491,6 +523,7 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
|||||||
const encryptionKeyIndex = this.getNewEncryptionKeyIndex();
|
const encryptionKeyIndex = this.getNewEncryptionKeyIndex();
|
||||||
logger.info("Generated new key at index " + encryptionKeyIndex);
|
logger.info("Generated new key at index " + encryptionKeyIndex);
|
||||||
this.setEncryptionKey(userId, deviceId, encryptionKeyIndex, encryptionKey, Date.now(), delayBeforeUse);
|
this.setEncryptionKey(userId, deviceId, encryptionKeyIndex, encryptionKey, Date.now(), delayBeforeUse);
|
||||||
|
return encryptionKeyIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -517,17 +550,17 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
|||||||
/**
|
/**
|
||||||
* Re-sends the encryption keys room event
|
* Re-sends the encryption keys room event
|
||||||
*/
|
*/
|
||||||
private sendEncryptionKeysEvent = async (): Promise<void> => {
|
private sendEncryptionKeysEvent = async (indexToSend?: number): Promise<void> => {
|
||||||
if (this.keysEventUpdateTimeout !== undefined) {
|
if (this.keysEventUpdateTimeout !== undefined) {
|
||||||
clearTimeout(this.keysEventUpdateTimeout);
|
clearTimeout(this.keysEventUpdateTimeout);
|
||||||
this.keysEventUpdateTimeout = undefined;
|
this.keysEventUpdateTimeout = undefined;
|
||||||
}
|
}
|
||||||
this.lastEncryptionKeyUpdateRequest = Date.now();
|
this.lastEncryptionKeyUpdateRequest = Date.now();
|
||||||
|
|
||||||
logger.info("Sending encryption keys event");
|
|
||||||
|
|
||||||
if (!this.isJoined()) return;
|
if (!this.isJoined()) return;
|
||||||
|
|
||||||
|
logger.info(`Sending encryption keys event. indexToSend=${indexToSend}`);
|
||||||
|
|
||||||
const userId = this.client.getUserId();
|
const userId = this.client.getUserId();
|
||||||
const deviceId = this.client.getDeviceId();
|
const deviceId = this.client.getDeviceId();
|
||||||
|
|
||||||
@@ -541,20 +574,33 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof indexToSend !== "number" && this.currentEncryptionKeyIndex === -1) {
|
||||||
|
logger.warn("Tried to send encryption keys event but no current key index found!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyIndexToSend = indexToSend ?? this.currentEncryptionKeyIndex;
|
||||||
|
const keyToSend = myKeys[keyIndexToSend];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.client.sendEvent(this.room.roomId, EventType.CallEncryptionKeysPrefix, {
|
const content: EncryptionKeysEventContent = {
|
||||||
keys: myKeys.map((key, index) => {
|
keys: [
|
||||||
return {
|
{
|
||||||
index,
|
index: keyIndexToSend,
|
||||||
key: encodeUnpaddedBase64(key),
|
key: encodeUnpaddedBase64(keyToSend),
|
||||||
};
|
},
|
||||||
}),
|
],
|
||||||
device_id: deviceId,
|
device_id: deviceId,
|
||||||
call_id: "",
|
call_id: "",
|
||||||
} as EncryptionKeysEventContent);
|
sent_ts: Date.now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.statistics.counters.roomEventEncryptionKeysSent += 1;
|
||||||
|
|
||||||
|
await this.client.sendEvent(this.room.roomId, EventType.CallEncryptionKeysPrefix, content);
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`Embedded-E2EE-LOG updateEncryptionKeyEvent participantId=${userId}:${deviceId} numSent=${myKeys.length}`,
|
`Embedded-E2EE-LOG updateEncryptionKeyEvent participantId=${userId}:${deviceId} numKeys=${myKeys.length} currentKeyIndex=${this.currentEncryptionKeyIndex} keyIndexToSend=${keyIndexToSend}`,
|
||||||
this.encryptionKeys,
|
this.encryptionKeys,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -649,6 +695,10 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.statistics.counters.roomEventEncryptionKeysReceived += 1;
|
||||||
|
const age = Date.now() - (typeof content.sent_ts === "number" ? content.sent_ts : event.getTs());
|
||||||
|
this.statistics.totals.roomEventEncryptionKeysReceivedTotalAge += age;
|
||||||
|
|
||||||
for (const key of content.keys) {
|
for (const key of content.keys) {
|
||||||
if (!key) {
|
if (!key) {
|
||||||
logger.info("Ignoring false-y key in keys event");
|
logger.info("Ignoring false-y key in keys event");
|
||||||
@@ -674,7 +724,7 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`Embedded-E2EE-LOG onCallEncryption userId=${userId}:${deviceId} encryptionKeyIndex=${encryptionKeyIndex}`,
|
`Embedded-E2EE-LOG onCallEncryption userId=${userId}:${deviceId} encryptionKeyIndex=${encryptionKeyIndex} age=${age}ms`,
|
||||||
this.encryptionKeys,
|
this.encryptionKeys,
|
||||||
);
|
);
|
||||||
this.setEncryptionKey(userId, deviceId, encryptionKeyIndex, encryptionKey, event.getTs());
|
this.setEncryptionKey(userId, deviceId, encryptionKeyIndex, encryptionKey, event.getTs());
|
||||||
@@ -1030,9 +1080,9 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
|||||||
|
|
||||||
this.makeNewKeyTimeout = undefined;
|
this.makeNewKeyTimeout = undefined;
|
||||||
logger.info("Making new sender key for key rotation");
|
logger.info("Making new sender key for key rotation");
|
||||||
this.makeNewSenderKey(true);
|
const newKeyIndex = this.makeNewSenderKey(true);
|
||||||
// send immediately: if we're about to start sending with a new key, it's
|
// send immediately: if we're about to start sending with a new key, it's
|
||||||
// important we get it out to others as soon as we can.
|
// important we get it out to others as soon as we can.
|
||||||
this.sendEncryptionKeysEvent();
|
this.sendEncryptionKeysEvent(newKeyIndex);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export interface EncryptionKeysEventContent {
|
|||||||
keys: EncryptionKeyEntry[];
|
keys: EncryptionKeyEntry[];
|
||||||
device_id: string;
|
device_id: string;
|
||||||
call_id: string;
|
call_id: string;
|
||||||
|
sent_ts?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CallNotifyType = "ring" | "notify";
|
export type CallNotifyType = "ring" | "notify";
|
||||||
|
|||||||
Reference in New Issue
Block a user