feat(cli): v3.2 multi-hop — per-hop cert, cell padding, 3-hop, CIDR whitelist

Closes the v3.1 unlinkability gap and resists volume/timing correlation:

1) Per-hop client cert (identity-unlinkable hops). [[client.circuit.hops]]
   now accepts {addr, cert_path, key_path, [server_name]} per hop — each
   hop sees a different CN, so a relay and an exit cannot correlate the
   same client by certificate. Old flat `hops = ["ip:port"]` form still
   parses (serde untagged enum) and falls back to [pki] cert/key.
   `aura provision-client --circuit-hops N` mints N fresh UUIDv4 certs.

2) Cell padding. CellPaddingConn wrapper pads every outgoing packet to a
   fixed size (default 1280 bytes; `cell_size = N` configurable) before
   it hits the inner AEAD. Format: u16_be(real_len) || pkt || zero_pad.
   On-wire sizes become constant -> defeats volume/timing fingerprints.
   Opt-in via [client.circuit] cell_padding = true and the mirror
   [server] cell_padding_for_circuit_clients = true.

3) 3-hop support. dial_circuit now accepts N >= 2 hops; iterative
   ExtendBridge nests N-1 forwarders and N handshakes. Client owns the
   full chain via CircuitConnection (forwarders abort on drop).
   New integration test multihop_v3_2_three_hops_end_to_end runs three
   in-process actors (A relay -> B relay -> C exit) on loopback and
   verifies peer_id == C's CN.

4) CIDR whitelist. [server.relay] allow_extend_to entries accept
   "10.0.0.0/24" (subnet, any port), "10.0.0.0/24:443" (subnet + port),
   "[2001:db8::/32]:443" (IPv6 with port), as well as exact IP:port.
   Empty list keeps the v3.1 open-relay (warn).

19 new tests; workspace 276 passed (+19), clippy -D warnings clean, fmt clean.
257 baseline tests untouched; all v2 / v3.1 / LE configs work as before.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
xah30
2026-05-27 20:07:12 +03:00
parent f26ed7fce0
commit 9b98004424
13 changed files with 1768 additions and 298 deletions
+23 -6
View File
@@ -151,11 +151,28 @@ jitter = 0.5
# Omitting the whole [server.relay] section (or `enabled = false`) keeps the v2 behaviour intact.
# [server.relay]
# enabled = true
# Whitelist of allowed downstream exit addresses. ONLY literal IP:port entries; DNS resolution
# is NOT performed in v3.1 (unparsable entries are logged at WARN and skipped). An empty list
# turns this server into an OPEN relay accepting any downstream — dangerous; the runtime logs
# a WARN on each accepted bridge.
# Whitelist of allowed downstream destinations. v3.2 accepts three entry formats:
# * "IP:port" — exact literal SocketAddr (the v3.1 form).
# * "10.0.0.0/24" — bare CIDR; matches ANY port at any IP in the subnet.
# * "10.0.0.0/24:443" — CIDR with explicit port; matches that port on any IP in the subnet.
# * "[2001:db8::/32]:443" — square-bracket IPv6 CIDR with port.
# * "2001:db8::/32" — bare IPv6 CIDR (any port).
# Unparseable entries are logged at WARN and skipped. An empty list turns this server into an
# OPEN relay accepting any downstream — dangerous; the runtime logs a WARN on each accepted bridge.
# allow_extend_to = [
# "198.51.100.5:443", # the exit you operate
# "203.0.113.10:443",
# "198.51.100.5:443", # the exit you operate (exact)
# "203.0.113.0/24", # a whole /24 of trusted exits (any port)
# "10.0.0.0/16:443", # a /16 of relays on port 443 only
# ]
#
# v3.2 cell padding: opt-in. The relay itself does NOT decode cells — it just forwards bytes.
# These knobs are documented here for symmetry; the actual decode happens on the EXIT (see
# [server] cell_padding_for_circuit_clients below).
# cell_padding = false
# cell_size = 1280
# v3.2 EXIT-side cell padding. When an exit-server serves cell-padded circuit clients (i.e. the
# clients have `[client.circuit] cell_padding = true`), add the following field to the [server]
# block at the top of this file so the inner-handshake session's recv decodes the constant-size
# cells and the send re-pads on the way back. Defaults to `false` for v3.1 compatibility.
# cell_padding_for_circuit_clients = true