Browse Source

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 months ago
parent
commit
f919c23aea

+ 22
- 13
modules/e2ee/Context.js View File

1
 /* eslint-disable no-bitwise */
1
 /* eslint-disable no-bitwise */
2
-/* global BigInt */
3
 
2
 
4
 import { deriveKeys, importKey, ratchet } from './crypto-utils';
3
 import { deriveKeys, importKey, ratchet } from './crypto-utils';
5
 
4
 
49
         this._sendCounts = new Map();
48
         this._sendCounts = new Map();
50
 
49
 
51
         this._sharedKey = sharedKey;
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
         }
95
         }
87
 
96
 
88
         this._cryptoKeyRing[this._currentKeyIndex] = keys;
97
         this._cryptoKeyRing[this._currentKeyIndex] = keys;
89
-
90
-        this._sendCount = BigInt(0); // eslint-disable-line new-cap
91
     }
98
     }
92
 
99
 
93
     /**
100
     /**
113
      * 9) Enqueue the encrypted frame for sending.
120
      * 9) Enqueue the encrypted frame for sending.
114
      */
121
      */
115
     encodeFunction(encodedFrame, controller) {
122
     encodeFunction(encodedFrame, controller) {
123
+        if (!this._enabled) {
124
+            return controller.enqueue(encodedFrame);
125
+        }
126
+
116
         const keyIndex = this._currentKeyIndex;
127
         const keyIndex = this._currentKeyIndex;
128
+        const currentKey = this._cryptoKeyRing[keyIndex];
117
 
129
 
118
-        if (this._cryptoKeyRing[keyIndex]) {
130
+        if (currentKey) {
119
             const iv = this._makeIV(encodedFrame.getMetadata().synchronizationSource, encodedFrame.timestamp);
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
             const frameHeader = new Uint8Array(encodedFrame.data, 0, UNENCRYPTED_BYTES[encodedFrame.type]);
134
             const frameHeader = new Uint8Array(encodedFrame.data, 0, UNENCRYPTED_BYTES[encodedFrame.type]);
123
 
135
 
124
             // Frame trailer contains the R|IV_LENGTH and key index
136
             // Frame trailer contains the R|IV_LENGTH and key index
139
                 name: ENCRYPTION_ALGORITHM,
151
                 name: ENCRYPTION_ALGORITHM,
140
                 iv,
152
                 iv,
141
                 additionalData: new Uint8Array(encodedFrame.data, 0, frameHeader.byteLength)
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
                 UNENCRYPTED_BYTES[encodedFrame.type]))
155
                 UNENCRYPTED_BYTES[encodedFrame.type]))
144
             .then(cipherText => {
156
             .then(cipherText => {
145
                 const newData = new ArrayBuffer(frameHeader.byteLength + cipherText.byteLength
157
                 const newData = new ArrayBuffer(frameHeader.byteLength + cipherText.byteLength
165
                 // We are not enqueuing the frame here on purpose.
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
      * @param {TransformStreamDefaultController} controller - TransportStreamController.
186
      * @param {TransformStreamDefaultController} controller - TransportStreamController.
181
      */
187
      */
182
     async decodeFunction(encodedFrame, controller) {
188
     async decodeFunction(encodedFrame, controller) {
189
+        if (!this._enabled) {
190
+            return controller.enqueue(encodedFrame);
191
+        }
192
+
183
         const data = new Uint8Array(encodedFrame.data);
193
         const data = new Uint8Array(encodedFrame.data);
184
         const keyIndex = data[encodedFrame.data.byteLength - 1];
194
         const keyIndex = data[encodedFrame.data.byteLength - 1];
185
 
195
 
186
         if (this._cryptoKeyRing[keyIndex]) {
196
         if (this._cryptoKeyRing[keyIndex]) {
187
-
188
             const decodedFrame = await this._decryptFrame(
197
             const decodedFrame = await this._decryptFrame(
189
                 encodedFrame,
198
                 encodedFrame,
190
                 keyIndex);
199
                 keyIndex);

+ 2
- 0
modules/e2ee/Context.spec.js View File

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

+ 12
- 0
modules/e2ee/E2EEContext.js View File

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
      * Set the E2EE key for the specified participant.
166
      * Set the E2EE key for the specified participant.
155
      *
167
      *

+ 11
- 16
modules/e2ee/KeyHandler.js View File

27
 
27
 
28
         this.enabled = false;
28
         this.enabled = false;
29
         this._enabling = undefined;
29
         this._enabling = undefined;
30
+        this._firstEnable = false;
30
 
31
 
31
         // Conference media events in order to attach the encryptor / decryptor.
32
         // Conference media events in order to attach the encryptor / decryptor.
32
         // FIXME add events to TraceablePeerConnection which will allow to see when there's new receiver or sender
33
         // FIXME add events to TraceablePeerConnection which will allow to see when there's new receiver or sender
73
 
74
 
74
         this.enabled = enabled;
75
         this.enabled = enabled;
75
 
76
 
76
-        if (!enabled) {
77
-            this.e2eeCtx.cleanupAll();
78
-        }
79
-
80
         this._setEnabled && await this._setEnabled(enabled);
77
         this._setEnabled && await this._setEnabled(enabled);
81
 
78
 
82
         this.conference.setLocalParticipantProperty('e2ee.enabled', enabled);
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
      * @private
120
      * @private
126
      */
121
      */
127
     _setupReceiverE2EEForTrack(tpc, track) {
122
     _setupReceiverE2EEForTrack(tpc, track) {
128
-        if (!this.enabled) {
123
+        if (!this.enabled && !this._firstEnable) {
129
             return;
124
             return;
130
         }
125
         }
131
 
126
 
146
      * @private
141
      * @private
147
      */
142
      */
148
     _setupSenderE2EEForTrack(session, track) {
143
     _setupSenderE2EEForTrack(session, track) {
149
-        if (!this.enabled) {
144
+        if (!this.enabled && !this._firstEnable) {
150
             return;
145
             return;
151
         }
146
         }
152
 
147
 

+ 0
- 1
modules/e2ee/ManagedKeyHandler.js View File

209
         this.conference.eventEmitter.emit(JitsiConferenceEvents.E2EE_VERIFICATION_AVAILABLE, pId);
209
         this.conference.eventEmitter.emit(JitsiConferenceEvents.E2EE_VERIFICATION_AVAILABLE, pId);
210
     }
210
     }
211
 
211
 
212
-
213
     /**
212
     /**
214
      * Handles the SAS completed event.
213
      * Handles the SAS completed event.
215
      *
214
      *

+ 9
- 1
modules/e2ee/Worker.js View File

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

Loading…
Cancel
Save