Getting Started
This page gets you from a fresh checkout to a running local KMS and the
attestation-verifier web app, then walks one challenge → get-key cycle
against your local instance. It is the fastest way to see the moving parts
before you read the flows in depth.
The production KMS only releases keys to nodes running inside a real Intel TDX confidential VM, with quotes signed by the hardware and keys derived from a cluster root that exists only inside TDX replicas. None of that exists on a laptop, so the KMS ships a dev-only mock path (a Cargo feature, off by default) that lets you run a single replica and exercise the HTTP surface and most of the verification pipeline without TDX hardware.
Prerequisites
Section titled “Prerequisites”- Rust 1.88+ (
rustup,cargo) — the KMS is a Cargo workspace. - Node.js 18+ and npm — for the attestation-verifier web app.
curland (optionally)jq— to drive the endpoints by hand.
Build the KMS
Section titled “Build the KMS”The workspace has a single member, mero-kms. From the repository root:
# Debug build of the whole workspacecargo build
# Or an optimized buildcargo build --releaseThis produces the production binary — it contains no mock-attestation code. To run locally you want the dev build described next.
Run the KMS in dev mode
Section titled “Run the KMS in dev mode”Two independent things stand between a fresh start and a working /get-key
locally, and you have to satisfy both:
- Mock attestation — accept mock TDX quotes instead of requiring real
hardware-signed ones. This is gated behind the default-off
mock-attestationCargo feature, and only then is theACCEPT_MOCK_ATTESTATIONenv var read. - A cluster root —
/challengeand/get-keyare fail-closed. Until the replica holds a root it returns503 policy_not_ready. Locally, make your single replica the bootstrap one withMERO_KMS_BOOTSTRAP=true, so it generates a root at startup instead of trying to join peers.
Build with the feature and set both knobs:
cd mero-kmsACCEPT_MOCK_ATTESTATION=true \MERO_KMS_BOOTSTRAP=true \ cargo run --features mock-attestationIn mock mode the replica’s own quote is a mock too, so the debug-TD and RTMR3 startup checks that a real replica enforces are skipped.
On startup the service logs its resolved configuration. Expect to see the mock
warning, the bootstrap flag, the locked-read-only profile, and the new root:
INFO Starting mero-kmsINFO Listen address: 0.0.0.0:8080INFO Cluster: bootstrap=true peers=[]INFO KMS profile cohort: locked-read-onlyWARN WARNING: Mock attestation acceptance is enabled. This should NEVER be used in production!INFO Generated a new cluster root; this replica bootstraps the clusterINFO Server listening on 0.0.0.0:8080Change the listen address (optional)
Section titled “Change the listen address (optional)”The HTTP listener defaults to 0.0.0.0:8080. Override with LISTEN_ADDR if
8080 is taken:
LISTEN_ADDR=127.0.0.1:9090 \ACCEPT_MOCK_ATTESTATION=true \MERO_KMS_BOOTSTRAP=true \ cargo run --features mock-attestationConfirm it is up
Section titled “Confirm it is up”/health is a liveness probe — it answers 200 whether or not the replica
holds a root, and says which:
curl -s http://localhost:8080/health# {"status":"alive","service":"mero-kms","clusterRootReady":true}Walk one challenge → get-key cycle
Section titled “Walk one challenge → get-key cycle”Key release is a two-round challenge-response. Round 1 (/challenge) is fully
self-contained and works locally; round 2 (/get-key) runs the whole
verification pipeline and is where the local/hardware boundary shows up. Both
routes are documented in full in Key Release.
Round 1 — get a challenge
Section titled “Round 1 — get a challenge”Send any base58btc peer ID. The KMS returns a stateless challenge ID (112 hex characters: 16 random bytes, the expiry and a tag) plus the base64 nonce, an HMAC of the ID and the peer ID under a root-derived key, and its expiry:
curl -s -X POST http://localhost:8080/challenge \ -H 'content-type: application/json' \ -d '{"peerId":"12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp"}'{ "challengeId": "<112 hex chars>", "nonceB64": "aGVsbG8td29ybGQtZXhhbXBsZS0zMi1ieXRlcy1ub25jZQ==", "expiresAt": 1753104000}The challenge is valid for CHALLENGE_TTL_SECS (default 60) and a replica
redeems it at most once.
Round 2 — redeem it at /get-key
Section titled “Round 2 — redeem it at /get-key”/get-key takes the challenge ID plus a TDX quote, the peer’s public key, and a
signature binding all of it to the peer identity:
curl -s -X POST http://localhost:8080/get-key \ -H 'content-type: application/json' \ -d '{ "challengeId": "<112 hex chars from round 1>", "quoteB64": "<base64 raw TDX quote with the nonce in report_data>", "peerId": "12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp", "peerPublicKeyB64": "<base64 protobuf libp2p public key>", "signatureB64": "<base64 signature over the canonical payload>", "sealToB64": "<base64 one-time X25519 public key>" }'The KMS then, in order: validates the shapes, redeems the challenge
(so a replay to the same replica always fails on the second try), verifies the
peer signature and public-key ownership, verifies the quote (mock quotes are
accepted here in dev), enforces the measurement policy (skipped for accepted
mocks), and finally derives the key by HKDF from the root and seals it to
sealToB64. On success it returns:
{ "sealedKeyB64": "<sealed storage key>", "sealNonceB64": "<12-byte nonce>" }You can still observe the fail-closed behavior directly. Send a /get-key
request with placeholder credentials and watch the gates reject it, and watch
the challenge get redeemed even on failure:
# Second identical request returns invalid_challenge — the first attempt# already redeemed the single-use challenge, regardless of why it failed.Every error is a machine-readable tag with an HTTP status; the full catalog is in Error handling.
Run the attestation verifier
Section titled “Run the attestation verifier”The verifier is a separate React/Vite app with a serverless /api/verify
backend. Run it locally:
cd attestation-verifiernpm installnpm run devVite serves it at http://localhost:5173. The two routes are /kms (verify a
pasted mero-kms /attest response; KMS replicas listen only inside their VPC)
and /mero-tee (verify a merod node by URL); the landing page redirects to
/kms.
Next steps
Section titled “Next steps”- Verify a release — check release assets and use the public verifier UI.
- Attestation flow — where quotes come from and the
64-byte
report_datalayout. - Key release — the full
challenge → get-keyprotocol and every field it checks. - Configuration reference — every environment variable and default.
- Calimero Core: TEE Attestation — the protocol-level view of the same primitive.