Files
tsunagi/docs/threat-model.md
T
Aleksandr Bogomiakov e701986a39 Add ephemeral pairwise Amnezia transport and runtime fallback
Plain WG and QUIC can be blocked or severely degraded while the iroh control
connection still works. Add an awg carrier with one independent UDP socket,
WireGuard engine and randomized masking profile per network/peer attempt.
The smaller endpoint id negotiates fresh outer keys, PSK, header protection,
header ranges, padding and signature templates through private authenticated
control messages. Restart or fallback reentry generates a new profile;
ordinary rekeying preserves the current profile. No pair parameters enter
signed state, capabilities, persistent storage or public message events.
Private payloads and serialized control buffers redact and zeroize themselves.

Adapt the bounded packet masking engine from RunaRoo/AmneziaWG-RS at commit
51579a7d6e8882d1d8d6d00822f09e18a9423b73, retaining its Apache license and
provenance. Declare WTFPL AND Apache-2.0 in package metadata to cover both
new glue and the adapted source. Reuse boringtun instead of upstream's Node/global executors and
interface machinery. This is Tsunagi AWG wire version 1, with standard WG
headers restored before authentication; external Amnezia server compatibility
is not implemented. DNS/SIP signature generators vary fields instead of
replaying captures. Each attempt closes independently, bounds allocation,
queues, candidates and fragment assemblies, and supports the full 64 KiB
carrier contract using UDP packets at most 1200 bytes. Yield between fragment
batches so large messages do not starve local socket readers.

Keep carrier encryption separate from end-to-end tunnels so multihop relays
still forward opaque ciphertext. The system owns the sole interface, signed
addresses and admission. Plugins receive neither network secrets nor paths.
Expose a bounded opaque control sender at the transport boundary; core control
messages remain outside protocol crates. Optional STUN on the pair's actual
socket adds IPv4 public candidates in CLI direct/relay mode, with a finite
query deadline and an off/alternate-server option. Local mode remains offline.
Symmetric NAT, IPv6-only paths and arbitrary UDP blocking can still require
fallback; no AWG-over-QUIC or hidden AWG relay is introduced.

Default carrier order is wg, awg, wg-quic, tcp-tls. Advance the fallback cycle
after setup failures and sustained runtime loss, even after earlier cooldowns
expire. Authenticated delivery probes traverse the actual encrypted tunnel,
fit the interface MTU and never enter the TUN. Retire after a 20-second warmup
and at least 60% loss over at least 15 completed recent observations. Preserve
logical tunnels and control sessions while explicitly closing physical links,
including TCP reader/writer tasks. Retain the newest private probe settings
across independently queued control negotiation and inbound installation.
CLI/tray reports keep end-to-end protocol and actual carrier separate.

Control ALPN/version becomes tsunagi/ctrl/6 for the new private message;
upgrade all members together. Data ALPN/envelopes remain version 5. Network
identity derivation, transcript encoding, device identities, signed records,
saved network configuration and release versions are unchanged.

Validation on Linux through nix develop:
- cargo fmt --all -- --check
- cargo clippy --locked --workspace --all-targets -- -D warnings
- cargo test --locked --workspace --all-targets, with the inherited
  TSUNAGI_CONTROL_SOCKET override removed: 504 passed, 2 pre-existing ignored

Tests use real iroh membership, SQLite, UDP sockets and WireGuard encryption
with memory TUNs. Cover three independent pairs, full-size IP and carrier
frames, restart freshness/socket release, 80% carrier datagram loss, fallback
to real iroh, fresh retries with working delivery checks, unequal small MTUs,
malformed negotiations/profiles, role and resource bounds, probe forgery and
STUN parsing. Earlier compile/lint iterations and focused tests caught RNG
API/initializer mistakes, a retry assertion racing path installation, and a
large-fragment burst overflowing loopback buffers; corrected before these
successful checks. Normalized upstream license line endings for diff checks.
Public NAT/STUN connectivity, actual DPI effectiveness, WAN performance,
per-pair memory measurement, macOS/Windows, privileged TUN operations and
manual tray interaction were not exercised.
2026-10-11 13:52:10 +01:00

7.5 KiB

Threat model and known limits

Read this before relying on anything here. The protocol is in protocol.md.

What is protected

  • Network membership. A peer must prove knowledge of auth_key, derived from the network name and shared secret, to get an authenticated session. Knowing the public NetworkId, or an agent's address, is not enough.
  • Endpoint authenticity. iroh's QUIC/TLS handshake authenticates the remote endpoint id, which is its public key. Identities in the membership transcript are taken from the certificate, never from a peer's claim.
  • Connection binding. The membership proof includes TLS exporter output, so a proof captured on one connection does not verify on another.
  • Role separation. Initiator and responder proofs cover different transcripts, so a proof cannot be reflected back at its sender.
  • Network isolation. A session authenticated for network A cannot carry messages for network B, even over a shared physical connection.
  • Confidentiality and integrity in transit. Provided by QUIC/TLS. This crate adds no encryption of its own.
  • Overlay address ownership. A peer's overlay address is derived from its public key, not taken from its announcement. Outbound packets go to the owner of the destination address; inbound packets are dropped unless their source is the address derived for the peer that sent them. A member can therefore neither receive nor forge another member's traffic. See wireguard.md.
  • Tunnelled traffic is end-to-end encrypted by WireGuard, independently of this crate. Carriers may be iroh, TLS, direct WG or pairwise AWG; confidentiality does not depend on a particular carrier.
  • Resource bounds. Frame lengths are validated before allocation; strings, lists, queues, concurrent dials and in-flight handshakes are all bounded; handshakes, dials and writes have timeouts.

