Key Release Flow
Challenge-response protocol for storage encryption key release
Protocol Overview
The key release protocol is a two-phase challenge-response flow. In Phase 1, the node obtains a fresh nonce from the KMS. In Phase 2, the node presents its TDX attestation quote with the nonce to prove its identity and integrity. Only after passing all checks does the KMS derive and release the storage encryption key.
Sequence Diagram
Phase 1 — Challenge
Request
Content-Type: application/json
{
"peerId": "12D3KooW..." // libp2p peer ID of the requesting node
}
Response
Content-Type: application/json
{
"challengeId": "uuid-v4", // opaque challenge identifier
"nonce": "hex-encoded-32-bytes" // cryptographically random nonce
}
Server-Side Logic
Nonce Generation
32 bytes from a CSPRNG. The nonce must be unpredictable to prevent pre-computation of TDX quotes.
Challenge Storage
The ChallengeStore maps challengeId → (nonce, peerId, expiry). InMemory uses a DashMap; Redis uses SETEX for automatic TTL cleanup. Max pending challenges are enforced per-peer.
Phase 2 — Key Release
Request
Content-Type: application/json
{
"challengeId": "uuid-v4",
"quote": "base64-encoded-tdx-quote",
"signature": "base64-encoded-ed25519-sig"
}
Verification Pipeline
Consume Challenge
Look up challengeId in the store and remove it atomically (one-time use). If not found or expired, return 400 InvalidChallenge.
Verify Signature
Decode the peerId to extract the Ed25519 public key. Verify signature over the original nonce. If invalid, return 401 InvalidSignature.
Parse TDX Quote
Decode the base64 quote. Extract MRTD, RTMR0–3, report_data, and TCB status fields from the TDX quote structure.
Verify Nonce Binding
Check that the quote’s report_data field contains the expected hash binding to the original nonce. This ensures the quote was freshly generated for this challenge.
Policy Evaluation
Compare extracted measurements against the attestation policy allowlists:
- MRTD ∈ allowed_mrtd
- RTMR0 ∈ allowed_rtmr0
- RTMR1 ∈ allowed_rtmr1
- RTMR2 ∈ allowed_rtmr2
- RTMR3 ∈ allowed_rtmr3 (profile check)
- TCB status ∈ allowed_tcb_status
Any mismatch returns 403 PolicyViolation with details about which field failed.
Key Derivation
Call dstack via the Unix socket at DSTACK_SOCKET_PATH. The derivation uses KEY_NAMESPACE_PREFIX + peerId as input. dstack’s HKDF produces a deterministic 32-byte key — the same inputs always yield the same key.
Response
Content-Type: application/json
{
"key": "base64-encoded-32-byte-key" // storage encryption key
}
Security Properties
One-Time Use
Each challenge can only be consumed once. Replaying a /get-key request with the same challengeId returns 400 InvalidChallenge.
Deterministic Keys
dstack derives keys deterministically from the namespace + peerId. The node always gets the same key regardless of which KMS instance it contacts (as long as the KMS runs the same dstack root).
TTL Expiry
Challenges expire after CHALLENGE_TTL_SECS (default: 300s). Slow or stale requests are automatically rejected.
Rate Limiting
MAX_PENDING_CHALLENGES limits how many active challenges a single peerId can have. Excess requests return 429 RateLimited.