Browse Source

feat(e2ee): makes olm sessions initialisation lazy

dev1
tmoldovan8x8 4 years ago
parent
commit
2b94da12e8
No account linked to committer's email address
3 changed files with 112 additions and 53 deletions
  1. 4
    4
      JitsiConference.js
  2. 19
    5
      modules/e2ee/E2EEncryption.js
  3. 89
    44
      modules/e2ee/OlmAdapter.js

+ 4
- 4
JitsiConference.js View File

2021
     try {
2021
     try {
2022
         jingleSession.initialize(this.room, this.rtc, {
2022
         jingleSession.initialize(this.room, this.rtc, {
2023
             ...this.options.config,
2023
             ...this.options.config,
2024
-            enableInsertableStreams: this._isE2EEEnabled()
2024
+            enableInsertableStreams: this.isE2EEEnabled()
2025
         });
2025
         });
2026
     } catch (error) {
2026
     } catch (error) {
2027
         GlobalOnErrorHandler.callErrorHandler(error);
2027
         GlobalOnErrorHandler.callErrorHandler(error);
2774
         this.room,
2774
         this.room,
2775
         this.rtc, {
2775
         this.rtc, {
2776
             ...this.options.config,
2776
             ...this.options.config,
2777
-            enableInsertableStreams: this._isE2EEEnabled()
2777
+            enableInsertableStreams: this.isE2EEEnabled()
2778
         });
2778
         });
2779
 
2779
 
2780
     logger.info('Starting CallStats for P2P connection...');
2780
     logger.info('Starting CallStats for P2P connection...');
3134
         this.room,
3134
         this.room,
3135
         this.rtc, {
3135
         this.rtc, {
3136
             ...this.options.config,
3136
             ...this.options.config,
3137
-            enableInsertableStreams: this._isE2EEEnabled()
3137
+            enableInsertableStreams: this.isE2EEEnabled()
3138
         });
3138
         });
3139
 
3139
 
3140
     logger.info('Starting CallStats for P2P connection...');
3140
     logger.info('Starting CallStats for P2P connection...');
3544
  *
3544
  *
3545
  * @returns {boolean}
3545
  * @returns {boolean}
3546
  */
3546
  */
3547
-JitsiConference.prototype._isE2EEEnabled = function() {
3547
+JitsiConference.prototype.isE2EEEnabled = function() {
3548
     return this._e2eEncryption && this._e2eEncryption.isEnabled();
3548
     return this._e2eEncryption && this._e2eEncryption.isEnabled();
3549
 };
3549
 };
3550
 
3550
 

+ 19
- 5
modules/e2ee/E2EEncryption.js View File

6
 import * as JitsiConferenceEvents from '../../JitsiConferenceEvents';
6
 import * as JitsiConferenceEvents from '../../JitsiConferenceEvents';
7
 import RTCEvents from '../../service/RTC/RTCEvents';
7
 import RTCEvents from '../../service/RTC/RTCEvents';
8
 import browser from '../browser';
8
 import browser from '../browser';
9
+import Deferred from '../util/Deferred';
9
 
10
 
10
 import E2EEContext from './E2EEContext';
11
 import E2EEContext from './E2EEContext';
11
 import { OlmAdapter } from './OlmAdapter';
12
 import { OlmAdapter } from './OlmAdapter';
32
         this._enabled = false;
33
         this._enabled = false;
33
         this._initialized = false;
34
         this._initialized = false;
34
         this._key = undefined;
35
         this._key = undefined;
36
+        this._enabling = undefined;
35
 
37
 
36
         this._e2eeCtx = new E2EEContext();
38
         this._e2eeCtx = new E2EEContext();
37
         this._olmAdapter = new OlmAdapter(conference);
39
         this._olmAdapter = new OlmAdapter(conference);
120
             return;
122
             return;
121
         }
123
         }
122
 
124
 
125
+        this._enabling && await this._enabling;
126
+
127
+        this._enabling = new Deferred();
128
+
123
         this._enabled = enabled;
129
         this._enabled = enabled;
124
 
130
 
131
+        if (enabled) {
132
+            await this._olmAdapter.initSessions();
133
+        }
134
+
135
+        this.conference.setLocalParticipantProperty('e2ee.enabled', enabled);
136
+
125
         if (!this._initialized && enabled) {
137
         if (!this._initialized && enabled) {
126
             // Need to re-create the peerconnections in order to apply the insertable streams constraint.
138
             // Need to re-create the peerconnections in order to apply the insertable streams constraint.
127
             // TODO: this was necessary due to some audio issues when indertable streams are used
139
             // TODO: this was necessary due to some audio issues when indertable streams are used
136
         this._key = enabled ? this._generateKey() : false;
148
         this._key = enabled ? this._generateKey() : false;
137
 
149
 
138
         // Send it to others using the E2EE olm channel.
150
         // Send it to others using the E2EE olm channel.
139
-        this._olmAdapter.updateKey(this._key).then(index => {
140
-            // Set our key so we begin encrypting.
141
-            this._e2eeCtx.setKey(this.conference.myUserId(), this._key, index);
142
-        });
151
+        const index = await this._olmAdapter.updateKey(this._key);
152
+
153
+        // Set our key so we begin encrypting.
154
+        this._e2eeCtx.setKey(this.conference.myUserId(), this._key, index);
155
+
156
+        this._enabling.resolve();
143
     }
157
     }
