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()
|
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
|
where the frame cryptor is guaranteed to exist. If the track is already
|
||||||
subscribed (late key arrival / rotation), set the key immediately.
|
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:
|
if not key:
|
||||||
return
|
return
|
||||||
if not self._caller_key:
|
if not self._caller_key:
|
||||||
self._caller_key = key
|
self._caller_key = key
|
||||||
self._caller_identity = f"{sender}:{device_id}"
|
self._caller_identity = f"{sender}:{device_id}"
|
||||||
self._caller_all_keys[index] = key
|
# Detect same-index key rotation: if index 0 changes value, use next internal index
|
||||||
logger.info("E2EE key received from %s:%s (index=%d, %d bytes, raw=%s)",
|
old_key = self._caller_all_keys.get(index)
|
||||||
sender, device_id, index, len(key), key.hex())
|
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.
|
# Late key arrival: if track already subscribed, frame cryptor exists — set key now.
|
||||||
if self.lk_room and self._caller_identity:
|
if self.lk_room and self._caller_identity:
|
||||||
caller_lk_id = self._caller_identity
|
caller_lk_id = self._caller_identity
|
||||||
@@ -515,8 +532,11 @@ class VoiceSession:
|
|||||||
if has_subscribed:
|
if has_subscribed:
|
||||||
try:
|
try:
|
||||||
kp = self.lk_room.e2ee_manager.key_provider
|
kp = self.lk_room.e2ee_manager.key_provider
|
||||||
_derive_and_set_key(kp, p.identity, key, index)
|
# Set ALL keys (old + new) so cryptor can try both
|
||||||
logger.info("Late key[%d] set for %s", index, p.identity)
|
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:
|
except Exception as e:
|
||||||
logger.warning("Late key set_key failed: %s", e)
|
logger.warning("Late key set_key failed: %s", e)
|
||||||
break
|
break
|
||||||
|
|||||||
Reference in New Issue
Block a user