Skip to content

Commit 10ef73b

Browse files
committed
participantId -> callMembershipIdentityParts
The participantId is a termonology from livekit. We do not want it in here! We want the js-sdk to be mostly transport agnostic. We do the transition from the identity parts to the acutal livekit identity in Element call (`sha256(userId+deviceId+memberId)`)
1 parent b94e83b commit 10ef73b

File tree

8 files changed

+160
-132
lines changed

8 files changed

+160
-132
lines changed

src/matrixrtc/EncryptionManager.ts

Lines changed: 65 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,15 @@ import { decodeBase64, encodeUnpaddedBase64 } from "../base64.ts";
55
import { safeGetRetryAfterMs } from "../http-api/errors.ts";
66
import { type CallMembership } from "./CallMembership.ts";
77
import { type KeyTransportEventListener, KeyTransportEvents, type IKeyTransport } from "./IKeyTransport.ts";
8-
import { isMyMembership, type ParticipantId, type Statistics } from "./types.ts";
9-
import { getParticipantId } from "./utils.ts";
8+
import { isMyMembership, type EncryptionKeyMapKey, type Statistics } from "./types.ts";
9+
10+
/**
11+
* The string used for the keys in the the encryption key map.
12+
* `@bob:examle.org:DEVICEID(UUIDRANDOM_MEMBERID_RANDOMUUID)`
13+
*/
14+
export function getEncryptionKeyMapKey(membership: CallMembershipIdentityParts): EncryptionKeyMapKey {
15+
return `${membership.userId}:${membership.deviceId}(${membership.memberId})`;
16+
}
1017

1118
/**
1219
* This interface is for testing and for making it possible to interchange the encryption manager.
@@ -38,9 +45,14 @@ export interface IEncryptionManager {
3845
*
3946
* @returns A map of participant IDs to their encryption keys.
4047
*/
41-
getEncryptionKeys(): ReadonlyMap<ParticipantId, ReadonlyArray<{ key: Uint8Array; keyIndex: number }>>;
48+
getEncryptionKeys(): ReadonlyMap<
49+
EncryptionKeyMapKey,
50+
ReadonlyArray<{ key: Uint8Array; keyIndex: number; membership: CallMembershipIdentityParts }>
51+
>;
4252
}
4353

