fix(e2ee): set caller keys at correct indices from timeline
Element Call may rotate encryption keys to index > 0. Previously we
always called set_key(identity, key, 0) regardless of the actual index,
causing decryption to fail when the active key was at a non-zero index.
- _fetch_encryption_key_http: collect all {index->key} pairs from event
- _run: set each caller key at its correct index
- on_encryption_key: handle multiple indices, remove first-key-only gate
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
52
voice.py
52
voice.py
@@ -100,24 +100,29 @@ class VoiceSession:
|
||||
self._http_session = None
|
||||
self._caller_key: bytes | None = None
|
||||
self._caller_identity: str | None = None
|
||||
self._caller_all_keys: dict = {} # {index: bytes} — all caller keys by index
|
||||
self._bot_key: bytes = bot_key or os.urandom(16)
|
||||
self._publish_key_cb = publish_key_cb
|
||||
|
||||
def on_encryption_key(self, sender, device_id, key, index):
|
||||
"""Receive E2EE key from Element Call participant."""
|
||||
if key and not self._caller_key:
|
||||
if not key:
|
||||
return
|
||||
if not self._caller_key:
|
||||
self._caller_key = key
|
||||
self._caller_identity = f"{sender}:{device_id}"
|
||||
logger.info("E2EE key received from %s:%s (index=%d, %d bytes)",
|
||||
sender, device_id, index, len(key))
|
||||
# Live-update key provider if already connected
|
||||
if self.lk_room and hasattr(self.lk_room, 'e2ee_manager'):
|
||||
try:
|
||||
kp = self.lk_room.e2ee_manager.key_provider
|
||||
kp.set_key(self._caller_identity, key, index)
|
||||
logger.info("Live-set caller key for %s", self._caller_identity)
|
||||
except Exception as e:
|
||||
logger.warning("Failed to live-set caller key: %s", e)
|
||||
self._caller_all_keys[index] = key
|
||||
logger.info("E2EE key received from %s:%s (index=%d, %d bytes)",
|
||||
sender, device_id, index, len(key))
|
||||
# Live-update key provider if already connected
|
||||
if self.lk_room and hasattr(self.lk_room, 'e2ee_manager'):
|
||||
try:
|
||||
kp = self.lk_room.e2ee_manager.key_provider
|
||||
caller_id = self._caller_identity or f"{sender}:{device_id}"
|
||||
kp.set_key(caller_id, key, index)
|
||||
logger.info("Live-set caller key[%d] for %s", index, caller_id)
|
||||
except Exception as e:
|
||||
logger.warning("Failed to live-set caller key: %s", e)
|
||||
|
||||
async def _fetch_encryption_key_http(self) -> bytes | None:
|
||||
"""Fetch encryption key from room timeline (NOT state) via Matrix HTTP API.
|
||||
@@ -149,15 +154,23 @@ class VoiceSession:
|
||||
device = content.get("device_id", "")
|
||||
logger.info("Found encryption_keys timeline event: sender=%s device=%s",
|
||||
sender, device)
|
||||
all_keys = {}
|
||||
import base64 as b64
|
||||
for k in content.get("keys", []):
|
||||
key_b64 = k.get("key", "")
|
||||
key_index = k.get("index", 0)
|
||||
if key_b64:
|
||||
key_b64 += "=" * (-len(key_b64) % 4)
|
||||
import base64 as b64
|
||||
key_bytes = b64.urlsafe_b64decode(key_b64)
|
||||
if device:
|
||||
self._caller_identity = f"{sender}:{device}"
|
||||
return key_bytes
|
||||
all_keys[key_index] = key_bytes
|
||||
if all_keys:
|
||||
if device:
|
||||
self._caller_identity = f"{sender}:{device}"
|
||||
self._caller_all_keys.update(all_keys)
|
||||
max_idx = max(all_keys.keys())
|
||||
logger.info("Loaded caller keys at indices %s (using %d)",
|
||||
sorted(all_keys.keys()), max_idx)
|
||||
return all_keys[max_idx]
|
||||
logger.info("No encryption_keys events in last %d timeline events", len(events))
|
||||
except Exception as e:
|
||||
logger.warning("HTTP encryption key fetch failed: %s", e)
|
||||
@@ -254,12 +267,15 @@ class VoiceSession:
|
||||
if remote_identity:
|
||||
break
|
||||
|
||||
# Set caller's key — decrypts incoming audio
|
||||
# Set caller's key(s) — decrypts incoming audio
|
||||
# Use all collected keys with their correct indices (Element Call may rotate)
|
||||
if self._caller_key:
|
||||
caller_id = remote_identity or self._caller_identity
|
||||
if caller_id:
|
||||
kp.set_key(caller_id, self._caller_key, 0)
|
||||
logger.info("Set caller key for %s (%d bytes)", caller_id, len(self._caller_key))
|
||||
keys_to_set = self._caller_all_keys or {0: self._caller_key}
|
||||
for idx, key in keys_to_set.items():
|
||||
kp.set_key(caller_id, key, idx)
|
||||
logger.info("Set caller key[%d] for %s (%d bytes)", idx, caller_id, len(key))
|
||||
else:
|
||||
logger.warning("Have caller key but no caller identity")
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user