@@ -20,7 +20,7 @@ import { type CallMembership } from "./CallMembership.ts";
20
20
import { decodeBase64 , encodeBase64 } from "../base64.ts" ;
21
21
import { type IKeyTransport , type KeyTransportEventListener , KeyTransportEvents } from "./IKeyTransport.ts" ;
22
22
import { logger as rootLogger , type Logger } from "../logger.ts" ;
23
- import { sleep } from "../utils.ts" ;
23
+ import { defer , type IDeferred , sleep } from "../utils.ts" ;
24
24
import type { InboundEncryptionSession , ParticipantDeviceInfo , ParticipantId , Statistics } from "./types.ts" ;
25
25
import { getParticipantId , KeyBuffer } from "./utils.ts" ;
26
26
import {
@@ -75,6 +75,8 @@ export class RTCEncryptionManager implements IEncryptionManager {
75
75
76
76
private logger : Logger ;
77
77
78
+ private currentRatchetRequest : IDeferred < { key : ArrayBuffer ; keyIndex : number } > | null = null ;
79
+
78
80
public constructor (
79
81
private userId : string ,
80
82
private deviceId : string ,
@@ -86,9 +88,10 @@ export class RTCEncryptionManager implements IEncryptionManager {
86
88
encryptionKeyIndex : number ,
87
89
participantId : ParticipantId ,
88
90
) => void ,
91
+ private ratchetKey : ( participantId : ParticipantId , encryptionKeyIndex : number ) => void ,
89
92
parentLogger ?: Logger ,
90
93
) {
91
- this . logger = ( parentLogger ?? rootLogger ) . getChild ( `[EncryptionManager ]` ) ;
94
+ this . logger = ( parentLogger ?? rootLogger ) . getChild ( `[RTCEncryptionManager ]` ) ;
92
95
}
93
96
94
97
public getEncryptionKeys ( ) : Map < string , Array < { key : Uint8Array ; timestamp : number } > > {
@@ -163,7 +166,9 @@ export class RTCEncryptionManager implements IEncryptionManager {
163
166
}
164
167
165
168
public onNewKeyReceived : KeyTransportEventListener = ( userId , deviceId , keyBase64Encoded , index , timestamp ) => {
166
- this . logger . debug ( `Received key over transport ${ userId } :${ deviceId } at index ${ index } ` ) ;
169
+ this . logger . debug (
170
+ `Received key over transport ${ userId } :${ deviceId } at index ${ index } key: ${ keyBase64Encoded } ` ,
171
+ ) ;
167
172
168
173
// We received a new key, notify the video layer of this new key so that it can decrypt the frames properly.
169
174
const participantId = getParticipantId ( userId , deviceId ) ;
@@ -216,7 +221,13 @@ export class RTCEncryptionManager implements IEncryptionManager {
216
221
// get current memberships
217
222
const toShareWith : ParticipantDeviceInfo [ ] = this . getMemberships ( )
218
223
. filter ( ( membership ) => {
219
- return membership . sender != undefined ;
224
+ return (
225
+ membership . sender != undefined &&
226
+ ! (
227
+ // filter me out
228
+ ( membership . sender == this . userId && membership . deviceId == this . deviceId )
229
+ )
230
+ ) ;
220
231
} )
221
232
. map ( ( membership ) => {
222
233
return {
@@ -272,23 +283,49 @@ export class RTCEncryptionManager implements IEncryptionManager {
272
283
toDistributeTo = toShareWith ;
273
284
outboundKey = newOutboundKey ;
274
285
} else if ( anyJoined . length > 0 ) {
275
- // keep the same key
276
- // XXX In the future we want to distribute a ratcheted key not the current one
286
+ if ( this . outboundSession ! . sharedWith . length > 0 ) {
287
+ // This key was already shared with someone, we need to ratchet it
288
+ // We want to ratchet the current key and only distribute the ratcheted key to the new joiners
289
+ // This needs to send some async messages, so we need to wait for the ratchet to finish
290
+ const deferredKey = defer < { key : ArrayBuffer ; keyIndex : number } > ( ) ;
291
+ this . currentRatchetRequest = deferredKey ;
292
+ this . logger . info ( `Query ratcheting key index:${ this . outboundSession ! . keyId } ...` ) ;
293
+ this . ratchetKey ( getParticipantId ( this . userId , this . deviceId ) , this . outboundSession ! . keyId ) ;
294
+ const res = await Promise . race ( [ deferredKey . promise , sleep ( 1000 ) ] ) ;
295
+ if ( res === undefined ) {
296
+ // TODO: we might want to rotate the key instead?
297
+ this . logger . error ( "Ratchet key timed out sharing the same key for now :/" ) ;
298
+ } else {
299
+ const { key, keyIndex } = await deferredKey . promise ;
300
+ this . logger . info (
301
+ `... Ratcheting done key index:${ keyIndex } key:${ encodeBase64 ( new Uint8Array ( key ) ) } ` ,
302
+ ) ;
303
+ this . outboundSession ! . key = new Uint8Array ( key ) ;
304
+ this . onEncryptionKeysChanged (
305
+ this . outboundSession ! . key ,
306
+ this . outboundSession ! . keyId ,
307
+ getParticipantId ( this . userId , this . deviceId ) ,
308
+ ) ;
309
+ }
310
+ }
277
311
toDistributeTo = anyJoined ;
278
312
outboundKey = this . outboundSession ! ;
279
313
} else {
280
- // no changes
281
- return ;
314
+ // No one joined or left, it could just be the first key, keep going
315
+ toDistributeTo = [ ] ;
316
+ outboundKey = this . outboundSession ! ;
282
317
}
283
318
284
319
try {
285
- this . logger . trace ( `Sending key...` ) ;
286
- await this . transport . sendKey ( encodeBase64 ( outboundKey . key ) , outboundKey . keyId , toDistributeTo ) ;
287
- this . statistics . counters . roomEventEncryptionKeysSent += 1 ;
288
- outboundKey . sharedWith . push ( ...toDistributeTo ) ;
289
- this . logger . trace (
290
- `key index:${ outboundKey . keyId } sent to ${ outboundKey . sharedWith . map ( ( m ) => `${ m . userId } :${ m . deviceId } ` ) . join ( "," ) } ` ,
291
- ) ;
320
+ if ( toDistributeTo . length > 0 ) {
321
+ this . logger . trace ( `Sending key...` ) ;
322
+ await this . transport . sendKey ( encodeBase64 ( outboundKey . key ) , outboundKey . keyId , toDistributeTo ) ;
323
+ this . statistics . counters . roomEventEncryptionKeysSent += 1 ;
324
+ outboundKey . sharedWith . push ( ...toDistributeTo ) ;
325
+ this . logger . trace (
326
+ `key index:${ outboundKey . keyId } sent to ${ outboundKey . sharedWith . map ( ( m ) => `${ m . userId } :${ m . deviceId } ` ) . join ( "," ) } ` ,
327
+ ) ;
328
+ }
292
329
if ( hasKeyChanged ) {
293
330
// Delay a bit before using this key
294
331
// It is recommended not to start using a key immediately but instead wait for a short time to make sure it is delivered.
@@ -318,4 +355,10 @@ export class RTCEncryptionManager implements IEncryptionManager {
318
355
globalThis . crypto . getRandomValues ( key ) ;
319
356
return key ;
320
357
}
358
+
359
+ public onOwnKeyRatcheted ( key : ArrayBuffer , keyIndex : number | undefined ) : void {
360
+ this . logger . debug ( `Own key ratcheted for key index:${ keyIndex } key:${ encodeBase64 ( new Uint8Array ( key ) ) } ` ) ;
361
+
362
+ this . currentRatchetRequest ?. resolve ( { key, keyIndex : keyIndex ! } ) ;
363
+ }
321
364
}
0 commit comments