瀏覽代碼

e2ee: refactor and clean up

in particular this allows sharing the ratcheting between the E2EEncryption
module and the Worker.
dev1
Philipp Hancke 5 年之前
父節點
當前提交
12604d96ff
共有 4 個檔案被更改,包括 117 行新增108 行删除
  1. 3
    7
      modules/e2ee/E2EEncryption.js
  2. 5
    101
      modules/e2ee/Worker.js
  3. 74
    0
      modules/e2ee/crypto-utils.js
  4. 35
    0
      modules/e2ee/utils.js

+ 3
- 7
modules/e2ee/E2EEncryption.js 查看文件

9
 
9
 
10
 import E2EEContext from './E2EEContext';
10
 import E2EEContext from './E2EEContext';
11
 import { OlmAdapter } from './OlmAdapter';
11
 import { OlmAdapter } from './OlmAdapter';
12
+import { importKey, ratchet } from './crypto-utils';
12
 
13
 
13
 const logger = getLogger(__filename);
14
 const logger = getLogger(__filename);
14
 
15
 
225
     async _ratchetKeyImpl() {
226
     async _ratchetKeyImpl() {
226
         logger.debug('Ratchetting key');
227
         logger.debug('Ratchetting key');
227
 
228
 
228
-        const material = await crypto.subtle.importKey('raw', this._key, 'HKDF', false, [ 'deriveBits' ]);
229
-        const newKey = await crypto.subtle.deriveBits({
230
-            name: 'HKDF',
231
-            salt: new TextEncoder().encode('JFrameRatchetKey'),
232
-            hash: 'SHA-256',
233
-            info: new ArrayBuffer()
234
-        }, material, 256);
229
+        const material = await importKey(this._key);
230
+        const newKey = await ratchet(material);
235
 
231
 
236
         this._key = new Uint8Array(newKey);
232
         this._key = new Uint8Array(newKey);
237
 
233
 

+ 5
- 101
modules/e2ee/Worker.js 查看文件

4
 // Worker for E2EE/Insertable streams.
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
 // We use a ringbuffer of keys so we can change them and still decode packets that were
10
 // We use a ringbuffer of keys so we can change them and still decode packets that were
42
 // encrypted with an old key.
11
 // encrypted with an old key.
74
 // tag on a remote packet does not match the current key.
43
 // tag on a remote packet does not match the current key.
75
 const ratchetWindow = 8;
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
  * Per-participant context holding the cryptographic keys and
47
  * Per-participant context holding the cryptographic keys and
142
  * encode/decode functions
48
  * encode/decode functions
170
         let newKey;
76
         let newKey;
171
 
77
 
172
         if (keyBytes) {
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
             newKey = await deriveKeys(material);
81
             newKey = await deriveKeys(material);
178
         } else {
82
         } else {
186
      * Sets a set of keys and resets the sendCount.
90
      * Sets a set of keys and resets the sendCount.
187
      * decryption.
91
      * decryption.
188
      * @param {Object} keys set of keys.
92
      * @param {Object} keys set of keys.
93
+     * @private
189
      */
94
      */
190
     _setKeys(keys) {
95
     _setKeys(keys) {
191
         this._cryptoKeyRing[this._currentKeyIndex] = keys;
96
         this._cryptoKeyRing[this._currentKeyIndex] = keys;
346
                 }
251
                 }
347
 
252
 
348
                 // Attempt to ratchet and generate the next set of keys.
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
                 newKeys = await deriveKeys(material);
255
                 newKeys = await deriveKeys(material);
352
                 authenticationKey = newKeys.authenticationKey;
256
                 authenticationKey = newKeys.authenticationKey;
353
             }
257
             }

+ 74
- 0
modules/e2ee/crypto-utils.js 查看文件

1
+/**
2
+ * Derives a set of keys from the master key.
3
+ * @param {CryptoKey} material - master key to derive from
4
+ *
5
+ * See https://tools.ietf.org/html/draft-omara-sframe-00#section-4.3.1
6
+ */
7
+export async function deriveKeys(material) {
8
+    const info = new ArrayBuffer();
9
+    const textEncoder = new TextEncoder();
10
+
11
+    // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveKey#HKDF
12
+    // https://developer.mozilla.org/en-US/docs/Web/API/HkdfParams
13
+    const encryptionKey = await crypto.subtle.deriveKey({
14
+        name: 'HKDF',
15
+        salt: textEncoder.encode('JFrameEncryptionKey'),
16
+        hash: 'SHA-256',
17
+        info
18
+    }, material, {
19
+        name: 'AES-CTR',
20
+        length: 128
21
+    }, false, [ 'encrypt', 'decrypt' ]);
22
+    const authenticationKey = await crypto.subtle.deriveKey({
23
+        name: 'HKDF',
24
+        salt: textEncoder.encode('JFrameAuthenticationKey'),
25
+        hash: 'SHA-256',
26
+        info
27
+    }, material, {
28
+        name: 'HMAC',
29
+        hash: 'SHA-256'
30
+    }, false, [ 'sign' ]);
31
+    const saltKey = await crypto.subtle.deriveBits({
32
+        name: 'HKDF',
33
+        salt: textEncoder.encode('JFrameSaltKey'),
34
+        hash: 'SHA-256',
35
+        info
36
+    }, material, 128);
37
+
38
+    return {
39
+        material,
40
+        encryptionKey,
41
+        authenticationKey,
42
+        saltKey
43
+    };
44
+}
45
+
46
+/**
47
+ * Ratchets a key. See
48
+ * https://tools.ietf.org/html/draft-omara-sframe-00#section-4.3.5.1
49
+ * @param {CryptoKey} material - base key material
50
+ * @returns {ArrayBuffer} - ratcheted key material
51
+ */
52
+export async function ratchet(material) {
53
+    const textEncoder = new TextEncoder();
54
+
55
+    // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveBits
56
+    return crypto.subtle.deriveBits({
57
+        name: 'HKDF',
58
+        salt: textEncoder.encode('JFrameRatchetKey'),
59
+        hash: 'SHA-256',
60
+        info: new ArrayBuffer()
61
+    }, material, 256);
62
+}
63
+
64
+/**
65
+ * Converts a raw key into a WebCrypto key object with default options
66
+ * suitable for our usage.
67
+ * @param {ArrayBuffer} keyBytes - raw key
68
+ * @param {Array} keyUsages - key usages, see importKey documentation
69
+ * @returns {CryptoKey} - the WebCrypto key.
70
+ */
71
+export async function importKey(keyBytes) {
72
+    // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey
73
+    return crypto.subtle.importKey('raw', keyBytes, 'HKDF', false, [ 'deriveBits', 'deriveKey' ]);
74
+}

+ 35
- 0
modules/e2ee/utils.js 查看文件

1
+/**
2
+ * Polyfill RTCEncoded(Audio|Video)Frame.getMetadata() (not available in M83, available M84+).
3
+ * The polyfill can not be done on the prototype since its not exposed in workers. Instead,
4
+ * it is done as another transformation to keep it separate.
5
+ * TODO: remove when we decode to drop M83 support.
6
+ */
7
+export function polyFillEncodedFrameMetadata(encodedFrame, controller) {
8
+    if (!encodedFrame.getMetadata) {
9
+        encodedFrame.getMetadata = function() {
10
+            return {
11
+                // TODO: provide a more complete polyfill based on additionalData for video.
12
+                synchronizationSource: this.synchronizationSource,
13
+                contributingSources: this.contributingSources
14
+            };
15
+        };
16
+    }
17
+    controller.enqueue(encodedFrame);
18
+}
19
+
20
+/**
21
+ * Compares two byteArrays for equality.
22
+ */
23
+export function isArrayEqual(a1, a2) {
24
+    if (a1.byteLength !== a2.byteLength) {
25
+        return false;
26
+    }
27
+    for (let i = 0; i < a1.byteLength; i++) {
28
+        if (a1[i] !== a2[i]) {
29
+            return false;
30
+        }
31
+    }
32
+
33
+    return true;
34
+}
35
+

Loading…
取消
儲存