Skip to content

Commit a122b1a

Browse files
authored
feat: e2ee data channel (#762)
* DataPacketCryptor JNI hooks * E2EE data channel implementation * spotless * Add encryption type fields to events and stream infos
1 parent b24aab9 commit a122b1a

27 files changed

Lines changed: 773 additions & 108 deletions

File tree

.changeset/nasty-kids-fail.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"client-sdk-android": minor
3+
---
4+
5+
End to end encryption for data channels option
6+
7+
* Added EncryptionType fields to DataReceived events and StreamInfo objects to indicate the
8+
encryption status.

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[versions]
2-
webrtc = "137.7151.03"
2+
webrtc = "137.7151.04"
33

44
androidJainSipRi = "1.3.0-91"
55
androidx-activity = "1.9.0"

livekit-android-sdk/src/main/java/io/livekit/android/dagger/RTCModule.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ import io.livekit.android.audio.AudioRecordSamplesDispatcher
3333
import io.livekit.android.audio.CommunicationWorkaround
3434
import io.livekit.android.audio.JavaAudioRecordPrewarmer
3535
import io.livekit.android.audio.NoAudioRecordPrewarmer
36+
import io.livekit.android.e2ee.DataPacketCryptorManager
37+
import io.livekit.android.e2ee.DataPacketCryptorManagerImpl
3638
import io.livekit.android.memory.CloseableManager
3739
import io.livekit.android.util.LKLog
3840
import io.livekit.android.util.LoggingLevel
@@ -373,6 +375,11 @@ internal object RTCModule {
373375
}!!
374376
}
375377

378+
@Provides
379+
fun dataPacketCryptorManagerFactory(): DataPacketCryptorManager.Factory {
380+
return DataPacketCryptorManagerImpl.Factory
381+
}
382+
376383
@Provides
377384
@Singleton
378385
fun peerConnectionFactory(
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright 2025 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+
17+
package io.livekit.android.e2ee
18+
19+
import io.livekit.android.room.participant.Participant
20+
import io.livekit.android.util.LKLog
21+
import livekit.LivekitModels
22+
import livekit.org.webrtc.DataPacketCryptor
23+
import livekit.org.webrtc.DataPacketCryptorFactory
24+
import livekit.org.webrtc.FrameCryptorAlgorithm
25+
26+
/**
27+
* @suppress
28+
*/
29+
interface DataPacketCryptorManager {
30+
fun encrypt(participantId: Participant.Identity, keyIndex: Int, payload: ByteArray): EncryptedPacket?
31+
fun decrypt(participantId: Participant.Identity, packet: EncryptedPacket): ByteArray?
32+
fun dispose()
33+
34+
interface Factory {
35+
fun create(keyProvider: KeyProvider): DataPacketCryptorManager
36+
}
37+
}
38+
39+
/**
40+
* @suppress
41+
*/
42+
class EncryptedPacket(
43+
val payload: ByteArray,
44+
val iv: ByteArray,
45+
val keyIndex: Int,
46+
)
47+
48+
/**
49+
* @suppress
50+
*/
51+
fun LivekitModels.EncryptedPacket.toSdkType() =
52+
EncryptedPacket(
53+
payload = this.encryptedValue.toByteArray(),
54+
iv = this.iv.toByteArray(),
55+
keyIndex = this.keyIndex,
56+
)
57+
58+
internal class DataPacketCryptorManagerImpl(
59+
keyProvider: KeyProvider,
60+
) : DataPacketCryptorManager {
61+
var isDisposed = false
62+
private val dataPacketCryptor: DataPacketCryptor = DataPacketCryptorFactory.createDataPacketCryptor(FrameCryptorAlgorithm.AES_GCM, keyProvider.rtcKeyProvider)
63+
64+
@Synchronized
65+
override fun encrypt(participantId: Participant.Identity, keyIndex: Int, payload: ByteArray): EncryptedPacket? {
66+
if (isDisposed) {
67+
return null
68+
}
69+
val packet = dataPacketCryptor.encrypt(
70+
participantId.value,
71+
keyIndex,
72+
payload,
73+
)
74+
75+
if (packet == null) {
76+
LKLog.i { "Error encrypting packet: null packet" }
77+
return null
78+
}
79+
80+
val payload = packet.payload
81+
val iv = packet.iv
82+
val keyIndex = packet.keyIndex
83+
84+
if (payload == null) {
85+
LKLog.w { "Error encrypting packet: null payload" }
86+
return null
87+
}
88+
if (iv == null) {
89+
LKLog.i { "Error encrypting packet: null iv returned" }
90+
return null
91+
}
92+
93+
return EncryptedPacket(
94+
payload = payload,
95+
iv = iv,
96+
keyIndex = keyIndex,
97+
)
98+
}
99+
100+
@Synchronized
101+
override fun decrypt(participantId: Participant.Identity, packet: EncryptedPacket): ByteArray? {
102+
if (isDisposed) {
103+
return null
104+
}
105+
return dataPacketCryptor.decrypt(
106+
participantId.value,
107+
DataPacketCryptor.EncryptedPacket(
108+
packet.payload,
109+
packet.iv,
110+
packet.keyIndex,
111+
),
112+
)
113+
}
114+
115+
@Synchronized
116+
override fun dispose() {
117+
if (isDisposed) {
118+
return
119+
}
120+
isDisposed = true
121+
dataPacketCryptor.dispose()
122+
}
123+
124+
object Factory : DataPacketCryptorManager.Factory {
125+
override fun create(keyProvider: KeyProvider): DataPacketCryptorManager {
126+
return DataPacketCryptorManagerImpl(keyProvider)
127+
}
128+
}
129+
}

0 commit comments

Comments
 (0)