Peering handshake: Ed25519 challenge-response + capability tokens (#102, part 1) #121
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/peering-handshake"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
First slice of #102 — the security-critical pieces, built test-first. No gRPC yet, so
go.modis unchanged and #118's tripwire stays unpulled; the proto, service and data plane follow in part 2.A design defect in the decided ADR, found by writing the test
IMPLEMENTED-PEERING.md§3.1 specified the proof asHMAC-SHA256(peerKey, …)while §4.1 required the receiver to store only a verifier. Those cannot both hold: verifying an HMAC needs the shared secret, so the receiver would have had to keep peer keys in the clear — destroying the exact property the directed-pair model exists for ("compromising this instance yields verifiers, not credentials").Corrected: the issued key is an Ed25519 private key. The peer holds and presents it; we store only the public half plus a lookup id; the proof is a signature over the challenge. Compromising a receiver now yields public keys — useless anywhere. Same UX (one opaque string exchanged out of band), no new dependency (
crypto/ed25519is stdlib). The reasoning is recorded at the top ofauth.goso the doc and the code don't diverge silently.This changes #101's stored format (
Hash→ID+PublicKey). Nothing is deployed, so there is no migration.What's here
KeyIDOf— both sides derive the same lookup id from the key (SHA-256 prefix over the public half), so the receiver selects the right key without trial verification.NewChallenge/Prove/VerifyProof— 32-byte single-use challenges with a 2-minute TTL and opportunistic sweep. The challenge is the anti-replay state, so no separate nonce cache. Signatures are bound to the key id, so a proof cannot be replayed under another identity. An unknown key id still gets a well-formed challenge:Hellomust not be an oracle for which peers exist. Challenges are spent whether or not the proof verifies, so a captured challenge can't be brute-forced.MintCapability/VerifyCapability: HMAC over transfer id, accepted size and expiry, keyed by a lazily-minted per-instance secret. Single-use, bound to their transfer, expiring, and unverifiable against another instance.Tests (13 new, watched fail first)
Stored state contains no secret · id derivable by the sender and matching the stored one · malformed key rejected · challenge round-trip · challenge single-use · proof signed by another key rejected · proof bound to another challenge rejected · revoked key cannot authenticate · unknown key id still gets a challenge · capability round-trip · capability single-use · capability bound to its transfer · capability expires · capability minted elsewhere rejected.
go vetclean,go buildclean, full repo suite green.Next (part 2): proto + committed codegen, the four RPCs, the data-plane PUT, wiring, and #118's doc corrections in that same PR.