Skip to content

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.

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.

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.

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.

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.

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.

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.

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.
  • Runbooks — the procedures that resolve these errors, including the incident table.
  • Configuration reference — the cluster settings behind policy_not_ready and the baked measurement policy.
  • Key release — the challenge → get-key flow these errors guard.