144
 
158
 
145
     /**
159
     /**
265
 
279
 
266
         this._key = new Uint8Array(newKey);
280
         this._key = new Uint8Array(newKey);
267
 
281
 
268
-        const index = await this._olmAdapter.updateCurrentKey(this._key);
282
+        const index = this._olmAdapter.updateCurrentKey(this._key);
269
 
283
 
270
         this._e2eeCtx.setKey(this.conference.myUserId(), this._key, index);
284
         this._e2eeCtx.setKey(this.conference.myUserId(), this._key, index);
271
     }
285
     }

+ 89
- 44
modules/e2ee/OlmAdapter.js View File

8
 import * as JitsiConferenceEvents from '../../JitsiConferenceEvents';
8
 import * as JitsiConferenceEvents from '../../JitsiConferenceEvents';
9
 import Deferred from '../util/Deferred';
9
 import Deferred from '../util/Deferred';
10
 import Listenable from '../util/Listenable';
10
 import Listenable from '../util/Listenable';
11
-import { JITSI_MEET_MUC_TYPE } from '../xmpp/xmpp';
11
+import { FEATURE_E2EE, JITSI_MEET_MUC_TYPE } from '../xmpp/xmpp';
12
 
12
 
13
 const logger = getLogger(__filename);
13
 const logger = getLogger(__filename);
14
 
14
 
62
         this._key = undefined;
62
         this._key = undefined;
63
         this._keyIndex = -1;
63
         this._keyIndex = -1;
64
         this._reqs = new Map();
64
         this._reqs = new Map();
65
+        this._sessionInitialization = undefined;
65
 
66
 
66
         if (OlmAdapter.isSupported()) {
67
         if (OlmAdapter.isSupported()) {
67
             this._bootstrapOlm();
68
             this._bootstrapOlm();
68
 
69
 
69
             this._conf.on(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, this._onEndpointMessageReceived.bind(this));
70
             this._conf.on(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, this._onEndpointMessageReceived.bind(this));
70
-            this._conf.on(JitsiConferenceEvents.CONFERENCE_JOINED, this._onConferenceJoined.bind(this));
71
             this._conf.on(JitsiConferenceEvents.CONFERENCE_LEFT, this._onConferenceLeft.bind(this));
71
             this._conf.on(JitsiConferenceEvents.CONFERENCE_LEFT, this._onConferenceLeft.bind(this));
72
             this._conf.on(JitsiConferenceEvents.USER_LEFT, this._onParticipantLeft.bind(this));
72
             this._conf.on(JitsiConferenceEvents.USER_LEFT, this._onParticipantLeft.bind(this));
73
+            this._conf.on(JitsiConferenceEvents.PARTICIPANT_PROPERTY_CHANGED,
74
+                this._onParticipantPropertyChanged.bind(this));
73
         } else {
75
         } else {
74
             this._init.reject(new Error('Olm not supported'));
76
             this._init.reject(new Error('Olm not supported'));
75
         }
77
         }
76
     }
78
     }
77
 
79
 
78
     /**
80
     /**
79
-     * Indicates if olm is supported on the current platform.
80
-     *
81
-     * @returns {boolean}
81
+     * Starts new olm sessions with every other participant that has the participantId "smaller" the localParticipantId.
82
      */
82
      */
83
-    static isSupported() {
84
-        return typeof window.Olm !== 'undefined';
83
+    async initSessions() {
84
+        if (this._sessionInitialization) {
85
+            throw new Error('OlmAdapte initSessions called multiple times');
86
+        } else {
87
+            this._sessionInitialization = new Deferred();
88
+
89
+            await this._init;
90
+
91
+            const promises = [];
92
+            const localParticipantId = this._conf.myUserId();
93
+
94
+            for (const participant of this._conf.getParticipants()) {
95
+                const participantFeatures = await participant.getFeatures();
96
+
97
+                if (participantFeatures.has(FEATURE_E2EE) && localParticipantId < participant.getId()) {
98
+                    promises.push(this._sendSessionInit(participant));
99
+                }
100
+            }
101
+
102
+            await Promise.allSettled(promises);
103
+
104
+            // TODO: retry failed ones.
105
+
106
+            this._sessionInitialization.resolve();
107
+            this._sessionInitialization = undefined;
108
+        }
85
     }
109
     }
86
 
110
 
87
     /**
111
     /**
88
-     * Updates the current participant key and distributes it to all participants in the conference
89
-     * by sending a key-info message.
112
+     * Indicates if olm is supported on the current platform.
90
      *
113
      *
91
-     * @param {Uint8Array|boolean} key - The new key.
92
-     * @returns {number}
114
+     * @returns {boolean}
93
      */
115
      */
