浏览代码

fix(e2ee) avoid restarting media sessions more than once

After enabling E2EE for the first time, keep the media transform
inplace, but make it a no-op.

This simplifies the flow a little since it doesn't require a media
session restart.

Way back when, there were performance concerns when having a transform,
which is what prompted us to implement the restar mechanism, but a lot
has changed since and both audio and video frames don't need to jump
threads these days. We are still not enabling an empty transform at all
times out of precaution.
master
Saúl Ibarra Corretgé 6 个月前
父节点
当前提交
f919c23aea

+ 22
- 13
modules/e2ee/Context.js 查看文件

@@ -1,5 +1,4 @@
1 1
 /* eslint-disable no-bitwise */
2
-/* global BigInt */
3 2
 
4 3
 import { deriveKeys, importKey, ratchet } from './crypto-utils';
5 4
 
@@ -49,6 +48,16 @@ export class Context {
49 48
         this._sendCounts = new Map();
50 49
 
51 50
         this._sharedKey = sharedKey;
51
+
52
+        this._enabled = false;
53
+    }
54
+
55
+    /**
56
+     * Enables or disables the E2EE context. When disabled packets are passed through.
57
+     * @param {boolean} enabled True if E2EE is enabled, false otherwise.
58
+     */
59
+    setEnabled(enabled) {
60
+        this._enabled = enabled;
52 61
     }
53 62
 
54 63
     /**
@@ -86,8 +95,6 @@ export class Context {
86 95
         }
87 96
 
88 97
         this._cryptoKeyRing[this._currentKeyIndex] = keys;
89
-
90
-        this._sendCount = BigInt(0); // eslint-disable-line new-cap
91 98
     }
92 99
 
93 100
     /**
@@ -113,12 +120,17 @@ export class Context {
113 120
      * 9) Enqueue the encrypted frame for sending.
114 121
      */
