1
- import { type MatrixClient } from "../client.ts" ;
2
1
import { logger as rootLogger } from "../logger.ts" ;
3
2
import { type MatrixEvent } from "../models/event.ts" ;
4
- import { type Room } from "../models/room.ts" ;
5
3
import { type EncryptionConfig } from "./MatrixRTCSession.ts" ;
6
4
import { secureRandomBase64Url } from "../randomstring.ts" ;
7
- import { type EncryptionKeysEventContent } from "./types.ts" ;
8
5
import { decodeBase64 , encodeUnpaddedBase64 } from "../base64.ts" ;
9
- import { type MatrixError , safeGetRetryAfterMs } from "../http-api/errors.ts" ;
6
+ import { safeGetRetryAfterMs } from "../http-api/errors.ts" ;
10
7
import { type CallMembership } from "./CallMembership.ts" ;
11
- import { EventType } from "../@types/event.ts" ;
8
+ import { type IKeyTransport } from "./IKeyTransport.ts" ;
9
+
12
10
const logger = rootLogger . getChild ( "MatrixRTCSession" ) ;
13
11
14
12
/**
@@ -40,8 +38,11 @@ export type Statistics = {
40
38
*/
41
39
export interface IEncryptionManager {
42
40
join ( joinConfig : EncryptionConfig | undefined ) : void ;
41
+
43
42
leave ( ) : void ;
43
+
44
44
onMembershipsUpdate ( oldMemberships : CallMembership [ ] ) : void ;
45
+
45
46
/**
46
47
* Process `m.call.encryption_keys` events to track the encryption keys for call participants.
47
48
* This should be called each time the relevant event is received from a room timeline.
@@ -50,7 +51,9 @@ export interface IEncryptionManager {
50
51
* @param event the event to process
51
52
*/
52
53
onCallEncryptionEventReceived ( event : MatrixEvent ) : void ;
54
+
53
55
getEncryptionKeys ( ) : Map < string , Array < { key : Uint8Array ; timestamp : number } > > ;
56
+
54
57
statistics : Statistics ;
55
58
}
56
59
@@ -71,9 +74,11 @@ export class EncryptionManager implements IEncryptionManager {
71
74
private get updateEncryptionKeyThrottle ( ) : number {
72
75
return this . joinConfig ?. updateEncryptionKeyThrottle ?? 3_000 ;
73
76
}
77
+
74
78
private get makeKeyDelay ( ) : number {
75
79
return this . joinConfig ?. makeKeyDelay ?? 3_000 ;
76
80
}
81
+
77
82
private get useKeyDelay ( ) : number {
78
83
return this . joinConfig ?. useKeyDelay ?? 5_000 ;
79
84
}
@@ -99,9 +104,10 @@ export class EncryptionManager implements IEncryptionManager {
99
104
private joinConfig : EncryptionConfig | undefined ;
100
105
101
106
public constructor (
102
- private client : Pick < MatrixClient , "sendEvent" | "getDeviceId" | "getUserId" | "cancelPendingEvent" > ,
103
- private room : Pick < Room , "roomId" > ,
107
+ private userId : string ,
108
+ private deviceId : string ,
104
109
private getMemberships : ( ) => CallMembership [ ] ,
110
+ private transport : IKeyTransport ,
105
111
private onEncryptionKeysChanged : (
106
112
keyBin : Uint8Array < ArrayBufferLike > ,
107
113
encryptionKeyIndex : number ,
@@ -112,7 +118,9 @@ export class EncryptionManager implements IEncryptionManager {
112
118
public getEncryptionKeys ( ) : Map < string , Array < { key : Uint8Array ; timestamp : number } > > {
113
119
return this . encryptionKeys ;
114
120
}
121
+
115
122
private joined = false ;
123
+
116
124
public join ( joinConfig : EncryptionConfig ) : void {
117
125
this . joinConfig = joinConfig ;
118
126
this . joined = true ;
@@ -124,15 +132,10 @@ export class EncryptionManager implements IEncryptionManager {
124
132
}
125
133
126
134
public leave ( ) : void {
127
- const userId = this . client . getUserId ( ) ;
128
- const deviceId = this . client . getDeviceId ( ) ;
129
-
130
- if ( ! userId ) throw new Error ( "No userId" ) ;
131
- if ( ! deviceId ) throw new Error ( "No deviceId" ) ;
132
135
// clear our encryption keys as we're done with them now (we'll
133
136
// make new keys if we rejoin). We leave keys for other participants
134
137
// as they may still be using the same ones.
135
- this . encryptionKeys . set ( getParticipantId ( userId , deviceId ) , [ ] ) ;
138
+ this . encryptionKeys . set ( getParticipantId ( this . userId , this . deviceId ) , [ ] ) ;
136
139
137
140
if ( this . makeNewKeyTimeout !== undefined ) {
138
141
clearTimeout ( this . makeNewKeyTimeout ) ;
@@ -146,9 +149,9 @@ export class EncryptionManager implements IEncryptionManager {
146
149
this . manageMediaKeys = false ;
147
150
this . joined = false ;
148
151
}
152
+
149
153
// TODO deduplicate this method. It also is in MatrixRTCSession.
150
- private isMyMembership = ( m : CallMembership ) : boolean =>
151
- m . sender === this . client . getUserId ( ) && m . deviceId === this . client . getDeviceId ( ) ;
154
+ private isMyMembership = ( m : CallMembership ) : boolean => m . sender === this . userId && m . deviceId === this . deviceId ;
152
155
153
156
public onMembershipsUpdate ( oldMemberships : CallMembership [ ] ) : void {
154
157
if ( this . manageMediaKeys && this . joined ) {
@@ -204,16 +207,17 @@ export class EncryptionManager implements IEncryptionManager {
204
207
* @returns The index of the new key
205
208
*/
206
209
private makeNewSenderKey ( delayBeforeUse = false ) : number {
207
- const userId = this . client . getUserId ( ) ;
208
- const deviceId = this . client . getDeviceId ( ) ;
209
-
210
- if ( ! userId ) throw new Error ( "No userId" ) ;
211
- if ( ! deviceId ) throw new Error ( "No deviceId" ) ;
212
-
213
210
const encryptionKey = secureRandomBase64Url ( 16 ) ;
214
211
const encryptionKeyIndex = this . getNewEncryptionKeyIndex ( ) ;
215
212
logger . info ( "Generated new key at index " + encryptionKeyIndex ) ;
216
- this . setEncryptionKey ( userId , deviceId , encryptionKeyIndex , encryptionKey , Date . now ( ) , delayBeforeUse ) ;
213
+ this . setEncryptionKey (
214
+ this . userId ,
215
+ this . deviceId ,
216
+ encryptionKeyIndex ,
217
+ encryptionKey ,
218
+ Date . now ( ) ,
219
+ delayBeforeUse ,
220
+ ) ;
217
221
return encryptionKeyIndex ;
218
222
}
219
223
@@ -266,13 +270,7 @@ export class EncryptionManager implements IEncryptionManager {
266
270
267
271
logger . info ( `Sending encryption keys event. indexToSend=${ indexToSend } ` ) ;
268
272
269
- const userId = this . client . getUserId ( ) ;
270
- const deviceId = this . client . getDeviceId ( ) ;
271
-
272
- if ( ! userId ) throw new Error ( "No userId" ) ;
273
- if ( ! deviceId ) throw new Error ( "No deviceId" ) ;
274
-
275
- const myKeys = this . getKeysForParticipant ( userId , deviceId ) ;
273
+ const myKeys = this . getKeysForParticipant ( this . userId , this . deviceId ) ;
276
274
277
275
if ( ! myKeys ) {
278
276
logger . warn ( "Tried to send encryption keys event but no keys found!" ) ;
@@ -288,35 +286,15 @@ export class EncryptionManager implements IEncryptionManager {
288
286
const keyToSend = myKeys [ keyIndexToSend ] ;
289
287
290
288
try {
291
- const content : EncryptionKeysEventContent = {
292
- keys : [
293
- {
294
- index : keyIndexToSend ,
295
- key : encodeUnpaddedBase64 ( keyToSend ) ,
296
- } ,
297
- ] ,
298
- device_id : deviceId ,
299
- call_id : "" ,
300
- sent_ts : Date . now ( ) ,
301
- } ;
302
-
303
289
this . statistics . counters . roomEventEncryptionKeysSent += 1 ;
304
-
305
- await this . client . sendEvent ( this . room . roomId , EventType . CallEncryptionKeysPrefix , content ) ;
306
-
290
+ await this . transport . sendKey ( encodeUnpaddedBase64 ( keyToSend ) , keyIndexToSend ) ;
307
291
logger . debug (
308
- `Embedded-E2EE-LOG updateEncryptionKeyEvent participantId=${ userId } :${ deviceId } numKeys=${ myKeys . length } currentKeyIndex=${ this . currentEncryptionKeyIndex } keyIndexToSend=${ keyIndexToSend } ` ,
292
+ `Embedded-E2EE-LOG updateEncryptionKeyEvent participantId=${ this . userId } :${ this . deviceId } numKeys=${ myKeys . length } currentKeyIndex=${ this . currentEncryptionKeyIndex } keyIndexToSend=${ keyIndexToSend } ` ,
309
293
this . encryptionKeys ,
310
294
) ;
311
295
} catch ( error ) {
312
- const matrixError = error as MatrixError ;
313
- if ( matrixError . event ) {
314
- // cancel the pending event: we'll just generate a new one with our latest
315
- // keys when we resend
316
- this . client . cancelPendingEvent ( matrixError . event ) ;
317
- }
318
296
if ( this . keysEventUpdateTimeout === undefined ) {
319
- const resendDelay = safeGetRetryAfterMs ( matrixError , 5000 ) ;
297
+ const resendDelay = safeGetRetryAfterMs ( error , 5000 ) ;
320
298
logger . warn ( `Failed to send m.call.encryption_key, retrying in ${ resendDelay } ` , error ) ;
321
299
this . keysEventUpdateTimeout = setTimeout ( ( ) => void this . sendEncryptionKeysEvent ( ) , resendDelay ) ;
322
300
} else {
@@ -326,74 +304,15 @@ export class EncryptionManager implements IEncryptionManager {
326
304
} ;
327
305
328
306
public onCallEncryptionEventReceived = ( event : MatrixEvent ) : void => {
329
- const userId = event . getSender ( ) ;
330
- const content = event . getContent < EncryptionKeysEventContent > ( ) ;
331
-
332
- const deviceId = content [ "device_id" ] ;
333
- const callId = content [ "call_id" ] ;
334
-
335
- if ( ! userId ) {
336
- logger . warn ( `Received m.call.encryption_keys with no userId: callId=${ callId } ` ) ;
337
- return ;
338
- }
339
-
340
- // We currently only handle callId = "" (which is the default for room scoped calls)
341
- if ( callId !== "" ) {
342
- logger . warn (
343
- `Received m.call.encryption_keys with unsupported callId: userId=${ userId } , deviceId=${ deviceId } , callId=${ callId } ` ,
344
- ) ;
345
- return ;
346
- }
347
-
348
- if ( ! Array . isArray ( content . keys ) ) {
349
- logger . warn ( `Received m.call.encryption_keys where keys wasn't an array: callId=${ callId } ` ) ;
350
- return ;
351
- }
352
-
353
- if ( userId === this . client . getUserId ( ) && deviceId === this . client . getDeviceId ( ) ) {
354
- // We store our own sender key in the same set along with keys from others, so it's
355
- // important we don't allow our own keys to be set by one of these events (apart from
356
- // the fact that we don't need it anyway because we already know our own keys).
357
- logger . info ( "Ignoring our own keys event" ) ;
358
- return ;
359
- }
360
-
361
- this . statistics . counters . roomEventEncryptionKeysReceived += 1 ;
362
- const age = Date . now ( ) - ( typeof content . sent_ts === "number" ? content . sent_ts : event . getTs ( ) ) ;
363
- this . statistics . totals . roomEventEncryptionKeysReceivedTotalAge += age ;
364
-
365
- for ( const key of content . keys ) {
366
- if ( ! key ) {
367
- logger . info ( "Ignoring false-y key in keys event" ) ;
368
- continue ;
369
- }
370
-
371
- const encryptionKey = key . key ;
372
- const encryptionKeyIndex = key . index ;
373
-
374
- if (
375
- ! encryptionKey ||
376
- encryptionKeyIndex === undefined ||
377
- encryptionKeyIndex === null ||
378
- callId === undefined ||
379
- callId === null ||
380
- typeof deviceId !== "string" ||
381
- typeof callId !== "string" ||
382
- typeof encryptionKey !== "string" ||
383
- typeof encryptionKeyIndex !== "number"
384
- ) {
385
- logger . warn (
386
- `Malformed call encryption_key: userId=${ userId } , deviceId=${ deviceId } , encryptionKeyIndex=${ encryptionKeyIndex } callId=${ callId } ` ,
387
- ) ;
388
- } else {
389
- logger . debug (
390
- `Embedded-E2EE-LOG onCallEncryption userId=${ userId } :${ deviceId } encryptionKeyIndex=${ encryptionKeyIndex } age=${ age } ms` ,
391
- this . encryptionKeys ,
392
- ) ;
393
- this . setEncryptionKey ( userId , deviceId , encryptionKeyIndex , encryptionKey , event . getTs ( ) ) ;
394
- }
395
- }
307
+ this . transport . receiveRoomEvent (
308
+ event ,
309
+ this . statistics ,
310
+ ( userId , deviceId , encryptionKeyIndex , encryptionKeyString , timestamp ) => {
311
+ this . setEncryptionKey ( userId , deviceId , encryptionKeyIndex , encryptionKeyString , timestamp ) ;
312
+ } ,
313
+ ) ;
396
314
} ;
315
+
397
316
private storeLastMembershipFingerprints ( ) : void {
398
317
this . lastMembershipFingerprints = new Set (
399
318
this . getMemberships ( )
@@ -466,14 +385,14 @@ export class EncryptionManager implements IEncryptionManager {
466
385
const useKeyTimeout = setTimeout ( ( ) => {
467
386
this . setNewKeyTimeouts . delete ( useKeyTimeout ) ;
468
387
logger . info ( `Delayed-emitting key changed event for ${ participantId } idx ${ encryptionKeyIndex } ` ) ;
469
- if ( userId === this . client . getUserId ( ) && deviceId === this . client . getDeviceId ( ) ) {
388
+ if ( userId === this . userId && deviceId === this . deviceId ) {
470
389
this . currentEncryptionKeyIndex = encryptionKeyIndex ;
471
390
}
472
391
this . onEncryptionKeysChanged ( keyBin , encryptionKeyIndex , participantId ) ;
473
392
} , this . useKeyDelay ) ;
474
393
this . setNewKeyTimeouts . add ( useKeyTimeout ) ;
475
394
} else {
476
- if ( userId === this . client . getUserId ( ) && deviceId === this . client . getDeviceId ( ) ) {
395
+ if ( userId === this . userId && deviceId === this . deviceId ) {
477
396
this . currentEncryptionKeyIndex = encryptionKeyIndex ;
478
397
}
479
398
this . onEncryptionKeysChanged ( keyBin , encryptionKeyIndex , participantId ) ;
@@ -493,8 +412,10 @@ export class EncryptionManager implements IEncryptionManager {
493
412
}
494
413
495
414
const getParticipantId = ( userId : string , deviceId : string ) : string => `${ userId } :${ deviceId } ` ;
415
+
496
416
function keysEqual ( a : Uint8Array | undefined , b : Uint8Array | undefined ) : boolean {
497
417
if ( a === b ) return true ;
498
418
return ! ! a && ! ! b && a . length === b . length && a . every ( ( x , i ) => x === b [ i ] ) ;
499
419
}
420
+
500
421
const getParticipantIdFromMembership = ( m : CallMembership ) : string => getParticipantId ( m . sender ! , m . deviceId ) ;
0 commit comments