fix: handle Element Call same-index key rotation on screen share
Element Call rotates E2EE keys by re-sending index 0 with a new value when screen share starts. The LiveKit frame cryptor caches derived AES keys per index, so overwriting index 0 does not force re-derivation. Fix: detect when index 0 value changes and map to incrementing internal index so the frame cryptor gets a fresh key slot. Sets all accumulated keys on late arrival so cryptor can try both during transition. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
30
voice.py
30
voice.py
@@ -495,15 +495,32 @@ class VoiceSession:
|
||||
Store-only: keys are applied in on_track_subscribed() / on_e2ee_state_changed()
|
||||
where the frame cryptor is guaranteed to exist. If the track is already
|
||||
subscribed (late key arrival / rotation), set the key immediately.
|
||||
|
||||
Element Call may rotate keys by re-sending index 0 with a new value
|
||||
(e.g. when screen share starts). The frame cryptor caches derived AES
|
||||
keys per index, so we use an internal incrementing index to force
|
||||
re-derivation.
|
||||
"""
|
||||
if not key:
|
||||
return
|
||||
if not self._caller_key:
|
||||
self._caller_key = key
|
||||
self._caller_identity = f"{sender}:{device_id}"
|
||||
self._caller_all_keys[index] = key
|
||||
logger.info("E2EE key received from %s:%s (index=%d, %d bytes, raw=%s)",
|
||||
sender, device_id, index, len(key), key.hex())
|
||||
# Detect same-index key rotation: if index 0 changes value, use next internal index
|
||||
old_key = self._caller_all_keys.get(index)
|
||||
if old_key and old_key != key:
|
||||
# Key rotated at same Element Call index — use next available internal index
|
||||
internal_idx = max(self._caller_all_keys.keys()) + 1
|
||||
logger.info("Key rotation detected: index %d changed value, mapping to internal index %d",
|
||||
index, internal_idx)
|
||||
self._caller_all_keys[internal_idx] = key
|
||||
self._caller_key = key
|
||||
effective_index = internal_idx
|
||||
else:
|
||||
self._caller_all_keys[index] = key
|
||||
effective_index = index
|
||||
logger.info("E2EE key received from %s:%s (index=%d, effective=%d, %d bytes, raw=%s)",
|
||||
sender, device_id, index, effective_index, len(key), key.hex())
|
||||
# Late key arrival: if track already subscribed, frame cryptor exists — set key now.
|
||||
if self.lk_room and self._caller_identity:
|
||||
caller_lk_id = self._caller_identity
|
||||
@@ -515,8 +532,11 @@ class VoiceSession:
|
||||
if has_subscribed:
|
||||
try:
|
||||
kp = self.lk_room.e2ee_manager.key_provider
|
||||
_derive_and_set_key(kp, p.identity, key, index)
|
||||
logger.info("Late key[%d] set for %s", index, p.identity)
|
||||
# Set ALL keys (old + new) so cryptor can try both
|
||||
for idx, k in sorted(self._caller_all_keys.items()):
|
||||
_derive_and_set_key(kp, p.identity, k, idx)
|
||||
logger.info("Late key rotation: set %d keys for %s",
|
||||
len(self._caller_all_keys), p.identity)
|
||||
except Exception as e:
|
||||
logger.warning("Late key set_key failed: %s", e)
|
||||
break
|
||||
|
||||
Reference in New Issue
Block a user