livekit-client 1.12.3 → 1.13.1
Sign up to get free protection for your applications and to get access to all the features.
- 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 +198 -107
- package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
- package/dist/livekit-client.esm.mjs +515 -192
- 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/api/SignalClient.d.ts +2 -5
- package/dist/src/api/SignalClient.d.ts.map +1 -1
- package/dist/src/connectionHelper/checks/turn.d.ts.map +1 -1
- package/dist/src/connectionHelper/checks/webrtc.d.ts.map +1 -1
- package/dist/src/connectionHelper/checks/websocket.d.ts.map +1 -1
- package/dist/src/e2ee/E2eeManager.d.ts +9 -3
- package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
- package/dist/src/e2ee/KeyProvider.d.ts +10 -7
- package/dist/src/e2ee/KeyProvider.d.ts.map +1 -1
- package/dist/src/e2ee/constants.d.ts +2 -0
- package/dist/src/e2ee/constants.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 +23 -33
- package/dist/src/e2ee/types.d.ts.map +1 -1
- package/dist/src/e2ee/utils.d.ts +1 -0
- package/dist/src/e2ee/utils.d.ts.map +1 -1
- package/dist/src/e2ee/worker/FrameCryptor.d.ts +18 -13
- 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/e2ee/worker/SifGuard.d.ts +11 -0
- package/dist/src/e2ee/worker/SifGuard.d.ts.map +1 -0
- package/dist/src/options.d.ts +5 -0
- package/dist/src/options.d.ts.map +1 -1
- package/dist/src/proto/livekit_models_pb.d.ts.map +1 -1
- package/dist/src/proto/livekit_rtc_pb.d.ts.map +1 -1
- package/dist/src/room/DeviceManager.d.ts +1 -0
- package/dist/src/room/DeviceManager.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 +1 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/defaults.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/participant/Participant.d.ts +5 -0
- package/dist/src/room/participant/Participant.d.ts.map +1 -1
- package/dist/src/room/participant/RemoteParticipant.d.ts +0 -5
- package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
- package/dist/src/room/timers.d.ts +2 -2
- package/dist/src/room/timers.d.ts.map +1 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts +9 -1
- package/dist/src/room/track/LocalAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalTrack.d.ts +3 -3
- package/dist/src/room/track/LocalTrack.d.ts.map +1 -1
- package/dist/src/room/track/LocalVideoTrack.d.ts +6 -0
- package/dist/src/room/track/LocalVideoTrack.d.ts.map +1 -1
- package/dist/src/room/track/RemoteAudioTrack.d.ts.map +1 -1
- package/dist/src/room/track/processor/types.d.ts +14 -2
- package/dist/src/room/track/processor/types.d.ts.map +1 -1
- package/dist/src/room/types.d.ts +1 -1
- package/dist/src/room/types.d.ts.map +1 -1
- package/dist/ts4.2/src/api/SignalClient.d.ts +2 -5
- package/dist/ts4.2/src/e2ee/E2eeManager.d.ts +9 -3
- package/dist/ts4.2/src/e2ee/KeyProvider.d.ts +10 -7
- package/dist/ts4.2/src/e2ee/constants.d.ts +2 -0
- 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 +23 -33
- package/dist/ts4.2/src/e2ee/utils.d.ts +1 -0
- package/dist/ts4.2/src/e2ee/worker/FrameCryptor.d.ts +18 -13
- package/dist/ts4.2/src/e2ee/worker/ParticipantKeyHandler.d.ts +6 -8
- package/dist/ts4.2/src/e2ee/worker/SifGuard.d.ts +11 -0
- package/dist/ts4.2/src/options.d.ts +5 -0
- package/dist/ts4.2/src/room/DeviceManager.d.ts +1 -0
- package/dist/ts4.2/src/room/Room.d.ts +1 -1
- package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +1 -0
- package/dist/ts4.2/src/room/participant/Participant.d.ts +5 -0
- package/dist/ts4.2/src/room/participant/RemoteParticipant.d.ts +0 -5
- package/dist/ts4.2/src/room/timers.d.ts +2 -2
- package/dist/ts4.2/src/room/track/LocalAudioTrack.d.ts +9 -1
- package/dist/ts4.2/src/room/track/LocalTrack.d.ts +3 -3
- package/dist/ts4.2/src/room/track/LocalVideoTrack.d.ts +6 -0
- package/dist/ts4.2/src/room/track/processor/types.d.ts +14 -2
- package/dist/ts4.2/src/room/types.d.ts +1 -1
- package/package.json +15 -16
- package/src/api/SignalClient.ts +13 -9
- package/src/connectionHelper/checks/turn.ts +1 -0
- package/src/connectionHelper/checks/webrtc.ts +9 -7
- package/src/connectionHelper/checks/websocket.ts +1 -0
- package/src/e2ee/E2eeManager.ts +129 -76
- package/src/e2ee/KeyProvider.ts +31 -16
- package/src/e2ee/constants.ts +3 -0
- package/src/e2ee/events.ts +48 -0
- package/src/e2ee/index.ts +1 -0
- package/src/e2ee/types.ts +27 -41
- package/src/e2ee/utils.ts +9 -0
- package/src/e2ee/worker/FrameCryptor.ts +90 -47
- package/src/e2ee/worker/ParticipantKeyHandler.ts +25 -26
- package/src/e2ee/worker/SifGuard.ts +47 -0
- package/src/e2ee/worker/e2ee.worker.ts +75 -68
- package/src/options.ts +6 -0
- package/src/proto/livekit_models_pb.ts +14 -0
- package/src/proto/livekit_rtc_pb.ts +14 -0
- package/src/room/DeviceManager.ts +7 -2
- package/src/room/PCTransport.ts +12 -2
- package/src/room/RTCEngine.ts +3 -2
- package/src/room/Room.ts +47 -22
- package/src/room/defaults.ts +1 -0
- package/src/room/participant/LocalParticipant.ts +18 -2
- package/src/room/participant/Participant.ts +16 -0
- package/src/room/participant/RemoteParticipant.ts +0 -12
- package/src/room/track/LocalAudioTrack.ts +45 -0
- package/src/room/track/LocalTrack.ts +22 -14
- package/src/room/track/LocalVideoTrack.ts +39 -0
- package/src/room/track/RemoteAudioTrack.ts +9 -1
- package/src/room/track/RemoteTrackPublication.ts +2 -2
- package/src/room/track/facingMode.ts +1 -1
- package/src/room/track/processor/types.ts +18 -2
- package/src/room/types.ts +5 -1
package/src/e2ee/KeyProvider.ts
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
import { EventEmitter } from 'events';
|
2
2
|
import type TypedEventEmitter from 'typed-emitter';
|
3
|
+
import log from '../logger';
|
3
4
|
import { KEY_PROVIDER_DEFAULTS } from './constants';
|
4
|
-
import
|
5
|
-
import {
|
5
|
+
import { type KeyProviderCallbacks, KeyProviderEvent } from './events';
|
6
|
+
import type { KeyInfo, KeyProviderOptions } from './types';
|
7
|
+
import { createKeyMaterialFromBuffer, createKeyMaterialFromString } from './utils';
|
6
8
|
|
7
9
|
/**
|
8
10
|
* @experimental
|
@@ -16,29 +18,29 @@ export class BaseKeyProvider extends (EventEmitter as new () => TypedEventEmitte
|
|
16
18
|
super();
|
17
19
|
this.keyInfoMap = new Map();
|
18
20
|
this.options = { ...KEY_PROVIDER_DEFAULTS, ...options };
|
19
|
-
this.on(
|
21
|
+
this.on(KeyProviderEvent.KeyRatcheted, this.onKeyRatcheted);
|
20
22
|
}
|
21
23
|
|
22
24
|
/**
|
23
25
|
* callback to invoke once a key has been set for a participant
|
24
26
|
* @param key
|
25
|
-
* @param
|
27
|
+
* @param participantIdentity
|
26
28
|
* @param keyIndex
|
27
29
|
*/
|
28
|
-
protected onSetEncryptionKey(key: CryptoKey,
|
29
|
-
const keyInfo: KeyInfo = { key,
|
30
|
-
this.keyInfoMap.set(`${
|
31
|
-
this.emit(
|
30
|
+
protected onSetEncryptionKey(key: CryptoKey, participantIdentity?: string, keyIndex?: number) {
|
31
|
+
const keyInfo: KeyInfo = { key, participantIdentity, keyIndex };
|
32
|
+
this.keyInfoMap.set(`${participantIdentity ?? 'shared'}-${keyIndex ?? 0}`, keyInfo);
|
33
|
+
this.emit(KeyProviderEvent.SetKey, keyInfo);
|
32
34
|
}
|
33
35
|
|
34
36
|
/**
|
35
|
-
* callback being invoked after a ratchet request has been performed on
|
37
|
+
* callback being invoked after a ratchet request has been performed on a participant
|
36
38
|
* that surfaces the new key material.
|
37
39
|
* @param material
|
38
40
|
* @param keyIndex
|
39
41
|
*/
|
40
42
|
protected onKeyRatcheted = (material: CryptoKey, keyIndex?: number) => {
|
41
|
-
|
43
|
+
log.debug('key ratcheted event received', { material, keyIndex });
|
42
44
|
};
|
43
45
|
|
44
46
|
getKeys() {
|
@@ -49,8 +51,8 @@ export class BaseKeyProvider extends (EventEmitter as new () => TypedEventEmitte
|
|
49
51
|
return this.options;
|
50
52
|
}
|
51
53
|
|
52
|
-
ratchetKey(
|
53
|
-
this.emit(
|
54
|
+
ratchetKey(participantIdentity?: string, keyIndex?: number) {
|
55
|
+
this.emit(KeyProviderEvent.RatchetRequest, participantIdentity, keyIndex);
|
54
56
|
}
|
55
57
|
}
|
56
58
|
|
@@ -63,16 +65,29 @@ export class ExternalE2EEKeyProvider extends BaseKeyProvider {
|
|
63
65
|
ratchetInterval: number | undefined;
|
64
66
|
|
65
67
|
constructor(options: Partial<Omit<KeyProviderOptions, 'sharedKey'>> = {}) {
|
66
|
-
const opts: Partial<KeyProviderOptions> = {
|
68
|
+
const opts: Partial<KeyProviderOptions> = {
|
69
|
+
...options,
|
70
|
+
sharedKey: true,
|
71
|
+
// for a shared key provider failing to decrypt for a specific participant
|
72
|
+
// should not mark the key as invalid, so we accept wrong keys forever
|
73
|
+
// and won't try to auto-ratchet
|
74
|
+
ratchetWindowSize: 0,
|
75
|
+
failureTolerance: -1,
|
76
|
+
};
|
67
77
|
super(opts);
|
68
78
|
}
|
69
79
|
|
70
80
|
/**
|
71
|
-
* Accepts a passphrase that's used to create the crypto keys
|
81
|
+
* Accepts a passphrase that's used to create the crypto keys.
|
82
|
+
* When passing in a string, PBKDF2 is used.
|
83
|
+
* When passing in an Array buffer of cryptographically random numbers, HKDF is being used. (recommended)
|
72
84
|
* @param key
|
73
85
|
*/
|
74
|
-
async setKey(key: string) {
|
75
|
-
const derivedKey =
|
86
|
+
async setKey(key: string | ArrayBuffer) {
|
87
|
+
const derivedKey =
|
88
|
+
typeof key === 'string'
|
89
|
+
? await createKeyMaterialFromString(key)
|
90
|
+
: await createKeyMaterialFromBuffer(key);
|
76
91
|
this.onSetEncryptionKey(derivedKey);
|
77
92
|
}
|
78
93
|
}
|
package/src/e2ee/constants.ts
CHANGED
@@ -0,0 +1,48 @@
|
|
1
|
+
import type Participant from '../room/participant/Participant';
|
2
|
+
import type { CryptorError } from './errors';
|
3
|
+
import type { KeyInfo } from './types';
|
4
|
+
|
5
|
+
export enum KeyProviderEvent {
|
6
|
+
SetKey = 'setKey',
|
7
|
+
RatchetRequest = 'ratchetRequest',
|
8
|
+
KeyRatcheted = 'keyRatcheted',
|
9
|
+
}
|
10
|
+
|
11
|
+
export type KeyProviderCallbacks = {
|
12
|
+
[KeyProviderEvent.SetKey]: (keyInfo: KeyInfo) => void;
|
13
|
+
[KeyProviderEvent.RatchetRequest]: (participantIdentity?: string, keyIndex?: number) => void;
|
14
|
+
[KeyProviderEvent.KeyRatcheted]: (material: CryptoKey, keyIndex?: number) => void;
|
15
|
+
};
|
16
|
+
|
17
|
+
export enum KeyHandlerEvent {
|
18
|
+
KeyRatcheted = 'keyRatcheted',
|
19
|
+
}
|
20
|
+
|
21
|
+
export type ParticipantKeyHandlerCallbacks = {
|
22
|
+
[KeyHandlerEvent.KeyRatcheted]: (
|
23
|
+
material: CryptoKey,
|
24
|
+
participantIdentity: string,
|
25
|
+
keyIndex?: number,
|
26
|
+
) => void;
|
27
|
+
};
|
28
|
+
|
29
|
+
export enum EncryptionEvent {
|
30
|
+
ParticipantEncryptionStatusChanged = 'participantEncryptionStatusChanged',
|
31
|
+
EncryptionError = 'encryptionError',
|
32
|
+
}
|
33
|
+
|
34
|
+
export type E2EEManagerCallbacks = {
|
35
|
+
[EncryptionEvent.ParticipantEncryptionStatusChanged]: (
|
36
|
+
enabled: boolean,
|
37
|
+
participant: Participant,
|
38
|
+
) => void;
|
39
|
+
[EncryptionEvent.EncryptionError]: (error: Error) => void;
|
40
|
+
};
|
41
|
+
|
42
|
+
export type CryptorCallbacks = {
|
43
|
+
[CryptorEvent.Error]: (error: CryptorError) => void;
|
44
|
+
};
|
45
|
+
|
46
|
+
export enum CryptorEvent {
|
47
|
+
Error = 'cryptorError',
|
48
|
+
}
|
package/src/e2ee/index.ts
CHANGED
package/src/e2ee/types.ts
CHANGED
@@ -1,7 +1,5 @@
|
|
1
|
-
import type Participant from '../room/participant/Participant';
|
2
1
|
import type { VideoCodec } from '../room/track/options';
|
3
2
|
import type { BaseKeyProvider } from './KeyProvider';
|
4
|
-
import type { CryptorError } from './errors';
|
5
3
|
|
6
4
|
export interface BaseMessage {
|
7
5
|
kind: string;
|
@@ -18,7 +16,8 @@ export interface InitMessage extends BaseMessage {
|
|
18
16
|
export interface SetKeyMessage extends BaseMessage {
|
19
17
|
kind: 'setKey';
|
20
18
|
data: {
|
21
|
-
|
19
|
+
participantIdentity?: string;
|
20
|
+
isPublisher: boolean;
|
22
21
|
key: CryptoKey;
|
23
22
|
keyIndex?: number;
|
24
23
|
};
|
@@ -28,13 +27,21 @@ export interface RTPVideoMapMessage extends BaseMessage {
|
|
28
27
|
kind: 'setRTPMap';
|
29
28
|
data: {
|
30
29
|
map: Map<number, VideoCodec>;
|
30
|
+
participantIdentity: string;
|
31
|
+
};
|
32
|
+
}
|
33
|
+
|
34
|
+
export interface SifTrailerMessage extends BaseMessage {
|
35
|
+
kind: 'setSifTrailer';
|
36
|
+
data: {
|
37
|
+
trailer: Uint8Array;
|
31
38
|
};
|
32
39
|
}
|
33
40
|
|
34
41
|
export interface EncodeMessage extends BaseMessage {
|
35
42
|
kind: 'decode' | 'encode';
|
36
43
|
data: {
|
37
|
-
|
44
|
+
participantIdentity: string;
|
38
45
|
readableStream: ReadableStream;
|
39
46
|
writableStream: WritableStream;
|
40
47
|
trackId: string;
|
@@ -45,7 +52,7 @@ export interface EncodeMessage extends BaseMessage {
|
|
45
52
|
export interface RemoveTransformMessage extends BaseMessage {
|
46
53
|
kind: 'removeTransform';
|
47
54
|
data: {
|
48
|
-
|
55
|
+
participantIdentity: string;
|
49
56
|
trackId: string;
|
50
57
|
};
|
51
58
|
}
|
@@ -53,7 +60,7 @@ export interface RemoveTransformMessage extends BaseMessage {
|
|
53
60
|
export interface UpdateCodecMessage extends BaseMessage {
|
54
61
|
kind: 'updateCodec';
|
55
62
|
data: {
|
56
|
-
|
63
|
+
participantIdentity: string;
|
57
64
|
trackId: string;
|
58
65
|
codec: VideoCodec;
|
59
66
|
};
|
@@ -62,7 +69,7 @@ export interface UpdateCodecMessage extends BaseMessage {
|
|
62
69
|
export interface RatchetRequestMessage extends BaseMessage {
|
63
70
|
kind: 'ratchetRequest';
|
64
71
|
data: {
|
65
|
-
|
72
|
+
participantIdentity?: string;
|
66
73
|
keyIndex?: number;
|
67
74
|
};
|
68
75
|
}
|
@@ -70,7 +77,7 @@ export interface RatchetRequestMessage extends BaseMessage {
|
|
70
77
|
export interface RatchetMessage extends BaseMessage {
|
71
78
|
kind: 'ratchetKey';
|
72
79
|
data: {
|
73
|
-
|
80
|
+
participantIdentity: string;
|
74
81
|
keyIndex?: number;
|
75
82
|
material: CryptoKey;
|
76
83
|
};
|
@@ -86,8 +93,14 @@ export interface ErrorMessage extends BaseMessage {
|
|
86
93
|
export interface EnableMessage extends BaseMessage {
|
87
94
|
kind: 'enable';
|
88
95
|
data: {
|
89
|
-
|
90
|
-
|
96
|
+
participantIdentity: string;
|
97
|
+
enabled: boolean;
|
98
|
+
};
|
99
|
+
}
|
100
|
+
|
101
|
+
export interface InitAck extends BaseMessage {
|
102
|
+
kind: 'initAck';
|
103
|
+
data: {
|
91
104
|
enabled: boolean;
|
92
105
|
};
|
93
106
|
}
|
@@ -102,7 +115,9 @@ export type E2EEWorkerMessage =
|
|
102
115
|
| RTPVideoMapMessage
|
103
116
|
| UpdateCodecMessage
|
104
117
|
| RatchetRequestMessage
|
105
|
-
| RatchetMessage
|
118
|
+
| RatchetMessage
|
119
|
+
| SifTrailerMessage
|
120
|
+
| InitAck;
|
106
121
|
|
107
122
|
export type KeySet = { material: CryptoKey; encryptionKey: CryptoKey };
|
108
123
|
|
@@ -113,38 +128,9 @@ export type KeyProviderOptions = {
|
|
113
128
|
failureTolerance: number;
|
114
129
|
};
|
115
130
|
|
116
|
-
export type KeyProviderCallbacks = {
|
117
|
-
setKey: (keyInfo: KeyInfo) => void;
|
118
|
-
ratchetRequest: (participantId?: string, keyIndex?: number) => void;
|
119
|
-
/** currently only emitted for local participant */
|
120
|
-
keyRatcheted: (material: CryptoKey, keyIndex?: number) => void;
|
121
|
-
};
|
122
|
-
|
123
|
-
export type ParticipantKeyHandlerCallbacks = {
|
124
|
-
keyRatcheted: (material: CryptoKey, keyIndex?: number, participantId?: string) => void;
|
125
|
-
};
|
126
|
-
|
127
|
-
export type E2EEManagerCallbacks = {
|
128
|
-
participantEncryptionStatusChanged: (enabled: boolean, participant?: Participant) => void;
|
129
|
-
encryptionError: (error: Error) => void;
|
130
|
-
};
|
131
|
-
|
132
|
-
export const EncryptionEvent = {
|
133
|
-
ParticipantEncryptionStatusChanged: 'participantEncryptionStatusChanged',
|
134
|
-
Error: 'encryptionError',
|
135
|
-
} as const;
|
136
|
-
|
137
|
-
export type CryptorCallbacks = {
|
138
|
-
cryptorError: (error: CryptorError) => void;
|
139
|
-
};
|
140
|
-
|
141
|
-
export const CryptorEvent = {
|
142
|
-
Error: 'cryptorError',
|
143
|
-
} as const;
|
144
|
-
|
145
131
|
export type KeyInfo = {
|
146
132
|
key: CryptoKey;
|
147
|
-
|
133
|
+
participantIdentity?: string;
|
148
134
|
keyIndex?: number;
|
149
135
|
};
|
150
136
|
|
package/src/e2ee/utils.ts
CHANGED
@@ -56,6 +56,15 @@ export async function createKeyMaterialFromString(password: string) {
|
|
56
56
|
return keyMaterial;
|
57
57
|
}
|
58
58
|
|
59
|
+
export async function createKeyMaterialFromBuffer(cryptoBuffer: ArrayBuffer) {
|
60
|
+
const keyMaterial = await crypto.subtle.importKey('raw', cryptoBuffer, 'HKDF', false, [
|
61
|
+
'deriveBits',
|
62
|
+
'deriveKey',
|
63
|
+
]);
|
64
|
+
|
65
|
+
return keyMaterial;
|
66
|
+
}
|
67
|
+
|
59
68
|
function getAlgoOptions(algorithmName: string, salt: string) {
|
60
69
|
const textEncoder = new TextEncoder();
|
61
70
|
const encodedSalt = textEncoder.encode(salt);
|
@@ -6,15 +6,13 @@ 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';
|
13
|
+
import { SifGuard } from './SifGuard';
|
14
|
+
|
15
|
+
export const encryptionEnabledMap: Map<string, boolean> = new Map();
|
18
16
|
|
19
17
|
export interface FrameCryptorConstructor {
|
20
18
|
new (opts?: unknown): BaseFrameCryptor;
|
@@ -28,14 +26,14 @@ export interface TransformerInfo {
|
|
28
26
|
}
|
29
27
|
|
30
28
|
export class BaseFrameCryptor extends (EventEmitter as new () => TypedEventEmitter<CryptorCallbacks>) {
|
31
|
-
encodeFunction(
|
29
|
+
protected encodeFunction(
|
32
30
|
encodedFrame: RTCEncodedVideoFrame | RTCEncodedAudioFrame,
|
33
31
|
controller: TransformStreamDefaultController,
|
34
32
|
): Promise<any> {
|
35
33
|
throw Error('not implemented for subclass');
|
36
34
|
}
|
37
35
|
|
38
|
-
decodeFunction(
|
36
|
+
protected decodeFunction(
|
39
37
|
encodedFrame: RTCEncodedVideoFrame | RTCEncodedAudioFrame,
|
40
38
|
controller: TransformStreamDefaultController,
|
41
39
|
): Promise<any> {
|
@@ -50,7 +48,7 @@ export class BaseFrameCryptor extends (EventEmitter as new () => TypedEventEmitt
|
|
50
48
|
export class FrameCryptor extends BaseFrameCryptor {
|
51
49
|
private sendCounts: Map<number, number>;
|
52
50
|
|
53
|
-
private
|
51
|
+
private participantIdentity: string | undefined;
|
54
52
|
|
55
53
|
private trackId: string | undefined;
|
56
54
|
|
@@ -65,22 +63,24 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
65
63
|
/**
|
66
64
|
* used for detecting server injected unencrypted frames
|
67
65
|
*/
|
68
|
-
private
|
66
|
+
private sifTrailer: Uint8Array;
|
67
|
+
|
68
|
+
private sifGuard: SifGuard;
|
69
69
|
|
70
70
|
constructor(opts: {
|
71
71
|
keys: ParticipantKeyHandler;
|
72
|
-
|
72
|
+
participantIdentity: string;
|
73
73
|
keyProviderOptions: KeyProviderOptions;
|
74
|
-
|
74
|
+
sifTrailer?: Uint8Array;
|
75
75
|
}) {
|
76
76
|
super();
|
77
77
|
this.sendCounts = new Map();
|
78
78
|
this.keys = opts.keys;
|
79
|
-
this.
|
79
|
+
this.participantIdentity = opts.participantIdentity;
|
80
80
|
this.rtpMap = new Map();
|
81
81
|
this.keyProviderOptions = opts.keyProviderOptions;
|
82
|
-
this.
|
83
|
-
|
82
|
+
this.sifTrailer = opts.sifTrailer ?? Uint8Array.from([]);
|
83
|
+
this.sifGuard = new SifGuard();
|
84
84
|
}
|
85
85
|
|
86
86
|
/**
|
@@ -90,16 +90,25 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
90
90
|
* @param keys
|
91
91
|
*/
|
92
92
|
setParticipant(id: string, keys: ParticipantKeyHandler) {
|
93
|
-
this.
|
93
|
+
this.participantIdentity = id;
|
94
94
|
this.keys = keys;
|
95
|
+
this.sifGuard.reset();
|
95
96
|
}
|
96
97
|
|
97
98
|
unsetParticipant() {
|
98
|
-
this.
|
99
|
+
this.participantIdentity = undefined;
|
100
|
+
}
|
101
|
+
|
102
|
+
isEnabled() {
|
103
|
+
if (this.participantIdentity) {
|
104
|
+
return encryptionEnabledMap.get(this.participantIdentity);
|
105
|
+
} else {
|
106
|
+
return undefined;
|
107
|
+
}
|
99
108
|
}
|
100
109
|
|
101
|
-
|
102
|
-
return this.
|
110
|
+
getParticipantIdentity() {
|
111
|
+
return this.participantIdentity;
|
103
112
|
}
|
104
113
|
|
105
114
|
getTrackId() {
|
@@ -130,9 +139,10 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
130
139
|
codec?: VideoCodec,
|
131
140
|
) {
|
132
141
|
if (codec) {
|
133
|
-
|
142
|
+
workerLogger.info('setting codec on cryptor to', { codec });
|
134
143
|
this.videoCodec = codec;
|
135
144
|
}
|
145
|
+
|
136
146
|
const transformFn = operation === 'encode' ? this.encodeFunction : this.decodeFunction;
|
137
147
|
const transformStream = new TransformStream({
|
138
148
|
transform: transformFn.bind(this),
|
@@ -142,12 +152,16 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
142
152
|
.pipeThrough(transformStream)
|
143
153
|
.pipeTo(writable)
|
144
154
|
.catch((e) => {
|
145
|
-
|
146
|
-
this.emit(
|
155
|
+
workerLogger.warn(e);
|
156
|
+
this.emit(CryptorEvent.Error, e instanceof CryptorError ? e : new CryptorError(e.message));
|
147
157
|
});
|
148
158
|
this.trackId = trackId;
|
149
159
|
}
|
150
160
|
|
161
|
+
setSifTrailer(trailer: Uint8Array) {
|
162
|
+
this.sifTrailer = trailer;
|
163
|
+
}
|
164
|
+
|
151
165
|
/**
|
152
166
|
* Function that will be injected in a stream and will encrypt the given encoded frames.
|
153
167
|
*
|
@@ -170,19 +184,26 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
170
184
|
* 8) Append a single byte for the key identifier.
|
171
185
|
* 9) Enqueue the encrypted frame for sending.
|
172
186
|
*/
|
173
|
-
async encodeFunction(
|
187
|
+
protected async encodeFunction(
|
174
188
|
encodedFrame: RTCEncodedVideoFrame | RTCEncodedAudioFrame,
|
175
189
|
controller: TransformStreamDefaultController,
|
176
190
|
) {
|
177
191
|
if (
|
178
|
-
!this.
|
192
|
+
!this.isEnabled() ||
|
179
193
|
// skip for encryption for empty dtx frames
|
180
194
|
encodedFrame.data.byteLength === 0
|
181
195
|
) {
|
182
196
|
return controller.enqueue(encodedFrame);
|
183
197
|
}
|
184
|
-
|
185
|
-
|
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;
|
186
207
|
const keyIndex = this.keys.getCurrentKeyIndex();
|
187
208
|
|
188
209
|
if (encryptionKey) {
|
@@ -253,19 +274,31 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
253
274
|
* @param {RTCEncodedVideoFrame|RTCEncodedAudioFrame} encodedFrame - Encoded video frame.
|
254
275
|
* @param {TransformStreamDefaultController} controller - TransportStreamController.
|
255
276
|
*/
|
256
|
-
async decodeFunction(
|
277
|
+
protected async decodeFunction(
|
257
278
|
encodedFrame: RTCEncodedVideoFrame | RTCEncodedAudioFrame,
|
258
279
|
controller: TransformStreamDefaultController,
|
259
280
|
) {
|
260
281
|
if (
|
261
|
-
!this.
|
282
|
+
!this.isEnabled() ||
|
262
283
|
// skip for decryption for empty dtx frames
|
263
|
-
encodedFrame.data.byteLength === 0
|
264
|
-
// skip decryption if frame is server injected
|
265
|
-
isFrameServerInjected(encodedFrame.data, this.unencryptedFrameByteTrailer)
|
284
|
+
encodedFrame.data.byteLength === 0
|
266
285
|
) {
|
286
|
+
this.sifGuard.recordUserFrame();
|
267
287
|
return controller.enqueue(encodedFrame);
|
268
288
|
}
|
289
|
+
|
290
|
+
if (isFrameServerInjected(encodedFrame.data, this.sifTrailer)) {
|
291
|
+
this.sifGuard.recordSif();
|
292
|
+
|
293
|
+
if (this.sifGuard.isSifAllowed()) {
|
294
|
+
return controller.enqueue(encodedFrame);
|
295
|
+
} else {
|
296
|
+
workerLogger.warn('SIF limit reached, dropping frame');
|
297
|
+
return;
|
298
|
+
}
|
299
|
+
} else {
|
300
|
+
this.sifGuard.recordUserFrame();
|
301
|
+
}
|
269
302
|
const data = new Uint8Array(encodedFrame.data);
|
270
303
|
const keyIndex = data[encodedFrame.data.byteLength - 1];
|
271
304
|
|
@@ -279,11 +312,10 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
279
312
|
} catch (error) {
|
280
313
|
if (error instanceof CryptorError && error.reason === CryptorErrorReason.InvalidKey) {
|
281
314
|
if (this.keys.hasValidKey) {
|
282
|
-
workerLogger.warn('invalid key');
|
283
315
|
this.emit(
|
284
316
|
CryptorEvent.Error,
|
285
317
|
new CryptorError(
|
286
|
-
`invalid key for participant ${this.
|
318
|
+
`invalid key for participant ${this.participantIdentity}`,
|
287
319
|
CryptorErrorReason.InvalidKey,
|
288
320
|
),
|
289
321
|
);
|
@@ -293,22 +325,33 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
293
325
|
workerLogger.warn('decoding frame failed', { error });
|
294
326
|
}
|
295
327
|
}
|
328
|
+
} else if (!this.keys.getKeySet(keyIndex) && this.keys.hasValidKey) {
|
329
|
+
// emit an error in case the key index is out of bounds but the key handler thinks we still have a valid key
|
330
|
+
workerLogger.warn('skipping decryption due to missing key at index');
|
331
|
+
this.emit(
|
332
|
+
CryptorEvent.Error,
|
333
|
+
new CryptorError(
|
334
|
+
`missing key at index for participant ${this.participantIdentity}`,
|
335
|
+
CryptorErrorReason.MissingKey,
|
336
|
+
),
|
337
|
+
);
|
296
338
|
}
|
297
|
-
|
298
|
-
return controller.enqueue(encodedFrame);
|
299
339
|
}
|
300
340
|
|
301
341
|
/**
|
302
342
|
* Function that will decrypt the given encoded frame. If the decryption fails, it will
|
303
343
|
* ratchet the key for up to RATCHET_WINDOW_SIZE times.
|
304
344
|
*/
|
305
|
-
async decryptFrame(
|
345
|
+
private async decryptFrame(
|
306
346
|
encodedFrame: RTCEncodedVideoFrame | RTCEncodedAudioFrame,
|
307
347
|
keyIndex: number,
|
308
348
|
initialMaterial: KeySet | undefined = undefined,
|
309
349
|
ratchetOpts: DecodeRatchetOptions = { ratchetCount: 0 },
|
310
350
|
): Promise<RTCEncodedVideoFrame | RTCEncodedAudioFrame | undefined> {
|
311
351
|
const keySet = this.keys.getKeySet(keyIndex);
|
352
|
+
if (!ratchetOpts.encryptionKey && !keySet) {
|
353
|
+
throw new TypeError(`no encryption key found for decryption of ${this.participantIdentity}`);
|
354
|
+
}
|
312
355
|
|
313
356
|
// Construct frame trailer. Similar to the frame header described in
|
314
357
|
// https://tools.ietf.org/html/draft-omara-sframe-00#section-4.2
|
@@ -344,7 +387,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
344
387
|
iv,
|
345
388
|
additionalData: new Uint8Array(encodedFrame.data, 0, frameHeader.byteLength),
|
346
389
|
},
|
347
|
-
ratchetOpts.encryptionKey ?? keySet
|
390
|
+
ratchetOpts.encryptionKey ?? keySet!.encryptionKey,
|
348
391
|
new Uint8Array(encodedFrame.data, cipherTextStart, cipherTextLength),
|
349
392
|
);
|
350
393
|
|
@@ -397,13 +440,10 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
397
440
|
this.keys.setKeyFromMaterial(initialMaterial.material, keyIndex);
|
398
441
|
}
|
399
442
|
|
400
|
-
workerLogger.warn('maximum ratchet attempts exceeded
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
`valid key missing for participant ${this.participantId}`,
|
405
|
-
CryptorErrorReason.MissingKey,
|
406
|
-
),
|
443
|
+
workerLogger.warn('maximum ratchet attempts exceeded');
|
444
|
+
throw new CryptorError(
|
445
|
+
`valid key missing for participant ${this.participantIdentity}`,
|
446
|
+
CryptorErrorReason.InvalidKey,
|
407
447
|
);
|
408
448
|
}
|
409
449
|
} else {
|
@@ -455,7 +495,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
455
495
|
return iv;
|
456
496
|
}
|
457
497
|
|
458
|
-
getUnencryptedBytes(frame: RTCEncodedVideoFrame | RTCEncodedAudioFrame): number {
|
498
|
+
private getUnencryptedBytes(frame: RTCEncodedVideoFrame | RTCEncodedAudioFrame): number {
|
459
499
|
if (isVideoFrame(frame)) {
|
460
500
|
let detectedCodec = this.getVideoCodec(frame) ?? this.videoCodec;
|
461
501
|
|
@@ -504,7 +544,7 @@ export class FrameCryptor extends BaseFrameCryptor {
|
|
504
544
|
/**
|
505
545
|
* inspects frame payloadtype if available and maps it to the codec specified in rtpMap
|
506
546
|
*/
|
507
|
-
getVideoCodec(frame: RTCEncodedVideoFrame): VideoCodec | undefined {
|
547
|
+
private getVideoCodec(frame: RTCEncodedVideoFrame): VideoCodec | undefined {
|
508
548
|
if (this.rtpMap.size === 0) {
|
509
549
|
return undefined;
|
510
550
|
}
|
@@ -605,6 +645,9 @@ export enum NALUType {
|
|
605
645
|
* @internal
|
606
646
|
*/
|
607
647
|
export function isFrameServerInjected(frameData: ArrayBuffer, trailerBytes: Uint8Array): boolean {
|
648
|
+
if (trailerBytes.byteLength === 0) {
|
649
|
+
return false;
|
650
|
+
}
|
608
651
|
const frameTrailer = new Uint8Array(
|
609
652
|
frameData.slice(frameData.byteLength - trailerBytes.byteLength),
|
610
653
|
);
|