94
-    async updateCurrentKey(key) {
95
-        this._key = key;
96
-
97
-        return this._keyIndex;
116
+    static isSupported() {
117
+        return typeof window.Olm !== 'undefined';
98
     }
118
     }
99
 
119
 
100
     /**
120
     /**
117
             const olmData = this._getParticipantOlmData(participant);
137
             const olmData = this._getParticipantOlmData(participant);
118
 
138
 
119
             // TODO: skip those who don't support E2EE.
139
             // TODO: skip those who don't support E2EE.
120
-
121
             if (!olmData.session) {
140
             if (!olmData.session) {
122
                 logger.warn(`Tried to send key to participant ${pId} but we have no session`);
141
                 logger.warn(`Tried to send key to participant ${pId} but we have no session`);
123
 
142
 
155
         return this._keyIndex;
174
         return this._keyIndex;
156
     }
175
     }
157
 
176
 
177
+    /**
178
+     * Updates the current participant key.
179
+     * @param {Uint8Array|boolean} key - The new key.
180
+     * @returns {number}
181
+    */
182
+    updateCurrentKey(key) {
183
+        this._key = key;
184
+
185
+        return this._keyIndex;
186
+    }
187
+
158
     /**
188
     /**
159
      * Internal helper to bootstrap the olm library.
189
      * Internal helper to bootstrap the olm library.
160
      *
190
      *
215
         return participant[kOlmData];
245
         return participant[kOlmData];
216
     }
246
     }
217
 
247
 
218
-    /**
219
-     * Handles the conference joined event. Upon joining a conference, the participant
220
-     * who just joined will start new olm sessions with every other participant.
221
-     *
222
-     * @private
223
-     */
224
-    async _onConferenceJoined() {
225
-        logger.debug('Conference joined');
226
-
227
-        await this._init;
228
-
229
-        const promises = [];
230
-
231
-        // Establish a 1-to-1 Olm session with every participant in the conference.
232
-        // We are forcing the last user to join the conference to start the exchange
233
-        // so we can send some pre-established secrets in the ACK.
234
-        for (const participant of this._conf.getParticipants()) {
235
-            promises.push(this._sendSessionInit(participant));
236
-        }
237
-
238
-        await Promise.allSettled(promises);
239
-
240
-        // TODO: retry failed ones.
241
-        // TODO: skip participants which don't support E2EE.
242
-    }
243
-
244
     /**
248
     /**
245
      * Handles leaving the conference, cleaning up olm sessions.
249
      * Handles leaving the conference, cleaning up olm sessions.
246
      *
250
      *
311
                 };
315
                 };
312
 
316
 
313
                 this._sendMessage(ack, pId);
317
                 this._sendMessage(ack, pId);
314
-
315
                 this.eventEmitter.emit(OlmAdapterEvents.PARTICIPANT_E2EE_CHANNEL_READY, pId);
318
                 this.eventEmitter.emit(OlmAdapterEvents.PARTICIPANT_E2EE_CHANNEL_READY, pId);
316
             }
319
             }
317
             break;
320
             break;
427
             break;
430
             break;
428
         }
431
         }
429
         }
432
         }
430
-
431
     }
433
     }
432
 
434
 
433
     /**
435
     /**
446
         }
448
         }
447
     }
449
     }
448
 
450
 
451
+    /**
452
+    * Handles an update in a participant's presence property.
453
+    *
454
+    * @param {JitsiParticipant} participant - The participant.
455
+    * @param {string} name - The name of the property that changed.
456
+    * @param {*} oldValue - The property's previous value.
457
+    * @param {*} newValue - The property's new value.
458
+    * @private
459
+    */
460
+    async _onParticipantPropertyChanged(participant, name, oldValue, newValue) {
461
+        switch (name) {
462
+        case 'e2ee.enabled':
463
+            if (newValue && this._conf.isE2EEEnabled()) {
464
+                const localParticipantId = this._conf.myUserId();
465
+                const participantId = participant.getId();
466
+                const participantFeatures = await participant.getFeatures();
467
+
468
+                if (participantFeatures.has(FEATURE_E2EE) && localParticipantId < participantId) {
469
+                    if (this._sessionInitialization) {
470
+                        await this._sessionInitialization;
471
+                    }
472
+                    await this._sendSessionInit(participant);
473
+
474
+                    const olmData = this._getParticipantOlmData(participant);
475
+                    const uuid = uuidv4();
476
+                    const data = {
477
+                        [JITSI_MEET_MUC_TYPE]: OLM_MESSAGE_TYPE,
478
+                        olm: {
479
+                            type: OLM_MESSAGE_TYPES.KEY_INFO,
480
+                            data: {
481
+                                ciphertext: this._encryptKeyInfo(olmData.session),
482
+                                uuid
483
+                            }
484
+                        }
485
+                    };
486
+
487
+                    this._sendMessage(data, participantId);
488
+                }
489
+            }
490
+            break;
491
+        }
492
+    }
493
+
449
     /**
494
     /**
450
      * Builds and sends an error message to the target participant.
495
      * Builds and sends an error message to the target participant.
451
      *
496
      *

Loading…
Cancel
Save