From baa6d135065637c9769c61325c69709d3618f5f1 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 27 Sep 2024 16:55:07 +0100 Subject: [PATCH] RTCSession cleanup: deprecate getKeysForParticipant() and getEncryption(); add emitEncryptionKeys() (#4427) * RTCSession cleanup: deprecate getKeysForParticipant() and getEncryption(); add emitEncryptionKeys() * Clarify comment * Feedback from code review * Update src/matrixrtc/MatrixRTCSession.ts Co-authored-by: Andrew Ferrazzutti * Fix test --------- Co-authored-by: Andrew Ferrazzutti --- spec/unit/matrixrtc/MatrixRTCSession.spec.ts | 107 +++++++++++++------ src/matrixrtc/MatrixRTCSession.ts | 22 +++- 2 files changed, 98 insertions(+), 31 deletions(-) diff --git a/spec/unit/matrixrtc/MatrixRTCSession.spec.ts b/spec/unit/matrixrtc/MatrixRTCSession.spec.ts index e3572b88d54..0e1f81498a9 100644 --- a/spec/unit/matrixrtc/MatrixRTCSession.spec.ts +++ b/spec/unit/matrixrtc/MatrixRTCSession.spec.ts @@ -585,12 +585,15 @@ describe("MatrixRTCSession", () => { it("creates a key when joining", () => { sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true }); - const keys = sess?.getKeysForParticipant("@alice:example.org", "AAAAAAA"); - expect(keys).toHaveLength(1); - - const allKeys = sess!.getEncryptionKeys(); - expect(allKeys).toBeTruthy(); - expect(Array.from(allKeys)).toHaveLength(1); + const encryptionKeyChangedListener = jest.fn(); + sess!.on(MatrixRTCSessionEvent.EncryptionKeyChanged, encryptionKeyChangedListener); + sess?.reemitEncryptionKeys(); + expect(encryptionKeyChangedListener).toHaveBeenCalledTimes(1); + expect(encryptionKeyChangedListener).toHaveBeenCalledWith( + expect.any(Uint8Array), + 0, + "@alice:example.org:AAAAAAA", + ); }); it("sends keys when joining", async () => { @@ -1204,9 +1207,16 @@ describe("MatrixRTCSession", () => { getTs: jest.fn().mockReturnValue(Date.now()), } as unknown as MatrixEvent); - const bobKeys = sess.getKeysForParticipant("@bob:example.org", "bobsphone")!; - expect(bobKeys).toHaveLength(1); - expect(bobKeys[0]).toEqual(Buffer.from("this is the key", "utf-8")); + const encryptionKeyChangedListener = jest.fn(); + sess!.on(MatrixRTCSessionEvent.EncryptionKeyChanged, encryptionKeyChangedListener); + sess!.reemitEncryptionKeys(); + expect(encryptionKeyChangedListener).toHaveBeenCalledTimes(1); + expect(encryptionKeyChangedListener).toHaveBeenCalledWith( + Buffer.from("this is the key", "utf-8"), + 0, + "@bob:example.org:bobsphone", + ); + expect(sess!.statistics.counters.roomEventEncryptionKeysReceived).toEqual(1); }); @@ -1229,13 +1239,16 @@ describe("MatrixRTCSession", () => { getTs: jest.fn().mockReturnValue(Date.now()), } as unknown as MatrixEvent); - const bobKeys = sess.getKeysForParticipant("@bob:example.org", "bobsphone")!; - expect(bobKeys).toHaveLength(5); - expect(bobKeys[0]).toBeFalsy(); - expect(bobKeys[1]).toBeFalsy(); - expect(bobKeys[2]).toBeFalsy(); - expect(bobKeys[3]).toBeFalsy(); - expect(bobKeys[4]).toEqual(Buffer.from("this is the key", "utf-8")); + const encryptionKeyChangedListener = jest.fn(); + sess!.on(MatrixRTCSessionEvent.EncryptionKeyChanged, encryptionKeyChangedListener); + sess!.reemitEncryptionKeys(); + expect(encryptionKeyChangedListener).toHaveBeenCalledTimes(1); + expect(encryptionKeyChangedListener).toHaveBeenCalledWith( + Buffer.from("this is the key", "utf-8"), + 4, + "@bob:example.org:bobsphone", + ); + expect(sess!.statistics.counters.roomEventEncryptionKeysReceived).toEqual(1); }); @@ -1258,9 +1271,16 @@ describe("MatrixRTCSession", () => { getTs: jest.fn().mockReturnValue(Date.now()), } as unknown as MatrixEvent); - let bobKeys = sess.getKeysForParticipant("@bob:example.org", "bobsphone")!; - expect(bobKeys).toHaveLength(1); - expect(bobKeys[0]).toEqual(Buffer.from("this is the key", "utf-8")); + const encryptionKeyChangedListener = jest.fn(); + sess!.on(MatrixRTCSessionEvent.EncryptionKeyChanged, encryptionKeyChangedListener); + sess!.reemitEncryptionKeys(); + expect(encryptionKeyChangedListener).toHaveBeenCalledTimes(1); + expect(encryptionKeyChangedListener).toHaveBeenCalledWith( + Buffer.from("this is the key", "utf-8"), + 0, + "@bob:example.org:bobsphone", + ); + expect(sess!.statistics.counters.roomEventEncryptionKeysReceived).toEqual(1); sess.onCallEncryption({ @@ -1279,9 +1299,20 @@ describe("MatrixRTCSession", () => { getTs: jest.fn().mockReturnValue(Date.now()), } as unknown as MatrixEvent); - bobKeys = sess.getKeysForParticipant("@bob:example.org", "bobsphone")!; - expect(bobKeys).toHaveLength(5); - expect(bobKeys[4]).toEqual(Buffer.from("this is the key", "utf-8")); + encryptionKeyChangedListener.mockClear(); + sess!.reemitEncryptionKeys(); + expect(encryptionKeyChangedListener).toHaveBeenCalledTimes(2); + expect(encryptionKeyChangedListener).toHaveBeenCalledWith( + Buffer.from("this is the key", "utf-8"), + 0, + "@bob:example.org:bobsphone", + ); + expect(encryptionKeyChangedListener).toHaveBeenCalledWith( + Buffer.from("this is the key", "utf-8"), + 4, + "@bob:example.org:bobsphone", + ); + expect(sess!.statistics.counters.roomEventEncryptionKeysReceived).toEqual(2); }); @@ -1320,9 +1351,16 @@ describe("MatrixRTCSession", () => { getTs: jest.fn().mockReturnValue(1000), // earlier timestamp than the newer key } as unknown as MatrixEvent); - const bobKeys = sess.getKeysForParticipant("@bob:example.org", "bobsphone")!; - expect(bobKeys).toHaveLength(1); - expect(bobKeys[0]).toEqual(Buffer.from("newer key", "utf-8")); + const encryptionKeyChangedListener = jest.fn(); + sess!.on(MatrixRTCSessionEvent.EncryptionKeyChanged, encryptionKeyChangedListener); + sess!.reemitEncryptionKeys(); + expect(encryptionKeyChangedListener).toHaveBeenCalledTimes(1); + expect(encryptionKeyChangedListener).toHaveBeenCalledWith( + Buffer.from("newer key", "utf-8"), + 0, + "@bob:example.org:bobsphone", + ); + expect(sess!.statistics.counters.roomEventEncryptionKeysReceived).toEqual(2); }); @@ -1361,9 +1399,15 @@ describe("MatrixRTCSession", () => { getTs: jest.fn().mockReturnValue(1000), // same timestamp as the first key } as unknown as MatrixEvent); - const bobKeys = sess.getKeysForParticipant("@bob:example.org", "bobsphone")!; - expect(bobKeys).toHaveLength(1); - expect(bobKeys[0]).toEqual(Buffer.from("second key", "utf-8")); + const encryptionKeyChangedListener = jest.fn(); + sess!.on(MatrixRTCSessionEvent.EncryptionKeyChanged, encryptionKeyChangedListener); + sess!.reemitEncryptionKeys(); + expect(encryptionKeyChangedListener).toHaveBeenCalledTimes(1); + expect(encryptionKeyChangedListener).toHaveBeenCalledWith( + Buffer.from("second key", "utf-8"), + 0, + "@bob:example.org:bobsphone", + ); }); it("ignores keys event for the local participant", () => { @@ -1385,8 +1429,11 @@ describe("MatrixRTCSession", () => { getTs: jest.fn().mockReturnValue(Date.now()), } as unknown as MatrixEvent); - const myKeys = sess.getKeysForParticipant(client.getUserId()!, client.getDeviceId()!)!; - expect(myKeys).toBeFalsy(); + const encryptionKeyChangedListener = jest.fn(); + sess!.on(MatrixRTCSessionEvent.EncryptionKeyChanged, encryptionKeyChangedListener); + sess!.reemitEncryptionKeys(); + expect(encryptionKeyChangedListener).toHaveBeenCalledTimes(0); + expect(sess!.statistics.counters.roomEventEncryptionKeysReceived).toEqual(0); }); diff --git a/src/matrixrtc/MatrixRTCSession.ts b/src/matrixrtc/MatrixRTCSession.ts index 5a1d6e6e73d..1e92793807d 100644 --- a/src/matrixrtc/MatrixRTCSession.ts +++ b/src/matrixrtc/MatrixRTCSession.ts @@ -405,20 +405,40 @@ export class MatrixRTCSession extends TypedEventEmitter { + keys.forEach((key, index) => { + this.emit(MatrixRTCSessionEvent.EncryptionKeyChanged, key.key, index, participantId); + }); + }); + } + /** * Get the known encryption keys for a given participant device. * * @param userId the user ID of the participant * @param deviceId the device ID of the participant * @returns The encryption keys for the given participant, or undefined if they are not known. + * + * @deprecated This will be made private in a future release. */ public getKeysForParticipant(userId: string, deviceId: string): Array | undefined { + return this.getKeysForParticipantInternal(userId, deviceId); + } + + private getKeysForParticipantInternal(userId: string, deviceId: string): Array | undefined { return this.encryptionKeys.get(getParticipantId(userId, deviceId))?.map((entry) => entry.key); } /** * A map of keys used to encrypt and decrypt (we are using a symmetric * cipher) given participant's media. This also includes our own key + * + * @deprecated This will be made private in a future release. */ public getEncryptionKeys(): IterableIterator<[string, Array]> { // the returned array doesn't contain the timestamps @@ -434,7 +454,7 @@ export class MatrixRTCSession extends TypedEventEmitter