Error Handling
Every mero-kms error path returns a consistent JSON body and maps to a
stable HTTP status and machine-readable tag. This page catalogs the actual
ServiceError variants (mero-kms/src/handlers/errors.rs), what triggers each,
and what an operator should do.
Response shape
Section titled “Response shape”All error responses share one body (ErrorResponse, camelCase, details
omitted when absent):
{ "error": "measurement_policy_rejected", "details": "RTMR3 '...' is not in allowlist"}The error tag is stable and safe for programmatic matching; details is a
human-readable string and may be absent.
ServiceError catalog
Section titled “ServiceError catalog”| HTTP | error tag |
Cause | Operator action |
|---|---|---|---|
400 |
invalid_request |
Base64 decode failed on a request field. | Fix the client payload encoding. |
400 |
invalid_peer_id |
peerId empty, over 128 chars, or non-base58btc. |
Send a valid libp2p base58 peer ID. |
400 |
invalid_attestation_request |
/attest nonceB64/bindingB64 malformed or not exactly 32 bytes. |
Send a base64-encoded 32-byte nonce/binding. |
400 |
invalid_peer_public_key |
peerPublicKeyB64 not valid base64 or not a decodable libp2p protobuf key. |
Send the correct protobuf-encoded public key. |
401 |
invalid_challenge |
Challenge ID malformed, not found, already redeemed, expired, MAC’d under another cluster’s root, or its peer ID does not match the caller. | Request a fresh challenge and retry within the TTL. |
401 |
invalid_signature |
Signature failed verification against the peer’s public key. | Re-sign the canonical challengeId + nonce + quoteHash + peerId payload with the node key. |
401 |
attestation_verification_failed |
TDX quote cryptographic verification failed (bad quote / nonce not bound). | Regenerate the quote with the issued nonce in report_data. |
401 |
peer_identity_mismatch |
The supplied public key does not derive to the claimed peerId. |
Send the public key that matches the peer ID. |
401 |
peer_id_mismatch |
The peer-ID hash bound into the attested quote does not match the claimed peerId. |
Bind the correct peer-ID hash into the quote’s report_data[32..64]. |
403 |
tcb_status_rejected |
The quote’s TCB status is not in the policy allowlist. | Update the platform TCB, or allow the status in the policy if acceptable. |
403 |
measurement_policy_rejected |
An MRTD/RTMR0–3 register is not in the policy allowlist (message names the register). | Ensure the node runs an image whose measurements the policy covers; align profiles / re-probe. |
503 |
policy_not_ready |
This replica does not hold its cluster’s root yet (it has not generated or joined it). | Check /health clusterRootReady and that kms-peers names live replicas of the same release. A replica that cannot join is replaced by a new VM, never restarted. |
500 |
key_derivation_failed |
An internal failure deriving or sealing a key from the root (a poisoned lock, a failed HKDF expansion or seal). | Should not happen; capture the logs and replace the replica. |
401 |
mock_attestation_rejected |
A mock quote was submitted. Only exists in mock-attestation dev builds; unreachable in production (a mock quote is just an unparseable quote and fails ordinary verification). |
N/A in production. |
Failure modes by endpoint
Section titled “Failure modes by endpoint”POST /challenge
Section titled “POST /challenge”Issues a nonce challenge. Validates the peer ID shape (400 invalid_peer_id).
Challenges are MAC’d with a key derived from the cluster root, so a replica that
does not hold the root yet answers 503 policy_not_ready here too. It keeps no
state, so it has no capacity limit. A clock error surfaces as
invalid_challenge.
POST /get-key
Section titled “POST /get-key”The gated path. In order: peer-ID and challenge-ID shape (400/401), root
readiness (503 policy_not_ready while the replica has no root), challenge
redemption (401 invalid_challenge), signature verification (401), TDX
attestation verification (401), measurement/TCB policy enforcement (403), and
finally key derivation from the cluster root (500 key_derivation_failed only on
an internal failure). The challenge is redeemed before the signature/attestation
checks, so a replay always fails the second time, whichever replica it reaches.
POST /attest
Section titled “POST /attest”KMS self-attestation. Validates the 32-byte nonce/binding (400 invalid_attestation_request) and takes a quote from configfs-tsm; a quote
failure is reported as attestation_verification_failed. This endpoint does not
depend on the root.
With eventLog: true it also returns eventLogB64, the TD’s CCEL event log:
every measurement the firmware and boot chain extended into RTMR0-3. It is
public, and it is how to tell which event makes two replicas’ registers differ
(scripts/attestation/shared/tdx_event_log.py diff). A TD without a readable
log answers attestation_verification_failed.
GET /health
Section titled “GET /health”Always returns 200 with {"status":"alive","service":"mero-kms","clusterRootReady":<bool>}.
While a joining replica does not hold the root yet, it also carries
lastJoinError: the peer and the error of its last failed join (for example a
refused TCB status, or peer measurements that differ). A locked image has no
console, so this is where to read why a replica has not joined. The replica
that was asked carries the other half, lastJoinRefusal: why it last refused to
give the root (such as peer measurements differ from this replica's: ...).
Neither carries a secret.
Attestation-verifier responses
Section titled “Attestation-verifier responses”The separate attestation-verifier service (attestation-verifier/api/verify.js)
brokers verification of a pasted KMS quote or a fetched node quote through Intel Trust Authority. Its
responses are independent of the KMS ServiceError set:
| HTTP | Meaning |
|---|---|
200 |
Verification result (ita_token_verified, nonce checks). |
204 |
CORS preflight. |
400 |
Invalid request body, validation error, or missing quote_b64. |
405 |
Method not allowed (non-POST). |
502 |
The node’s /admin-api/tee/attest failed, or ITA verification failed. |
503 |
ITA_API_KEY not configured. |
Next steps
Section titled “Next steps”- Runbooks — the procedures that resolve these errors, including the incident table.
- Configuration reference — the cluster settings behind
policy_not_readyand the baked measurement policy. - Key release — the
challenge → get-keyflow these errors guard.