@@ -18,7 +18,7 @@ import { logger as rootLogger } from "../logger.ts";
18
18
import { TypedEventEmitter } from "../models/typed-event-emitter.ts" ;
19
19
import { EventTimeline } from "../models/event-timeline.ts" ;
20
20
import { Room } from "../models/room.ts" ;
21
- import { MatrixClient } from "../client.ts" ;
21
+ import { MatrixClient , SendToDeviceContentMap } from "../client.ts" ;
22
22
import { EventType } from "../@types/event.ts" ;
23
23
import { UpdateDelayedEventAction } from "../@types/requests.ts" ;
24
24
import {
@@ -31,14 +31,15 @@ import {
31
31
import { RoomStateEvent } from "../models/room-state.ts" ;
32
32
import { Focus } from "./focus.ts" ;
33
33
import { randomString , secureRandomBase64Url } from "../randomstring.ts" ;
34
- import { EncryptionKeysEventContent } from "./types.ts" ;
34
+ import { EncryptionKeysEventContent , EncryptionKeysToDeviceContent } from "./types.ts" ;
35
35
import { decodeBase64 , encodeUnpaddedBase64 } from "../base64.ts" ;
36
36
import { KnownMembership } from "../@types/membership.ts" ;
37
37
import { MatrixError } from "../http-api/errors.ts" ;
38
38
import { MatrixEvent } from "../models/event.ts" ;
39
39
import { isLivekitFocusActive } from "./LivekitFocus.ts" ;
40
40
import { ExperimentalGroupCallRoomMemberState } from "../webrtc/groupCall.ts" ;
41
41
import { sleep } from "../utils.ts" ;
42
+ import type { RoomWidgetClient } from "../embedded.ts" ;
42
43
43
44
const logger = rootLogger . getChild ( "MatrixRTCSession" ) ;
44
45
@@ -162,8 +163,21 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
162
163
* The number of times we have received a room event containing encryption keys.
163
164
*/
164
165
roomEventEncryptionKeysReceived : 0 ,
166
+ /**
167
+ * The number of times we have sent a to-device event containing encryption keys.
168
+ */
169
+ toDeviceEncryptionKeysSent : 0 ,
170
+ /**
171
+ * The number of times we have received a to-device event containing encryption keys.
172
+ */
173
+ toDeviceEncryptionKeysReceived : 0 ,
165
174
} ,
166
175
totals : {
176
+ /**
177
+ * The total age (in milliseconds) of all to-device events containing encryption keys that we have received.
178
+ * We track the total age so that we can later calculate the average age of all keys received.
179
+ */
180
+ toDeviceEncryptionKeysReceivedTotalAge : 0 ,
167
181
/**
168
182
* The total age (in milliseconds) of all room events containing encryption keys that we have received.
169
183
* We track the total age so that we can later calculate the average age of all keys received.
@@ -546,7 +560,7 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
546
560
}
547
561
548
562
/**
549
- * Requests that we resend our current keys to the room. May send a keys event immediately
563
+ * Requests that we (re)-send our current keys to the room. May send a keys event immediately
550
564
* or queue for alter if one has already been sent recently.
551
565
*/
552
566
private requestSendCurrentKey ( ) : void {
@@ -602,21 +616,10 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
602
616
const keyToSend = myKeys [ keyIndexToSend ] ;
603
617
604
618
try {
605
- const content : EncryptionKeysEventContent = {
606
- keys : [
607
- {
608
- index : keyIndexToSend ,
609
- key : encodeUnpaddedBase64 ( keyToSend ) ,
610
- } ,
611
- ] ,
612
- device_id : deviceId ,
613
- call_id : "" ,
614
- sent_ts : Date . now ( ) ,
615
- } ;
616
-
617
- this . statistics . counters . roomEventEncryptionKeysSent += 1 ;
618
-
619
- await this . client . sendEvent ( this . room . roomId , EventType . CallEncryptionKeysPrefix , content ) ;
619
+ await Promise . all ( [
620
+ this . sendKeysViaRoomEvent ( deviceId , keyToSend , keyIndexToSend ) ,
621
+ this . sendKeysViaToDevice ( deviceId , keyToSend , keyIndexToSend ) ,
622
+ ] ) ;
620
623
621
624
logger . debug (
622
625
`Embedded-E2EE-LOG updateEncryptionKeyEvent participantId=${ userId } :${ deviceId } numKeys=${ myKeys . length } currentKeyIndex=${ this . currentEncryptionKeyIndex } keyIndexToSend=${ keyIndexToSend } ` ,
@@ -639,6 +642,96 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
639
642
}
640
643
} ;
641
644
645
+ private async sendKeysViaRoomEvent ( deviceId : string , key : Uint8Array , index : number ) : Promise < void > {
646
+ const membersRequiringRoomEvent = this . memberships . filter (
647
+ ( m ) => ! this . isMyMembership ( m ) && m . keyDistributionMethod === "room_event" ,
648
+ ) ;
649
+
650
+ if ( membersRequiringRoomEvent . length === 0 ) {
651
+ logger . info ( "No members require keys via room event" ) ;
652
+ return ;
653
+ }
654
+
655
+ logger . info (
656
+ `Sending encryption keys event for: ${ membersRequiringRoomEvent . map ( ( m ) => `${ m . sender } :${ m . deviceId } ` ) . join ( ", " ) } ` ,
657
+ ) ;
658
+
659
+ const content : EncryptionKeysEventContent = {
660
+ keys : [
661
+ {
662
+ index,
663
+ key : encodeUnpaddedBase64 ( key ) ,
664
+ } ,
665
+ ] ,
666
+ device_id : deviceId ,
667
+ call_id : "" ,
668
+ sent_ts : Date . now ( ) ,
669
+ } ;
670
+
671
+ this . statistics . counters . roomEventEncryptionKeysSent += 1 ;
672
+
673
+ await this . client . sendEvent ( this . room . roomId , EventType . CallEncryptionKeysPrefix , content ) ;
674
+ }
675
+
676
+ private async sendKeysViaToDevice ( deviceId : string , key : Uint8Array , index : number ) : Promise < void > {
677
+ const membershipsRequiringToDevice = this . memberships . filter (
678
+ ( m ) => ! this . isMyMembership ( m ) && m . sender && m . keyDistributionMethod === "to_device" ,
679
+ ) ;
680
+
681
+ if ( membershipsRequiringToDevice . length === 0 ) {
682
+ logger . info ( "No members require keys via to-device event" ) ;
683
+ return ;
684
+ }
685
+
686
+ const content : EncryptionKeysToDeviceContent = {
687
+ keys : [ { index, key : encodeUnpaddedBase64 ( key ) } ] ,
688
+ device_id : deviceId ,
689
+ call_id : "" ,
690
+ room_id : this . room . roomId ,
691
+ sent_ts : Date . now ( ) ,
692
+ } ;
693
+
694
+ logger . info (
695
+ `Sending encryption keys to-device batch for: ${ membershipsRequiringToDevice . map ( ( { sender, deviceId } ) => `${ sender } :${ deviceId } ` ) . join ( ", " ) } ` ,
696
+ ) ;
697
+
698
+ this . statistics . counters . toDeviceEncryptionKeysSent += membershipsRequiringToDevice . length ;
699
+
700
+ // we don't do an instanceof due to circular dependency issues
701
+ if ( "widgetApi" in this . client ) {
702
+ logger . info ( "Sending keys via widgetApi" ) ;
703
+ // embedded mode, getCrypto() returns null and so we make some assumptions about the underlying implementation
704
+
705
+ const contentMap : SendToDeviceContentMap = new Map ( ) ;
706
+
707
+ membershipsRequiringToDevice . forEach ( ( { sender, deviceId } ) => {
708
+ if ( ! contentMap . has ( sender ! ) ) {
709
+ contentMap . set ( sender ! , new Map ( ) ) ;
710
+ }
711
+
712
+ contentMap . get ( sender ! ) ! . set ( deviceId , content ) ;
713
+ } ) ;
714
+
715
+ await ( this . client as unknown as RoomWidgetClient ) . sendToDeviceViaWidgetApi (
716
+ EventType . CallEncryptionKeysPrefix ,
717
+ true ,
718
+ contentMap ,
719
+ ) ;
720
+ } else {
721
+ const crypto = this . client . getCrypto ( ) ;
722
+ if ( ! crypto ) {
723
+ logger . error ( "No crypto instance available to send keys via to-device event" ) ;
724
+ return ;
725
+ }
726
+
727
+ const devices = membershipsRequiringToDevice . map ( ( { deviceId, sender } ) => ( { userId : sender ! , deviceId } ) ) ;
728
+
729
+ const batch = await crypto . encryptToDeviceMessages ( EventType . CallEncryptionKeysPrefix , devices , content ) ;
730
+
731
+ await this . client . queueToDevice ( batch ) ;
732
+ }
733
+ }
734
+
642
735
/**
643
736
* Sets a timer for the soonest membership expiry
644
737
*/
@@ -714,9 +807,17 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
714
807
return ;
715
808
}
716
809
717
- this . statistics . counters . roomEventEncryptionKeysReceived += 1 ;
718
- const age = Date . now ( ) - ( typeof content . sent_ts === "number" ? content . sent_ts : event . getTs ( ) ) ;
719
- this . statistics . totals . roomEventEncryptionKeysReceivedTotalAge += age ;
810
+ let age : number ;
811
+
812
+ if ( event . getRoomId ( ) ) {
813
+ this . statistics . counters . roomEventEncryptionKeysReceived += 1 ;
814
+ age = Date . now ( ) - ( typeof content . sent_ts === "number" ? content . sent_ts : event . getTs ( ) ) ;
815
+ this . statistics . totals . roomEventEncryptionKeysReceivedTotalAge += age ;
816
+ } else {
817
+ this . statistics . counters . toDeviceEncryptionKeysReceived += 1 ;
818
+ age = Date . now ( ) - ( content as EncryptionKeysToDeviceContent ) . sent_ts ;
819
+ this . statistics . totals . toDeviceEncryptionKeysReceivedTotalAge += age ;
820
+ }
720
821
721
822
for ( const key of content . keys ) {
722
823
if ( ! key ) {
@@ -795,8 +896,8 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
795
896
logger . debug ( `Member(s) have left: queueing sender key rotation` ) ;
796
897
this . makeNewKeyTimeout = setTimeout ( this . onRotateKeyTimeout , MAKE_KEY_DELAY ) ;
797
898
} else if ( anyJoined ) {
798
- logger . debug ( `New member(s) have joined: re-sending keys` ) ;
799
- this . requestSendCurrentKey ( ) ;
899
+ logger . debug ( `New member(s) have joined: rotating keys` ) ;
900
+ this . makeNewKeyTimeout = setTimeout ( this . onRotateKeyTimeout , MAKE_KEY_DELAY ) ;
800
901
} else if ( oldFingerprints ) {
801
902
// does it look like any of the members have updated their memberships?
802
903
const newFingerprints = this . lastMembershipFingerprints ! ;
@@ -849,6 +950,7 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
849
950
foci_active : this . ownFociPreferred ,
850
951
membershipID : this . membershipId ,
851
952
...( createdTs ? { created_ts : createdTs } : { } ) ,
953
+ key_distribution : "to_device" ,
852
954
} ;
853
955
}
854
956
/**
@@ -862,6 +964,7 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
862
964
device_id : deviceId ,
863
965
focus_active : { type : "livekit" , focus_selection : "oldest_membership" } ,
864
966
foci_preferred : this . ownFociPreferred ?? [ ] ,
967
+ key_distribution : "to_device" ,
865
968
} ;
866
969
}
867
970
0 commit comments