|
@@ -4,39 +4,8 @@
|
4
|
4
|
// Worker for E2EE/Insertable streams.
|
5
|
5
|
//
|
6
|
6
|
|
7
|
|
-/**
|
8
|
|
- * Polyfill RTCEncoded(Audio|Video)Frame.getMetadata() (not available in M83, available M84+).
|
9
|
|
- * The polyfill can not be done on the prototype since its not exposed in workers. Instead,
|
10
|
|
- * it is done as another transformation to keep it separate.
|
11
|
|
- */
|
12
|
|
-function polyFillEncodedFrameMetadata(encodedFrame, controller) {
|
13
|
|
- if (!encodedFrame.getMetadata) {
|
14
|
|
- encodedFrame.getMetadata = function() {
|
15
|
|
- return {
|
16
|
|
- // TODO: provide a more complete polyfill based on additionalData for video.
|
17
|
|
- synchronizationSource: this.synchronizationSource,
|
18
|
|
- contributingSources: this.contributingSources
|
19
|
|
- };
|
20
|
|
- };
|
21
|
|
- }
|
22
|
|
- controller.enqueue(encodedFrame);
|
23
|
|
-}
|
24
|
|
-
|
25
|
|
-/**
|
26
|
|
- * Compares two byteArrays for equality.
|
27
|
|
- */
|
28
|
|
-function isArrayEqual(a1, a2) {
|
29
|
|
- if (a1.byteLength !== a2.byteLength) {
|
30
|
|
- return false;
|
31
|
|
- }
|
32
|
|
- for (let i = 0; i < a1.byteLength; i++) {
|
33
|
|
- if (a1[i] !== a2[i]) {
|
34
|
|
- return false;
|
35
|
|
- }
|
36
|
|
- }
|
37
|
|
-
|
38
|
|
- return true;
|
39
|
|
-}
|
|
7
|
+import { deriveKeys, importKey, ratchet } from './crypto-utils';
|
|
8
|
+import { polyFillEncodedFrameMetadata, isArrayEqual } from './utils';
|
40
|
9
|
|
41
|
10
|
// We use a ringbuffer of keys so we can change them and still decode packets that were
|
42
|
11
|
// encrypted with an old key.
|
|
@@ -74,69 +43,6 @@ const digestLength = {
|
74
|
43
|
// tag on a remote packet does not match the current key.
|
75
|
44
|
const ratchetWindow = 8;
|
76
|
45
|
|
77
|
|
-/**
|
78
|
|
- * Derives a set of keys from the master key.
|
79
|
|
- * @param {CryptoKey} material - master key to derive from
|
80
|
|
- *
|
81
|
|
- * See https://tools.ietf.org/html/draft-omara-sframe-00#section-4.3.1
|
82
|
|
- */
|
83
|
|
-async function deriveKeys(material) {
|
84
|
|
- const info = new ArrayBuffer();
|
85
|
|
- const textEncoder = new TextEncoder();
|
86
|
|
-
|
87
|
|
- // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveKey#HKDF
|
88
|
|
- // https://developer.mozilla.org/en-US/docs/Web/API/HkdfParams
|
89
|
|
- const encryptionKey = await crypto.subtle.deriveKey({
|
90
|
|
- name: 'HKDF',
|
91
|
|
- salt: textEncoder.encode('JFrameEncryptionKey'),
|
92
|
|
- hash: 'SHA-256',
|
93
|
|
- info
|
94
|
|
- }, material, {
|
95
|
|
- name: 'AES-CTR',
|
96
|
|
- length: 128
|
97
|
|
- }, false, [ 'encrypt', 'decrypt' ]);
|
98
|
|
- const authenticationKey = await crypto.subtle.deriveKey({
|
99
|
|
- name: 'HKDF',
|
100
|
|
- salt: textEncoder.encode('JFrameAuthenticationKey'),
|
101
|
|
- hash: 'SHA-256',
|
102
|
|
- info
|
103
|
|
- }, material, {
|
104
|
|
- name: 'HMAC',
|
105
|
|
- hash: 'SHA-256'
|
106
|
|
- }, false, [ 'sign' ]);
|
107
|
|
- const saltKey = await crypto.subtle.deriveBits({
|
108
|
|
- name: 'HKDF',
|
109
|
|
- salt: textEncoder.encode('JFrameSaltKey'),
|
110
|
|
- hash: 'SHA-256',
|
111
|
|
- info
|
112
|
|
- }, material, 128);
|
113
|
|
-
|
114
|
|
- return {
|
115
|
|
- material,
|
116
|
|
- encryptionKey,
|
117
|
|
- authenticationKey,
|
118
|
|
- saltKey
|
119
|
|
- };
|
120
|
|
-}
|
121
|
|
-
|
122
|
|
-/**
|
123
|
|
- * Ratchets a key. See
|
124
|
|
- * https://tools.ietf.org/html/draft-omara-sframe-00#section-4.3.5.1
|
125
|
|
- * @param {CryptoKey} material - base key material
|
126
|
|
- * @returns {ArrayBuffer} - ratcheted key material
|
127
|
|
- */
|
128
|
|
-async function ratchet(material) {
|
129
|
|
- const textEncoder = new TextEncoder();
|
130
|
|
-
|
131
|
|
- return crypto.subtle.deriveBits({
|
132
|
|
- name: 'HKDF',
|
133
|
|
- salt: textEncoder.encode('JFrameRatchetKey'),
|
134
|
|
- hash: 'SHA-256',
|
135
|
|
- info: new ArrayBuffer()
|
136
|
|
- }, material, 256);
|
137
|
|
-}
|
138
|
|
-
|
139
|
|
-
|
140
|
46
|
/**
|
141
|
47
|
* Per-participant context holding the cryptographic keys and
|
142
|
48
|
* encode/decode functions
|
|
@@ -170,9 +76,7 @@ class Context {
|
170
|
76
|
let newKey;
|
171
|
77
|
|
172
|
78
|
if (keyBytes) {
|
173
|
|
- // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey
|
174
|
|
- const material = await crypto.subtle.importKey('raw', keyBytes,
|
175
|
|
- 'HKDF', false, [ 'deriveBits', 'deriveKey' ]);
|
|
79
|
+ const material = await importKey(keyBytes);
|
176
|
80
|
|
177
|
81
|
newKey = await deriveKeys(material);
|
178
|
82
|
} else {
|
|
@@ -186,6 +90,7 @@ class Context {
|
186
|
90
|
* Sets a set of keys and resets the sendCount.
|
187
|
91
|
* decryption.
|
188
|
92
|
* @param {Object} keys set of keys.
|
|
93
|
+ * @private
|
189
|
94
|
*/
|
190
|
95
|
_setKeys(keys) {
|
191
|
96
|
this._cryptoKeyRing[this._currentKeyIndex] = keys;
|
|
@@ -346,8 +251,7 @@ class Context {
|
346
|
251
|
}
|
347
|
252
|
|
348
|
253
|
// Attempt to ratchet and generate the next set of keys.
|
349
|
|
- material = await crypto.subtle.importKey('raw', await ratchet(material),
|
350
|
|
- 'HKDF', false, [ 'deriveBits', 'deriveKey' ]);
|
|
254
|
+ material = await importKey(await ratchet(material));
|
351
|
255
|
newKeys = await deriveKeys(material);
|
352
|
256
|
authenticationKey = newKeys.authenticationKey;
|
353
|
257
|
}
|