Files
omnivox-rs/README.md
T
michael c614d8345f Protocol v2: X25519 handshake, sequence-number nonces, hardened transport
Red-team review of the (byte-exact) v1 port found replay, cross-session
frame portability, pre-auth allocation amplification, and a symmetric
key that let either peer forge as itself — flaws inherent to the v1
wire format. v2 closes them:

- fixed 69-byte OVX2 hello; server validates before replying; v1 and
  wrong-version peers see an immediate close
- HKDF(ephemeral X25519 DH, PSK, transcript) -> direction-separated
  session keys; forward secrecy per connection
- strictly increasing u64 sequence nonces: any replay or reorder drops
  the connection (regression tests in tests/protocol_v2.rs)
- Config: message-size cap (64 KiB default), connection semaphore,
  idle/body-stall/handshake/send timeouts on both roles (client-side
  timers retire the old no-client-timeouts quirk)
- message processing inline per connection; user callbacks panic-
  isolated; poison-tolerant mutexes; demo key refused in release builds
- PROTOCOL-V2.md supersedes PARITY-CONTRACT.md (retired, kept as port
  history); Cereal payload encoding is unchanged

Payload serialization remains byte-exact with the original C++ (the
golden scrypt/cereal vectors still govern). Zero unsafe. Verified live:
server<->client handshake, encrypted echo, clean teardown.

Cuts over from the C++ implementation, which is deleted (phase 5 of the
port contract); it remains in the original gitea repository.
2026-09-30 06:56:52 -04:00

2.5 KiB

omnivox-rs

Encrypted async TCP client-server messaging — protocol v2. A Rust library plus example binaries, descended from the OmniVox C++ library and hardened past it after a full red-team review of v1.

  • X25519 handshake on every connection → forward secrecy, session-bound keys
  • ChaCha20-Poly1305 with strictly increasing sequence numbers → replay-proof
  • Direction-separated traffic keys, fixed-size pre-auth surface
  • scrypt (RFC 7914) password-based key derivation; Cereal payload encoding (byte-exact with the original's cereal binary format) for data portability
  • tokio transport: connection caps, idle/stall/send timeouts, panic-isolated callbacks; zero unsafe

Not wire-compatible with the C++ original or v1 — by design; v1 peers are refused at the handshake. See PROTOCOL-V2.md for the full normative spec and PARITY-CONTRACT.md (retired) for the port's history.

Usage

use omnivox::{Config, Cereal, CodecError, Decision, OmniVox, derive_encrypt_key};
use std::sync::Arc;

let endpoint = OmniVox::<MyPayload>::new();
endpoint.set_encrypt_key(derive_encrypt_key(b"password", b"salt", 17, 8, 1)?);
endpoint.start_server(8080, Config::default(), on_data, on_disconnect, on_connect).await?;

Payload types implement Cereal (raw little-endian fields, u64 vector lengths — the original's serialization). Runnable demos: examples/.

Security posture

v1 weakness v2 answer
captured frames replay forever strict sequence numbers; a replay kills the connection
frames portable across connections keys bound to per-session nonces
5 MiB pre-auth allocation from 8 bytes fixed 69-byte handshake before any attacker-sized read
one symmetric key, either peer can forge as itself direction-separated HKDF session keys
static key, no forward secrecy ephemeral X25519 per connection
no client-side timeouts full idle/stall discipline both roles
panicking user callback killed the read loop callbacks panic-isolated
demo password in every repo checkout release builds refuse the demo key

Use random 32-byte PSKs where you can; an active attacker can grind scrypt against human passwords (PAKE planned).

Build & test

cargo test           # includes attack regressions: replay, version, panic, timeouts
cargo clippy --all-targets -- -D warnings
cargo run --example omnivox-server   # terminal 1
cargo run --example omnivox-client   # terminal 2

License

GPL-3.0-or-later.