Commit ceb101a
committed
encryption: the sealed channel — an actual encrypted connection, not a sealed record
This is the POC the session was for and had not delivered. What existed was
`envelope`, which seals a RECORD under a password. The job was a CONNECTION
between the browser and the server, above TLS, so that the proxy / TLS
terminator / remote-desktop layer in front of the server sees ciphertext only.
`channel.rs` is that, end to end:
handshake: client -> server ephemeral X448 public key (56 bytes)
record: counter (8, BE) | ciphertext | Poly1305 tag (16)
nonce = direction (1) | zeros (15) | counter (8)
aad = direction (1) | counter (8)
Server authentication comes from the key agreement, NOT from a signature: the
client holds the server's static X448 public key (pinned in the client build),
both sides compute X448(ephemeral, static), and only the holder of the static
private key derives the same value. A man in the middle can substitute its own
key and complete a handshake — and then nothing it forwards opens, in either
direction. That is a test, not a claim. The whole Ed25519/Ed448 detour earlier
in this session was unnecessary: the DH IS the authentication (Noise NK shape).
Directions are separated by key AND nonce, so a record cannot be reflected back
at its sender. The per-direction counter is strictly increasing and only
committed after the tag verifies, so replays and rewinds are refused and a
forged record cannot advance the window to lock out real traffic.
x448 comes from the AdaWorldAPI fork of RustCrypto/elliptic-curves — no dalek,
and crypto-bigint underneath means no foreign AVX2 intrinsics for the polyfill
to have to displace.
hkdf_sha384.rs is HMAC-SHA384 + HKDF written directly on the crate's own
sha384, deliberately: the `hmac`/`hkdf` crates sit on the digest 0.11 trait
generation while this crate's sha2 is 0.10, and dragging a second generation in
for forty lines is the tail wagging the dog. It is pinned by RFC 4231 vectors
(cases 1, 2 and 6 — case 6 covers the oversized-key branch), not by trust.
Measured, release, this box:
handshake (both sides) 1.55 ms
record 64 B seal+open 1.17 us 52 MiB/s
record 4096 B seal+open 8.26 us 473 MiB/s
record 65536 B seal+open 122.20 us 512 MiB/s
So the per-session cost is one 1.5 ms handshake and the steady state runs at
~500 MiB/s. X448 is the slow part; that is the price of the non-dalek curve.
15 new tests (9 channel, 6 kdf), 40 green in the crate, clippy clean.
Not in scope here, and stated rather than hidden: x448's own README says the
code has not been audited. And the server process still sees plaintext — this
is not zero-knowledge against the operator, and the module doc says so where
someone reading the code will find it.
Generated by [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mwq1QKpw4zRd6oaGRoJhF21 parent 3694a3c commit ceb101a
5 files changed
Lines changed: 879 additions & 7 deletions
0 commit comments