Skip to content

fix: an app's certificate authenticates as the KMS to any guest that pins the root CA - #1255

Merged
kvinwang merged 5 commits into
nextfrom
fix/kms-signcert-scope
Sep 24, 2026
Merged

kvinwang merged 5 commits into
nextfrom
fix/kms-signcert-scope

Conversation

@kvinwang

@kvinwang kvinwang commented Sep 20, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

The KMS signs a per-app CA under the same root every guest pins, and SignCert copies an app's requested SANs and serverAuth usage into the leaf. cert-client pinned that root with no cert_validator, so it checked only chain + hostname: any app authorized for key release could mint a certificate for the KMS hostname, and a network attacker holding it would be accepted as the KMS for SignCert/GetMeta. (GetAppKey in dstack-util was not affected — it already checks the usage.)

Two related issues in the same path:

  • device_id was always sha256("") in KMS-issued certificates: sign_csr decoded app info from the requester's unverified Attestation<()>, which has no device id.
  • os_image_hash in the certificate came from the config embedded in the CSR, not the vm_config the KMS authorized. Nothing binds that embedded config to the quote on TDX, so a requester could make the two disagree.

Fix

  • cert-client installs validate_kms_rpc_cert (requires special_usage == "kms:rpc"), now shared with dstack-util instead of duplicated. Apps can't choose this value: the KMS writes kms:rpc only on its own RPC cert and app:custom on everything it signs for apps. The KMS quote is not re-verified per call: the root pin plus kms:rpc already identify the KMS.
  • CaCert::sign_csr takes app_info: Option<&AppInfo> (the identity the signer verified) instead of app_id, and stamps both app extensions from it. It refuses a CSR asking for ext_app_info when none is supplied.
  • The KMS passes attestation.decode_app_info_ex(false, &request.vm_config) — the same verified attestation and vm_config it authorized. Runtime measurements are unchanged; for honest callers only device_id (now real) and os_image_hash differ.

Name constraints / SAN allowlists / EKU restrictions were deliberately not used: apps are meant to get serverAuth certificates for their own domains.

Tests

  • cert-client: serves a real app leaf (root → derive_app_ca → sign_csr) and a real kms:rpc leaf over TLS, and drives the production client config against both: the app cert is refused, the KMS cert is accepted.
  • ra-tls: sign_csr refuses to embed app info the signer did not supply.
  • cargo test -p cert-client -p ra-tls -p dstack-kms passes; cargo check -p dstack-util -p dstack-guest-agent --all-targets is clean.

- Remove the doc comment left dangling on AppIdValidator after moving
  validate_kms_rpc_cert into cert-client.
- Lowercase the server cert usage error message.
- Shorten history-narrating comments.
- Replace ra-tls tests that only asserted sign_csr copies its input with a
  single fail-closed test; condense the cert-client TLS tests.
The root pin plus the kms:rpc usage already identify the KMS. Verifying
its quote on every SignCert added a collateral fetch per GetTlsKey and
made that path depend on collateral availability.

Also let the test server bind port 0 and report it on liftoff instead of
racing a released port, and stop shadowing app_info in sign_csr.
@kvinwang
kvinwang enabled auto-merge September 24, 2026 04:09
@kvinwang
kvinwang merged commit 3cc27d8 into next Sep 24, 2026
13 checks passed
@kvinwang
kvinwang deleted the fix/kms-signcert-scope branch September 24, 2026 04:12
kvinwang added a commit that referenced this pull request Sep 25, 2026
#1255 made SignCert stamp the verified app info instead of the requester's
claims. tc-kms-keys-certs-004 decodes the leaf and requires app:custom, a real
device_id rather than sha256(""), and the authorized vm_config os_image_hash.

Signed-off-by: Kevin Wang <wy721@qq.com>
kvinwang added a commit that referenced this pull request Sep 25, 2026
…tls test count

#1333 makes --verify-cert report is_valid false with a reason when the
attested app info does not decode; run its test from the verifier binary.
The ra-tls suite grew (#1232, #1255), so match any passing count and keep
requiring the named tests.

Signed-off-by: Kevin Wang <wy721@qq.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant