Skip to content

Commit

Permalink
improvements for E2EE. (#243)
Browse files Browse the repository at this point in the history
* improvements for E2EE.

* wip.

* exposed More APIs.
  • Loading branch information
cloudwebrtc authored Sep 6, 2023
1 parent e49434e commit 3088867
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 57 deletions.
2 changes: 1 addition & 1 deletion LiveKitClient.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Pod::Spec.new do |spec|

spec.source_files = 'Sources/**/*'

spec.dependency 'WebRTC-SDK', '~> 114.5735.02'
spec.dependency 'WebRTC-SDK', '~> 114.5735.05'
spec.dependency 'SwiftProtobuf'
spec.dependency 'PromisesSwift'
spec.dependency 'Logging'
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ let package = Package(
)
],
dependencies: [
.package(name: "WebRTC", url: "https://github.com/webrtc-sdk/Specs.git", .exact("114.5735.04")),
.package(name: "WebRTC", url: "https://github.com/webrtc-sdk/Specs.git", .exact("114.5735.05")),
.package(name: "SwiftProtobuf", url: "https://github.com/apple/swift-protobuf.git", .upToNextMajor(from: "1.21.0")),
.package(name: "Promises", url: "https://github.com/google/promises.git", .upToNextMajor(from: "2.2.0")),
.package(url: "https://github.com/apple/swift-log.git", .upToNextMajor(from: "1.5.2"))
Expand Down
116 changes: 73 additions & 43 deletions Sources/LiveKit/E2EE/E2EEManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,30 +23,44 @@ public class E2EEManager: NSObject, ObservableObject, Loggable {
internal weak var room: Room?
internal var enabled: Bool = true
public var e2eeOptions: E2EEOptions
internal var frameCryptors = [String: RTCFrameCryptor]()
internal var trackPublications = [String: TrackPublication]()
internal var frameCryptors = [[String: Sid]: RTCFrameCryptor]()
internal var trackPublications = [RTCFrameCryptor: TrackPublication]()

public init(e2eeOptions: E2EEOptions) {
self.e2eeOptions = e2eeOptions
}

public func keyProvider() -> BaseKeyProvider {
return self.e2eeOptions.keyProvider
}

public func getFrameCryptors() -> [[String: Sid]: RTCFrameCryptor] {
return self.frameCryptors
}

public func setup(room: Room) {
if self.room != room {
cleanUp()
}
self.room = room
self.room?.delegates.add(delegate: self)
self.room?.localParticipant?.tracks.forEach({ (_: Sid, publication: TrackPublication) in
let kind = publication.kind == .video ? "video" : "audio"
let pid = addRtpSender(sender: publication.track!.rtpSender!, participantId: self.room!.localParticipant!.identity, trackId: publication.sid, kind: kind)
trackPublications[pid] = publication
if publication.encryptionType == EncryptionType.none {
self.log("E2EEManager::setup: local participant \(self.room!.localParticipant!.identity) track \(publication.sid) encryptionType is none, skip");
return
}
let fc = addRtpSender(sender: publication.track!.rtpSender!, participantId: self.room!.localParticipant!.identity, trackSid: publication.sid)
trackPublications[fc] = publication
})

self.room?.remoteParticipants.forEach({ (_: Sid, participant: RemoteParticipant) in
participant.tracks.forEach({ (_: Sid, publication: TrackPublication) in
let kind = publication.kind == .video ? "video" : "audio"
let pid = addRtpReceiver(receiver: publication.track!.rtpReceiver!, participantId: participant.identity, trackId: publication.sid, kind: kind)
trackPublications[pid] = publication
if publication.encryptionType == EncryptionType.none {
self.log("E2EEManager::setup: remote participant \(participant.identity) track \(publication.sid) encryptionType is none, skip");
return
}
let fc = addRtpReceiver(receiver: publication.track!.rtpReceiver!, participantId: participant.identity, trackSid: publication.sid)
trackPublications[fc] = publication
})
})
}
Expand All @@ -58,40 +72,22 @@ public class E2EEManager: NSObject, ObservableObject, Loggable {
}
}

func addRtpSender(sender: RTCRtpSender, participantId: String, trackId: String, kind: String) -> String {
let pid = String(format: "%@-sender-%@-%@", kind, participantId, trackId)
self.log("addRtpSender \(pid) to E2EEManager")
let frameCryptor = RTCFrameCryptor(rtpSender: sender, participantId: pid, algorithm: RTCCyrptorAlgorithm.aesGcm, keyProvider: self.e2eeOptions.keyProvider.rtcKeyProvider!)
func addRtpSender(sender: RTCRtpSender, participantId: String, trackSid: Sid) -> RTCFrameCryptor {
self.log("addRtpSender \(participantId) to E2EEManager")
let frameCryptor = RTCFrameCryptor(rtpSender: sender, participantId: participantId, algorithm: RTCCyrptorAlgorithm.aesGcm, keyProvider: self.e2eeOptions.keyProvider.rtcKeyProvider!)
frameCryptor.delegate = self
frameCryptors[pid] = frameCryptor
frameCryptors[[participantId: trackSid]] = frameCryptor
frameCryptor.enabled = self.enabled

if self.e2eeOptions.keyProvider.isSharedKey == true {
let rtcKeyProvider = self.e2eeOptions.keyProvider.rtcKeyProvider
let keyData = self.e2eeOptions.keyProvider.sharedKey!.data(using: .utf8)!
rtcKeyProvider?.setKey(keyData, with: 0, forParticipant: pid)
frameCryptor.keyIndex = 0
}

return pid
return frameCryptor
}

func addRtpReceiver(receiver: RTCRtpReceiver, participantId: String, trackId: String, kind: String) -> String {
let pid = String(format: "%@-receiver-%@-%@", kind, participantId, trackId)
self.log("addRtpReceiver \(pid) to E2EEManager")
let frameCryptor = RTCFrameCryptor(rtpReceiver: receiver, participantId: pid, algorithm: RTCCyrptorAlgorithm.aesGcm, keyProvider: self.e2eeOptions.keyProvider.rtcKeyProvider!)
func addRtpReceiver(receiver: RTCRtpReceiver, participantId: String, trackSid: Sid) -> RTCFrameCryptor {
self.log("addRtpReceiver \(participantId) to E2EEManager")
let frameCryptor = RTCFrameCryptor(rtpReceiver: receiver, participantId: participantId, algorithm: RTCCyrptorAlgorithm.aesGcm, keyProvider: self.e2eeOptions.keyProvider.rtcKeyProvider!)
frameCryptor.delegate = self
frameCryptors[pid] = frameCryptor
frameCryptors[[participantId: trackSid]] = frameCryptor
frameCryptor.enabled = self.enabled

if self.e2eeOptions.keyProvider.isSharedKey == true {
let rtcKeyProvider = self.e2eeOptions.keyProvider.rtcKeyProvider
let keyData = self.e2eeOptions.keyProvider.sharedKey!.data(using: .utf8)!
rtcKeyProvider?.setKey(keyData, with: 0, forParticipant: pid)
frameCryptor.keyIndex = 0
}

return pid
return frameCryptor
}

public func cleanUp() {
Expand All @@ -108,7 +104,7 @@ extension E2EEManager: RTCFrameCryptorDelegate {

public func frameCryptor(_ frameCryptor: RTCFrameCryptor, didStateChangeWithParticipantId participantId: String, with state: FrameCryptionState) {
self.log("frameCryptor didStateChangeWithParticipantId \(participantId) with state \(state.rawValue)")
let publication: TrackPublication? = trackPublications[participantId]
let publication: TrackPublication? = trackPublications[frameCryptor]
if publication == nil {
self.log("frameCryptor didStateChangeWithParticipantId \(participantId) with state \(state.rawValue) publication is nil")
return
Expand All @@ -127,14 +123,48 @@ extension E2EEManager: RTCFrameCryptorDelegate {
extension E2EEManager: RoomDelegate {

public func room(_ room: Room, localParticipant: LocalParticipant, didPublish publication: LocalTrackPublication) {
let kind = publication.kind == .video ? "video" : "audio"
let pid = addRtpSender(sender: localParticipant.rtpSender!, participantId: localParticipant.identity, trackId: publication.sid, kind: kind)
trackPublications[pid] = publication
if publication.encryptionType == EncryptionType.none {
self.log("E2EEManager::RoomDelegate: local participant \(localParticipant.identity) track \(publication.sid) encryptionType is none, skip");
return
}
let fc = addRtpSender(sender: localParticipant.rtpSender!, participantId: localParticipant.identity, trackSid: publication.sid)
trackPublications[fc] = publication
}

public func room(_ room: Room, localParticipant: LocalParticipant, didUnpublish publication: LocalTrackPublication) {
let frameCryptor = frameCryptors.first(where: { (key: [String: Sid], _: RTCFrameCryptor) -> Bool in
return key[localParticipant.identity] == publication.sid
})?.value

frameCryptor?.delegate = nil
frameCryptor?.enabled = false
frameCryptors.removeValue(forKey: [localParticipant.identity: publication.sid])

if frameCryptor != nil {
trackPublications.removeValue(forKey: frameCryptor!)
}
}

public func room(_ room: Room, participant: RemoteParticipant, didSubscribe publication: RemoteTrackPublication, track: Track) {
let kind = publication.kind == .video ? "video" : "audio"
let pid = addRtpReceiver(receiver: participant.rtpReceiver!, participantId: participant.identity, trackId: publication.sid, kind: kind)
trackPublications[pid] = publication
if publication.encryptionType == EncryptionType.none {
self.log("E2EEManager::RoomDelegate: remote participant \(participant.identity) track \(publication.sid) encryptionType is none, skip");
return
}
let fc = addRtpReceiver(receiver: participant.rtpReceiver!, participantId: participant.identity, trackSid: publication.sid)
trackPublications[fc] = publication
}

public func room(_ room: Room, participant: RemoteParticipant, didUnsubscribe publication: RemoteTrackPublication, track: Track) {
let frameCryptor = frameCryptors.first(where: { (key: [String: Sid], _: RTCFrameCryptor) -> Bool in
return key[participant.identity] == publication.sid
})?.value

frameCryptor?.delegate = nil
frameCryptor?.enabled = false
frameCryptors.removeValue(forKey: [participant.identity: publication.sid])

if frameCryptor != nil {
trackPublications.removeValue(forKey: frameCryptor!)
}
}
}
82 changes: 70 additions & 12 deletions Sources/LiveKit/E2EE/KeyProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,33 +17,91 @@
import Foundation
import WebRTC

let defaultRatchetSalt: String = "LKFrameEncryptionKey"
let defaultMagicBytes: String = "LK-ROCKS"
let defaultRatchetWindowSize: Int32 = 16
public let defaultRatchetSalt: String = "LKFrameEncryptionKey"
public let defaultMagicBytes: String = "LK-ROCKS"
public let defaultRatchetWindowSize: Int32 = 16

public class KeyProviderOptions {
let sharedKey: Bool
let ratchetSalt: Data
let ratchetWindowSize: Int32
let uncryptedMagicBytes: Data

public init(sharedKey: Bool = true,
ratchetSalt: Data = defaultRatchetSalt.data(using: .utf8)!,
ratchetWindowSize: Int32 = defaultRatchetWindowSize,
uncryptedMagicBytes: Data = defaultMagicBytes.data(using: .utf8)!
) {
self.sharedKey = sharedKey
self.ratchetSalt = ratchetSalt
self.ratchetWindowSize = ratchetWindowSize
self.uncryptedMagicBytes = uncryptedMagicBytes
}
}

public class BaseKeyProvider: Loggable {
var options: KeyProviderOptions
var rtcKeyProvider: RTCFrameCryptorKeyProvider?
var isSharedKey: Bool = true
var sharedKey: String?

public init(isSharedKey: Bool, sharedKey: String? = nil) {
self.rtcKeyProvider = RTCFrameCryptorKeyProvider(ratchetSalt: defaultRatchetSalt.data(using: .utf8)!, ratchetWindowSize: defaultRatchetWindowSize, sharedKeyMode: isSharedKey, uncryptedMagicBytes: defaultMagicBytes.data(using: .utf8)!)
self.isSharedKey = isSharedKey
self.sharedKey = sharedKey
self.options = KeyProviderOptions(sharedKey: isSharedKey)
self.rtcKeyProvider = RTCFrameCryptorKeyProvider(ratchetSalt: options.ratchetSalt,
ratchetWindowSize: options.ratchetWindowSize,
sharedKeyMode: isSharedKey,
uncryptedMagicBytes: options.uncryptedMagicBytes)
if isSharedKey && sharedKey != nil {
let keyData = sharedKey!.data(using: .utf8)!
self.rtcKeyProvider?.setSharedKey(keyData, with: 0)
}
}

public init(options: KeyProviderOptions = KeyProviderOptions()) {
self.options = options
self.rtcKeyProvider = RTCFrameCryptorKeyProvider(ratchetSalt: options.ratchetSalt,
ratchetWindowSize: options.ratchetWindowSize,
sharedKeyMode: options.sharedKey,
uncryptedMagicBytes: options.uncryptedMagicBytes)
}

public func setKey(key: String, participantId: String? = nil, index: Int32? = 0) {
if isSharedKey {
self.sharedKey = key

if options.sharedKey {
let keyData = key.data(using: .utf8)!
self.rtcKeyProvider?.setSharedKey(keyData, with: index ?? 0)
return
}

if participantId == nil {
self.log("Please provide valid participantId for non-SharedKey mode.")
self.log("setKey: Please provide valid participantId for non-SharedKey mode.")
return
}

let keyData = key.data(using: .utf8)!
rtcKeyProvider?.setKey(keyData, with: index!, forParticipant: participantId!)
}

public func ratchetKey(participantId: String? = nil, index: Int32? = 0) -> Data? {
if options.sharedKey {
return rtcKeyProvider?.ratchetSharedKey(index ?? 0)
}

if participantId == nil {
self.log("ratchetKey: Please provide valid participantId for non-SharedKey mode.")
return nil
}

return rtcKeyProvider?.ratchetKey(participantId!, with: index ?? 0)
}

public func exportKey(participantId: String? = nil, index: Int32? = 0) -> Data? {
if options.sharedKey {
return rtcKeyProvider?.exportSharedKey(index ?? 0)
}

if participantId == nil {
self.log("exportKey: Please provide valid participantId for non-SharedKey mode.")
return nil
}

return rtcKeyProvider?.exportKey(participantId!, with: index ?? 0)
}
}

0 comments on commit 3088867

Please sign in to comment.