feat(signing): sign agent binaries with age key + XEdDSA (VA-62) #66
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/VA-62-age-xeddsa-signer"
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?
Implements the publisher half of the in-house age-key + XEdDSA agent-binary signing scheme (VA-61 decision, VS-36 epic), replacing minisign entirely. Companion verifier PR is in vervain-server (VS-41).
What changed
signing_keygen.rs: generates the keypair in-process with theagecrate (drops theminisign -G -Wshell-out), writes the secret0600, refuses to clobber, prints theage1...recipient.signing_sign.rs+ asignsubcommand: reads the secret fromVERVAIN_AGENT_SIGNING_SECRET_KEY(or--secret-key-file), bech32-decodes it to the X25519 scalar, XEdDSA-signs the input's full bytes (pure Rustxeddsa, no external tool), and writes the two-linemeshagent-<id>.sig(anuntrusted comment:line + base64 of the raw 64-byte signature)..forgejo/workflows/build-package.yml: drops the Alpine minisign container; the freshly builtvervain-agentbinary signs both published binaries via its ownsignsubcommand. Publishes.siginstead of.minisig; CI secret renamed toVERVAIN_AGENT_SIGNING_SECRET_KEY.agent-signing-architecture.md,agent-package-distribution.md,agent-signing-rotation-runbook.md) rewritten for the age scheme; ROADMAP entry added (VA-62).age,xeddsa,bech32,secrecy, andrand0.10 (aliasedrand010so the crate's existingrand0.8 is untouched).Why age + XEdDSA
ageis encryption-only (X25519, no signature primitive). To sign with an age key, XEdDSA (Signal's scheme) derives an Ed25519 signature from the X25519 key. This is bespoke (non-standard) crypto by design, per the VA-61 decision: it is isolated behind thesigning_signmodule and leans entirely on the maintainedxeddsacrate (no hand-rolled curve math).Interop guarantee
The two repos share no crate, so a fixed cross-repo known-answer vector (
age1...recipient + message + signature) is embedded as a unit test in BOTH this crate and vervain-server'smeshcentral-agent-fetch. Any drift in signature format, bech32 handling, or curve handling fails the test in both repos.Tests
cargo test -p vervain-agent --lib signingpasses (8 tests): keygen produces a parseable age keypair (secret0600, public half matches),signthen verify roundtrips, a tampered binary and a bad secret are rejected, and the shared known-answer vector verifies.cargo clippy -p vervain-agent --lib --bins -- -D warningsandcargo fmt --all --checkare clean.Multi-key rotation (a
publickeysset) stays out of scope; the server holds a singlepublickey, rotated by a live value swap (documented in the rotation runbook).