54+
export type CallMembershipIdentityParts = Pick<CallMembership, "userId" | "deviceId" | "memberId">;
55+
4456
/**
4557
* This class implements the IEncryptionManager interface,
4658
* and takes care of managing the encryption keys of all rtc members:
@@ -67,7 +79,10 @@ export class EncryptionManager implements IEncryptionManager {
6779
return this.joinConfig?.useKeyDelay ?? 5_000;
6880
}
6981

70-
private encryptionKeys = new Map<string, Array<{ key: Uint8Array; timestamp: number }>>();
82+
private encryptionKeys = new Map<
83+
string,
84+
Array<{ key: Uint8Array; timestamp: number; membership: CallMembershipIdentityParts }>
85+
>();
7186
private lastEncryptionKeyUpdateRequest?: number;
7287

7388
// We use this to store the last membership fingerprints we saw, so we can proactively re-send encryption keys
@@ -79,29 +94,35 @@ export class EncryptionManager implements IEncryptionManager {
7994
private logger: Logger;
8095

8196
public constructor(
82-
private userId: string,
83-
private deviceId: string,
97+
private membership: CallMembershipIdentityParts,
8498
private getMemberships: () => CallMembership[],
8599
private transport: IKeyTransport,
86100
private statistics: Statistics,
87101
private onEncryptionKeysChanged: (
88102
keyBin: Uint8Array,
89103
encryptionKeyIndex: number,
90-
participantId: string,
104+
membership: CallMembershipIdentityParts,
91105
) => void,
92106
parentLogger?: Logger,
93107
) {
94108
this.logger = (parentLogger ?? rootLogger).getChild(`[EncryptionManager]`);
95109
}
96110

97-
public getEncryptionKeys(): ReadonlyMap<ParticipantId, ReadonlyArray<{ key: Uint8Array; keyIndex: number }>> {
98-
const keysMap = new Map<ParticipantId, ReadonlyArray<{ key: Uint8Array; keyIndex: number }>>();
99-
for (const [userId, userKeys] of this.encryptionKeys) {
100-
const keys = userKeys.map((entry, index) => ({
111+
public getEncryptionKeys(): ReadonlyMap<
112+
EncryptionKeyMapKey,
113+
ReadonlyArray<{ key: Uint8Array; keyIndex: number; membership: CallMembershipIdentityParts }>
114+
> {
115+
const keysMap = new Map<
116+
EncryptionKeyMapKey,
117+
ReadonlyArray<{ key: Uint8Array; keyIndex: number; membership: CallMembershipIdentityParts }>
118+
>();
119+
for (const [userId, userKeyEntry] of this.encryptionKeys) {
120+
const keys = userKeyEntry.map((entry, index) => ({
101121
key: entry.key,
122+
membership: entry.membership,
102123
keyIndex: index,
103124
}));
104-
keysMap.set(userId as ParticipantId, keys);
125+
keysMap.set(userId as EncryptionKeyMapKey, keys);
105126
}
106127
return keysMap;
107128
}
@@ -126,7 +147,7 @@ export class EncryptionManager implements IEncryptionManager {
126147
// clear our encryption keys as we're done with them now (we'll
127148
// make new keys if we rejoin). We leave keys for other participants
128149
// as they may still be using the same ones.
129-
this.encryptionKeys.set(getParticipantId(this.userId, this.deviceId), []);
150+
this.encryptionKeys.set(getEncryptionKeyMapKey(this.membership), []);
130151
this.transport.off(KeyTransportEvents.ReceivedKeys, this.onNewKeyReceived);
131152
this.transport.stop();
132153

@@ -147,13 +168,13 @@ export class EncryptionManager implements IEncryptionManager {
147168
if (this.manageMediaKeys && this.joined) {
148169
const oldMembershipIds = new Set(
149170
oldMemberships
150-
.filter((m) => !isMyMembership(m, this.userId, this.deviceId))
151-
.map(getParticipantIdFromMembership),
171+
.filter((m) => !isMyMembership(m, this.membership.userId, this.membership.deviceId))
172+
.map(getEncryptionKeyMapKey),
152173
);
153174
const newMembershipIds = new Set(
154175
this.getMemberships()
155-
.filter((m) => !isMyMembership(m, this.userId, this.deviceId))
156-
.map(getParticipantIdFromMembership),
176+
.filter((m) => !isMyMembership(m, this.membership.userId, this.membership.deviceId))
177+
.map(getEncryptionKeyMapKey),
157178
);
158179

159180
// We can use https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/symmetricDifference
@@ -202,14 +223,7 @@ export class EncryptionManager implements IEncryptionManager {
202223
const encryptionKey = secureRandomBase64Url(16);
203224
const encryptionKeyIndex = this.getNewEncryptionKeyIndex();
204225
this.logger.info("Generated new key at index " + encryptionKeyIndex);
205-
this.setEncryptionKey(
206-
this.userId,
207-
this.deviceId,
208-
encryptionKeyIndex,
209-
encryptionKey,
210-
Date.now(),
211-
delayBeforeUse,
212-
);
226+
this.setEncryptionKey(this.membership, encryptionKeyIndex, encryptionKey, Date.now(), delayBeforeUse);
213227
return encryptionKeyIndex;
214228
}
215229

@@ -242,10 +256,11 @@ export class EncryptionManager implements IEncryptionManager {
242256
*
243257
* @param userId the user ID of the participant
244258
* @param deviceId the device ID of the participant
259+
* @param memberId the member ID of the participant
245260
* @returns The encryption keys for the given participant, or undefined if they are not known.
246261
*/
247-
private getKeysForParticipant(userId: string, deviceId: string): Array<Uint8Array> | undefined {
248-
return this.encryptionKeys.get(getParticipantId(userId, deviceId))?.map((entry) => entry.key);
262+
private getKeysForParticipant(membership: CallMembershipIdentityParts): Array<Uint8Array> | undefined {
263+
return this.encryptionKeys.get(getEncryptionKeyMapKey(membership))?.map((entry) => entry.key);
249264
}
250265

251266
/**
@@ -260,7 +275,7 @@ export class EncryptionManager implements IEncryptionManager {
260275

261276
if (!this.joined) return;
262277

263-
const myKeys = this.getKeysForParticipant(this.userId, this.deviceId);
278+
const myKeys = this.getKeysForParticipant(this.membership);
264279

265280
if (!myKeys) {
266281
this.logger.warn("Tried to send encryption keys event but no keys found!");
@@ -294,7 +309,7 @@ export class EncryptionManager implements IEncryptionManager {
294309
});
295310
await this.transport.sendKey(encodeUnpaddedBase64(keyToSend), keyIndexToSend, targets);
296311
this.logger.debug(
297-
`sendEncryptionKeysEvent participantId=${this.userId}:${this.deviceId} numKeys=${myKeys.length} currentKeyIndex=${this.latestGeneratedKeyIndex} keyIndexToSend=${keyIndexToSend}`,
312+
`sendEncryptionKeysEvent participantId=${this.membership.userId}:${this.membership.deviceId} numKeys=${myKeys.length} currentKeyIndex=${this.latestGeneratedKeyIndex} keyIndexToSend=${keyIndexToSend}`,
298313
);
299314
} catch (error) {
300315
if (this.keysEventUpdateTimeout === undefined) {
@@ -307,16 +322,18 @@ export class EncryptionManager implements IEncryptionManager {
307322
}
308323
};
309324

310-
public onNewKeyReceived: KeyTransportEventListener = (userId, deviceId, keyBase64Encoded, index, timestamp) => {
311-
this.logger.debug(`Received key over key transport ${userId}:${deviceId} at index ${index}`);
312-
this.setEncryptionKey(userId, deviceId, index, keyBase64Encoded, timestamp);
325+
public onNewKeyReceived: KeyTransportEventListener = (membership, keyBase64Encoded, index, timestamp) => {
326+
this.logger.debug(
327+
`Received key over key transport ${membership.userId}:${membership.deviceId} at index ${index}`,
328+
);
329+
this.setEncryptionKey(membership, index, keyBase64Encoded, timestamp);
313330
};
314331

315332
private storeLastMembershipFingerprints(): void {
316333
this.lastMembershipFingerprints = new Set(
317334
this.getMemberships()
318-
.filter((m) => !isMyMembership(m, this.userId, this.deviceId))
319-
.map((m) => `${getParticipantIdFromMembership(m)}:${m.createdTs()}`),
335+
.filter((m) => !isMyMembership(m, this.membership.userId, this.membership.deviceId))
336+
.map((m) => `${getEncryptionKeyMapKey(m)}:${m.createdTs()}`),
320337
);
321338
}
322339

@@ -344,28 +361,29 @@ export class EncryptionManager implements IEncryptionManager {
344361
* be distributed.
345362
*/
346363
private setEncryptionKey(
347-
userId: string,
348-
deviceId: string,
364+
membership: CallMembershipIdentityParts,
349365
encryptionKeyIndex: number,
350366
encryptionKeyString: string,
351367
timestamp: number,
352368
delayBeforeUse = false,
353369
): void {
354-
this.logger.debug(`Setting encryption key for ${userId}:${deviceId} at index ${encryptionKeyIndex}`);
370+
this.logger.debug(
371+
`Setting encryption key for ${membership.userId}:${membership.deviceId} at index ${encryptionKeyIndex}`,
372+
);
355373
const keyBin = decodeBase64(encryptionKeyString);
356374

357-
const participantId = getParticipantId(userId, deviceId);
358-
if (!this.encryptionKeys.has(participantId)) {
359-
this.encryptionKeys.set(participantId, []);
375+
const mapKey = getEncryptionKeyMapKey(membership);
376+
if (!this.encryptionKeys.has(mapKey)) {
377+
this.encryptionKeys.set(mapKey, []);
360378
}
361-
const participantKeys = this.encryptionKeys.get(participantId)!;
379+
const participantKeys = this.encryptionKeys.get(mapKey)!;
362380

363381
const existingKeyAtIndex = participantKeys[encryptionKeyIndex];
364382

365383
if (existingKeyAtIndex) {
366384
if (existingKeyAtIndex.timestamp > timestamp) {
367385
this.logger.info(
368-
`Ignoring new key at index ${encryptionKeyIndex} for ${participantId} as it is older than existing known key`,
386+
`Ignoring new key at index ${encryptionKeyIndex} for ${mapKey} as it is older than existing known key`,
369387
);
370388
return;
371389
}
@@ -376,7 +394,7 @@ export class EncryptionManager implements IEncryptionManager {
376394
}
377395
}
378396

379-
if (userId === this.userId && deviceId === this.deviceId) {
397+
if (membership.userId === this.membership.userId && membership.deviceId === this.membership.deviceId) {
380398
// It is important to already update the latestGeneratedKeyIndex here
381399
// NOT IN THE `delayBeforeUse` `setTimeout`.
382400
// Even though this is where we call onEncryptionKeysChanged and set the key in EC (and livekit).
@@ -388,18 +406,19 @@ export class EncryptionManager implements IEncryptionManager {
388406
participantKeys[encryptionKeyIndex] = {
389407
key: keyBin,
390408
timestamp,
409+
membership: membership,
391410
};
392411

393412
if (delayBeforeUse) {
394413
const useKeyTimeout = setTimeout(() => {
395414
this.setNewKeyTimeouts.delete(useKeyTimeout);
396-
this.logger.info(`Delayed-emitting key changed event for ${participantId} index ${encryptionKeyIndex}`);
415+
this.logger.info(`Delayed-emitting key changed event for ${mapKey} index ${encryptionKeyIndex}`);
397416

398-
this.onEncryptionKeysChanged(keyBin, encryptionKeyIndex, participantId);
417+
this.onEncryptionKeysChanged(keyBin, encryptionKeyIndex, membership);
399418
}, this.useKeyDelay);
400419
this.setNewKeyTimeouts.add(useKeyTimeout);
401420
} else {
402-
this.onEncryptionKeysChanged(keyBin, encryptionKeyIndex, participantId);
421+
this.onEncryptionKeysChanged(keyBin, encryptionKeyIndex, membership);
403422
}
404423
}
405424

@@ -419,5 +438,3 @@ function keysEqual(a: Uint8Array | undefined, b: Uint8Array | undefined): boolea
419438
if (a === b) return true;
420439
return !!a && !!b && a.length === b.length && a.every((x, i) => x === b[i]);
421440
}
422-
423-
const getParticipantIdFromMembership = (m: CallMembership): string => getParticipantId(m.sender!, m.deviceId);

src/matrixrtc/IKeyTransport.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17+
import { type CallMembershipIdentityParts } from "./EncryptionManager.ts";
1718
import { type ParticipantDeviceInfo } from "./types.ts";
1819

1920
export enum KeyTransportEvents {
@@ -27,8 +28,7 @@ export type KeyTransportEventsHandlerMap = {
2728
};
2829

2930
export type KeyTransportEventListener = (
30-
userId: string,
31-
deviceId: string,
31+
membership: CallMembershipIdentityParts,
3232
keyBase64Encoded: string,
3333
index: number,
3434
timestamp: number,

src/matrixrtc/MatrixRTCSession.ts

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { type ISendEventResponse } from "../@types/requests.ts";
2525
import { CallMembership } from "./CallMembership.ts";
2626
import { RoomStateEvent } from "../models/room-state.ts";
2727
import { MembershipManager, StickyEventMembershipManager } from "./MembershipManager.ts";
28-
import { EncryptionManager, type IEncryptionManager } from "./EncryptionManager.ts";
28+
import { type CallMembershipIdentityParts, EncryptionManager, type IEncryptionManager } from "./EncryptionManager.ts";
2929
import { deepCompare, logDurationSync } from "../utils.ts";
3030
import type {
3131
Statistics,
@@ -75,7 +75,7 @@ export type MatrixRTCSessionEventHandlerMap = {
7575
[MatrixRTCSessionEvent.EncryptionKeyChanged]: (
7676
key: Uint8Array,
7777
encryptionKeyIndex: number,
78-
participantId: string,
78+
membership: CallMembershipIdentityParts,
7979
) => void;
8080
[MatrixRTCSessionEvent.MembershipManagerError]: (error: unknown) => void;
8181
[MatrixRTCSessionEvent.DidSendCallNotification]: (
@@ -621,40 +621,33 @@ export class MatrixRTCSession extends TypedEventEmitter<
621621
if (joinConfig?.useExperimentalToDeviceTransport) {
622622
this.logger.info("Using experimental to-device transport for encryption keys");
623623
this.logger.info("Using to-device with room fallback transport for encryption keys");
624-
const [uId, dId] = [this.client.getUserId()!, this.client.getDeviceId()!];
625624
const [room, client, statistics] = [this.roomSubset, this.client, this.statistics];
626-
const transport = new ToDeviceKeyTransport(uId, dId, room.roomId, client, statistics);
625+
const transport = new ToDeviceKeyTransport(
626+
{ userId, deviceId, memberId },
627+
room.roomId,
628+
client,
629+
statistics,
630+
);
627631
this.encryptionManager = new RTCEncryptionManager(
628-
this.client.getUserId()!,
629-
this.client.getDeviceId()!,
632+
{ userId, deviceId, memberId },
630633
() => this.memberships,
631634
transport,
632635
this.statistics,
633-
(keyBin: Uint8Array, encryptionKeyIndex: number, participantId: string) => {
634-
this.emit(
635-
MatrixRTCSessionEvent.EncryptionKeyChanged,
636-
keyBin,
637-
encryptionKeyIndex,
638-
participantId,
639-
);
636+
(keyBin: Uint8Array, encryptionKeyIndex: number, membership: CallMembershipIdentityParts) => {
637+
this.emit(MatrixRTCSessionEvent.EncryptionKeyChanged, keyBin, encryptionKeyIndex, membership);
640638
},
641639
this.logger,
642640
);
643641
} else {
642+
// TODO REMOVE ME!
644643
transport = new RoomKeyTransport(this.roomSubset, this.client, this.statistics);
645644
this.encryptionManager = new EncryptionManager(
646-
this.client.getUserId()!,
647-
this.client.getDeviceId()!,
645+
{ userId, deviceId, memberId },
648646
() => this.memberships,
649647
transport,
650648
this.statistics,
651-
(keyBin: Uint8Array, encryptionKeyIndex: number, participantId: string) => {
652-
this.emit(
653-
MatrixRTCSessionEvent.EncryptionKeyChanged,
654-
keyBin,
655-
encryptionKeyIndex,
656-
participantId,
657-
);
649+
(keyBin: Uint8Array, encryptionKeyIndex: number, membership: CallMembershipIdentityParts) => {
650+
this.emit(MatrixRTCSessionEvent.EncryptionKeyChanged, keyBin, encryptionKeyIndex, membership);
658651
},
659652
);
660653
}
@@ -753,7 +746,12 @@ export class MatrixRTCSession extends TypedEventEmitter<
753746
public reemitEncryptionKeys(): void {
754747
this.encryptionManager?.getEncryptionKeys().forEach((keyRing, participantId) => {
755748
keyRing.forEach((keyInfo) => {
756-
this.emit(MatrixRTCSessionEvent.EncryptionKeyChanged, keyInfo.key, keyInfo.keyIndex, participantId);
749+
this.emit(
750+
MatrixRTCSessionEvent.EncryptionKeyChanged,
751+
keyInfo.key,
752+
keyInfo.keyIndex,
753+
keyInfo.membership,
754+
);
757755
});
758756
});
759757
}

0 commit comments

Comments
 (0)