|
|
@@ -10,6 +10,9 @@ import Deferred from '../util/Deferred';
|
|
10
|
10
|
import Listenable from '../util/Listenable';
|
|
11
|
11
|
import { FEATURE_E2EE, JITSI_MEET_MUC_TYPE } from '../xmpp/xmpp';
|
|
12
|
12
|
|
|
|
13
|
+import { E2EEErrors } from './E2EEErrors';
|
|
|
14
|
+import { generateSas } from './SAS';
|
|
|
15
|
+
|
|
13
|
16
|
const logger = getLogger(__filename);
|
|
14
|
17
|
|
|
15
|
18
|
const REQ_TIMEOUT = 5 * 1000;
|
|
|
@@ -19,15 +22,25 @@ const OLM_MESSAGE_TYPES = {
|
|
19
|
22
|
KEY_INFO: 'key-info',
|
|
20
|
23
|
KEY_INFO_ACK: 'key-info-ack',
|
|
21
|
24
|
SESSION_ACK: 'session-ack',
|
|
22
|
|
- SESSION_INIT: 'session-init'
|
|
|
25
|
+ SESSION_INIT: 'session-init',
|
|
|
26
|
+ SAS_START: 'sas-start',
|
|
|
27
|
+ SAS_ACCEPT: 'sas-accept',
|
|
|
28
|
+ SAS_KEY: 'sas-key',
|
|
|
29
|
+ SAS_MAC: 'sas-mac'
|
|
23
|
30
|
};
|
|
24
|
31
|
|
|
|
32
|
+const OLM_SAS_NUM_BYTES = 6;
|
|
|
33
|
+const OLM_KEY_VERIFICATION_MAC_INFO = 'Jitsi-KEY_VERIFICATION_MAC';
|
|
|
34
|
+const OLM_KEY_VERIFICATION_MAC_KEY_IDS = 'Jitsi-KEY_IDS';
|
|
|
35
|
+
|
|
25
|
36
|
const kOlmData = Symbol('OlmData');
|
|
26
|
37
|
|
|
27
|
38
|
const OlmAdapterEvents = {
|
|
28
|
|
- OLM_ID_KEY_READY: 'olm.id_key_ready',
|
|
29
|
39
|
PARTICIPANT_E2EE_CHANNEL_READY: 'olm.participant_e2ee_channel_ready',
|
|
30
|
|
- PARTICIPANT_KEY_UPDATED: 'olm.partitipant_key_updated'
|
|
|
40
|
+ PARTICIPANT_SAS_AVAILABLE: 'olm.participant_sas_available',
|
|
|
41
|
+ PARTICIPANT_SAS_READY: 'olm.participant_sas_ready',
|
|
|
42
|
+ PARTICIPANT_KEY_UPDATED: 'olm.partitipant_key_updated',
|
|
|
43
|
+ PARTICIPANT_VERIFICATION_COMPLETED: 'olm.participant_verification_completed'
|
|
31
|
44
|
};
|
|
32
|
45
|
|
|
33
|
46
|
/**
|
|
|
@@ -59,8 +72,8 @@ export class OlmAdapter extends Listenable {
|
|
59
|
72
|
|
|
60
|
73
|
this._conf = conference;
|
|
61
|
74
|
this._init = new Deferred();
|
|
62
|
|
- this._key = undefined;
|
|
63
|
|
- this._keyIndex = -1;
|
|
|
75
|
+ this._mediaKey = undefined;
|
|
|
76
|
+ this._mediaKeyIndex = -1;
|
|
64
|
77
|
this._reqs = new Map();
|
|
65
|
78
|
this._sessionInitialization = undefined;
|
|
66
|
79
|
|
|
|
@@ -77,6 +90,15 @@ export class OlmAdapter extends Listenable {
|
|
77
|
90
|
}
|
|
78
|
91
|
}
|
|
79
|
92
|
|
|
|
93
|
+ /**
|
|
|
94
|
+ * Returns the current participants conference ID.
|
|
|
95
|
+ *
|
|
|
96
|
+ * @returns {string}
|
|
|
97
|
+ */
|
|
|
98
|
+ get myId() {
|
|
|
99
|
+ return this._conf.myUserId();
|
|
|
100
|
+ }
|
|
|
101
|
+
|
|
80
|
102
|
/**
|
|
81
|
103
|
* Starts new olm sessions with every other participant that has the participantId "smaller" the localParticipantId.
|
|
82
|
104
|
*/
|
|
|
@@ -124,8 +146,8 @@ export class OlmAdapter extends Listenable {
|
|
124
|
146
|
*/
|
|
125
|
147
|
async updateKey(key) {
|
|
126
|
148
|
// Store it locally for new sessions.
|
|
127
|
|
- this._key = key;
|
|
128
|
|
- this._keyIndex++;
|
|
|
149
|
+ this._mediaKey = key;
|
|
|
150
|
+ this._mediaKeyIndex++;
|
|
129
|
151
|
|
|
130
|
152
|
// Broadcast it.
|
|
131
|
153
|
const promises = [];
|
|
|
@@ -169,7 +191,7 @@ export class OlmAdapter extends Listenable {
|
|
169
|
191
|
|
|
170
|
192
|
// TODO: retry failed ones?
|
|
171
|
193
|
|
|
172
|
|
- return this._keyIndex;
|
|
|
194
|
+ return this._mediaKeyIndex;
|
|
173
|
195
|
}
|
|
174
|
196
|
|
|
175
|
197
|
/**
|
|
|
@@ -177,10 +199,10 @@ export class OlmAdapter extends Listenable {
|
|
177
|
199
|
* @param {Uint8Array|boolean} key - The new key.
|
|
178
|
200
|
* @returns {number}
|
|
179
|
201
|
*/
|
|
180
|
|
- updateCurrentKey(key) {
|
|
181
|
|
- this._key = key;
|
|
|
202
|
+ updateCurrentMediaKey(key) {
|
|
|
203
|
+ this._mediaKey = key;
|
|
182
|
204
|
|
|
183
|
|
- return this._keyIndex;
|
|
|
205
|
+ return this._mediaKeyIndex;
|
|
184
|
206
|
}
|
|
185
|
207
|
|
|
186
|
208
|
/**
|
|
|
@@ -196,7 +218,6 @@ export class OlmAdapter extends Listenable {
|
|
196
|
218
|
}
|
|
197
|
219
|
}
|
|
198
|
220
|
|
|
199
|
|
-
|
|
200
|
221
|
/**
|
|
201
|
222
|
* Frees the olmData sessions for all participants.
|
|
202
|
223
|
*
|
|
|
@@ -207,6 +228,48 @@ export class OlmAdapter extends Listenable {
|
|
207
|
228
|
}
|
|
208
|
229
|
}
|
|
209
|
230
|
|
|
|
231
|
+ /**
|
|
|
232
|
+ * Sends sacMac if channel verification waas successful.
|
|
|
233
|
+ *
|
|
|
234
|
+ */
|
|
|
235
|
+ markParticipantVerified(participant, isVerified) {
|
|
|
236
|
+ const olmData = this._getParticipantOlmData(participant);
|
|
|
237
|
+
|
|
|
238
|
+ const pId = participant.getId();
|
|
|
239
|
+
|
|
|
240
|
+ if (!isVerified) {
|
|
|
241
|
+ olmData.sasVerification = undefined;
|
|
|
242
|
+ logger.warn(`Verification failed for participant ${pId}`);
|
|
|
243
|
+ this.eventEmitter.emit(
|
|
|
244
|
+ OlmAdapterEvents.PARTICIPANT_VERIFICATION_COMPLETED,
|
|
|
245
|
+ pId,
|
|
|
246
|
+ false,
|
|
|
247
|
+ E2EEErrors.E2EE_SAS_CHANNEL_VERIFICATION_FAILED);
|
|
|
248
|
+
|
|
|
249
|
+ return;
|
|
|
250
|
+ }
|
|
|
251
|
+
|
|
|
252
|
+ if (!olmData.sasVerification) {
|
|
|
253
|
+ logger.warn(`Participant ${pId} does not have valid sasVerification`);
|
|
|
254
|
+ this.eventEmitter.emit(
|
|
|
255
|
+ OlmAdapterEvents.PARTICIPANT_VERIFICATION_COMPLETED,
|
|
|
256
|
+ pId,
|
|
|
257
|
+ false,
|
|
|
258
|
+ E2EEErrors.E2EE_SAS_INVALID_SAS_VERIFICATION);
|
|
|
259
|
+
|
|
|
260
|
+ return;
|
|
|
261
|
+ }
|
|
|
262
|
+
|
|
|
263
|
+ const { sas, sasMacSent } = olmData.sasVerification;
|
|
|
264
|
+
|
|
|
265
|
+ if (sas && sas.is_their_key_set() && !sasMacSent) {
|
|
|
266
|
+ this._sendSasMac(participant);
|
|
|
267
|
+
|
|
|
268
|
+ // Mark the MAC as sent so we don't send it multiple times.
|
|
|
269
|
+ olmData.sasVerification.sasMacSent = true;
|
|
|
270
|
+ }
|
|
|
271
|
+ }
|
|
|
272
|
+
|
|
210
|
273
|
/**
|
|
211
|
274
|
* Internal helper to bootstrap the olm library.
|
|
212
|
275
|
*
|
|
|
@@ -222,29 +285,99 @@ export class OlmAdapter extends Listenable {
|
|
222
|
285
|
this._olmAccount = new Olm.Account();
|
|
223
|
286
|
this._olmAccount.create();
|
|
224
|
287
|
|
|
225
|
|
- const idKeys = JSON.parse(this._olmAccount.identity_keys());
|
|
226
|
|
-
|
|
227
|
|
- this._idKey = idKeys.curve25519;
|
|
|
288
|
+ this._idKeys = JSON.parse(this._olmAccount.identity_keys());
|
|
228
|
289
|
|
|
229
|
290
|
logger.debug(`Olm ${Olm.get_library_version().join('.')} initialized`);
|
|
230
|
291
|
this._init.resolve();
|
|
231
|
|
- this._onIdKeyReady(this._idKey);
|
|
|
292
|
+ this._onIdKeysReady(this._idKeys);
|
|
232
|
293
|
} catch (e) {
|
|
233
|
294
|
logger.error('Failed to initialize Olm', e);
|
|
234
|
295
|
this._init.reject(e);
|
|
235
|
296
|
}
|
|
|
297
|
+ }
|
|
|
298
|
+
|
|
|
299
|
+ /**
|
|
|
300
|
+ * Starts the verification process for the given participant as described here
|
|
|
301
|
+ * https://spec.matrix.org/latest/client-server-api/#short-authentication-string-sas-verification
|
|
|
302
|
+ *
|
|
|
303
|
+ * | |
|
|
|
304
|
+ | m.key.verification.start |
|
|
|
305
|
+ |-------------------------------->|
|
|
|
306
|
+ | |
|
|
|
307
|
+ | m.key.verification.accept |
|
|
|
308
|
+ |<--------------------------------|
|
|
|
309
|
+ | |
|
|
|
310
|
+ | m.key.verification.key |
|
|
|
311
|
+ |-------------------------------->|
|
|
|
312
|
+ | |
|
|
|
313
|
+ | m.key.verification.key |
|
|
|
314
|
+ |<--------------------------------|
|
|
|
315
|
+ | |
|
|
|
316
|
+ | m.key.verification.mac |
|
|
|
317
|
+ |-------------------------------->|
|
|
|
318
|
+ | |
|
|
|
319
|
+ | m.key.verification.mac |
|
|
|
320
|
+ |<--------------------------------|
|
|
|
321
|
+ | |
|
|
|
322
|
+ *
|
|
|
323
|
+ * @param {JitsiParticipant} participant - The target participant.
|
|
|
324
|
+ * @returns {Promise<void>}
|
|
|
325
|
+ * @private
|
|
|
326
|
+ */
|
|
|
327
|
+ startVerification(participant) {
|
|
|
328
|
+ const pId = participant.getId();
|
|
|
329
|
+ const olmData = this._getParticipantOlmData(participant);
|
|
|
330
|
+
|
|
|
331
|
+ if (!olmData.session) {
|
|
|
332
|
+ logger.warn(`Tried to start verification with participant ${pId} but we have no session`);
|
|
|
333
|
+
|
|
|
334
|
+ return;
|
|
|
335
|
+ }
|
|
|
336
|
+
|
|
|
337
|
+ if (olmData.sasVerification) {
|
|
|
338
|
+ logger.warn(`There is already a verification in progress with participant ${pId}`);
|
|
|
339
|
+
|
|
|
340
|
+ return;
|
|
|
341
|
+ }
|
|
|
342
|
+
|
|
|
343
|
+ olmData.sasVerification = {
|
|
|
344
|
+ sas: new Olm.SAS(),
|
|
|
345
|
+ transactionId: uuidv4()
|
|
|
346
|
+ };
|
|
236
|
347
|
|
|
|
348
|
+ const startContent = {
|
|
|
349
|
+ transactionId: olmData.sasVerification.transactionId
|
|
|
350
|
+ };
|
|
|
351
|
+
|
|
|
352
|
+ olmData.sasVerification.startContent = startContent;
|
|
|
353
|
+ olmData.sasVerification.isInitiator = true;
|
|
|
354
|
+
|
|
|
355
|
+ const startMessage = {
|
|
|
356
|
+ [JITSI_MEET_MUC_TYPE]: OLM_MESSAGE_TYPE,
|
|
|
357
|
+ olm: {
|
|
|
358
|
+ type: OLM_MESSAGE_TYPES.SAS_START,
|
|
|
359
|
+ data: startContent
|
|
|
360
|
+ }
|
|
|
361
|
+ };
|
|
|
362
|
+
|
|
|
363
|
+ this._sendMessage(startMessage, pId);
|
|
237
|
364
|
}
|
|
238
|
365
|
|
|
239
|
366
|
/**
|
|
240
|
367
|
* Publishes our own Olmn id key in presence.
|
|
241
|
368
|
* @private
|
|
242
|
369
|
*/
|
|
243
|
|
- _onIdKeyReady(idKey) {
|
|
244
|
|
- logger.debug(`Olm id key ready: ${idKey}`);
|
|
|
370
|
+ _onIdKeysReady(idKeys) {
|
|
|
371
|
+ logger.debug(`Olm id key ready: ${idKeys}`);
|
|
245
|
372
|
|
|
246
|
373
|
// Publish it in presence.
|
|
247
|
|
- this._conf.setLocalParticipantProperty('e2ee.idKey', idKey);
|
|
|
374
|
+ for (const keyType in idKeys) {
|
|
|
375
|
+ if (idKeys.hasOwnProperty(keyType)) {
|
|
|
376
|
+ const key = idKeys[keyType];
|
|
|
377
|
+
|
|
|
378
|
+ this._conf.setLocalParticipantProperty(`e2ee.idKey.${keyType}`, key);
|
|
|
379
|
+ }
|
|
|
380
|
+ }
|
|
248
|
381
|
}
|
|
249
|
382
|
|
|
250
|
383
|
/**
|
|
|
@@ -265,9 +398,9 @@ export class OlmAdapter extends Listenable {
|
|
265
|
398
|
_encryptKeyInfo(session) {
|
|
266
|
399
|
const keyInfo = {};
|
|
267
|
400
|
|
|
268
|
|
- if (this._key !== undefined) {
|
|
269
|
|
- keyInfo.key = this._key ? base64js.fromByteArray(this._key) : false;
|
|
270
|
|
- keyInfo.keyIndex = this._keyIndex;
|
|
|
401
|
+ if (this._mediaKey !== undefined) {
|
|
|
402
|
+ keyInfo.key = this._mediaKey ? base64js.fromByteArray(this._mediaKey) : false;
|
|
|
403
|
+ keyInfo.keyIndex = this._mediaKeyIndex;
|
|
271
|
404
|
}
|
|
272
|
405
|
|
|
273
|
406
|
return session.encrypt(JSON.stringify(keyInfo));
|
|
|
@@ -470,6 +603,266 @@ export class OlmAdapter extends Listenable {
|
|
470
|
603
|
}
|
|
471
|
604
|
break;
|
|
472
|
605
|
}
|
|
|
606
|
+ case OLM_MESSAGE_TYPES.SAS_START: {
|
|
|
607
|
+ if (!olmData.session) {
|
|
|
608
|
+ logger.debug(`Received sas init message from ${pId} but we have no session for them!`);
|
|
|
609
|
+
|
|
|
610
|
+ this._sendError(participant, 'No session found while processing sas-init');
|
|
|
611
|
+
|
|
|
612
|
+ return;
|
|
|
613
|
+ }
|
|
|
614
|
+
|
|
|
615
|
+ if (olmData.sasVerification?.sas) {
|
|
|
616
|
+ logger.warn(`SAS already created for participant ${pId}`);
|
|
|
617
|
+ this.eventEmitter.emit(
|
|
|
618
|
+ OlmAdapterEvents.PARTICIPANT_VERIFICATION_COMPLETED,
|
|
|
619
|
+ pId,
|
|
|
620
|
+ false,
|
|
|
621
|
+ E2EEErrors.E2EE_SAS_INVALID_SAS_VERIFICATION);
|
|
|
622
|
+
|
|
|
623
|
+ return;
|
|
|
624
|
+ }
|
|
|
625
|
+
|
|
|
626
|
+ const { transactionId } = msg.data;
|
|
|
627
|
+
|
|
|
628
|
+ const sas = new Olm.SAS();
|
|
|
629
|
+
|
|
|
630
|
+ olmData.sasVerification = {
|
|
|
631
|
+ sas,
|
|
|
632
|
+ transactionId,
|
|
|
633
|
+ isInitiator: false
|
|
|
634
|
+ };
|
|
|
635
|
+
|
|
|
636
|
+ const pubKey = olmData.sasVerification.sas.get_pubkey();
|
|
|
637
|
+ const commitment = this._computeCommitment(pubKey, msg.data);
|
|
|
638
|
+
|
|
|
639
|
+ /* The first phase of the verification process, the Key agreement phase
|
|
|
640
|
+ https://spec.matrix.org/latest/client-server-api/#short-authentication-string-sas-verification
|
|
|
641
|
+ */
|
|
|
642
|
+ const acceptMessage = {
|
|
|
643
|
+ [JITSI_MEET_MUC_TYPE]: OLM_MESSAGE_TYPE,
|
|
|
644
|
+ olm: {
|
|
|
645
|
+ type: OLM_MESSAGE_TYPES.SAS_ACCEPT,
|
|
|
646
|
+ data: {
|
|
|
647
|
+ transactionId,
|
|
|
648
|
+ commitment
|
|
|
649
|
+ }
|
|
|
650
|
+ }
|
|
|
651
|
+ };
|
|
|
652
|
+
|
|
|
653
|
+ this._sendMessage(acceptMessage, pId);
|
|
|
654
|
+ break;
|
|
|
655
|
+ }
|
|
|
656
|
+ case OLM_MESSAGE_TYPES.SAS_ACCEPT: {
|
|
|
657
|
+ if (!olmData.session) {
|
|
|
658
|
+ logger.debug(`Received sas accept message from ${pId} but we have no session for them!`);
|
|
|
659
|
+
|
|
|
660
|
+ this._sendError(participant, 'No session found while processing sas-accept');
|
|
|
661
|
+
|
|
|
662
|
+ return;
|
|
|
663
|
+ }
|
|
|
664
|
+
|
|
|
665
|
+ const { commitment, transactionId } = msg.data;
|
|
|
666
|
+
|
|
|
667
|
+
|
|
|
668
|
+ if (!olmData.sasVerification) {
|
|
|
669
|
+ logger.warn(`SAS_ACCEPT Participant ${pId} does not have valid sasVerification`);
|
|
|
670
|
+ this.eventEmitter.emit(
|
|
|
671
|
+ OlmAdapterEvents.PARTICIPANT_VERIFICATION_COMPLETED,
|
|
|
672
|
+ pId,
|
|
|
673
|
+ false,
|
|
|
674
|
+ E2EEErrors.E2EE_SAS_INVALID_SAS_VERIFICATION);
|
|
|
675
|
+
|
|
|
676
|
+ return;
|
|
|
677
|
+ }
|
|
|
678
|
+
|
|
|
679
|
+ if (olmData.sasVerification.sasCommitment) {
|
|
|
680
|
+ logger.debug(`Already received sas commitment message from ${pId}!`);
|
|
|
681
|
+
|
|
|
682
|
+ this._sendError(participant, 'Already received sas commitment message from ${pId}!');
|
|
|
683
|
+
|
|
|
684
|
+ return;
|
|
|
685
|
+ }
|
|
|
686
|
+
|
|
|
687
|
+ olmData.sasVerification.sasCommitment = commitment;
|
|
|
688
|
+
|
|
|
689
|
+ const pubKey = olmData.sasVerification.sas.get_pubkey();
|
|
|
690
|
+
|
|
|
691
|
+ // Send KEY.
|
|
|
692
|
+ const keyMessage = {
|
|
|
693
|
+ [JITSI_MEET_MUC_TYPE]: OLM_MESSAGE_TYPE,
|
|
|
694
|
+ olm: {
|
|
|
695
|
+ type: OLM_MESSAGE_TYPES.SAS_KEY,
|
|
|
696
|
+ data: {
|
|
|
697
|
+ key: pubKey,
|
|
|
698
|
+ transactionId
|
|
|
699
|
+ }
|
|
|
700
|
+ }
|
|
|
701
|
+ };
|
|
|
702
|
+
|
|
|
703
|
+ this._sendMessage(keyMessage, pId);
|
|
|
704
|
+
|
|
|
705
|
+ olmData.sasVerification.keySent = true;
|
|
|
706
|
+ break;
|
|
|
707
|
+ }
|
|
|
708
|
+ case OLM_MESSAGE_TYPES.SAS_KEY: {
|
|
|
709
|
+ if (!olmData.session) {
|
|
|
710
|
+ logger.debug(`Received sas key message from ${pId} but we have no session for them!`);
|
|
|
711
|
+
|
|
|
712
|
+ this._sendError(participant, 'No session found while processing sas-key');
|
|
|
713
|
+
|
|
|
714
|
+ return;
|
|
|
715
|
+ }
|
|
|
716
|
+
|
|
|
717
|
+ if (!olmData.sasVerification) {
|
|
|
718
|
+ logger.warn(`SAS_KEY Participant ${pId} does not have valid sasVerification`);
|
|
|
719
|
+ this.eventEmitter.emit(
|
|
|
720
|
+ OlmAdapterEvents.PARTICIPANT_VERIFICATION_COMPLETED,
|
|
|
721
|
+ pId,
|
|
|
722
|
+ false,
|
|
|
723
|
+ E2EEErrors.E2EE_SAS_INVALID_SAS_VERIFICATION);
|
|
|
724
|
+
|
|
|
725
|
+ return;
|
|
|
726
|
+ }
|
|
|
727
|
+
|
|
|
728
|
+ const { isInitiator, sas, sasCommitment, startContent, keySent } = olmData.sasVerification;
|
|
|
729
|
+
|
|
|
730
|
+ if (sas.is_their_key_set()) {
|
|
|
731
|
+ logger.warn('SAS already has their key!');
|
|
|
732
|
+
|
|
|
733
|
+ return;
|
|
|
734
|
+ }
|
|
|
735
|
+
|
|
|
736
|
+ const { key: theirKey, transactionId } = msg.data;
|
|
|
737
|
+
|
|
|
738
|
+ if (sasCommitment) {
|
|
|
739
|
+ const commitment = this._computeCommitment(theirKey, startContent);
|
|
|
740
|
+
|
|
|
741
|
+ if (sasCommitment !== commitment) {
|
|
|
742
|
+ this._sendError(participant, 'OlmAdapter commitments mismatched');
|
|
|
743
|
+ this.eventEmitter.emit(
|
|
|
744
|
+ OlmAdapterEvents.PARTICIPANT_VERIFICATION_COMPLETED,
|
|
|
745
|
+ pId,
|
|
|
746
|
+ false,
|
|
|
747
|
+ E2EEErrors.E2EE_SAS_COMMITMENT_MISMATCHED);
|
|
|
748
|
+ olmData.sasVerification.free();
|
|
|
749
|
+
|
|
|
750
|
+ return;
|
|
|
751
|
+ }
|
|
|
752
|
+ }
|
|
|
753
|
+
|
|
|
754
|
+ sas.set_their_key(theirKey);
|
|
|
755
|
+
|
|
|
756
|
+ const pubKey = sas.get_pubkey();
|
|
|
757
|
+
|
|
|
758
|
+ const myInfo = `${this.myId}|${pubKey}`;
|
|
|
759
|
+ const theirInfo = `${pId}|${theirKey}`;
|
|
|
760
|
+
|
|
|
761
|
+ const info = isInitiator ? `${myInfo}|${theirInfo}` : `${theirInfo}|${myInfo}`;
|
|
|
762
|
+
|
|
|
763
|
+ const sasBytes = sas.generate_bytes(info, OLM_SAS_NUM_BYTES);
|
|
|
764
|
+ const generatedSas = generateSas(sasBytes);
|
|
|
765
|
+
|
|
|
766
|
+ this.eventEmitter.emit(OlmAdapterEvents.PARTICIPANT_SAS_READY, pId, generatedSas);
|
|
|
767
|
+
|
|
|
768
|
+ if (keySent) {
|
|
|
769
|
+ return;
|
|
|
770
|
+ }
|
|
|
771
|
+
|
|
|
772
|
+ const keyMessage = {
|
|
|
773
|
+ [JITSI_MEET_MUC_TYPE]: OLM_MESSAGE_TYPE,
|
|
|
774
|
+ olm: {
|
|
|
775
|
+ type: OLM_MESSAGE_TYPES.SAS_KEY,
|
|
|
776
|
+ data: {
|
|
|
777
|
+ key: pubKey,
|
|
|
778
|
+ transactionId
|
|
|
779
|
+ }
|
|
|
780
|
+ }
|
|
|
781
|
+ };
|
|
|
782
|
+
|
|
|
783
|
+ this._sendMessage(keyMessage, pId);
|
|
|
784
|
+
|
|
|
785
|
+ olmData.sasVerification.keySent = true;
|
|
|
786
|
+ break;
|
|
|
787
|
+ }
|
|
|
788
|
+ case OLM_MESSAGE_TYPES.SAS_MAC: {
|
|
|
789
|
+ if (!olmData.session) {
|
|
|
790
|
+ logger.debug(`Received sas mac message from ${pId} but we have no session for them!`);
|
|
|
791
|
+
|
|
|
792
|
+ this._sendError(participant, 'No session found while processing sas-mac');
|
|
|
793
|
+
|
|
|
794
|
+ return;
|
|
|
795
|
+ }
|
|
|
796
|
+
|
|
|
797
|
+ const { keys, mac, transactionId } = msg.data;
|
|
|
798
|
+
|
|
|
799
|
+ if (!mac || !keys) {
|
|
|
800
|
+ logger.warn('Invalid SAS MAC message');
|
|
|
801
|
+
|
|
|
802
|
+ return;
|
|
|
803
|
+ }
|
|
|
804
|
+
|
|
|
805
|
+ if (!olmData.sasVerification) {
|
|
|
806
|
+ logger.warn(`SAS_MAC Participant ${pId} does not have valid sasVerification`);
|
|
|
807
|
+
|
|
|
808
|
+ return;
|
|
|
809
|
+ }
|
|
|
810
|
+
|
|
|
811
|
+ const sas = olmData.sasVerification.sas;
|
|
|
812
|
+
|
|
|
813
|
+ // Verify the received MACs.
|
|
|
814
|
+ const baseInfo = `${OLM_KEY_VERIFICATION_MAC_INFO}${pId}${this.myId}${transactionId}`;
|
|
|
815
|
+ const keysMac = sas.calculate_mac(
|
|
|
816
|
+ Object.keys(mac).sort().join(','), // eslint-disable-line newline-per-chained-call
|
|
|
817
|
+ baseInfo + OLM_KEY_VERIFICATION_MAC_KEY_IDS
|
|
|
818
|
+ );
|
|
|
819
|
+
|
|
|
820
|
+ if (keysMac !== keys) {
|
|
|
821
|
+ logger.error('SAS verification error: keys MAC mismatch');
|
|
|
822
|
+ this.eventEmitter.emit(
|
|
|
823
|
+ OlmAdapterEvents.PARTICIPANT_VERIFICATION_COMPLETED,
|
|
|
824
|
+ pId,
|
|
|
825
|
+ false,
|
|
|
826
|
+ E2EEErrors.E2EE_SAS_KEYS_MAC_MISMATCH);
|
|
|
827
|
+
|
|
|
828
|
+ return;
|
|
|
829
|
+ }
|
|
|
830
|
+
|
|
|
831
|
+ if (!olmData.ed25519) {
|
|
|
832
|
+ logger.warn('SAS verification error: Missing ed25519 key');
|
|
|
833
|
+
|
|
|
834
|
+ this.eventEmitter.emit(
|
|
|
835
|
+ OlmAdapterEvents.PARTICIPANT_VERIFICATION_COMPLETED,
|
|
|
836
|
+ pId,
|
|
|
837
|
+ false,
|
|
|
838
|
+ E2EEErrors.E2EE_SAS_MISSING_KEY);
|
|
|
839
|
+
|
|
|
840
|
+ return;
|
|
|
841
|
+ }
|
|
|
842
|
+
|
|
|
843
|
+ for (const [ keyInfo, computedMac ] of Object.entries(mac)) {
|
|
|
844
|
+ const ourComputedMac = sas.calculate_mac(
|
|
|
845
|
+ olmData.ed25519,
|
|
|
846
|
+ baseInfo + keyInfo
|
|
|
847
|
+ );
|
|
|
848
|
+
|
|
|
849
|
+ if (computedMac !== ourComputedMac) {
|
|
|
850
|
+ logger.error('SAS verification error: MAC mismatch');
|
|
|
851
|
+ this.eventEmitter.emit(
|
|
|
852
|
+ OlmAdapterEvents.PARTICIPANT_VERIFICATION_COMPLETED,
|
|
|
853
|
+ pId,
|
|
|
854
|
+ false,
|
|
|
855
|
+ E2EEErrors.E2EE_SAS_MAC_MISMATCH);
|
|
|
856
|
+
|
|
|
857
|
+ return;
|
|
|
858
|
+ }
|
|
|
859
|
+ }
|
|
|
860
|
+
|
|
|
861
|
+ logger.info(`SAS MAC verified for participant ${pId}`);
|
|
|
862
|
+ this.eventEmitter.emit(OlmAdapterEvents.PARTICIPANT_VERIFICATION_COMPLETED, pId, true);
|
|
|
863
|
+
|
|
|
864
|
+ break;
|
|
|
865
|
+ }
|
|
473
|
866
|
}
|
|
474
|
867
|
}
|
|
475
|
868
|
|
|
|
@@ -494,11 +887,13 @@ export class OlmAdapter extends Listenable {
|
|
494
|
887
|
* @private
|
|
495
|
888
|
*/
|
|
496
|
889
|
async _onParticipantPropertyChanged(participant, name, oldValue, newValue) {
|
|
|
890
|
+ const participantId = participant.getId();
|
|
|
891
|
+ const olmData = this._getParticipantOlmData(participant);
|
|
|
892
|
+
|
|
497
|
893
|
switch (name) {
|
|
498
|
894
|
case 'e2ee.enabled':
|
|
499
|
895
|
if (newValue && this._conf.isE2EEEnabled()) {
|
|
500
|
896
|
const localParticipantId = this._conf.myUserId();
|
|
501
|
|
- const participantId = participant.getId();
|
|
502
|
897
|
const participantFeatures = await participant.getFeatures();
|
|
503
|
898
|
|
|
504
|
899
|
if (participantFeatures.has(FEATURE_E2EE) && localParticipantId < participantId) {
|
|
|
@@ -507,7 +902,6 @@ export class OlmAdapter extends Listenable {
|
|
507
|
902
|
}
|
|
508
|
903
|
await this._sendSessionInit(participant);
|
|
509
|
904
|
|
|
510
|
|
- const olmData = this._getParticipantOlmData(participant);
|
|
511
|
905
|
const uuid = uuidv4();
|
|
512
|
906
|
|
|
513
|
907
|
const d = new Deferred();
|
|
|
@@ -534,6 +928,10 @@ export class OlmAdapter extends Listenable {
|
|
534
|
928
|
}
|
|
535
|
929
|
}
|
|
536
|
930
|
break;
|
|
|
931
|
+ case 'e2ee.idKey.ed25519':
|
|
|
932
|
+ olmData.ed25519 = newValue;
|
|
|
933
|
+ this.eventEmitter.emit(OlmAdapterEvents.PARTICIPANT_SAS_AVAILABLE, participantId);
|
|
|
934
|
+ break;
|
|
537
|
935
|
}
|
|
538
|
936
|
}
|
|
539
|
937
|
|
|
|
@@ -613,7 +1011,7 @@ export class OlmAdapter extends Listenable {
|
|
613
|
1011
|
olm: {
|
|
614
|
1012
|
type: OLM_MESSAGE_TYPES.SESSION_INIT,
|
|
615
|
1013
|
data: {
|
|
616
|
|
- idKey: this._idKey,
|
|
|
1014
|
+ idKey: this._idKeys.curve25519,
|
|
617
|
1015
|
otKey,
|
|
618
|
1016
|
uuid
|
|
619
|
1017
|
}
|
|
|
@@ -636,6 +1034,60 @@ export class OlmAdapter extends Listenable {
|
|
636
|
1034
|
|
|
637
|
1035
|
return d;
|
|
638
|
1036
|
}
|
|
|
1037
|
+
|
|
|
1038
|
+ /**
|
|
|
1039
|
+ * Builds and sends the SAS MAC message to the given participant.
|
|
|
1040
|
+ * The second phase of the verification process, the Key verification phase
|
|
|
1041
|
+ https://spec.matrix.org/latest/client-server-api/#short-authentication-string-sas-verification
|
|
|
1042
|
+ */
|
|
|
1043
|
+ _sendSasMac(participant) {
|
|
|
1044
|
+ const pId = participant.getId();
|
|
|
1045
|
+ const olmData = this._getParticipantOlmData(participant);
|
|
|
1046
|
+ const { sas, transactionId } = olmData.sasVerification;
|
|
|
1047
|
+
|
|
|
1048
|
+ // Calculate and send MAC with the keys to be verified.
|
|
|
1049
|
+ const mac = {};
|
|
|
1050
|
+ const keyList = [];
|
|
|
1051
|
+ const baseInfo = `${OLM_KEY_VERIFICATION_MAC_INFO}${this.myId}${pId}${transactionId}`;
|
|
|
1052
|
+
|
|
|
1053
|
+ const deviceKeyId = `ed25519:${pId}`;
|
|
|
1054
|
+
|
|
|
1055
|
+ mac[deviceKeyId] = sas.calculate_mac(
|
|
|
1056
|
+ this._idKeys.ed25519,
|
|
|
1057
|
+ baseInfo + deviceKeyId);
|
|
|
1058
|
+ keyList.push(deviceKeyId);
|
|
|
1059
|
+
|
|
|
1060
|
+ const keys = sas.calculate_mac(
|
|
|
1061
|
+ keyList.sort().join(','),
|
|
|
1062
|
+ baseInfo + OLM_KEY_VERIFICATION_MAC_KEY_IDS
|
|
|
1063
|
+ );
|
|
|
1064
|
+
|
|
|
1065
|
+ const macMessage = {
|
|
|
1066
|
+ [JITSI_MEET_MUC_TYPE]: OLM_MESSAGE_TYPE,
|
|
|
1067
|
+ olm: {
|
|
|
1068
|
+ type: OLM_MESSAGE_TYPES.SAS_MAC,
|
|
|
1069
|
+ data: {
|
|
|
1070
|
+ keys,
|
|
|
1071
|
+ mac,
|
|
|
1072
|
+ transactionId
|
|
|
1073
|
+ }
|
|
|
1074
|
+ }
|
|
|
1075
|
+ };
|
|
|
1076
|
+
|
|
|
1077
|
+ this._sendMessage(macMessage, pId);
|
|
|
1078
|
+ }
|
|
|
1079
|
+
|
|
|
1080
|
+ /**
|
|
|
1081
|
+ * Computes the commitment.
|
|
|
1082
|
+ */
|
|
|
1083
|
+ _computeCommitment(pubKey, data) {
|
|
|
1084
|
+ const olmUtil = new Olm.Utility();
|
|
|
1085
|
+ const commitment = olmUtil.sha256(pubKey + JSON.stringify(data));
|
|
|
1086
|
+
|
|
|
1087
|
+ olmUtil.free();
|
|
|
1088
|
+
|
|
|
1089
|
+ return commitment;
|
|
|
1090
|
+ }
|
|
639
|
1091
|
}
|
|
640
|
1092
|
|
|
641
|
1093
|
/**
|