CloudWebRTC
Committed by GitHub

Improvements for E2EE. (#276)

* Improvements for E2EE.

* format.

* spotlessApply.

* Update livekit-android-sdk/src/main/java/io/livekit/android/e2ee/E2EEManager.kt

Co-authored-by: davidliu <davidliu@deviange.net>

* Update livekit-android-sdk/src/main/java/io/livekit/android/e2ee/E2EEManager.kt

Co-authored-by: davidliu <davidliu@deviange.net>

* Update livekit-android-sdk/src/main/java/io/livekit/android/e2ee/E2EEManager.kt

Co-authored-by: davidliu <davidliu@deviange.net>

* Update livekit-android-sdk/src/main/java/io/livekit/android/room/Room.kt

Co-authored-by: davidliu <davidliu@deviange.net>

* Update livekit-android-sdk/src/main/java/io/livekit/android/room/Room.kt

Co-authored-by: davidliu <davidliu@deviange.net>

* fix.

* fix.

* spotlessKotlinCheck.

* fix

---------

Co-authored-by: davidliu <davidliu@deviange.net>
@@ -37,7 +37,7 @@ class E2EEManager @@ -37,7 +37,7 @@ class E2EEManager
37 constructor(keyProvider: KeyProvider) { 37 constructor(keyProvider: KeyProvider) {
38 private var room: Room? = null 38 private var room: Room? = null
39 private var keyProvider: KeyProvider 39 private var keyProvider: KeyProvider
40 - private var frameCryptors = mutableMapOf<String, FrameCryptor>() 40 + private var frameCryptors = mutableMapOf<Pair<String, String>, FrameCryptor>()
41 private var algorithm: FrameCryptorAlgorithm = FrameCryptorAlgorithm.AES_GCM 41 private var algorithm: FrameCryptorAlgorithm = FrameCryptorAlgorithm.AES_GCM
42 private lateinit var emitEvent: (roomEvent: RoomEvent) -> Unit? 42 private lateinit var emitEvent: (roomEvent: RoomEvent) -> Unit?
43 var enabled: Boolean = false 43 var enabled: Boolean = false
@@ -45,6 +45,10 @@ constructor(keyProvider: KeyProvider) { @@ -45,6 +45,10 @@ constructor(keyProvider: KeyProvider) {
45 this.keyProvider = keyProvider 45 this.keyProvider = keyProvider
46 } 46 }
47 47
  48 + public fun keyProvider(): KeyProvider {
  49 + return this.keyProvider
  50 + }
  51 +
48 suspend fun setup(room: Room, emitEvent: (roomEvent: RoomEvent) -> Unit) { 52 suspend fun setup(room: Room, emitEvent: (roomEvent: RoomEvent) -> Unit) {
49 if (this.room != room) { 53 if (this.room != room) {
50 // E2EEManager already setup, clean up first 54 // E2EEManager already setup, clean up first
@@ -72,8 +76,6 @@ constructor(keyProvider: KeyProvider) { @@ -72,8 +76,6 @@ constructor(keyProvider: KeyProvider) {
72 } 76 }
73 77
74 public fun addSubscribedTrack(track: Track, publication: TrackPublication, participant: RemoteParticipant, room: Room) { 78 public fun addSubscribedTrack(track: Track, publication: TrackPublication, participant: RemoteParticipant, room: Room) {
75 - var trackId = publication.sid  
76 - var participantId = participant.sid  
77 var rtpReceiver: RtpReceiver? = when (publication.track!!) { 79 var rtpReceiver: RtpReceiver? = when (publication.track!!) {
78 is RemoteAudioTrack -> (publication.track!! as RemoteAudioTrack).receiver 80 is RemoteAudioTrack -> (publication.track!! as RemoteAudioTrack).receiver
79 is RemoteVideoTrack -> (publication.track!! as RemoteVideoTrack).receiver 81 is RemoteVideoTrack -> (publication.track!! as RemoteVideoTrack).receiver
@@ -81,7 +83,7 @@ constructor(keyProvider: KeyProvider) { @@ -81,7 +83,7 @@ constructor(keyProvider: KeyProvider) {
81 throw IllegalArgumentException("unsupported track type") 83 throw IllegalArgumentException("unsupported track type")
82 } 84 }
83 } 85 }
84 - var frameCryptor = addRtpReceiver(rtpReceiver!!, participantId, trackId, publication.track!!.kind.name.lowercase()) 86 + var frameCryptor = addRtpReceiver(rtpReceiver!!, participant.identity!!, publication.sid, publication.track!!.kind.name.lowercase())
85 frameCryptor.setObserver { trackId, state -> 87 frameCryptor.setObserver { trackId, state ->
86 LKLog.i { "Receiver::onFrameCryptionStateChanged: $trackId, state: $state" } 88 LKLog.i { "Receiver::onFrameCryptionStateChanged: $trackId, state: $state" }
87 emitEvent( 89 emitEvent(
@@ -96,9 +98,18 @@ constructor(keyProvider: KeyProvider) { @@ -96,9 +98,18 @@ constructor(keyProvider: KeyProvider) {
96 } 98 }
97 } 99 }
98 100
99 - public fun addPublishedTrack(track: Track, publication: TrackPublication, participant: LocalParticipant, room: Room) { 101 + public fun removeSubscribedTrack(track: Track, publication: TrackPublication, participant: RemoteParticipant, room: Room) {
100 var trackId = publication.sid 102 var trackId = publication.sid
101 - var participantId = participant.sid 103 + var participantId = participant.identity
  104 + var frameCryptor = frameCryptors.get(trackId to participantId)
  105 + if (frameCryptor != null) {
  106 + frameCryptor.isEnabled = false
  107 + frameCryptor.dispose()
  108 + frameCryptors.remove(trackId to participantId)
  109 + }
  110 + }
  111 +
  112 + public fun addPublishedTrack(track: Track, publication: TrackPublication, participant: LocalParticipant, room: Room) {
102 var rtpSender: RtpSender? = when (publication.track!!) { 113 var rtpSender: RtpSender? = when (publication.track!!) {
103 is LocalAudioTrack -> (publication.track!! as LocalAudioTrack)?.sender 114 is LocalAudioTrack -> (publication.track!! as LocalAudioTrack)?.sender
104 is LocalVideoTrack -> (publication.track!! as LocalVideoTrack)?.sender 115 is LocalVideoTrack -> (publication.track!! as LocalVideoTrack)?.sender
@@ -107,7 +118,7 @@ constructor(keyProvider: KeyProvider) { @@ -107,7 +118,7 @@ constructor(keyProvider: KeyProvider) {
107 } 118 }
108 } ?: throw IllegalArgumentException("rtpSender is null") 119 } ?: throw IllegalArgumentException("rtpSender is null")
109 120
110 - var frameCryptor = addRtpSender(rtpSender!!, participantId, trackId, publication.track!!.kind.name.lowercase()) 121 + var frameCryptor = addRtpSender(rtpSender!!, participant.identity!!, publication.sid, publication.track!!.kind.name.lowercase())
111 frameCryptor.setObserver { trackId, state -> 122 frameCryptor.setObserver { trackId, state ->
112 LKLog.i { "Sender::onFrameCryptionStateChanged: $trackId, state: $state" } 123 LKLog.i { "Sender::onFrameCryptionStateChanged: $trackId, state: $state" }
113 emitEvent( 124 emitEvent(
@@ -122,6 +133,17 @@ constructor(keyProvider: KeyProvider) { @@ -122,6 +133,17 @@ constructor(keyProvider: KeyProvider) {
122 } 133 }
123 } 134 }
124 135
  136 + public fun removePublishedTrack(track: Track, publication: TrackPublication, participant: LocalParticipant, room: Room) {
  137 + var trackId = publication.sid
  138 + var participantId = participant.identity
  139 + var frameCryptor = frameCryptors.get(trackId to participantId)
  140 + if (frameCryptor != null) {
  141 + frameCryptor.isEnabled = false
  142 + frameCryptor.dispose()
  143 + frameCryptors.remove(trackId to participantId)
  144 + }
  145 + }
  146 +
125 private fun e2eeStateFromFrameCryptoState(state: FrameCryptionState?): E2EEState { 147 private fun e2eeStateFromFrameCryptoState(state: FrameCryptionState?): E2EEState {
126 return when (state) { 148 return when (state) {
127 FrameCryptionState.NEW -> E2EEState.NEW 149 FrameCryptionState.NEW -> E2EEState.NEW
@@ -136,41 +158,28 @@ constructor(keyProvider: KeyProvider) { @@ -136,41 +158,28 @@ constructor(keyProvider: KeyProvider) {
136 } 158 }
137 159
138 private fun addRtpSender(sender: RtpSender, participantId: String, trackId: String, kind: String): FrameCryptor { 160 private fun addRtpSender(sender: RtpSender, participantId: String, trackId: String, kind: String): FrameCryptor {
139 - var pid = "$kind-sender-$participantId-$trackId"  
140 var frameCryptor = FrameCryptorFactory.createFrameCryptorForRtpSender( 161 var frameCryptor = FrameCryptorFactory.createFrameCryptorForRtpSender(
141 sender, 162 sender,
142 - pid, 163 + participantId,
143 algorithm, 164 algorithm,
144 keyProvider.rtcKeyProvider, 165 keyProvider.rtcKeyProvider,
145 ) 166 )
146 167
147 - frameCryptors[trackId] = frameCryptor 168 + frameCryptors[trackId to participantId] = frameCryptor
148 frameCryptor.setEnabled(enabled) 169 frameCryptor.setEnabled(enabled)
149 - if (keyProvider.enableSharedKey) {  
150 - keyProvider.rtcKeyProvider?.setKey(pid, 0, keyProvider?.sharedKey)  
151 - frameCryptor.setKeyIndex(0)  
152 - }  
153 -  
154 return frameCryptor 170 return frameCryptor
155 } 171 }
156 172
157 private fun addRtpReceiver(receiver: RtpReceiver, participantId: String, trackId: String, kind: String): FrameCryptor { 173 private fun addRtpReceiver(receiver: RtpReceiver, participantId: String, trackId: String, kind: String): FrameCryptor {
158 - var pid = "$kind-receiver-$participantId-$trackId"  
159 var frameCryptor = FrameCryptorFactory.createFrameCryptorForRtpReceiver( 174 var frameCryptor = FrameCryptorFactory.createFrameCryptorForRtpReceiver(
160 receiver, 175 receiver,
161 - pid, 176 + participantId,
162 algorithm, 177 algorithm,
163 keyProvider.rtcKeyProvider, 178 keyProvider.rtcKeyProvider,
164 ) 179 )
165 180
166 - frameCryptors[trackId] = frameCryptor 181 + frameCryptors[trackId to participantId] = frameCryptor
167 frameCryptor.setEnabled(enabled) 182 frameCryptor.setEnabled(enabled)
168 -  
169 - if (keyProvider.enableSharedKey) {  
170 - keyProvider.rtcKeyProvider?.setKey(pid, 0, keyProvider?.sharedKey)  
171 - frameCryptor.setKeyIndex(0)  
172 - }  
173 -  
174 return frameCryptor 183 return frameCryptor
175 } 184 }
176 185
@@ -182,12 +191,7 @@ constructor(keyProvider: KeyProvider) { @@ -182,12 +191,7 @@ constructor(keyProvider: KeyProvider) {
182 this.enabled = enabled 191 this.enabled = enabled
183 for (item in frameCryptors.entries) { 192 for (item in frameCryptors.entries) {
184 var frameCryptor = item.value 193 var frameCryptor = item.value
185 - var participantId = item.key  
186 frameCryptor.setEnabled(enabled) 194 frameCryptor.setEnabled(enabled)
187 - if (keyProvider.enableSharedKey) {  
188 - keyProvider.rtcKeyProvider?.setKey(participantId, 0, keyProvider?.sharedKey)  
189 - frameCryptor.setKeyIndex(0)  
190 - }  
191 } 195 }
192 } 196 }
193 197
@@ -195,10 +199,8 @@ constructor(keyProvider: KeyProvider) { @@ -195,10 +199,8 @@ constructor(keyProvider: KeyProvider) {
195 * Ratchet key for local participant 199 * Ratchet key for local participant
196 */ 200 */
197 fun ratchetKey() { 201 fun ratchetKey() {
198 - for (participantId in frameCryptors.keys) {  
199 - var newKey = keyProvider.rtcKeyProvider?.ratchetKey(participantId, 0)  
200 - LKLog.d { "ratchetKey: newKey: $newKey" }  
201 - } 202 + var newKey = keyProvider.ratchetSharedKey()
  203 + LKLog.d { "ratchetSharedKey: newKey: $newKey" }
202 } 204 }
203 205
204 fun cleanUp() { 206 fun cleanUp() {
@@ -28,13 +28,16 @@ constructor(var participantId: String, var keyIndex: Int, var key: String) { @@ -28,13 +28,16 @@ constructor(var participantId: String, var keyIndex: Int, var key: String) {
28 } 28 }
29 29
30 public interface KeyProvider { 30 public interface KeyProvider {
  31 + fun setSharedKey(key: String, keyIndex: Int? = 0): Boolean
  32 + fun ratchetSharedKey(keyIndex: Int? = 0): ByteArray
  33 + fun exportSharedKey(keyIndex: Int? = 0): ByteArray
31 fun setKey(key: String, participantId: String?, keyIndex: Int? = 0) 34 fun setKey(key: String, participantId: String?, keyIndex: Int? = 0)
32 - fun ratchetKey(participantId: String, index: Int): ByteArray 35 + fun ratchetKey(participantId: String, keyIndex: Int? = 0): ByteArray
  36 + fun exportKey(participantId: String, keyIndex: Int? = 0): ByteArray
  37 + fun setSifTrailer(trailer: ByteArray)
33 38
34 val rtcKeyProvider: FrameCryptorKeyProvider 39 val rtcKeyProvider: FrameCryptorKeyProvider
35 40
36 - var sharedKey: ByteArray?  
37 -  
38 var enableSharedKey: Boolean 41 var enableSharedKey: Boolean
39 } 42 }
40 43
@@ -47,8 +50,18 @@ constructor( @@ -47,8 +50,18 @@ constructor(
47 private var failureTolerance: Int, 50 private var failureTolerance: Int,
48 ) : 51 ) :
49 KeyProvider { 52 KeyProvider {
50 - override var sharedKey: ByteArray? = null  
51 private var keys: MutableMap<String, MutableMap<Int, String>> = mutableMapOf() 53 private var keys: MutableMap<String, MutableMap<Int, String>> = mutableMapOf()
  54 + override fun setSharedKey(key: String, keyIndex: Int?): Boolean {
  55 + return rtcKeyProvider.setSharedKey(keyIndex ?: 0, key.toByteArray())
  56 + }
  57 +
  58 + override fun ratchetSharedKey(keyIndex: Int?): ByteArray {
  59 + return rtcKeyProvider.ratchetSharedKey(keyIndex ?: 0)
  60 + }
  61 +
  62 + override fun exportSharedKey(keyIndex: Int?): ByteArray {
  63 + return rtcKeyProvider.exportSharedKey(keyIndex ?: 0)
  64 + }
52 65
53 /** 66 /**
54 * Set a key for a participant 67 * Set a key for a participant
@@ -58,7 +71,6 @@ constructor( @@ -58,7 +71,6 @@ constructor(
58 */ 71 */
59 override fun setKey(key: String, participantId: String?, keyIndex: Int?) { 72 override fun setKey(key: String, participantId: String?, keyIndex: Int?) {
60 if (enableSharedKey) { 73 if (enableSharedKey) {
61 - sharedKey = key.toByteArray()  
62 return 74 return
63 } 75 }
64 76
@@ -76,8 +88,16 @@ constructor( @@ -76,8 +88,16 @@ constructor(
76 rtcKeyProvider.setKey(participantId, keyInfo.keyIndex, key.toByteArray()) 88 rtcKeyProvider.setKey(participantId, keyInfo.keyIndex, key.toByteArray())
77 } 89 }
78 90
79 - override fun ratchetKey(participantId: String, index: Int): ByteArray {  
80 - return rtcKeyProvider.ratchetKey(participantId, index) 91 + override fun ratchetKey(participantId: String, keyIndex: Int?): ByteArray {
  92 + return rtcKeyProvider.ratchetKey(participantId, keyIndex ?: 0)
  93 + }
  94 +
  95 + override fun exportKey(participantId: String, keyIndex: Int?): ByteArray {
  96 + return rtcKeyProvider.exportKey(participantId, keyIndex ?: 0)
  97 + }
  98 +
  99 + override fun setSifTrailer(trailer: ByteArray) {
  100 + rtcKeyProvider.setSifTrailer(trailer)
81 } 101 }
82 102
83 override val rtcKeyProvider: FrameCryptorKeyProvider 103 override val rtcKeyProvider: FrameCryptorKeyProvider
@@ -322,6 +322,10 @@ constructor( @@ -322,6 +322,10 @@ constructor(
322 name = response.room.name 322 name = response.room.name
323 metadata = response.room.metadata 323 metadata = response.room.metadata
324 324
  325 + if (e2eeManager != null && !response.sifTrailer.isEmpty) {
  326 + e2eeManager!!.keyProvider().setSifTrailer(response.sifTrailer.toByteArray())
  327 + }
  328 +
325 if (response.room.activeRecording != isRecording) { 329 if (response.room.activeRecording != isRecording) {
326 isRecording = response.room.activeRecording 330 isRecording = response.room.activeRecording
327 eventBus.postEvent(RoomEvent.RecordingStatusChanged(this, isRecording), coroutineScope) 331 eventBus.postEvent(RoomEvent.RecordingStatusChanged(this, isRecording), coroutineScope)
@@ -938,6 +942,9 @@ constructor( @@ -938,6 +942,9 @@ constructor(
938 */ 942 */
939 override fun onTrackUnpublished(publication: LocalTrackPublication, participant: LocalParticipant) { 943 override fun onTrackUnpublished(publication: LocalTrackPublication, participant: LocalParticipant) {
940 listener?.onTrackUnpublished(publication, participant, this) 944 listener?.onTrackUnpublished(publication, participant, this)
  945 + e2eeManager?.let { e2eeManager ->
  946 + e2eeManager!!.removePublishedTrack(publication.track!!, publication, participant, this)
  947 + }
941 eventBus.postEvent(RoomEvent.TrackUnpublished(this, publication, participant), coroutineScope) 948 eventBus.postEvent(RoomEvent.TrackUnpublished(this, publication, participant), coroutineScope)
942 } 949 }
943 950
@@ -973,6 +980,9 @@ constructor( @@ -973,6 +980,9 @@ constructor(
973 participant: RemoteParticipant, 980 participant: RemoteParticipant,
974 ) { 981 ) {
975 listener?.onTrackUnsubscribed(track, publication, participant, this) 982 listener?.onTrackUnsubscribed(track, publication, participant, this)
  983 + e2eeManager?.let { e2eeManager ->
  984 + e2eeManager!!.removeSubscribedTrack(track, publication, participant, this)
  985 + }
976 eventBus.postEvent(RoomEvent.TrackUnsubscribed(this, track, publication, participant), coroutineScope) 986 eventBus.postEvent(RoomEvent.TrackUnsubscribed(this, track, publication, participant), coroutineScope)
977 } 987 }
978 988
  1 +/*
  2 + * Copyright 2023 LiveKit, Inc.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
1 package io.livekit.android.sample 17 package io.livekit.android.sample
2 18
3 import android.app.Application 19 import android.app.Application
@@ -47,7 +63,7 @@ class CallViewModel( @@ -47,7 +63,7 @@ class CallViewModel(
47 if (e2ee && e2eeKey != null) { 63 if (e2ee && e2eeKey != null) {
48 e2eeOptions = E2EEOptions() 64 e2eeOptions = E2EEOptions()
49 } 65 }
50 - e2eeOptions?.keyProvider?.setKey(e2eeKey!!, null, 0) 66 + e2eeOptions?.keyProvider?.setSharedKey(e2eeKey!!)
51 return e2eeOptions 67 return e2eeOptions
52 } 68 }
53 69