What is not protected

  • Anyone who knows the secret is a full participant. They can create arbitrarily many identities, flood the network with records and collide with other participants' names. This is why a majority is not a root of trust. Signatures protect authorship; they do not make a participant honest.
  • Weak secrets. This targets high-entropy secrets. There is no PAKE, so a short human passphrase can be guessed offline by anyone who can reach the handshake. Use NetworkSecret::generate().
  • Public address publication. With TransportPolicy::N0Defaults or DirectOnly, iroh publishes a signed record of this endpoint's addresses, keyed by its endpoint id, to the public pkarr/DNS service run by Number 0 ("n0", the company behind iroh) at dns.iroh.link, and resolves peers through it. The network secret is never published and membership cannot be inferred from a single record, but the endpoint's existence and its addresses become public. LocalOnly publishes nothing.
  • Addresses and metadata are observable. Anyone able to watch the network sees addresses, timing and volume. Discovery backends see the discovery_key and the addresses published under it, which is enough to map a network's participants. This library does not make a network anonymous, and having iroh under it does not make it so.
  • A cloned state directory is a cloned identity. state.sqlite holds the device secret key and the network secrets. Copying it copies the participant. Restoring an old backup rolls the agent's state back, which — once signed records exist — can resurrect revoked information or replay stale versions.
  • A compromised host. The secret is on disk to survive restarts. File permissions are owner-only where the platform supports it, and the state directory takes an ownership lock, but neither defends against a user who can read the file or against malware running as that user.
  • User IP traffic. Carried by the WireGuard plugin over an iroh data connection, and encrypted by WireGuard end to end. Filtering it is still the operating system's and the user's job: the plugin creates connectivity between members and does not police what flows over it.
  • Traffic metadata reaches the relay when one is used. If iroh cannot hole punch, the data connection goes through a relay, which then sees the volume and timing of tunnelled traffic — though not its contents, which WireGuard encrypted, nor the iroh layer's contents.
  • Exit nodes. A member that offers one carries other members' internet traffic and sees where it goes; a member that uses one trusts it completely. Anyone holding the network secret can use an offered exit node. See exit-node.md.
  • Overlay address squatting. A member can mint many WireGuard keys and therefore occupy many overlay addresses. It cannot pick which ones, but it can consume them and appear as many participants.
  • Plugin keys on disk. The WireGuard private keys live in the plugin's own wireguard.sqlite, owner-only where the platform supports it. Copying that file copies this agent's overlay identity, exactly as copying state.sqlite copies its control plane identity.
  • What the data plane does not police. Address ownership stops a member impersonating another member. It does not stop a member sending whatever it likes from its own address.
  • Denial of service. Bounds and timeouts stop trivial resource exhaustion from a single peer. They do not make the agent resistant to a determined attacker who knows the secret, and no rate limiting per identity exists yet.
  • Global freshness. A signature proves authorship, not that you have the newest state. See sync-model.md.
  • Discovery is not trustworthy. It returns candidates. A hostile or stale discovery backend can waste dial attempts and learn addresses; it cannot forge membership.

Deliberate design consequences

  • No owner, no vote. Nobody can evict anybody. Removing a participant means changing the secret, which creates a different network space that the removed participant cannot enter.
  • Rotating the secret is not revocation of past access. Anyone who held the old secret keeps whatever they already saw.
  • Local deactivation is not revocation. Deactivating a network stops this agent participating. It says nothing about anyone else.
  • Failures are contained, not escalated. A bad proof, wrong secret, malformed frame or unknown version rejects one message or one session. It never stops another network and never stops the agent, and there is no irreversible global error flag.

Cryptographic choices

Standard primitives only, no home-made constructions: HKDF-SHA256 (RFC 5869) for key separation, HMAC-SHA256 for the membership proof, constant-time verification, iroh's Ed25519 endpoint keys and QUIC/TLS for the transport, and RFC 5705 TLS exporter output for channel binding. There is no custom encryption layer and no custom PAKE.

Pairwise AWG masking

AWG profiles, PSKs and outer keys are private to one authenticated pair attempt, not shared network state. Address candidates are unverified until authenticated outer WireGuard data arrives. Header protection and structured signature packets reduce fixed wire markers; they do not guarantee admission by DPI or hide traffic analysis. iroh control availability remains a prerequisite. Encrypted end-to-end delivery probes detect sustained carrier loss, never confer membership and pass the same signed source/destination admission as ordinary IP traffic. See amnezia.md for limits and tested scope.