feat(agent): token-bootstrapped mTLS enrollment; remove .vagent + handshake (VA-67) #77
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/VA-67-token-enroll-mtls"
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?
Summary
Implements VA-67: the agent now bootstraps over the server's mTLS port with a one-time enrollment certificate, obtains a server-signed client certificate, and connects with it thereafter. The
agentcore.cchallenge-response handshake and the*.vagentseed are removed. The server half landed in VS-63 (vervain-serverPR #86, merged tomain).This is the security-first design ("Path C"): the agent's mTLS key is a fresh ECDSA P-256 key that is non-exportable and TPM-backed when a TPM is present (the TLS handshake and the CSR are signed inside the chip via a custom
rustls::sign::SigningKeyand anrcgenRemoteKeyPair), with a software keystore fallback. No legacy RSA handshake-signing key remains.What changed
node//<hex SHA-384 of the SPKI>, present the bootstrap chain[enroll_cert, enrollment_ca]over mTLS,POST /api/v1/enroll {csr_pem}, and persist the issued leaf + server CA + domain + group id (crates/vervain-agent/src/net.rs)./api/v1/agentpresenting the enrolled client cert, send a singleAuthInfoframe, then idle. No handshake, noAuthConfirm.PinnedServerVerifierchecks the chain to the pinned server CA from the enrollment bundle and captures the leaf for the OOB-update trust anchor. The server name is intentionally not enforced (identity is the private-CA pin; the agent-port leaf carries no SAN).CapturingNoopVerifieris gone.identity.rs+identity/tpm.rs): ECDSA P-256 software key or TPM-held ECC key; CSR + client-cert resolver for both backends;sign_handshakeand the RSA self-signed path removed. The TPM backend now mints an ECC NIST-P256 key and signs in-chip.bootstrap.rs): server domain + the four enrollment PEMs come fromVERVAIN_*env vars (inline or_FILE).group_iddefaults to all-zeros, overridable viaVERVAIN_GROUP_ID.parse_vagent_text/from_vagent_file/ServerSettings, themigratefeature, the seed import (settings.rs,config import,install --from <seed>), and the handshake wire structs (AuthRequest/AuthVerify/ServerIdHint+AgentCommand1/2/4/5) fromvervain-agent-protocol.install --from bootstrap.json(the server mint bundle + domain) writes the PEMs into the config dir and exportsVERVAIN_SERVER_DOMAIN+VERVAIN_*_FILE(plusVERVAIN_DB_KEY) in the unit. Mirrored on Windows viasetx /M.ProtectSystem=strict(read-only config dir) is respected.Acceptance criteria
POST /api/v1/enrollover mTLS.AuthInfoand idling (no handshake, noAuthConfirm).*.vagentfile is read, required, or importable;parse_vagent_textand the seed-import paths are removed.AuthRequest/AuthVerify/ServerId/ServerID-pin handshake andCapturingNoopVerifierare removed from the agent.Testing
just pre-commitgreen (fmt,clippy --all-targets -D warnings, build, lib tests, Windows cross-compile, agent-gui, and the--features tpmswtpm test that exercises ECC-P256 key create / in-TPM sign / verify / reload).Follow-up
just dev) under mTLS-only enrollment; the now-inertdev-insecureplain-WS path should be removed or repurposed.🤖 Generated with Claude Code