Start .NET rewrite with wire and media crypto conformance
.NET port / test (macos-latest) (push) Canceled after 0s
.NET port / test (ubuntu-24.04) (push) Canceled after 0s
.NET port / test (windows-latest) (push) Canceled after 0s
.NET port / cpp-conformance (push) Canceled after 0s

This commit is contained in:
2026-09-15 17:54:16 +02:00
parent c6c003b8a7
commit b76181d9fb
37 changed files with 1328 additions and 19 deletions
+13 -10
View File
@@ -67,13 +67,15 @@ mandatory from the first build. This was chosen over DTLS after weighing two fin
### How it works
1. During the TLS 1.3 control handshake, both sides call the keying-material exporter with a
fixed label (`"voicecat media v1"`) to derive independent **send/recv media keys** and a
salt. No second handshake, no certificates on the UDP path — the UDP channel inherits the
1. After the TLS 1.3 control handshake, both sides call the keying-material exporter with
label `"voicecat media v1"` and a one-byte context: `0x00` for client→server,
`0x01` for server→client. Each export yields a 32-byte directional media key.
No second handshake, no certificates on the UDP path — the UDP channel inherits the
authenticated, MITM-resistant TLS session's trust.
2. Each UDP voice frame is sealed with **ChaCha20-Poly1305** (libsodium, ISC license).
3. The readable routing field (`ssrc`) is passed as AEAD **associated data** so the relay can
route without decrypting and an attacker cannot tamper with it undetected.
3. The full 20-byte header is AEAD **associated data**. The server authenticates/decrypts
inbound media and reseals for each recipient, replacing the sequence with that
recipient's next send counter. It forwards the encoded Opus bytes without decoding audio.
This keeps the entire crypto surface on two permissive libraries (mbedTLS + libsodium), adds
no handshake latency to voice startup, and is small enough to audit fully. It is abstracted
@@ -84,11 +86,12 @@ the design depends on that.
### Per-frame protections
- **AEAD** (ChaCha20-Poly1305) over each voice frame — confidentiality + integrity.
- **Associated data:** the `ssrc` (and version/flags) are authenticated-but-visible so the
relay routes without decrypting; everything else is encrypted.
- **Nonce discipline:** `nonce = direction_bit ‖ ssrc ‖ monotonic_packet_counter`. The
counter never repeats under one key; the session **rekeys** (re-derives via the exporter
with a bumped epoch) well before counter exhaustion or on a time/byte budget.
- **Associated data:** all 20 header bytes remain visible and authenticated; the Opus
payload is encrypted and followed by a 16-byte tag.
- **Nonce discipline:** `nonce = four_zero_bytes ‖ counter_u64_big_endian`. Counters are
per directional session key, shared across its streams. Direction separation comes
from exporter contexts, not nonce bits. Automatic epoch rekeying is not implemented;
the .NET encryptor refuses counter exhaustion and requires a new session.
- **Anti-replay:** a 64-bit sliding-window replay filter keyed on the packet counter (à la
IPsec). The window is **advanced only after the AEAD tag verifies** (RFC 3711 §3.3 order:
replay-check → authenticate → update). The counter is read from the unauthenticated