Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RTCSession cleanup: deprecate getKeysForParticipant() and getEncryption(); add emitEncryptionKeys() #4427

Merged
merged 5 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 77 additions & 30 deletions spec/unit/matrixrtc/MatrixRTCSession.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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?.emitEncryptionKeys();
hughns marked this conversation as resolved.
Show resolved Hide resolved
expect(encryptionKeyChangedListener).toHaveBeenCalledTimes(1);
expect(encryptionKeyChangedListener).toHaveBeenCalledWith(
expect.any(Uint8Array),
0,
"@alice:example.org:AAAAAAA",
);
});

it("sends keys when joining", async () => {
Expand Down Expand Up @@ -1197,9 +1200,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!.emitEncryptionKeys();
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);
});

Expand All @@ -1222,13 +1232,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!.emitEncryptionKeys();
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);
});

Expand All @@ -1251,9 +1264,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!.emitEncryptionKeys();
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({
Expand All @@ -1272,9 +1292,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!.emitEncryptionKeys();
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);
});

Expand Down Expand Up @@ -1313,9 +1344,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!.emitEncryptionKeys();
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);
});

Expand Down Expand Up @@ -1354,9 +1392,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!.emitEncryptionKeys();
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", () => {
Expand All @@ -1378,8 +1422,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!.emitEncryptionKeys();
expect(encryptionKeyChangedListener).toHaveBeenCalledTimes(0);

expect(sess!.statistics.counters.roomEventEncryptionKeysReceived).toEqual(0);
});

Expand Down
26 changes: 25 additions & 1 deletion src/matrixrtc/MatrixRTCSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -405,22 +405,46 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
}
}

/**
* Emit an EncryptionKeyChanged event for every tracked encryption keys.
*/
public emitEncryptionKeys(): void {
AndrewFerr marked this conversation as resolved.
Show resolved Hide resolved
this.encryptionKeys.forEach((keys, participantId) => {
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<Uint8Array> | undefined {
return this.getKeysForParticipantInternal(userId, deviceId);
}

private getKeysForParticipantInternal(userId: string, deviceId: string): Array<Uint8Array> | 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<Uint8Array>]> {
// the returned array doesn't contain the timestamps
return this.getEncryptionKeysInternal();
}

private getEncryptionKeysInternal(): IterableIterator<[string, Array<Uint8Array>]> {
AndrewFerr marked this conversation as resolved.
Show resolved Hide resolved
// the returned array doesn't contain the timestamps
return Array.from(this.encryptionKeys.entries())
.map(([participantId, keys]): [string, Uint8Array[]] => [participantId, keys.map((k) => k.key)])
Expand All @@ -434,7 +458,7 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
if (!userId) throw new Error("No userId!");
if (!deviceId) throw new Error("No deviceId!");

return (this.getKeysForParticipant(userId, deviceId)?.length ?? 0) % 16;
return (this.getKeysForParticipantInternal(userId, deviceId)?.length ?? 0) % 16;
}

/**
Expand Down