Skip to content

SPEC.md draft 2: WireGuard as the connection, pluggable carriers - #28

Open
kylemclaren wants to merge 2 commits into
mainfrom
spec-draft-2
Open

kylemclaren wants to merge 2 commits into
mainfrom
spec-draft-2

Conversation

@kylemclaren

Copy link
Copy Markdown
Contributor

Draft 2 of the protocol. Spec text only; the implementation still speaks draft 1 (protocol v0), and TestSpecExamples skips with a reason until the wire package follows.

What changes

  • The identity is the connection. A peer's Ed25519 key, converted to X25519 (RFC 7748 §4.1, the libsodium/age conversion), is its WireGuard static key. A connection is a WireGuard session (Noise IKpsk2, unmodified) between the two identity keys. auth and the hello nonce are gone: the tunnel already proved both keys. hello.key must match the tunnel's remote key. Worked key vectors are in §4.2, cross-checked both ways.
  • Carriers are pluggable and untrusted. A carrier moves WireGuard datagrams and reports source endpoints, nothing more. Defined kinds: udp (6PN, LAN, public addresses), tailcat (DERP bootstrap, NAT traversal, relay; still the default awp listen carrier and meant to stay the path of least resistance), ws (one datagram per binary WebSocket message; Cloudflare quick/named tunnels, ngrok, a Fly app, browsers), unix. There is no plaintext binding any more and checkPrivate-style heuristics have no place in the spec.
  • Addresses are awp1 + base64url(CBOR{key, psk, ep[]}). The PSK is the admission secret: without it a listener is silent, with it any key is admitted to the tunnel (Tailscale's wireguard-go fork already supports this via its lookup hook, which is how tailcat admits clients). hello.ep carries the sender's endpoints so either side can reconnect. Tunnels roam: an endpoint change is not a reconnect.
  • Draft 1 notes folded in (NOTES.md): aud on grants and attenuated delegation, cumulative acks, chunks before their msg, seen counts content lines only, strictly increasing ids, one connection per pair with the smaller-key rule, a refused code, too_large closes, encodings and canonical JSON spelled out, outbox retention, what "open thread" means.
  • Other languages (§18.2): SDKs use the reference peer for the tunnel, behind the daemon or through an awp tunnel stdio helper. Native tunnels and a WASM build are possible and conformant, not required.

Not in this PR

  • Any implementation. Suggested order, each step shippable alone: carrier interface in transport with udp/unix and the identity binding; tailcat behind the interface using the identity-derived node key; ws carrier and a cloudflared test; tunnel conformance scenarios and dropping auth; SDK driver contract from tailcat to awp tunnel; schema v1.
  • Docs site updates (the protocol reference page mirrors SPEC.md; will follow once the text settles).

Open questions are reset in §19 (native datagram framing, multi-party, capability chains, key rotation, blobs on a second stream, PSK in introductions, more carriers).

A peer's Ed25519 key, converted, is its WireGuard key, so the tunnel's
mutual authentication is the protocol's and `auth` goes away. Carriers
move WireGuard datagrams and are untrusted and interchangeable: udp,
tailcat (the default), ws through any HTTP tunnel, unix. Addresses are
awp1 blobs of key, pre-shared key and endpoints; hello carries `ep`.
Draft 1's implementation notes are folded in. Not compatible with
draft 1; the implementation still speaks v0, so the spec example test
skips until the wire package follows.
- Pre-shared keys are per pair: the key two peers first connected with
  becomes theirs and both use it from then on, whoever dials; rotating
  the listener's key locks out only keys not met yet (5.2).
- The tunnel IPv6 derives from the X25519 key, since a responder only
  learns that from a handshake; the Ed25519 sign-bit ambiguity is noted.
- hello.addr is the sender's awp1 address without its pre-shared key,
  and introduce.peer.address an awp1 address that carries the key when
  the introducer holds it (open question 6 resolved).
- Connection carriers frame datagrams with a 16-bit length prefix; the
  tailcat carrier's value is a tailcat address, and tailcat is an outer
  carrier trusted for nothing rather than the identity's own node.
- The awp tunnel helper is described as built: one process per SDK peer,
  a control socket for dials, forwarding for accepted streams, and the
  hello-key check done before the SDK sees a line.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant