Repository navigation
Follow key provider key index on media frame cryptors #1042
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "client-sdk-android": patch | ||
| --- | ||
|
|
||
| Media frame cryptors now follow the key provider's current key index, matching the JavaScript and Swift SDKs. `BaseKeyProvider` implements the new `KeyIndexObservable` contract: installing a shared key moves the current index, `setCurrentKeyIndex` re-points live senders, and `getLatestKeyIndex` returns the current index. Custom `KeyProvider` implementations can opt into live re-pointing by implementing `KeyIndexObservable`. Behavior change: in non-shared key mode `setKey` no longer moves the sender index — the key may belong to a remote participant — use `setCurrentKeyIndex` to switch the local one. |
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -42,6 +42,34 @@ interface KeyProvider { | |||||||
| var enableSharedKey: Boolean | ||||||||
| } | ||||||||
|
|
||||||||
| /** | ||||||||
| * Implemented by [KeyProvider]s that track the key index the local | ||||||||
| * participant encrypts with and can push changes to observers. | ||||||||
| * | ||||||||
| * [E2EEManager] subscribes so the frame cryptors of media tracks that are | ||||||||
| * already published get re-stamped whenever the index changes. Receivers | ||||||||
| * are unaffected: they read the index from each incoming frame. | ||||||||
| */ | ||||||||
| interface KeyIndexObservable { | ||||||||
|
|
||||||||
| /** | ||||||||
| * The key index the local participant encrypts with. | ||||||||
| */ | ||||||||
| val currentKeyIndex: Int | ||||||||
|
|
||||||||
| /** | ||||||||
| * Registers [observer] under [owner]; [removeKeyIndexObserver] with the | ||||||||
| * same owner detaches it. The observer is invoked with the latest index | ||||||||
| * on every change and may be called from any thread. | ||||||||
| */ | ||||||||
| fun addKeyIndexObserver(owner: Any, observer: (keyIndex: Int) -> Unit) | ||||||||
|
|
||||||||
| /** | ||||||||
| * Detaches the observer previously registered under [owner]. | ||||||||
| */ | ||||||||
| fun removeKeyIndexObserver(owner: Any) | ||||||||
| } | ||||||||
|
|
||||||||
| class BaseKeyProvider( | ||||||||
| ratchetSalt: String = defaultRatchetSalt, | ||||||||
| uncryptedMagicBytes: String = defaultMagicBytes, | ||||||||
|
|
@@ -51,9 +79,18 @@ class BaseKeyProvider( | |||||||
| keyRingSize: Int = defaultKeyRingSize, | ||||||||
| discardFrameWhenCryptorNotReady: Boolean = defaultDiscardFrameWhenCryptorNotReady, | ||||||||
| keyDerivationAlgorithm: FrameCryptorKeyDerivationAlgorithm = defaultKeyDerivationAlgorithm, | ||||||||
| ) : KeyProvider { | ||||||||
| ) : KeyProvider, KeyIndexObservable { | ||||||||
|
|
||||||||
| private val keyRingSize: Int = keyRingSize | ||||||||
| private val keyIndexStateLock = Any() | ||||||||
| private val keyIndexDispatchLock = Any() | ||||||||
| private val keyIndexObservers = mutableMapOf<Any, (Int) -> Unit>() | ||||||||
|
|
||||||||
| @Volatile | ||||||||
| private var _currentKeyIndex: Int = 0 | ||||||||
|
|
||||||||
| private val latestSetIndex = mutableMapOf<String, Int>() | ||||||||
| override val currentKeyIndex: Int | ||||||||
| get() = _currentKeyIndex | ||||||||
|
|
||||||||
| override val rtcKeyProvider: FrameCryptorKeyProvider = FrameCryptorFactory.createFrameCryptorKeyProvider( | ||||||||
| enableSharedKey, | ||||||||
|
|
@@ -67,7 +104,11 @@ class BaseKeyProvider( | |||||||
| ) | ||||||||
|
|
||||||||
| override fun setSharedKey(key: String, keyIndex: Int?): Boolean { | ||||||||
| return rtcKeyProvider.setSharedKey(keyIndex ?: 0, key.toByteArray()) | ||||||||
| return rtcKeyProvider.setSharedKey(keyIndex ?: 0, key.toByteArray()).also { succeeded -> | ||||||||
| if (succeeded) { | ||||||||
| setCurrentKeyIndex(keyIndex ?: 0) | ||||||||
| } | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| override fun ratchetSharedKey(keyIndex: Int?): ByteArray { | ||||||||
|
|
@@ -94,10 +135,7 @@ class BaseKeyProvider( | |||||||
| return | ||||||||
| } | ||||||||
|
|
||||||||
| val keyIndex = keyIndex ?: 0 | ||||||||
| latestSetIndex[participantId] = keyIndex | ||||||||
|
|
||||||||
| rtcKeyProvider.setKey(participantId, keyIndex, key.toByteArray()) | ||||||||
| rtcKeyProvider.setKey(participantId, keyIndex ?: 0, key.toByteArray()) | ||||||||
| } | ||||||||
|
|
||||||||
| override fun ratchetKey(participantId: String, keyIndex: Int?): ByteArray { | ||||||||
|
|
@@ -112,7 +150,42 @@ class BaseKeyProvider( | |||||||
| rtcKeyProvider.setSifTrailer(trailer) | ||||||||
| } | ||||||||
|
|
||||||||
| /** | ||||||||
| * Switches the local participant's encryption to the key at [keyIndex] | ||||||||
| * (modulo the key ring size). | ||||||||
| * | ||||||||
| * Takes effect immediately for the media tracks already published and | ||||||||
| * for every data packet sent afterwards. Receivers are unaffected: they | ||||||||
| * read the index from each frame. | ||||||||
| */ | ||||||||
| fun setCurrentKeyIndex(keyIndex: Int) { | ||||||||
| val observers = synchronized(keyIndexStateLock) { | ||||||||
| _currentKeyIndex = Math.floorMod(keyIndex, keyRingSize) | ||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Key rotation crashes on older Android On Android API 21–23, Learn moreThe SDK supports Android API 21, but Example: On an API 21 device, construct Recommended fix: Normalize the remainder using arithmetic supported on API 21, retaining nonnegative indices for negative inputs; verify initialization on an API 21 device or emulator.
Suggested change
Was this helpful? React with 👍 or 👎 to provide feedback. |
||||||||
| keyIndexObservers.values.toList() | ||||||||
| } | ||||||||
| // Dispatch is serialized and each pass re-reads the newest index, | ||||||||
| // so overlapping rotations can't leave senders on a stale index. | ||||||||
| synchronized(keyIndexDispatchLock) { | ||||||||
| val latest = _currentKeyIndex | ||||||||
| observers.forEach { it(latest) } | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| /** | ||||||||
| * The key index outgoing media frames and data packets are stamped with. | ||||||||
| * In non-shared key mode [setKey] does not move it — the key may belong | ||||||||
| * to a remote participant — so switch the local one with | ||||||||
| * [setCurrentKeyIndex]. | ||||||||
| */ | ||||||||
| override fun getLatestKeyIndex(participantId: String): Int { | ||||||||
| return latestSetIndex[participantId] ?: 0 | ||||||||
| return currentKeyIndex | ||||||||
| } | ||||||||
|
|
||||||||
| override fun addKeyIndexObserver(owner: Any, observer: (keyIndex: Int) -> Unit) { | ||||||||
| synchronized(keyIndexStateLock) { keyIndexObservers[owner] = observer } | ||||||||
| } | ||||||||
|
|
||||||||
| override fun removeKeyIndexObserver(owner: Any) { | ||||||||
| synchronized(keyIndexStateLock) { keyIndexObservers.remove(owner) } | ||||||||
| } | ||||||||
| } | ||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Key rotation lacks required Room example
setCurrentKeyIndexadds a public rotation API without the required short example at theRoomentry point. The same omission affectsKeyIndexObservable.Learn more
The repository's review rules require short code examples for new public APIs at the entry point, such as
Room. This change exposessetCurrentKeyIndexandKeyIndexObservablewithout showing room users how to access the provider and rotate an existing sender.Example: A consumer reading
Roomdocumentation has no example connectingroom.e2eeManager?.keyProvider()with an index change for published media.Recommended fix: Add a short example in the
Roomentry-point documentation that shows the supported key-provider rotation flow and how a custom provider can opt intoKeyIndexObservable.Was this helpful? React with 👍 or 👎 to provide feedback.