fix: generate and publish E2EE key, always connect with encryption
Element Call encrypts media by default. Bot must: 1. Generate its own 32-byte E2EE key 2. Publish it to room state (io.element.call.encryption_keys) 3. Connect to LiveKit with HKDF E2EE enabled 4. Use caller's key when received, own key as fallback This fixes: Nicht verschlüsselt warning, silent audio (encrypted frames couldn't be decoded by VAD/STT) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
46
voice.py
46
voice.py
@@ -102,6 +102,26 @@ class VoiceSession:
|
|||||||
logger.info("E2EE key received from %s:%s (index=%d, %d bytes)",
|
logger.info("E2EE key received from %s:%s (index=%d, %d bytes)",
|
||||||
sender, device_id, index, len(key))
|
sender, device_id, index, len(key))
|
||||||
|
|
||||||
|
async def _publish_e2ee_key(self, key: bytes):
|
||||||
|
"""Publish our E2EE key to room state so Element Call shares its key with us."""
|
||||||
|
import base64 as b64
|
||||||
|
key_b64 = b64.urlsafe_b64encode(key).decode().rstrip("=")
|
||||||
|
content = {
|
||||||
|
"call_id": "",
|
||||||
|
"device_id": self.device_id,
|
||||||
|
"keys": [{"index": 0, "key": key_b64}],
|
||||||
|
}
|
||||||
|
user_id = self.nio_client.user_id
|
||||||
|
state_key = f"{user_id}:{self.device_id}"
|
||||||
|
try:
|
||||||
|
ENCRYPTION_KEYS_TYPE = "io.element.call.encryption_keys"
|
||||||
|
await self.nio_client.room_put_state(
|
||||||
|
self.room_id, ENCRYPTION_KEYS_TYPE, content, state_key=state_key,
|
||||||
|
)
|
||||||
|
logger.info("Published E2EE key (state_key=%s)", state_key)
|
||||||
|
except Exception:
|
||||||
|
logger.exception("Failed to publish E2EE key")
|
||||||
|
|
||||||
async def start(self):
|
async def start(self):
|
||||||
self._task = asyncio.create_task(self._run())
|
self._task = asyncio.create_task(self._run())
|
||||||
|
|
||||||
@@ -129,20 +149,29 @@ class VoiceSession:
|
|||||||
user_id = self.nio_client.user_id
|
user_id = self.nio_client.user_id
|
||||||
jwt = _generate_lk_jwt(self.room_id, user_id, self.device_id)
|
jwt = _generate_lk_jwt(self.room_id, user_id, self.device_id)
|
||||||
|
|
||||||
# Wait up to 10s for E2EE encryption key from Element Call
|
# Generate our own E2EE key and publish it to the room
|
||||||
|
# Element Call requires ALL participants to publish keys
|
||||||
|
import secrets
|
||||||
|
our_key = secrets.token_bytes(32)
|
||||||
|
await self._publish_e2ee_key(our_key)
|
||||||
|
logger.info("Published our E2EE key (%d bytes)", len(our_key))
|
||||||
|
|
||||||
|
# Wait up to 10s for caller's E2EE encryption key
|
||||||
for _ in range(100):
|
for _ in range(100):
|
||||||
if self._e2ee_key:
|
if self._e2ee_key:
|
||||||
break
|
break
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
if not self._e2ee_key:
|
|
||||||
logger.warning("No E2EE key received after 10s, connecting without encryption")
|
|
||||||
|
|
||||||
# Connect with E2EE if key available
|
# Use caller's key if available, otherwise use our own
|
||||||
e2ee_opts = None
|
shared_key = self._e2ee_key or our_key
|
||||||
if self._e2ee_key:
|
if self._e2ee_key:
|
||||||
e2ee_opts = _build_e2ee_options(self._e2ee_key)
|
logger.info("Using caller's E2EE key (%d bytes)", len(self._e2ee_key))
|
||||||
logger.info("E2EE enabled with HKDF (%d byte key)", len(self._e2ee_key))
|
else:
|
||||||
|
logger.warning("No caller key received after 10s, using our own key")
|
||||||
|
|
||||||
|
e2ee_opts = _build_e2ee_options(shared_key)
|
||||||
|
|
||||||
|
logger.info("E2EE enabled with HKDF")
|
||||||
room_opts = rtc.RoomOptions(e2ee=e2ee_opts)
|
room_opts = rtc.RoomOptions(e2ee=e2ee_opts)
|
||||||
self.lk_room = rtc.Room()
|
self.lk_room = rtc.Room()
|
||||||
|
|
||||||
@@ -159,8 +188,7 @@ class VoiceSession:
|
|||||||
logger.info("Track sub: %s %s kind=%s", p.identity, pub.sid, t.kind)
|
logger.info("Track sub: %s %s kind=%s", p.identity, pub.sid, t.kind)
|
||||||
|
|
||||||
await self.lk_room.connect(self.lk_url, jwt, options=room_opts)
|
await self.lk_room.connect(self.lk_url, jwt, options=room_opts)
|
||||||
logger.info("Connected (E2EE=%s), remote=%d",
|
logger.info("Connected (E2EE=HKDF), remote=%d",
|
||||||
"HKDF" if self._e2ee_key else "off",
|
|
||||||
len(self.lk_room.remote_participants))
|
len(self.lk_room.remote_participants))
|
||||||
|
|
||||||
# Find the remote participant to link to
|
# Find the remote participant to link to
|
||||||
|
|||||||
Reference in New Issue
Block a user