115 122
     encodeFunction(encodedFrame, controller) {
123
+        if (!this._enabled) {
124
+            return controller.enqueue(encodedFrame);
125
+        }
126
+
116 127
         const keyIndex = this._currentKeyIndex;
128
+        const currentKey = this._cryptoKeyRing[keyIndex];
117 129
 
118
-        if (this._cryptoKeyRing[keyIndex]) {
130
+        if (currentKey) {
119 131
             const iv = this._makeIV(encodedFrame.getMetadata().synchronizationSource, encodedFrame.timestamp);
120 132
 
121
-            // Thіs is not encrypted and contains the VP8 payload descriptor or the Opus TOC byte.
133
+            // This is not encrypted and contains the VP8 payload descriptor or the Opus TOC byte.
122 134
             const frameHeader = new Uint8Array(encodedFrame.data, 0, UNENCRYPTED_BYTES[encodedFrame.type]);
123 135
 
124 136
             // Frame trailer contains the R|IV_LENGTH and key index
@@ -139,7 +151,7 @@ export class Context {
139 151
                 name: ENCRYPTION_ALGORITHM,
140 152
                 iv,
141 153
                 additionalData: new Uint8Array(encodedFrame.data, 0, frameHeader.byteLength)
142
-            }, this._cryptoKeyRing[keyIndex].encryptionKey, new Uint8Array(encodedFrame.data,
154
+            }, currentKey.encryptionKey, new Uint8Array(encodedFrame.data,
143 155
                 UNENCRYPTED_BYTES[encodedFrame.type]))
144 156
             .then(cipherText => {
145 157
                 const newData = new ArrayBuffer(frameHeader.byteLength + cipherText.byteLength
@@ -165,12 +177,6 @@ export class Context {
165 177
                 // We are not enqueuing the frame here on purpose.
166 178
             });
167 179
         }
168
-
169
-        /* NOTE WELL:
170
-         * This will send unencrypted data (only protected by DTLS transport encryption) when no key is configured.
171
-         * This is ok for demo purposes but should not be done once this becomes more relied upon.
172
-         */
173
-        controller.enqueue(encodedFrame);
174 180
     }
175 181
 
176 182
     /**
@@ -180,11 +186,14 @@ export class Context {
180 186
      * @param {TransformStreamDefaultController} controller - TransportStreamController.
181 187
      */
182 188
     async decodeFunction(encodedFrame, controller) {
189
+        if (!this._enabled) {
190
+            return controller.enqueue(encodedFrame);
191
+        }
192
+
183 193
         const data = new Uint8Array(encodedFrame.data);
184 194
         const keyIndex = data[encodedFrame.data.byteLength - 1];
185 195
 
186 196
         if (this._cryptoKeyRing[keyIndex]) {
187
-
188 197
             const decodedFrame = await this._decryptFrame(
189 198
                 encodedFrame,
190 199
                 keyIndex);

+ 2
- 0
modules/e2ee/Context.spec.js 查看文件

@@ -67,7 +67,9 @@ describe('E2EE Context', () => {
67 67
 
68 68
     beforeEach(() => {
69 69
         sender = new Context('sender');
70
+        sender.setEnabled(true);
70 71
         receiver = new Context('receiver');
72
+        receiver.setEnabled(true);
71 73
     });
72 74
 
73 75
     describe('encode function', () => {

+ 12
- 0
modules/e2ee/E2EEContext.js 查看文件

@@ -150,6 +150,18 @@ export default class E2EEcontext {
150 150
         }
151 151
     }
152 152
 
153
+    /**
154
+     * Set the E2EE enabled state.
155
+     *
156
+     * @param {boolean} enabled - whether E2EE is enabled or not.
157
+     */
158
+    setEnabled(enabled) {
159
+        this._worker.postMessage({
160
+            operation: 'setEnabled',
161
+            enabled
162
+        });
163
+    }
164
+
153 165
     /**
154 166
      * Set the E2EE key for the specified participant.
155 167
      *

+ 11
- 16
modules/e2ee/KeyHandler.js 查看文件

@@ -27,6 +27,7 @@ export class KeyHandler extends Listenable {
27 27
 
28 28
         this.enabled = false;
29 29
         this._enabling = undefined;
30
+        this._firstEnable = false;
30 31
 
31 32
         // Conference media events in order to attach the encryptor / decryptor.
32 33
         // FIXME add events to TraceablePeerConnection which will allow to see when there's new receiver or sender
@@ -73,26 +74,20 @@ export class KeyHandler extends Listenable {
73 74
 
74 75
         this.enabled = enabled;
75 76
 
76
-        if (!enabled) {
77
-            this.e2eeCtx.cleanupAll();
78
-        }
79
-
80 77
         this._setEnabled && await this._setEnabled(enabled);
81 78
 
82 79
         this.conference.setLocalParticipantProperty('e2ee.enabled', enabled);
83 80
 
84
-        this.conference._restartMediaSessions();
81
+        // Only restart media sessions if E2EE is enabled. If it's later disabled
82
+        // we'll continue to use the existing media sessions with an empty transform.
83
+        if (!this._firstEnable && enabled) {
84
+            this._firstEnable = true;
85
+            this.conference._restartMediaSessions();
86
+        }
85 87
 
86
-        this._enabling.resolve();
87
-    }
88
+        this.e2eeCtx.setEnabled(enabled);
88 89
 
89
-    /**
90
-     * Sets the key for End-to-End encryption.
91
-     *
92
-     * @returns {void}
93
-     */
94
-    setEncryptionKey() {
95
-        throw new Error('Not implemented by subclass');
90
+        this._enabling.resolve();
96 91
     }
97 92
 
98 93
     /**
@@ -125,7 +120,7 @@ export class KeyHandler extends Listenable {
125 120
      * @private
126 121
      */
127 122
     _setupReceiverE2EEForTrack(tpc, track) {
128
-        if (!this.enabled) {
123
+        if (!this.enabled && !this._firstEnable) {
129 124
             return;
130 125
         }
131 126
 
@@ -146,7 +141,7 @@ export class KeyHandler extends Listenable {
146 141
      * @private
147 142
      */
148 143
     _setupSenderE2EEForTrack(session, track) {
149
-        if (!this.enabled) {
144
+        if (!this.enabled && !this._firstEnable) {
150 145
             return;
151 146
         }
152 147
 

+ 0
- 1
modules/e2ee/ManagedKeyHandler.js 查看文件

@@ -209,7 +209,6 @@ export class ManagedKeyHandler extends KeyHandler {
209 209
         this.conference.eventEmitter.emit(JitsiConferenceEvents.E2EE_VERIFICATION_AVAILABLE, pId);
210 210
     }
211 211
 
212
-
213 212
     /**
214 213
      * Handles the SAS completed event.
215 214
      *

+ 9
- 1
modules/e2ee/Worker.js 查看文件

@@ -8,6 +8,8 @@ const contexts = new Map(); // Map participant id => context
8 8
 
9 9
 let sharedContext;
10 10
 
11
+let enabled = false;
12
+
11 13
 /**
12 14
  * Retrieves the participant {@code Context}, creating it if necessary.
13 15
  *
@@ -20,7 +22,10 @@ function getParticipantContext(participantId) {
20 22
     }
21 23
 
22 24
     if (!contexts.has(participantId)) {
23
-        contexts.set(participantId, new Context());
25
+        const context = new Context();
26
+
27
+        context.setEnabled(enabled);
28
+        contexts.set(participantId, context);
24 29
     }
25 30
 
26 31
     return contexts.get(participantId);
@@ -63,6 +68,9 @@ onmessage = event => {
63 68
         const context = getParticipantContext(participantId);
64 69
 
65 70
         handleTransform(context, operation, readableStream, writableStream);
71
+    } else if (operation === 'setEnabled') {
72
+        enabled = event.data.enabled;
73
+        contexts.forEach(context => context.setEnabled(enabled));
66 74
     } else if (operation === 'setKey') {
67 75
         const { participantId, key, keyIndex } = event.data;
68 76
         const context = getParticipantContext(participantId);

正在加载...
取消
保存