Server now pushes its signed CRL to each connecting client right after the
handshake; the client verifies the signature against the CA and applies the
revocation list to its verifier (and caches it on disk for restarts).
Removes the v1 "CRL distributed out-of-band" honest limitation.
Wire (multiplexed over existing PacketConnection, no trait change):
control envelope = MAGIC[4]=[0xAA,0xAA,0xC0,0x01] || kind(u8) || u32_be(len)
|| payload. IPv4/IPv6 start with 0x4X/0x6X, so 0xAA cannot collide; an old
peer just drops it as a junk packet in the TUN — back-compat preserved.
- aura-proto: ControlKind { CrlPush, CrlAck, Unknown }, encode/decode_control_
envelope, CONTROL_ENVELOPE_MAGIC; 7 frame tests.
- aura-pki: CrlStore::{encode_signed, save_signed, decode_signed_verified,
load_signed_verified} — ECDSA-P256/SHA-256 from the CA private key against
a textual "CRL-Aura-v1" body + --SIGNATURE--; 7 signing tests. ring 0.17
added crate-local (already in lockfile via rustls-webpki).
- aura-cli: crl_push module — server pushes via conn.send_packet on accept;
client wraps the Arc<dyn PacketConnection> in AcceptPushedCrlConn which
sniffs the magic in recv_packet, verifies the signature, updates the
AuraCertVerifier, caches to disk. PkiSection gets ca_key, crl_push (default
true), accept_pushed_crl (default true).
- 5 in_band_crl integration tests via mock PacketConnection.
Workspace: 235 tests passed (+28), clippy -D warnings clean, fmt clean. v2
COMPLETE — all 9 honest v1 limitations resolved (except sing-box, per user).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two opt-in (default off) features directly targeting the kind of operator
dragnet described in the news context — make the server harder to identify
on a scan, and the traffic harder to fingerprint by volume/timing analysis.
1) Port-knocking (probe resistance, UDP)
- Wire: every HS datagram (0x01) is prefixed with a 16-byte HMAC token
when UdpOpts.knock_required is on:
knock = HMAC-SHA256(knock_key, u64_be(unix_minute))[..16]
- Server-side: validates against {now-1, now, now+1} minutes (3-minute
window for clock skew, constant-time compare). Invalid -> silent drop;
the port looks closed to scanners.
- knock_key comes from the CLI (derived from CA fingerprint at the
deployment layer); transport just consumes it.
- DATA datagrams unchanged (AEAD already proves legitimacy past hs).
2) Cover traffic (chaff, UDP)
- Optional background task per UdpConnection: every random delay
(mean_interval_ms +/- jitter, default 500ms +/- 50%) sends a
Frame::Ping{seq=random} when no Data was sent in the recent window
(idle-skip => zero overhead under load). RAII-aborted on Drop.
- Receiver answers Ping with Pong (existing logic); both are consumed
internally by recv_packet, invisible to the app.
API: UdpOpts gains knock_required/knock_key/cover_traffic_enabled/
cover_mean_interval_ms/cover_jitter (all defaults preserve v2 behavior).
Helpers exported: knock_for_minute, KNOCK_LEN.
Local deps: hmac 0.12 + sha2 0.10 (already in workspace lockfile, no new
resolution). Workspace: 185 tests passed (+11), clippy -D warnings clean,
fmt clean. 174 baseline tests unchanged.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three v2-hardening features in aura-cli, one pass:
- nat::NatGuard: RAII auto-config of IP forwarding + MASQUERADE on server
startup. Linux (sysctl ip_forward + iptables -t nat MASQUERADE) and
macOS (sysctl ip.forwarding + pfctl with /tmp/aura-nat.conf). dry_run
works on every platform (logs "would run: ..."). Reverts everything in
Drop. New [server.nat] {auto, egress_iface, dry_run}; absent section =
back-compat no-op. Removes v1's "manual NAT/forwarding" step.
- privdrop::drop_to_user: drop euid/gid after binding TUN + privileged
ports. Linux setresuid/setresgid, macOS setgid+setuid (permanent drop),
Windows no-op with warning. New [server] / [client] run_as = "..."
(optional). Skipped with info-log if already non-root.
- admin: split transport into cfg(unix) Unix-socket and cfg(windows) Tokio
named-pipe modules sharing one JSON-line serve/request flow over
AsyncRead/AsyncWrite. DEFAULT_SOCKET = "/tmp/aura-admin.sock" on Unix,
r"\\.\pipe\aura-admin" on Windows. Removes v1's "admin Unix-only".
Deps: nix 0.29 user feature under [target.'cfg(unix)'.dependencies] (cli-
local, not workspace). Workspace: 155 tests passed (+13), clippy -D warnings
clean, fmt clean.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The TCP fallback now does a full outer TLS handshake (tokio-rustls 0.26 over
rustls 0.23, ring provider) before the Aura proto handshake, exactly like the
QUIC backend: on the wire it is indistinguishable from genuine HTTPS until the
inner Aura mutual-auth handshake starts. Removes v1's "light HTTP masquerade"
limitation; the real security boundary remains the inner PQ handshake.
- aura-transport::tcp: dropped the HTTP/1.1 preamble helpers and TcpOpts
fields (masquerade, host, user_agent, server_header). New flow:
TlsAcceptor::accept (server) / TlsConnector::connect (client) →
tokio::io::split(TlsStream) → server_handshake / client_handshake → Session.
Client reuses crate::quic::AcceptAnyServerCert (outer SNI not authenticated;
inner handshake is the security boundary). Outer server cert auto-sourced
from proto_cfg.server_cert_pem (no API change for the CLI's bind).
- ALPN default: ["h2", "http/1.1"] (DEFAULT_TCP_ALPN, exported).
- TcpOpts: now just { alpn: Option<Vec<Vec<u8>>> }.
- TcpClient::connect gains an outer-SNI &str param; DialConfig.sni passes it
through (separate from the inner proto_cfg.server_name).
- tokio-rustls 0.26 added as a transport-local dependency (not workspace).
CLI updates: removed dead host/user_agent/server_header wiring; mask rotation
no longer touches TCP outer parameters (TLS doesn't have a Host header on
the wire). [transport] masquerade kept as a no-op for back-compat with old
configs (documented).
3 new tcp_loopback tests (default ALPN end-to-end, custom ALPN, outer SNI
mismatch still connects = proves accept-any is in effect). Workspace: 142
tests passed (+1), clippy -D warnings clean, fmt clean.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
aura-cli: clap command tree (pki init/issue-server/issue-client/revoke/list,
server, client, route add/list/remove, status, bench-crypto); TOML config with
~ expansion and split-tunnel rules -> RouteTable; JSON-over-Unix-socket admin
IPC; server/client data paths wiring transport + tunnel (TUN run needs root).
config/{server,client}.toml.example. 15 tests (pki roundtrip, config parse,
admin-socket roundtrip, loopback connection). Verified the real binary: --help,
bench-crypto, and a full CA->server->client cert workflow.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Worktree isolation is unavailable in this environment, so make Wave 3 safe for
same-tree parallel work instead: the PacketConnection contract now lives in
aura-proto (stable) and aura-tunnel no longer depends on aura-transport. With
transport and tunnel both depending only on proto (and not each other), the two
crates are independent leaves and can be built/edited concurrently without one
breaking the other's build. proto: 13 tests still green.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Define the async PacketConnection trait (send_packet/recv_packet over &self)
that aura-tunnel's router consumes and the QUIC connection will implement.
Committed before Wave 3 so the transport and tunnel agents build against a
stable cross-crate contract from isolated worktrees.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- 6-crate Cargo workspace, dependency tree frozen (cargo check green in ~1m)
- ml-kem 0.3 (FIPS 203) replaces spec's pqcrypto-kyber for ML-KEM-768
- fix invalid target-gated workspace.dependencies: Windows deps (wintun/windows)
declared untargeted, cfg-gated per-crate in aura-tunnel
- version bumps vs spec: tun 0.8, rcgen 0.14, wintun 0.5
- stub lib/main per crate; real implementations land wave by wave
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>