Skip to content

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

Open
kvinwang wants to merge 4 commits into
nextfrom
fix/kms-signcert-scope
Open

kvinwang wants to merge 4 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.
  • 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.

Behavior change

With a validator installed, RaClient also verifies the KMS's attestation on SignCert/GetMeta, as dstack-util already does against the same endpoint earlier in boot.

Tests

  • cert-client: serves a real app leaf (root → derive_app_casign_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.
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