feat(agent-fetch): verify agent binaries with age key + XEdDSA (VS-41) #62

Merged
nrupard merged 1 commit from feat/VS-41-age-xeddsa-verifier into main 2026-06-19 16:20:45 +02:00
Owner

Implements the verifier half of the in-house age-key + XEdDSA agent-binary signing scheme (VA-61 decision, VS-36 epic), replacing the minisign-verify crate. Companion publisher PR is in vervain-agent (VA-62).

What changed

  • meshcentral-agent-fetch/src/verify.rs: MinisignVerifier becomes AgeXeddsaVerifier. It validates the operator's age1... recipient through the age crate, recovers the raw 32-byte X25519 public key via bech32 (age exposes no byte accessor), and verifies a detached XEdDSA signature with the pure-Rust xeddsa crate (Montgomery-to-Edwards conversion and sign-bit handling are internal to the crate). The method surface (from_pubkey_str, verify) and the VerifyError variants are unchanged, so call sites only see the rename.
  • Signature file: meshagent-<id>.minisig becomes a two-line meshagent-<id>.sig (an untrusted comment: line plus base64 of the raw 64-byte signature). Verification is over the full binary bytes. Fail-closed is preserved: a binary that does not verify is never written to disk.
  • publickey config now holds the age1... recipient. Boot (boot.rs), the VS-37 runtime overlay (settings_overlay.rs), the env seed (agent_fetcher_seed.rs), and .env.example are updated.
  • Dependencies: drop minisign-verify; add age, xeddsa, bech32, base64.

Interop guarantee

The two repos have no shared 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-agent. Any drift in signature format, bech32 handling, or curve handling fails the test in both repos.

Tests

cargo test -p meshcentral-agent-fetch passes (19 tests), including the roundtrip (age keypair signs and verifies), tampered-binary rejection, wrong-key rejection, and the shared known-answer vector. cargo check -p meshcentral -p meshcentral-web --tests is clean.

This is bespoke (non-standard) cryptography by design, per the VA-61 decision: it is isolated behind the verifier module and leans entirely on the xeddsa crate (no hand-rolled curve math). Multi-key rotation (a publickeys set) stays out of scope; the config remains a single publickey.

Implements the verifier half of the in-house age-key + XEdDSA agent-binary signing scheme (VA-61 decision, VS-36 epic), replacing the `minisign-verify` crate. Companion publisher PR is in vervain-agent (VA-62). ## What changed - `meshcentral-agent-fetch/src/verify.rs`: `MinisignVerifier` becomes `AgeXeddsaVerifier`. It validates the operator's `age1...` recipient through the `age` crate, recovers the raw 32-byte X25519 public key via `bech32` (age exposes no byte accessor), and verifies a detached XEdDSA signature with the pure-Rust `xeddsa` crate (Montgomery-to-Edwards conversion and sign-bit handling are internal to the crate). The method surface (`from_pubkey_str`, `verify`) and the `VerifyError` variants are unchanged, so call sites only see the rename. - Signature file: `meshagent-<id>.minisig` becomes a two-line `meshagent-<id>.sig` (an `untrusted comment:` line plus base64 of the raw 64-byte signature). Verification is over the full binary bytes. Fail-closed is preserved: a binary that does not verify is never written to disk. - `publickey` config now holds the `age1...` recipient. Boot (`boot.rs`), the VS-37 runtime overlay (`settings_overlay.rs`), the env seed (`agent_fetcher_seed.rs`), and `.env.example` are updated. - Dependencies: drop `minisign-verify`; add `age`, `xeddsa`, `bech32`, `base64`. ## Interop guarantee The two repos have no shared 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-agent. Any drift in signature format, bech32 handling, or curve handling fails the test in both repos. ## Tests `cargo test -p meshcentral-agent-fetch` passes (19 tests), including the roundtrip (age keypair signs and verifies), tampered-binary rejection, wrong-key rejection, and the shared known-answer vector. `cargo check -p meshcentral -p meshcentral-web --tests` is clean. This is bespoke (non-standard) cryptography by design, per the VA-61 decision: it is isolated behind the verifier module and leans entirely on the `xeddsa` crate (no hand-rolled curve math). Multi-key rotation (a `publickeys` set) stays out of scope; the config remains a single `publickey`.
feat(agent-fetch): verify agent binaries with age key + XEdDSA
All checks were successful
Create release / Create release from merged PR (pull_request) Has been skipped
Check / fmt + clippy + build + tests (pull_request) Successful in 2m12s
45f53db513
Replace the minisign-verify-based MinisignVerifier with AgeXeddsaVerifier: the server now verifies each fetched meshagent-<id> against an operator-supplied age recipient (age1...) using an XEdDSA signature (Signal's scheme, via the pure-Rust xeddsa crate), recovering the raw X25519 key bytes with bech32 since age exposes none. The detached signature moves from meshagent-<id>.minisig to a two-line meshagent-<id>.sig (an untrusted comment line plus base64 of the raw 64-byte signature). Fail-closed behavior is unchanged: a binary that does not verify is never written to disk.

The publickey config value now holds the age1... recipient instead of a minisign RW... key; the boot/runtime call sites, env seed, and .env.example examples are updated to match. A shared cross-repo known-answer vector (identical to the one embedded in vervain-agent under VA-62) guards signature-format interop so any drift fails CI in both repos.

Drops the minisign-verify dependency; adds age, xeddsa, bech32, and base64.

#VS-41

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nrupard deleted branch feat/VS-41-age-xeddsa-verifier 2026-06-19 16:20:45 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/vervain-server!62
No description provided.