fix(e2ee): switch to PR #921 Rust FFI branch for native HKDF (MAT-144)
PR #904 callback-based HKDF hack only fired for the first frame cryptor (audio), leaving video frame cryptors with PBKDF2 - DEC_FAILED oscillation. PR #921 integrates HKDF natively at the WebRTC C++ level, applying uniformly to all frame cryptors (audio + video). Also removes aggressive video re-keying workaround and adds 5s cooldown to DEC_FAILED re-keying handler to prevent tight loops. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
10
Dockerfile
10
Dockerfile
@@ -1,6 +1,8 @@
|
|||||||
# Stage 1: Build patched Rust FFI with HKDF support for Element Call E2EE
|
# Stage 1: Build patched Rust FFI with native HKDF support for Element Call E2EE
|
||||||
# Fork: onestacked/livekit-rust-sdks branch EC-compat-changes
|
# Fork: onestacked/livekit-rust-sdks branch EC-compat-changes-webrtc-change
|
||||||
# PR: https://github.com/livekit/rust-sdks/pull/904
|
# PR: https://github.com/livekit/rust-sdks/pull/921 (proper HKDF at WebRTC C++ level)
|
||||||
|
# Replaces #904 which used a callback hack that only worked for the first frame cryptor
|
||||||
|
# (audio), causing DEC_FAILED on video tracks (MAT-144).
|
||||||
# Must use rust:latest (trixie/sid) — bookworm GCC 12 can't compile webrtc C++20 code
|
# Must use rust:latest (trixie/sid) — bookworm GCC 12 can't compile webrtc C++20 code
|
||||||
FROM rust:latest AS rust-build
|
FROM rust:latest AS rust-build
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
@@ -8,7 +10,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
libva-dev libglib2.0-dev nasm make clang \
|
libva-dev libglib2.0-dev nasm make clang \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
RUN git clone --branch EC-compat-changes --depth 1 --recurse-submodules \
|
RUN git clone --branch EC-compat-changes-webrtc-change --depth 1 --recurse-submodules \
|
||||||
https://github.com/onestacked/livekit-rust-sdks.git
|
https://github.com/onestacked/livekit-rust-sdks.git
|
||||||
WORKDIR /build/livekit-rust-sdks/livekit-ffi
|
WORKDIR /build/livekit-rust-sdks/livekit-ffi
|
||||||
RUN cargo build --release
|
RUN cargo build --release
|
||||||
|
|||||||
29
voice.py
29
voice.py
@@ -695,23 +695,6 @@ class VoiceSession:
|
|||||||
_derive_and_set_key(kp_local, caller_id, base_k, idx)
|
_derive_and_set_key(kp_local, caller_id, base_k, idx)
|
||||||
logger.info("on_ts: set key[%d] for %s (%s track)",
|
logger.info("on_ts: set key[%d] for %s (%s track)",
|
||||||
idx, caller_id, track_type)
|
idx, caller_id, track_type)
|
||||||
# MAT-144: Video frame cryptors may not be fully initialized
|
|
||||||
# when set_key is first called. Schedule aggressive re-keying.
|
|
||||||
if int(t.kind) == 2:
|
|
||||||
async def _video_rekey(pid=caller_id):
|
|
||||||
for delay in (0.3, 0.8, 2.0, 5.0):
|
|
||||||
await asyncio.sleep(delay)
|
|
||||||
if not self.lk_room:
|
|
||||||
break
|
|
||||||
try:
|
|
||||||
kp_v = self.lk_room.e2ee_manager.key_provider
|
|
||||||
for idx, base_k in sorted(self._caller_all_keys.items()):
|
|
||||||
_derive_and_set_key(kp_v, pid, base_k, idx)
|
|
||||||
logger.info("video_rekey: re-set %d keys for %s (delay=%.1fs)",
|
|
||||||
len(self._caller_all_keys), pid, delay)
|
|
||||||
except Exception as exc:
|
|
||||||
logger.warning("video_rekey failed: %s", exc)
|
|
||||||
asyncio.ensure_future(_video_rekey())
|
|
||||||
else:
|
else:
|
||||||
logger.warning("on_ts: no caller keys yet — scheduling 0.5s retry")
|
logger.warning("on_ts: no caller keys yet — scheduling 0.5s retry")
|
||||||
async def _brief_key_retry(pid=caller_id):
|
async def _brief_key_retry(pid=caller_id):
|
||||||
@@ -731,19 +714,27 @@ class VoiceSession:
|
|||||||
logger.warning("on_ts: set_key failed: %s", exc)
|
logger.warning("on_ts: set_key failed: %s", exc)
|
||||||
|
|
||||||
_e2ee_state_names = {0:"NEW",1:"OK",2:"ENC_FAILED",3:"DEC_FAILED",4:"MISSING_KEY",5:"RATCHETED",6:"INTERNAL_ERR"}
|
_e2ee_state_names = {0:"NEW",1:"OK",2:"ENC_FAILED",3:"DEC_FAILED",4:"MISSING_KEY",5:"RATCHETED",6:"INTERNAL_ERR"}
|
||||||
|
_last_rekey_time = {} # per-participant cooldown for DEC_FAILED re-keying
|
||||||
@self.lk_room.on("e2ee_state_changed")
|
@self.lk_room.on("e2ee_state_changed")
|
||||||
def on_e2ee_state(participant, state):
|
def on_e2ee_state(participant, state):
|
||||||
state_name = _e2ee_state_names.get(int(state), f"UNKNOWN_{state}")
|
state_name = _e2ee_state_names.get(int(state), f"UNKNOWN_{state}")
|
||||||
p_id = participant.identity if participant else "local"
|
p_id = participant.identity if participant else "local"
|
||||||
logger.info("E2EE_STATE: participant=%s state=%s", p_id, state_name)
|
logger.info("E2EE_STATE: participant=%s state=%s", p_id, state_name)
|
||||||
# When remote participant frame cryptor is NEW or MISSING_KEY → set their key
|
# When remote participant needs key: NEW, MISSING_KEY, or DEC_FAILED (with cooldown)
|
||||||
if participant and p_id != bot_identity and int(state) in (0, 3, 4):
|
if participant and p_id != bot_identity and int(state) in (0, 3, 4):
|
||||||
|
now = time.monotonic()
|
||||||
|
# DEC_FAILED: only re-key every 5s to avoid tight loops
|
||||||
|
if int(state) == 3:
|
||||||
|
last = _last_rekey_time.get(p_id, 0)
|
||||||
|
if (now - last) < 5.0:
|
||||||
|
return
|
||||||
|
_last_rekey_time[p_id] = now
|
||||||
if self._caller_all_keys:
|
if self._caller_all_keys:
|
||||||
try:
|
try:
|
||||||
kp_e = self.lk_room.e2ee_manager.key_provider
|
kp_e = self.lk_room.e2ee_manager.key_provider
|
||||||
for idx, base_k in sorted(self._caller_all_keys.items()):
|
for idx, base_k in sorted(self._caller_all_keys.items()):
|
||||||
_derive_and_set_key(kp_e, p_id, base_k, idx)
|
_derive_and_set_key(kp_e, p_id, base_k, idx)
|
||||||
logger.info("e2ee_state: derived+set key[%d] for %s on %s",
|
logger.info("e2ee_state: set key[%d] for %s on %s",
|
||||||
idx, p_id, state_name)
|
idx, p_id, state_name)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.warning("e2ee_state set_key failed: %s", exc)
|
logger.warning("e2ee_state set_key failed: %s", exc)
|
||||||
|
|||||||
Reference in New Issue
Block a user