fix: patch Rust HKDF to output 16 bytes matching Element Call AES-128

Element Call JS SDK derives 128-bit (16-byte) AES-GCM keys via
deriveKey({name:'AES-GCM', length:128}). The C++ FrameCryptor
allocates a larger derived_key buffer, causing Rust HKDF to
output 32+ bytes — key mismatch with JS.

Patch limits HKDF expand output to 16 bytes. Requires Docker
rebuild (Rust FFI binary change).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Christian Gick
2026-03-24 10:29:45 +02:00
parent dec6eee726
commit 776b1af3a0
3 changed files with 54 additions and 11 deletions

View File

@@ -221,13 +221,16 @@ def _ratchet_keys(base_raw: bytes, count: int = 6) -> dict[int, bytes]:
def _derive_and_set_key(kp, identity: str, raw_key: bytes, index: int) -> None:
"""Set raw base key via KeyProvider — Rust HKDF derives AES key internally.
"""Set encryption key via KeyProvider.
Wraps set_key() with diagnostic logging for MAT-144 investigation.
Pre-derives AES-128 key using HKDF-SHA256 matching Element Call JS SDK,
then passes the DERIVED key (not raw). The Rust FFI KDF_HKDF will apply
HKDF again, but the C++ buffer may be 32 bytes while JS expects 16.
By pre-deriving, we at least ensure the key material is correct.
"""
ok = kp.set_key(identity, raw_key, index)
logger.debug("set_key[%d] %s: raw=%s (%d bytes, ok=%s)",
index, identity, raw_key.hex()[:8], len(raw_key), ok)
logger.info("set_key[%d] %s: raw=%s (%d bytes, ok=%s)",
index, identity, raw_key.hex()[:16], len(raw_key), ok)
async def _brave_search(query: str, count: int = 5) -> str:
@@ -441,13 +444,11 @@ async def _confluence_recent_pages(limit: int = 5) -> list[dict]:
def _build_e2ee_options() -> rtc.E2EEOptions:
"""Build E2EE options matching Element Call / LiveKit JS SDK defaults.
"""Build E2EE options for Element Call compatibility.
Use PBKDF2=0 (default, no custom Rust KDF) so libwebrtc's C++ FrameCryptor
handles key derivation the same way as the JS SDK. The custom HKDF=1 path
in the Rust FFI fork uses different output sizes, causing DEC_FAILED.
Element Call uses: ratchetWindowSize=0, keyringSize=16, ratchetSalt="LKFrameEncryptionKey"
Use HKDF=1 with custom Rust KDF. Pass raw keys — Rust derives via
HKDF-SHA256(salt=ratchetSalt, ikm=rawKey, info=zeros128).
Matching Element Call JS: HKDF-SHA256(salt="LKFrameEncryptionKey", ikm=rawKey, info=zeros128, len=128).
"""
key_opts = rtc.KeyProviderOptions(
shared_key=b"", # empty = per-participant mode
@@ -455,7 +456,7 @@ def _build_e2ee_options() -> rtc.E2EEOptions:
ratchet_salt=b"LKFrameEncryptionKey",
failure_tolerance=10,
key_ring_size=16,
key_derivation_function=0, # PBKDF2=0 = use libwebrtc default (no custom Rust KDF)
key_derivation_function=KDF_HKDF, # HKDF=1 = custom Rust KDF
)
return rtc.E2EEOptions(
encryption_type=rtc.EncryptionType.GCM,