feat(issuer): implement the dstack-kms key provider (+ deploy/verify runbook)#25
Merged
Conversation
…gent client)
Turn the reserved/fails-closed `TESSERA_KEY_PROVIDER=dstack-kms` seam into a
working client that derives the ARC server key from the dstack guest agent inside
an Intel TDX CVM, sealed to the enclave (never on disk).
- `dstack_kms.rs` (NEW): a std-only HTTP/1.1+JSON client over `UnixStream`
(mirrors the existing `mint::eth_call` reader) — POST `/GetKey` to the
guest-agent socket, hex-decode the derived secret, and expand it into the ARC
`ServerPrivateKey` by SEEDING the canonical `SetupServer()` keygen via a SHAKE256
XOF DRBG (the raw bytes are never used as a curve scalar). Deterministic in the
app identity + key path, so an issuer and its exit in the same app converge on
one key with no shared key file. ZERO new deps (reuses sha3/hex/rand_core/zeroize).
- Probes the dstack SDK socket fallbacks; bounds the response read; fail-closed on
any transport/status/parse/length error (a wrong KVAC key both forges AND
verifies, so never fall back to ephemeral/disk).
- Wire into `KeyProviderConfig::{preflight,establish}`; drop the reserved-error
const. preflight now validates the socket is reachable (fail-closed off-TEE).
- Tests: 11 unit tests against a faithful in-process mock guest agent (determinism,
issuer/exit convergence, and fail-closed on absent socket / non-200 / short
secret / missing field) + the two `--check` integration tests updated.
Research-grade, UNAUDITED: validated only against a mock + the dstack simulator,
NOT yet proven on real TDX silicon. Docs updated separately in this PR.
…ilicon-proven) The dstack-kms provider went from reserved/fails-closed-stub to an implemented guest-agent client, so update every site that claimed otherwise — consistently and without overclaiming (it's tested vs a mock + the dstack simulator, NOT real TDX): DEPLOY.md §2 (+ a new deploy/verify runbook: where to run — Phala/GCP/Azure TDX — the simulator for off-TEE testing, and the client/auditor quote-verification steps), KEY_MANAGEMENT.md §4.2, DEPLOYMENT_TOPOLOGY.md §3, STATUS.md row 13, TRUST_MODEL.md §4.1/§4.2/§7, NEXT_STEPS.md (track + guardrail), CLEAN_ONION_EGRESS.md, the dstack compose comment (+ wire the exit to dstack-kms), and a CHANGELOG entry.
Security hardening (defense-in-depth on the catastrophic-if-broken key path): - match the HTTP status code token exactly (a loose " 200" substring could be satisfied by a non-200 reason phrase); - reject an all-zero derived secret (a predictable seed → predictable ARC key, which under keyed verification both forges AND verifies); - reject chunked transfer-encoding loudly instead of silently mis-framing; - strip a `0x`/`0X` key prefix case-insensitively. Tests (now 21 in dstack_kms): + non-hex key, + 0x-prefix equivalence, + all-zero rejection, + key_id JSON-escaping proven through the LIVE request path (mock now captures the request body), + missing-separator / empty-body / no-Content-Length framing, + chunked rejection, + socket_candidates fallback gating, and a **KAT** that freezes the seed→key derivation so an accidental change to the domain separator / SHAKE order / endianness can't silently break issuer↔exit convergence across separately-built binaries. Docs: fix every remaining "reserved/not-wired/fails-closed-until" contradiction the review found (KEY_MANAGEMENT §4.2 heading, STATUS reconciliations, DEPLOYMENT_TOPOLOGY §7, TRUST_MODEL §8, DEPLOY env table, CLEAN_ONION_EGRESS, ONION_EGRESS, the issuer binary's doc header, CHANGELOG) → consistently "implemented; fail-closed off-TEE; mock/simulator-proven, not silicon-proven." Protocol-faithfulness lens verified the client matches real dstack source (JSON via Content-Type alone, field names/hex, path=key_id determinism) — ship-as-is there.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Turns the reserved/fails-closed
TESSERA_KEY_PROVIDER=dstack-kmsseam into aworking client that derives the ARC server key from the dstack guest agent
inside an Intel TDX CVM, sealed to the enclave (never on disk) — plus the
deploy/verify runbook for actually standing it up.
The client (
crates/tessera-issuer/src/dstack_kms.rs)UnixStream(mirrors the existingmint::eth_callreader) —POST /GetKeyto/var/run/dstack.sock, hex-decodethe derived secret. Zero new deps; no SDK/async/protobuf (the "pRPC" socket
speaks JSON via
Content-Type: application/json, verified against real dstacksource).
ServerPrivateKeyby seeding thecanonical
SetupServer()keygen via a SHAKE256 XOF DRBG — the raw bytes arenever used as a curve scalar. Deterministic in app-identity + key path, so an
issuer and its exit in the same dstack app converge on one key with no shared
key file.
preflightrejects an unreachable socket). Probes the dstack socket fallbacks; bounds the
read. The dstack compose now wires the exit to this provider.
Adversarial review applied (4 lenses)
The protocol-faithfulness lens verified the client matches real dstack source
(
ship-as-is). The security lens's hardening is applied: exact HTTP status-codematch, reject an all-zero secret (a predictable seed → predictable KVAC key
that both forges and verifies), reject chunked encoding, case-insensitive
0x. Tests went 11 → 21, adding: non-hex key,0x-prefix equivalence,all-zero rejection, key_id JSON-escaping proven through the live request path
(the mock now captures the body), HTTP-framing edge cases (missing separator /
empty body / no Content-Length / chunked), socket-fallback gating, and a KAT
that freezes the seed→key derivation so an accidental change can't silently break
issuer↔exit convergence across separately-built binaries.
Runbook (
DEPLOY.md§2 "Verifying a deployment")Where to run (Phala ~$40/mo, or GCP/Azure TDX), the dstack simulator for
off-TEE testing (labeled wire-only, not security), and the client/auditor
quote-verification steps (dcap-qvl, MRTD/RTMR,
compose_hash, pin digests).Honest scope (unchanged posture)
Validated only against a mock + the dstack simulator — NOT real Intel TDX
hardware + a live KMS, so a simulator/mock key carries no security guarantee; a
client-side quote-check before routing is still not built. Every touched doc
(
DEPLOY/KEY_MANAGEMENT/DEPLOYMENT_TOPOLOGY/TRUST_MODEL/STATUS/NEXT_STEPS/CLEAN_ONION_EGRESS/ONION_EGRESS/CHANGELOG/ the compose)now says implemented · fail-closed off-TEE · mock/simulator-proven, not
silicon-proven — no residual "reserved" contradiction.
Research-grade · UNAUDITED.