livekit-client 1.13.0 → 1.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/livekit-client.e2ee.worker.js +1 -1
- package/dist/livekit-client.e2ee.worker.js.map +1 -1
- package/dist/livekit-client.e2ee.worker.mjs +122 -105
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +172 -109
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/e2ee/E2eeManager.d.ts +4 -3
- package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
- package/dist/src/e2ee/KeyProvider.d.ts +7 -6
- package/dist/src/e2ee/KeyProvider.d.ts.map +1 -1
- package/dist/src/e2ee/events.d.ts +34 -0
- package/dist/src/e2ee/events.d.ts.map +1 -0
- package/dist/src/e2ee/index.d.ts +1 -0
- package/dist/src/e2ee/index.d.ts.map +1 -1
- package/dist/src/e2ee/types.d.ts +17 -33
- package/dist/src/e2ee/types.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts +15 -12
- package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
- package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts +6 -8
- package/dist/src/e2ee/worker/ParticipantKeyHandler.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/RTCEngine.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +1 -0
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/processor/types.d.ts +2 -1
- package/dist/src/room/track/processor/types.d.ts.map +1 -1
- package/dist/ts4.2/src/e2ee/E2eeManager.d.ts +4 -3
- package/dist/ts4.2/src/e2ee/KeyProvider.d.ts +7 -6
- package/dist/ts4.2/src/e2ee/events.d.ts +34 -0
- package/dist/ts4.2/src/e2ee/index.d.ts +1 -0
- package/dist/ts4.2/src/e2ee/types.d.ts +17 -33
- package/dist/ts4.2/src/e2ee/worker/FrameCryptor.d.ts +15 -12
- package/dist/ts4.2/src/e2ee/worker/ParticipantKeyHandler.d.ts +6 -8
- package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +1 -0
- package/dist/ts4.2/src/room/track/processor/types.d.ts +2 -1
- package/package.json +1 -1
- package/src/e2ee/E2eeManager.ts +105 -77
- package/src/e2ee/KeyProvider.ts +23 -13
- package/src/e2ee/events.ts +48 -0
- package/src/e2ee/index.ts +1 -0
- package/src/e2ee/types.ts +19 -41
- package/src/e2ee/worker/FrameCryptor.ts +51 -43
- package/src/e2ee/worker/ParticipantKeyHandler.ts +25 -27
- package/src/e2ee/worker/e2ee.worker.ts +61 -68
- package/src/room/PCTransport.ts +12 -2
- package/src/room/RTCEngine.ts +0 -1
- package/src/room/Room.ts +20 -15
- package/src/room/participant/LocalParticipant.ts +5 -1
- package/src/room/track/LocalTrack.ts +18 -10
- package/src/room/track/facingMode.ts +1 -1
- package/src/room/track/processor/types.ts +2 -1
@@ -6,17 +6,14 @@ import { workerLogger } from '../../logger';
|
|
6
6
|
import type { VideoCodec } from '../../room/track/options';
|
7
7
|
import { ENCRYPTION_ALGORITHM, IV_LENGTH, UNENCRYPTED_BYTES } from '../constants';
|
8
8
|
import { CryptorError, CryptorErrorReason } from '../errors';
|
9
|
-
import {
|
10
|
-
|
11
|
-
CryptorEvent,
|
12
|
-
DecodeRatchetOptions,
|
13
|
-
KeyProviderOptions,
|
14
|
-
KeySet,
|
15
|
-
} from '../types';
|
9
|
+
import { CryptorCallbacks, CryptorEvent } from '../events';
|
10
|
+
import type { DecodeRatchetOptions, KeyProviderOptions, KeySet } from '../types';
|
16
11
|
import { deriveKeys, isVideoFrame } from '../utils';
|
17
12
|
import type { ParticipantKeyHandler } from './ParticipantKeyHandler';
|
18
13
|
import { SifGuard } from './SifGuard';
|
19
14
|
|
15
|
+
export const encryptionEnabledMap: Map<string, boolean> = new Map();
|
16
|
+
|
20
17
|
export interface FrameCryptorConstructor {
|
21
18
|
new (opts?: unknown): BaseFrameCryptor;
|
22
19
|
}
|
@@ -29,14 +26,14 @@ export interface TransformerInfo {
|
|
29
26
|
}
|
30
27
|
|
31
28
|
export class BaseFrameCryptor extends (EventEmitter as new () => TypedEventEmitter<CryptorCallbacks>) {
|
32
|
-
encodeFunction(
|
29
|
+
protected encodeFunction(
|
33
30
|
encodedFrame: RTCEncodedVideoFrame | RTCEncodedAudioFrame,
|
34
31
|
controller: TransformStreamDefaultController,
|
35
32
|
): Promise<any> {
|
36
33
|
throw Error('not implemented for subclass');
|
37
34
|
}
|
38
35
|
|
39
|
-
decodeFunction(
|
36
|
+
protected decodeFunction(
|
40
37
|
encodedFrame: RTCEncodedVideoFrame | RTCEncodedAudioFrame,
|
41
38
|
controller: TransformStreamDefaultController,
|
42
39
|
): Promise<any> {
|
@@ -51,7 +48,7 @@ export class BaseFrameCryptor extends (EventEmitter as new () => TypedEventEmitt
|
|
51
48
|
export class FrameCryptor extends BaseFrameCryptor {
|
52
49
|
private sendCounts: Map<number, number>;
|
53
50
|
|
54
|
-
private
|
51
|
+
private participantIdentity: string | undefined;
|
55
52
|
|
56
53
|
private trackId: string | undefined;
|
57
54
|
|
@@ -72,14 +69,14 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
72
69
|
|
73
70
|
constructor(opts: {
|
74
71
|
keys: ParticipantKeyHandler;
|
75
|
-
|
72
|
+
participantIdentity: string;
|
76
73
|
keyProviderOptions: KeyProviderOptions;
|
77
74
|
sifTrailer?: Uint8Array;
|
78
75
|
}) {
|
79
76
|
super();
|
80
77
|
this.sendCounts = new Map();
|
81
78
|
this.keys = opts.keys;
|
82
|
-
this.
|
79
|
+
this.participantIdentity = opts.participantIdentity;
|
83
80
|
this.rtpMap = new Map();
|
84
81
|
this.keyProviderOptions = opts.keyProviderOptions;
|
85
82
|
this.sifTrailer = opts.sifTrailer ?? Uint8Array.from([]);
|
@@ -93,17 +90,25 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
93
90
|
* @param keys
|
94
91
|
*/
|
95
92
|
setParticipant(id: string, keys: ParticipantKeyHandler) {
|
96
|
-
this.
|
93
|
+
this.participantIdentity = id;
|
97
94
|
this.keys = keys;
|
98
95
|
this.sifGuard.reset();
|
99
96
|
}
|
100
97
|
|
101
98
|
unsetParticipant() {
|
102
|
-
this.
|
99
|
+
this.participantIdentity = undefined;
|
103
100
|
}
|
104
101
|
|
105
|
-
|
106
|
-
|
102
|
+
isEnabled() {
|
103
|
+
if (this.participantIdentity) {
|
104
|
+
return encryptionEnabledMap.get(this.participantIdentity);
|
105
|
+
} else {
|
106
|
+
return undefined;
|
107
|
+
}
|
108
|
+
}
|
109
|
+
|
110
|
+
getParticipantIdentity() {
|
111
|
+
return this.participantIdentity;
|
107
112
|
}
|
108
113
|
|
109
114
|
getTrackId() {
|
@@ -148,11 +153,15 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
148
153
|
.pipeTo(writable)
|
149
154
|
.catch((e) => {
|
150
155
|
workerLogger.warn(e);
|
151
|
-
this.emit(
|
156
|
+
this.emit(CryptorEvent.Error, e instanceof CryptorError ? e : new CryptorError(e.message));
|
152
157
|
});
|
153
158
|
this.trackId = trackId;
|
154
159
|
}
|
155
160
|
|
161
|
+
setSifTrailer(trailer: Uint8Array) {
|
162
|
+
this.sifTrailer = trailer;
|
163
|
+
}
|
164
|
+
|
156
165
|
/**
|
157
166
|
* Function that will be injected in a stream and will encrypt the given encoded frames.
|
158
167
|
*
|
@@ -175,19 +184,26 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
175
184
|
* 8) Append a single byte for the key identifier.
|
176
185
|
* 9) Enqueue the encrypted frame for sending.
|
177
186
|
*/
|
178
|
-
async encodeFunction(
|
187
|
+
protected async encodeFunction(
|
179
188
|
encodedFrame: RTCEncodedVideoFrame | RTCEncodedAudioFrame,
|
180
189
|
controller: TransformStreamDefaultController,
|
181
190
|
) {
|
182
191
|
if (
|
183
|
-
!this.
|
192
|
+
!this.isEnabled() ||
|
184
193
|
// skip for encryption for empty dtx frames
|
185
194
|
encodedFrame.data.byteLength === 0
|
186
195
|
) {
|
187
196
|
return controller.enqueue(encodedFrame);
|
188
197
|
}
|
189
|
-
|
190
|
-
|
198
|
+
const keySet = this.keys.getKeySet();
|
199
|
+
if (!keySet) {
|
200
|
+
throw new TypeError(
|
201
|
+
`key set not found for ${
|
202
|
+
this.participantIdentity
|
203
|
+
} at index ${this.keys.getCurrentKeyIndex()}`,
|
204
|
+
);
|
205
|
+
}
|
206
|
+
const { encryptionKey } = keySet;
|
191
207
|
const keyIndex = this.keys.getCurrentKeyIndex();
|
192
208
|
|
193
209
|
if (encryptionKey) {
|
@@ -258,12 +274,12 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
258
274
|
* @param {RTCEncodedVideoFrame|RTCEncodedAudioFrame} encodedFrame - Encoded video frame.
|
259
275
|
* @param {TransformStreamDefaultController} controller - TransportStreamController.
|
260
276
|
*/
|
261
|
-
async decodeFunction(
|
277
|
+
protected async decodeFunction(
|
262
278
|
encodedFrame: RTCEncodedVideoFrame | RTCEncodedAudioFrame,
|
263
279
|
controller: TransformStreamDefaultController,
|
264
280
|
) {
|
265
281
|
if (
|
266
|
-
!this.
|
282
|
+
!this.isEnabled() ||
|
267
283
|
// skip for decryption for empty dtx frames
|
268
284
|
encodedFrame.data.byteLength === 0
|
269
285
|
) {
|
@@ -296,14 +312,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
296
312
|
} catch (error) {
|
297
313
|
if (error instanceof CryptorError && error.reason === CryptorErrorReason.InvalidKey) {
|
298
314
|
if (this.keys.hasValidKey) {
|
299
|
-
|
300
|
-
this.emit(
|
301
|
-
CryptorEvent.Error,
|
302
|
-
new CryptorError(
|
303
|
-
`invalid key for participant ${this.participantId}`,
|
304
|
-
CryptorErrorReason.InvalidKey,
|
305
|
-
),
|
306
|
-
);
|
315
|
+
this.emit(CryptorEvent.Error, error);
|
307
316
|
this.keys.decryptionFailure();
|
308
317
|
}
|
309
318
|
} else {
|
@@ -316,7 +325,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
316
325
|
this.emit(
|
317
326
|
CryptorEvent.Error,
|
318
327
|
new CryptorError(
|
319
|
-
`missing key at index for participant ${this.
|
328
|
+
`missing key at index for participant ${this.participantIdentity}`,
|
320
329
|
CryptorErrorReason.MissingKey,
|
321
330
|
),
|
322
331
|
);
|
@@ -327,13 +336,16 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
327
336
|
* Function that will decrypt the given encoded frame. If the decryption fails, it will
|
328
337
|
* ratchet the key for up to RATCHET_WINDOW_SIZE times.
|
329
338
|
*/
|
330
|
-
async decryptFrame(
|
339
|
+
private async decryptFrame(
|
331
340
|
encodedFrame: RTCEncodedVideoFrame | RTCEncodedAudioFrame,
|
332
341
|
keyIndex: number,
|
333
342
|
initialMaterial: KeySet | undefined = undefined,
|
334
343
|
ratchetOpts: DecodeRatchetOptions = { ratchetCount: 0 },
|
335
344
|
): Promise<RTCEncodedVideoFrame | RTCEncodedAudioFrame | undefined> {
|
336
345
|
const keySet = this.keys.getKeySet(keyIndex);
|
346
|
+
if (!ratchetOpts.encryptionKey && !keySet) {
|
347
|
+
throw new TypeError(`no encryption key found for decryption of ${this.participantIdentity}`);
|
348
|
+
}
|
337
349
|
|
338
350
|
// Construct frame trailer. Similar to the frame header described in
|
339
351
|
// https://tools.ietf.org/html/draft-omara-sframe-00#section-4.2
|
@@ -369,7 +381,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
369
381
|
iv,
|
370
382
|
additionalData: new Uint8Array(encodedFrame.data, 0, frameHeader.byteLength),
|
371
383
|
},
|
372
|
-
ratchetOpts.encryptionKey ?? keySet
|
384
|
+
ratchetOpts.encryptionKey ?? keySet!.encryptionKey,
|
373
385
|
new Uint8Array(encodedFrame.data, cipherTextStart, cipherTextLength),
|
374
386
|
);
|
375
387
|
|
@@ -422,15 +434,15 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
422
434
|
this.keys.setKeyFromMaterial(initialMaterial.material, keyIndex);
|
423
435
|
}
|
424
436
|
|
425
|
-
workerLogger.warn('maximum ratchet attempts exceeded
|
437
|
+
workerLogger.warn('maximum ratchet attempts exceeded');
|
426
438
|
throw new CryptorError(
|
427
|
-
`valid key missing for participant ${this.
|
439
|
+
`valid key missing for participant ${this.participantIdentity}`,
|
428
440
|
CryptorErrorReason.InvalidKey,
|
429
441
|
);
|
430
442
|
}
|
431
443
|
} else {
|
432
444
|
throw new CryptorError(
|
433
|
-
|
445
|
+
`Decryption failed: ${error.message}`,
|
434
446
|
CryptorErrorReason.InvalidKey,
|
435
447
|
);
|
436
448
|
}
|
@@ -477,7 +489,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
477
489
|
return iv;
|
478
490
|
}
|
479
491
|
|
480
|
-
getUnencryptedBytes(frame: RTCEncodedVideoFrame | RTCEncodedAudioFrame): number {
|
492
|
+
private getUnencryptedBytes(frame: RTCEncodedVideoFrame | RTCEncodedAudioFrame): number {
|
481
493
|
if (isVideoFrame(frame)) {
|
482
494
|
let detectedCodec = this.getVideoCodec(frame) ?? this.videoCodec;
|
483
495
|
|
@@ -526,7 +538,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
526
538
|
/**
|
527
539
|
* inspects frame payloadtype if available and maps it to the codec specified in rtpMap
|
528
540
|
*/
|
529
|
-
getVideoCodec(frame: RTCEncodedVideoFrame): VideoCodec | undefined {
|
541
|
+
private getVideoCodec(frame: RTCEncodedVideoFrame): VideoCodec | undefined {
|
530
542
|
if (this.rtpMap.size === 0) {
|
531
543
|
return undefined;
|
532
544
|
}
|
@@ -535,10 +547,6 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
535
547
|
const codec = payloadType ? this.rtpMap.get(payloadType) : undefined;
|
536
548
|
return codec;
|
537
549
|
}
|
538
|
-
|
539
|
-
setSifTrailer(trailer: Uint8Array) {
|
540
|
-
this.sifTrailer = trailer;
|
541
|
-
}
|
542
550
|
}
|
543
551
|
|
544
552
|
/**
|
@@ -2,7 +2,8 @@ import { EventEmitter } from 'events';
|
|
2
2
|
import type TypedEventEmitter from 'typed-emitter';
|
3
3
|
import { workerLogger } from '../../logger';
|
4
4
|
import { KEYRING_SIZE } from '../constants';
|
5
|
-
import
|
5
|
+
import { KeyHandlerEvent, type ParticipantKeyHandlerCallbacks } from '../events';
|
6
|
+
import type { KeyProviderOptions, KeySet } from '../types';
|
6
7
|
import { deriveKeys, importKey, ratchet } from '../utils';
|
7
8
|
|
8
9
|
// TODO ParticipantKeyHandlers currently don't get destroyed on participant disconnect
|
@@ -19,15 +20,13 @@ import { deriveKeys, importKey, ratchet } from '../utils';
|
|
19
20
|
export class ParticipantKeyHandler extends (EventEmitter as new () => TypedEventEmitter<ParticipantKeyHandlerCallbacks>) {
|
20
21
|
private currentKeyIndex: number;
|
21
22
|
|
22
|
-
private cryptoKeyRing: Array<KeySet>;
|
23
|
-
|
24
|
-
private enabled: boolean;
|
23
|
+
private cryptoKeyRing: Array<KeySet | undefined>;
|
25
24
|
|
26
25
|
private keyProviderOptions: KeyProviderOptions;
|
27
26
|
|
28
27
|
private ratchetPromiseMap: Map<number, Promise<CryptoKey>>;
|
29
28
|
|
30
|
-
private
|
29
|
+
private participantIdentity: string;
|
31
30
|
|
32
31
|
private decryptionFailureCount = 0;
|
33
32
|
|
@@ -37,25 +36,16 @@ export class ParticipantKeyHandler extends (EventEmitter as new () => TypedEvent
|
|
37
36
|
return this._hasValidKey;
|
38
37
|
}
|
39
38
|
|
40
|
-
constructor(
|
41
|
-
participantId: string | undefined,
|
42
|
-
isEnabled: boolean,
|
43
|
-
keyProviderOptions: KeyProviderOptions,
|
44
|
-
) {
|
39
|
+
constructor(participantIdentity: string, keyProviderOptions: KeyProviderOptions) {
|
45
40
|
super();
|
46
41
|
this.currentKeyIndex = 0;
|
47
|
-
this.cryptoKeyRing = new Array(KEYRING_SIZE);
|
48
|
-
this.enabled = isEnabled;
|
42
|
+
this.cryptoKeyRing = new Array(KEYRING_SIZE).fill(undefined);
|
49
43
|
this.keyProviderOptions = keyProviderOptions;
|
50
44
|
this.ratchetPromiseMap = new Map();
|
51
|
-
this.
|
45
|
+
this.participantIdentity = participantIdentity;
|
52
46
|
this.resetKeyStatus();
|
53
47
|
}
|
54
48
|
|
55
|
-
setEnabled(enabled: boolean) {
|
56
|
-
this.enabled = enabled;
|
57
|
-
}
|
58
|
-
|
59
49
|
decryptionFailure() {
|
60
50
|
if (this.keyProviderOptions.failureTolerance < 0) {
|
61
51
|
return;
|
@@ -63,7 +53,7 @@ export class ParticipantKeyHandler extends (EventEmitter as new () => TypedEvent
|
|
63
53
|
this.decryptionFailureCount += 1;
|
64
54
|
|
65
55
|
if (this.decryptionFailureCount > this.keyProviderOptions.failureTolerance) {
|
66
|
-
workerLogger.warn(`key for ${this.
|
56
|
+
workerLogger.warn(`key for ${this.participantIdentity} is being marked as invalid`);
|
67
57
|
this._hasValidKey = false;
|
68
58
|
}
|
69
59
|
}
|
@@ -89,7 +79,7 @@ export class ParticipantKeyHandler extends (EventEmitter as new () => TypedEvent
|
|
89
79
|
* @param setKey
|
90
80
|
*/
|
91
81
|
ratchetKey(keyIndex?: number, setKey = true): Promise<CryptoKey> {
|
92
|
-
const currentKeyIndex =
|
82
|
+
const currentKeyIndex = keyIndex ?? this.getCurrentKeyIndex();
|
93
83
|
|
94
84
|
const existingPromise = this.ratchetPromiseMap.get(currentKeyIndex);
|
95
85
|
if (typeof existingPromise !== 'undefined') {
|
@@ -97,7 +87,13 @@ export class ParticipantKeyHandler extends (EventEmitter as new () => TypedEvent
|
|
97
87
|
}
|
98
88
|
const ratchetPromise = new Promise<CryptoKey>(async (resolve, reject) => {
|
99
89
|
try {
|
100
|
-
const
|
90
|
+
const keySet = this.getKeySet(currentKeyIndex);
|
91
|
+
if (!keySet) {
|
92
|
+
throw new TypeError(
|
93
|
+
`Cannot ratchet key without a valid keyset of participant ${this.participantIdentity}`,
|
94
|
+
);
|
95
|
+
}
|
96
|
+
const currentMaterial = keySet.material;
|
101
97
|
const newMaterial = await importKey(
|
102
98
|
await ratchet(currentMaterial, this.keyProviderOptions.ratchetSalt),
|
103
99
|
currentMaterial.algorithm.name,
|
@@ -106,8 +102,13 @@ export class ParticipantKeyHandler extends (EventEmitter as new () => TypedEvent
|
|
106
102
|
|
107
103
|
if (setKey) {
|
108
104
|
this.setKeyFromMaterial(newMaterial, currentKeyIndex, true);
|
105
|
+
this.emit(
|
106
|
+
KeyHandlerEvent.KeyRatcheted,
|
107
|
+
newMaterial,
|
108
|
+
this.participantIdentity,
|
109
|
+
currentKeyIndex,
|
110
|
+
);
|
109
111
|
}
|
110
|
-
this.emit('keyRatcheted', newMaterial, keyIndex, this.participantId);
|
111
112
|
resolve(newMaterial);
|
112
113
|
} catch (e) {
|
113
114
|
reject(e);
|
@@ -145,10 +146,11 @@ export class ParticipantKeyHandler extends (EventEmitter as new () => TypedEvent
|
|
145
146
|
this.setKeySet(keySet, this.currentKeyIndex, emitRatchetEvent);
|
146
147
|
}
|
147
148
|
|
148
|
-
|
149
|
+
setKeySet(keySet: KeySet, keyIndex: number, emitRatchetEvent = false) {
|
149
150
|
this.cryptoKeyRing[keyIndex % this.cryptoKeyRing.length] = keySet;
|
151
|
+
|
150
152
|
if (emitRatchetEvent) {
|
151
|
-
this.emit(
|
153
|
+
this.emit(KeyHandlerEvent.KeyRatcheted, keySet.material, this.participantIdentity, keyIndex);
|
152
154
|
}
|
153
155
|
}
|
154
156
|
|
@@ -157,10 +159,6 @@ export class ParticipantKeyHandler extends (EventEmitter as new () => TypedEvent
|
|
157
159
|
this.resetKeyStatus();
|
158
160
|
}
|
159
161
|
|
160
|
-
isEnabled() {
|
161
|
-
return this.enabled;
|
162
|
-
}
|
163
|
-
|
164
162
|
getCurrentKeyIndex() {
|
165
163
|
return this.currentKeyIndex;
|
166
164
|
}
|
@@ -1,22 +1,21 @@
|
|
1
1
|
import { workerLogger } from '../../logger';
|
2
2
|
import { KEY_PROVIDER_DEFAULTS } from '../constants';
|
3
3
|
import { CryptorErrorReason } from '../errors';
|
4
|
+
import { CryptorEvent, KeyHandlerEvent } from '../events';
|
4
5
|
import type {
|
5
6
|
E2EEWorkerMessage,
|
6
|
-
EnableMessage,
|
7
7
|
ErrorMessage,
|
8
|
+
InitAck,
|
8
9
|
KeyProviderOptions,
|
9
10
|
RatchetMessage,
|
10
11
|
RatchetRequestMessage,
|
11
12
|
} from '../types';
|
12
|
-
import { FrameCryptor } from './FrameCryptor';
|
13
|
+
import { FrameCryptor, encryptionEnabledMap } from './FrameCryptor';
|
13
14
|
import { ParticipantKeyHandler } from './ParticipantKeyHandler';
|
14
15
|
|
15
16
|
const participantCryptors: FrameCryptor[] = [];
|
16
17
|
const participantKeys: Map<string, ParticipantKeyHandler> = new Map();
|
17
|
-
|
18
|
-
let publishCryptors: FrameCryptor[] = [];
|
19
|
-
let publisherKeys: ParticipantKeyHandler;
|
18
|
+
let sharedKeyHandler: ParticipantKeyHandler | undefined;
|
20
19
|
|
21
20
|
let isEncryptionEnabled: boolean = false;
|
22
21
|
|
@@ -39,22 +38,20 @@ onmessage = (ev) => {
|
|
39
38
|
keyProviderOptions = data.keyProviderOptions;
|
40
39
|
useSharedKey = !!data.keyProviderOptions.sharedKey;
|
41
40
|
// acknowledge init successful
|
42
|
-
const
|
43
|
-
kind: '
|
41
|
+
const ackMsg: InitAck = {
|
42
|
+
kind: 'initAck',
|
44
43
|
data: { enabled: isEncryptionEnabled },
|
45
44
|
};
|
46
|
-
|
47
|
-
publisherKeys.on('keyRatcheted', emitRatchetedKeys);
|
48
|
-
postMessage(enableMsg);
|
45
|
+
postMessage(ackMsg);
|
49
46
|
break;
|
50
47
|
case 'enable':
|
51
|
-
setEncryptionEnabled(data.enabled, data.
|
48
|
+
setEncryptionEnabled(data.enabled, data.participantIdentity);
|
52
49
|
workerLogger.info('updated e2ee enabled status');
|
53
50
|
// acknowledge enable call successful
|
54
51
|
postMessage(ev.data);
|
55
52
|
break;
|
56
53
|
case 'decode':
|
57
|
-
let cryptor = getTrackCryptor(data.
|
54
|
+
let cryptor = getTrackCryptor(data.participantIdentity, data.trackId);
|
58
55
|
cryptor.setupTransform(
|
59
56
|
kind,
|
60
57
|
data.readableStream,
|
@@ -64,7 +61,7 @@ onmessage = (ev) => {
|
|
64
61
|
);
|
65
62
|
break;
|
66
63
|
case 'encode':
|
67
|
-
let pubCryptor =
|
64
|
+
let pubCryptor = getTrackCryptor(data.participantIdentity, data.trackId);
|
68
65
|
pubCryptor.setupTransform(
|
69
66
|
kind,
|
70
67
|
data.readableStream,
|
@@ -75,10 +72,11 @@ onmessage = (ev) => {
|
|
75
72
|
break;
|
76
73
|
case 'setKey':
|
77
74
|
if (useSharedKey) {
|
78
|
-
workerLogger.
|
75
|
+
workerLogger.warn('set shared key');
|
79
76
|
setSharedKey(data.key, data.keyIndex);
|
80
|
-
} else if (data.
|
81
|
-
|
77
|
+
} else if (data.participantIdentity) {
|
78
|
+
workerLogger.warn(`set participant sender key ${data.participantIdentity}`);
|
79
|
+
getParticipantKeyHandler(data.participantIdentity).setKey(data.key, data.keyIndex);
|
82
80
|
} else {
|
83
81
|
workerLogger.error('no participant Id was provided and shared key usage is disabled');
|
84
82
|
}
|
@@ -87,11 +85,14 @@ onmessage = (ev) => {
|
|
87
85
|
unsetCryptorParticipant(data.trackId);
|
88
86
|
break;
|
89
87
|
case 'updateCodec':
|
90
|
-
getTrackCryptor(data.
|
88
|
+
getTrackCryptor(data.participantIdentity, data.trackId).setVideoCodec(data.codec);
|
91
89
|
break;
|
92
90
|
case 'setRTPMap':
|
93
|
-
|
94
|
-
|
91
|
+
// this is only used for the local participant
|
92
|
+
participantCryptors.forEach((cr) => {
|
93
|
+
if (cr.getParticipantIdentity() === data.participantIdentity) {
|
94
|
+
cr.setRtpMap(data.map);
|
95
|
+
}
|
95
96
|
});
|
96
97
|
break;
|
97
98
|
case 'ratchetRequest':
|
@@ -106,92 +107,85 @@ onmessage = (ev) => {
|
|
106
107
|
};
|
107
108
|
|
108
109
|
async function handleRatchetRequest(data: RatchetRequestMessage['data']) {
|
109
|
-
|
110
|
-
|
111
|
-
|
110
|
+
if (useSharedKey) {
|
111
|
+
const keyHandler = getSharedKeyHandler();
|
112
|
+
await keyHandler.ratchetKey(data.keyIndex);
|
113
|
+
keyHandler.resetKeyStatus();
|
114
|
+
} else if (data.participantIdentity) {
|
115
|
+
const keyHandler = getParticipantKeyHandler(data.participantIdentity);
|
116
|
+
await keyHandler.ratchetKey(data.keyIndex);
|
117
|
+
keyHandler.resetKeyStatus();
|
118
|
+
} else {
|
119
|
+
workerLogger.error(
|
120
|
+
'no participant Id was provided for ratchet request and shared key usage is disabled',
|
121
|
+
);
|
122
|
+
}
|
112
123
|
}
|
113
124
|
|
114
|
-
function getTrackCryptor(
|
125
|
+
function getTrackCryptor(participantIdentity: string, trackId: string) {
|
115
126
|
let cryptor = participantCryptors.find((c) => c.getTrackId() === trackId);
|
116
127
|
if (!cryptor) {
|
117
|
-
workerLogger.info('creating new cryptor for', {
|
128
|
+
workerLogger.info('creating new cryptor for', { participantIdentity });
|
118
129
|
if (!keyProviderOptions) {
|
119
130
|
throw Error('Missing keyProvider options');
|
120
131
|
}
|
121
132
|
cryptor = new FrameCryptor({
|
122
|
-
|
123
|
-
keys: getParticipantKeyHandler(
|
133
|
+
participantIdentity,
|
134
|
+
keys: getParticipantKeyHandler(participantIdentity),
|
124
135
|
keyProviderOptions,
|
125
136
|
sifTrailer,
|
126
137
|
});
|
127
138
|
|
128
139
|
setupCryptorErrorEvents(cryptor);
|
129
140
|
participantCryptors.push(cryptor);
|
130
|
-
} else if (
|
141
|
+
} else if (participantIdentity !== cryptor.getParticipantIdentity()) {
|
131
142
|
// assign new participant id to track cryptor and pass in correct key handler
|
132
|
-
cryptor.setParticipant(
|
143
|
+
cryptor.setParticipant(participantIdentity, getParticipantKeyHandler(participantIdentity));
|
133
144
|
}
|
134
145
|
if (sharedKey) {
|
135
146
|
}
|
136
147
|
return cryptor;
|
137
148
|
}
|
138
149
|
|
139
|
-
function getParticipantKeyHandler(
|
140
|
-
if (
|
141
|
-
return
|
150
|
+
function getParticipantKeyHandler(participantIdentity: string) {
|
151
|
+
if (useSharedKey) {
|
152
|
+
return getSharedKeyHandler();
|
142
153
|
}
|
143
|
-
let keys = participantKeys.get(
|
154
|
+
let keys = participantKeys.get(participantIdentity);
|
144
155
|
if (!keys) {
|
145
|
-
keys = new ParticipantKeyHandler(
|
156
|
+
keys = new ParticipantKeyHandler(participantIdentity, keyProviderOptions);
|
146
157
|
if (sharedKey) {
|
147
158
|
keys.setKey(sharedKey);
|
148
159
|
}
|
149
|
-
|
160
|
+
keys.on(KeyHandlerEvent.KeyRatcheted, emitRatchetedKeys);
|
161
|
+
participantKeys.set(participantIdentity, keys);
|
150
162
|
}
|
151
163
|
return keys;
|
152
164
|
}
|
153
165
|
|
154
|
-
function
|
155
|
-
|
166
|
+
function getSharedKeyHandler() {
|
167
|
+
if (!sharedKeyHandler) {
|
168
|
+
sharedKeyHandler = new ParticipantKeyHandler('shared-key', keyProviderOptions);
|
169
|
+
}
|
170
|
+
return sharedKeyHandler;
|
156
171
|
}
|
157
172
|
|
158
|
-
function
|
159
|
-
|
160
|
-
if (!publishCryptor) {
|
161
|
-
if (!keyProviderOptions) {
|
162
|
-
throw new TypeError('Missing keyProvider options');
|
163
|
-
}
|
164
|
-
publishCryptor = new FrameCryptor({
|
165
|
-
keys: publisherKeys!,
|
166
|
-
participantId: 'publisher',
|
167
|
-
keyProviderOptions,
|
168
|
-
});
|
169
|
-
setupCryptorErrorEvents(publishCryptor);
|
170
|
-
publishCryptors.push(publishCryptor);
|
171
|
-
}
|
172
|
-
return publishCryptor;
|
173
|
+
function unsetCryptorParticipant(trackId: string) {
|
174
|
+
participantCryptors.find((c) => c.getTrackId() === trackId)?.unsetParticipant();
|
173
175
|
}
|
174
176
|
|
175
|
-
function setEncryptionEnabled(enable: boolean,
|
176
|
-
|
177
|
-
isEncryptionEnabled = enable;
|
178
|
-
publisherKeys.setEnabled(enable);
|
179
|
-
} else {
|
180
|
-
getParticipantKeyHandler(participantId).setEnabled(enable);
|
181
|
-
}
|
177
|
+
function setEncryptionEnabled(enable: boolean, participantIdentity: string) {
|
178
|
+
encryptionEnabledMap.set(participantIdentity, enable);
|
182
179
|
}
|
183
180
|
|
184
181
|
function setSharedKey(key: CryptoKey, index?: number) {
|
185
182
|
workerLogger.debug('setting shared key');
|
186
183
|
sharedKey = key;
|
187
|
-
|
188
|
-
for (const [, keyHandler] of participantKeys) {
|
189
|
-
keyHandler.setKey(key, index);
|
190
|
-
}
|
184
|
+
getSharedKeyHandler().setKey(key, index);
|
191
185
|
}
|
192
186
|
|
193
187
|
function setupCryptorErrorEvents(cryptor: FrameCryptor) {
|
194
|
-
cryptor.on(
|
188
|
+
cryptor.on(CryptorEvent.Error, (error) => {
|
195
189
|
const msg: ErrorMessage = {
|
196
190
|
kind: 'error',
|
197
191
|
data: { error: new Error(`${CryptorErrorReason[error.reason]}: ${error.message}`) },
|
@@ -200,11 +194,11 @@ function setupCryptorErrorEvents(cryptor: FrameCryptor) {
|
|
200
194
|
});
|
201
195
|
}
|
202
196
|
|
203
|
-
function emitRatchetedKeys(material: CryptoKey, keyIndex?: number) {
|
197
|
+
function emitRatchetedKeys(material: CryptoKey, participantIdentity: string, keyIndex?: number) {
|
204
198
|
const msg: RatchetMessage = {
|
205
199
|
kind: `ratchetKey`,
|
206
200
|
data: {
|
207
|
-
|
201
|
+
participantIdentity,
|
208
202
|
keyIndex,
|
209
203
|
material,
|
210
204
|
},
|
@@ -228,9 +222,8 @@ if (self.RTCTransformEvent) {
|
|
228
222
|
const transformer = event.transformer;
|
229
223
|
workerLogger.debug('transformer', transformer);
|
230
224
|
transformer.handled = true;
|
231
|
-
const { kind,
|
232
|
-
const cryptor =
|
233
|
-
kind === 'encode' ? getPublisherCryptor(trackId) : getTrackCryptor(participantId, trackId);
|
225
|
+
const { kind, participantIdentity, trackId, codec } = transformer.options;
|
226
|
+
const cryptor = getTrackCryptor(participantIdentity, trackId);
|
234
227
|
workerLogger.debug('transform', { codec });
|
235
228
|
cryptor.setupTransform(kind, transformer.readable, transformer.writable, trackId, codec);
|
236
229
|
};
|
package/src/room/PCTransport.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import { EventEmitter } from 'events';
|
2
|
-
import { parse, write } from 'sdp-transform';
|
3
2
|
import type { MediaDescription } from 'sdp-transform';
|
3
|
+
import { parse, write } from 'sdp-transform';
|
4
4
|
import { debounce } from 'ts-debounce';
|
5
5
|
import log from '../logger';
|
6
6
|
import { NegotiationError, UnexpectedConnectionState } from './errors';
|
@@ -16,7 +16,7 @@ interface TrackBitrateInfo {
|
|
16
16
|
|
17
17
|
/* The svc codec (av1/vp9) would use a very low bitrate at the begining and
|
18
18
|
increase slowly by the bandwidth estimator until it reach the target bitrate. The
|
19
|
-
process commonly cost more than 10 seconds cause subscriber will get blur video at
|
19
|
+
process commonly cost more than 10 seconds cause subscriber will get blur video at
|
20
20
|
the first few seconds. So we use a 70% of target bitrate here as the start bitrate to
|
21
21
|
eliminate this issue.
|
22
22
|
*/
|
@@ -308,6 +308,7 @@ export default class PCTransport extends EventEmitter {
|
|
308
308
|
} catch (e) {
|
309
309
|
log.warn(`not able to set ${sd.type}, falling back to unmodified sdp`, {
|
310
310
|
error: e,
|
311
|
+
sdp: munged,
|
311
312
|
});
|
312
313
|
sd.sdp = originalSdp;
|
313
314
|
}
|
@@ -328,6 +329,15 @@ export default class PCTransport extends EventEmitter {
|
|
328
329
|
} else if (typeof e === 'string') {
|
329
330
|
msg = e;
|
330
331
|
}
|
332
|
+
|
333
|
+
const fields: any = {
|
334
|
+
error: msg,
|
335
|
+
sdp: sd.sdp,
|
336
|
+
};
|
337
|
+
if (!remote && this.pc.remoteDescription) {
|
338
|
+
fields.remoteSdp = this.pc.remoteDescription;
|
339
|
+
}
|
340
|
+
log.error(`unable to set ${sd.type}`, fields);
|
331
341
|
throw new NegotiationError(msg);
|
332
342
|
}
|
333
343
|
}
|
package/src/room/RTCEngine.ts
CHANGED