fix(voice): poll for EC key rotation post-connect, set all key indices
Element Call rotates its encryption key when a new participant joins the LiveKit room. Previously the bot fetched only the pre-join key and set it at index 0, while EC was already encrypting with the rotated key (index 1). Changes: - After connecting to LiveKit, poll the Matrix timeline up to 5s (10×0.5s) to detect the post-join key rotation - Set ALL known caller key indices (not just 0) so the Rust FFI cryptor has the correct key regardless of which index EC is currently using - Also set via caller_identity (belt+suspenders) if different from LK identity Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
43
voice.py
43
voice.py
@@ -257,6 +257,24 @@ class VoiceSession:
|
||||
logger.info("Connected (E2EE=HKDF), remote=%d",
|
||||
len(self.lk_room.remote_participants))
|
||||
|
||||
# Element Call rotates its encryption key when bot joins the LiveKit room.
|
||||
# We fetched the pre-join key above; now wait for EC to generate and publish
|
||||
# the post-join rotated key via Matrix timeline.
|
||||
pre_max_idx = max(self._caller_all_keys.keys()) if self._caller_all_keys else -1
|
||||
logger.info("Polling for EC key rotation (pre-join max_idx=%d)...", pre_max_idx)
|
||||
for _attempt in range(10): # poll up to 5s (10 × 0.5s)
|
||||
await asyncio.sleep(0.5)
|
||||
await self._fetch_encryption_key_http()
|
||||
new_max = max(self._caller_all_keys.keys()) if self._caller_all_keys else -1
|
||||
if new_max > pre_max_idx:
|
||||
self._caller_key = self._caller_all_keys[new_max]
|
||||
logger.info("Key rotated: index %d→%d (%d bytes)",
|
||||
pre_max_idx, new_max, len(self._caller_key))
|
||||
break
|
||||
logger.debug("Key rotation poll %d: max_idx still %d", _attempt + 1, new_max)
|
||||
else:
|
||||
logger.warning("No key rotation after 5s — using pre-join key[%d]", pre_max_idx)
|
||||
|
||||
# Set per-participant keys via key provider
|
||||
kp = self.lk_room.e2ee_manager.key_provider
|
||||
|
||||
@@ -279,21 +297,24 @@ class VoiceSession:
|
||||
if remote_identity:
|
||||
break
|
||||
|
||||
# Set caller's per-participant key (HKDF info=caller_identity = matching EC JS).
|
||||
if self._caller_key and remote_identity:
|
||||
# Set ALL known caller keys (per-participant, HKDF info=remote_identity).
|
||||
# EC may have already rotated (index 0→1) by the time bot connects.
|
||||
if self._caller_all_keys and remote_identity:
|
||||
try:
|
||||
kp.set_key(remote_identity, self._caller_key, 0)
|
||||
logger.info("Set caller key for %s (index=0, %d bytes)", remote_identity, len(self._caller_key))
|
||||
# Also set via caller_identity (belt+suspenders if identities differ)
|
||||
for idx, k in sorted(self._caller_all_keys.items()):
|
||||
kp.set_key(remote_identity, k, idx)
|
||||
logger.info("Set caller key[%d] for %s (%d bytes)", idx, remote_identity, len(k))
|
||||
# Belt+suspenders: also set via matrix identity if different from LK identity
|
||||
if self._caller_identity and self._caller_identity != remote_identity:
|
||||
kp.set_key(self._caller_identity, self._caller_key, 0)
|
||||
logger.info("Also set caller key via identity %s", self._caller_identity)
|
||||
for idx, k in sorted(self._caller_all_keys.items()):
|
||||
kp.set_key(self._caller_identity, k, idx)
|
||||
logger.info("Also set all caller keys via identity %s", self._caller_identity)
|
||||
except Exception as e:
|
||||
logger.warning("Failed to set caller per-participant key: %s", e)
|
||||
elif not self._caller_key:
|
||||
logger.warning("No caller E2EE key — incoming audio will be silence")
|
||||
logger.warning("Failed to set caller per-participant keys: %s", e)
|
||||
elif not self._caller_all_keys:
|
||||
logger.warning("No caller E2EE keys — incoming audio will be silence")
|
||||
elif not remote_identity:
|
||||
logger.warning("No remote participant found — caller key not set")
|
||||
logger.warning("No remote participant found — caller keys not set")
|
||||
|
||||
if remote_identity:
|
||||
logger.info("Linking to remote participant: %s", remote_identity)
|
||||
|
||||
Reference in New Issue
Block a user