From da464f7ca774551c26b9464a7a7dcf66177872ca Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Wed, 22 Jul 2026 05:04:11 -0700 Subject: [PATCH 1/3] feat(attestation): add TCB out-of-date grace period --- docs/security/security-model.md | 10 +- dstack/Cargo.lock | 408 +++++++++++++++++++--- dstack/Cargo.toml | 2 +- dstack/dstack-attest/src/attestation.rs | 40 ++- dstack/local-key-provider/src/provider.rs | 36 +- 5 files changed, 423 insertions(+), 73 deletions(-) diff --git a/docs/security/security-model.md b/docs/security/security-model.md index baab5d161..6ba67f4cf 100644 --- a/docs/security/security-model.md +++ b/docs/security/security-model.md @@ -295,15 +295,11 @@ misconfiguration or denial of service, but unvalidated ACPI/AML cannot tamper with confidential private memory or extract secrets. That residual availability risk is already outside dstack's confidentiality/integrity guarantees. -### TCB status is surfaced, not gated, during verification +### Out-of-date TCBs have a bounded grace period -dstack's `validate_tcb` does not reject a quote based on its TCB status string (`UpToDate`, `OutOfDate`, `ConfigurationNeeded`, `SWHardeningNeeded`, ...). It only enforces hard invariants: debug mode must be off, and the SEAM/service-TD measurements must be well-formed. The verified report carries the `status` field through to the caller. +dstack applies `dcap-qvl`'s quote policy after cryptographic verification. Platform and quoting-enclave TCB levels reported as `OutOfDate` are accepted for 15 days after the corresponding TCB level's publication date. Once that grace period expires, verification fails. `Revoked` TCBs are always rejected. -This is deliberate: whether a non-current TCB (e.g. `OutOfDate`) is acceptable is a **policy decision that belongs downstream**, not in the verification primitive. Different deployments have different risk tolerances, so the verifier surfaces the status and lets the consuming policy decide. The "TCB status is up-to-date" item in the verification checklist above is exactly such a downstream policy check. - -The one case dstack does not leave to downstream is a genuinely invalid TCB: `dcap-qvl` rejects `Revoked` outright (its `is_valid()` returns false only for `Revoked`), so a revoked TCB never reaches the policy layer in the first place. - -> **Future work:** this will be refactored toward a grace-period model, where an out-of-date TCB is accepted for a bounded window after a new TCB level is published rather than being a binary downstream decision. +Other non-current statuses (`ConfigurationNeeded`, `SWHardeningNeeded`, and their combinations) remain accepted and are surfaced in the verified report for downstream policy decisions. `validate_tcb` separately enforces hard invariants: debug mode must be off, and the SEAM/service-TD measurements must be well-formed. ### Development modes are auditable, not production-safe diff --git a/dstack/Cargo.lock b/dstack/Cargo.lock index 7e940d40e..f41122a65 100644 --- a/dstack/Cargo.lock +++ b/dstack/Cargo.lock @@ -14,7 +14,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "generic-array", ] @@ -382,6 +382,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base16ct" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" + [[package]] name = "base64" version = "0.13.1" @@ -577,6 +583,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + [[package]] name = "blowfish" version = "0.9.1" @@ -923,7 +938,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "inout", "zeroize", ] @@ -1003,6 +1018,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + [[package]] name = "codicon" version = "3.0.0" @@ -1042,6 +1063,12 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "const_format" version = "0.2.36" @@ -1139,6 +1166,12 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1254,6 +1287,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "crypto-bigint" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a52aa3fcda4e6302a9f48734f234d35d4721b96f8fe07d073f07ce9df4f0271" +dependencies = [ + "cpubits", + "ctutils", + "hybrid-array", + "num-traits", + "rand_core 0.10.1", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -1265,6 +1313,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", + "rand_core 0.10.1", +] + [[package]] name = "crypto-mac" version = "0.11.0" @@ -1304,6 +1362,16 @@ dependencies = [ "cipher 0.4.4", ] +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", + "subtle", +] + [[package]] name = "cuckoofilter" version = "0.5.0" @@ -1433,9 +1501,9 @@ checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] name = "dcap-qvl" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92a14fb8954c867d6855e44d98eab18e769816357738406691ebe60d8fdd005d" +checksum = "82af46ebeaf7d7aa0481f324bad8848b90b44f377d23787b2d8e79e479dd06b1" dependencies = [ "anyhow", "asn1_der", @@ -1443,14 +1511,14 @@ dependencies = [ "borsh", "byteorder", "chrono", - "const-oid", + "const-oid 0.10.2", "dcap-qvl-webpki", - "der", + "der 0.8.1", "derive_more 2.1.1", "futures", "hex", "log", - "p256", + "p256 0.14.0", "parity-scale-codec", "pem", "reqwest", @@ -1460,12 +1528,12 @@ dependencies = [ "serde", "serde-human-bytes", "serde_json", - "sha2 0.10.9", - "signature", + "sha2 0.11.0", + "signature 3.0.0", "tracing", "urlencoding", "wasm-bindgen-futures", - "x509-cert", + "x509-cert 0.3.0", ] [[package]] @@ -1474,15 +1542,15 @@ version = "0.103.4+dcap.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0af040afe66c4f26ca05f308482d98bd75a35a80a227d877c2e28c9947a9fa6" dependencies = [ - "ecdsa", + "ecdsa 0.16.9", "ed25519-dalek", - "p256", + "p256 0.13.2", "p384", "ring", "rsa", "rustls-pki-types", "sha2 0.10.9", - "signature", + "signature 2.2.0", "untrusted 0.9.0", ] @@ -1509,13 +1577,25 @@ version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "const-oid", - "der_derive", + "const-oid 0.9.6", + "der_derive 0.7.3", "flagset", "pem-rfc7468", "zeroize", ] +[[package]] +name = "der" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" +dependencies = [ + "const-oid 0.10.2", + "der_derive 0.8.0", + "flagset", + "zeroize", +] + [[package]] name = "der-parser" version = "9.0.0" @@ -1541,6 +1621,17 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "der_derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59600e2c2d636fde9b65e99cc6445ac770c63d3628195ff39932b8d6d7409903" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "deranged" version = "0.5.8" @@ -1645,11 +1736,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", - "const-oid", - "crypto-common", + "const-oid 0.9.6", + "crypto-common 0.1.7", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "const-oid 0.10.2", + "crypto-common 0.2.2", + "ctutils", +] + [[package]] name = "dirs" version = "5.0.1" @@ -2401,12 +2504,26 @@ version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ - "der", + "der 0.7.10", "digest 0.10.7", - "elliptic-curve", - "rfc6979", - "signature", - "spki", + "elliptic-curve 0.13.8", + "rfc6979 0.4.0", + "signature 2.2.0", + "spki 0.7.3", +] + +[[package]] +name = "ecdsa" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0681a4fc24c767085329728d8dfba959af91228aa4610cca4f8ce317ba46ae0" +dependencies = [ + "der 0.8.1", + "digest 0.11.3", + "elliptic-curve 0.14.1", + "rfc6979 0.6.0", + "signature 3.0.0", + "zeroize", ] [[package]] @@ -2416,7 +2533,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ "pkcs8", - "signature", + "signature 2.2.0", ] [[package]] @@ -2446,17 +2563,36 @@ version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ - "base16ct", - "crypto-bigint", + "base16ct 0.2.0", + "crypto-bigint 0.5.5", "digest 0.10.7", - "ff", + "ff 0.13.1", "generic-array", - "group", + "group 0.13.0", "hkdf", "pem-rfc7468", "pkcs8", "rand_core 0.6.4", - "sec1", + "sec1 0.7.3", + "subtle", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d65aa39b3a5c1c9c1b745c9a019234bb7a21b77abcb4f4d266d706e2d577d65" +dependencies = [ + "base16ct 1.0.0", + "crypto-bigint 0.7.5", + "crypto-common 0.2.2", + "digest 0.11.3", + "ff 0.14.0", + "group 0.14.0", + "hybrid-array", + "rand_core 0.10.1", + "sec1 0.8.1", "subtle", "zeroize", ] @@ -2619,6 +2755,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "ff" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f686ab92a9fb0eaf188f6c6c87b89490baa6fdb0db4544ba4dc47f7942489f" +dependencies = [ + "rand_core 0.10.1", + "subtle", +] + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -3026,11 +3172,22 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ - "ff", + "ff 0.13.1", "rand_core 0.6.4", "subtle", ] +[[package]] +name = "group" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fd1a1c7a5206c5b7a3f5a0d7ccd3ff85d0c8f5133d62a02680255b0004af5f4" +dependencies = [ + "ff 0.14.0", + "rand_core 0.10.1", + "subtle", +] + [[package]] name = "guest-api" version = "0.6.0" @@ -3289,6 +3446,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.3", +] + [[package]] name = "home" version = "0.5.12" @@ -3390,6 +3556,17 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" +[[package]] +name = "hybrid-array" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" +dependencies = [ + "subtle", + "typenum", + "zeroize", +] + [[package]] name = "hyper" version = "1.10.1" @@ -3966,11 +4143,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ "cfg-if", - "ecdsa", - "elliptic-curve", + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", "once_cell", "sha2 0.10.9", - "signature", + "signature 2.2.0", ] [[package]] @@ -4356,7 +4533,7 @@ dependencies = [ "fs-err", "hex", "nsm-qvl", - "p256", + "p256 0.13.2", "p384", "parity-scale-codec", "rand 0.8.6", @@ -4873,21 +5050,34 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "primeorder 0.13.6", "sha2 0.10.9", ] +[[package]] +name = "p256" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c9239b2dbc807adbbe147e8cf72ea7450c3a0aabe62cb8e75ff4ec22e1f72a" +dependencies = [ + "ecdsa 0.17.0", + "elliptic-curve 0.14.1", + "primefield", + "primeorder 0.14.0", + "sha2 0.11.0", +] + [[package]] name = "p384" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "primeorder 0.13.6", "sha2 0.10.9", ] @@ -5180,9 +5370,9 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ - "der", + "der 0.7.10", "pkcs8", - "spki", + "spki 0.7.3", ] [[package]] @@ -5191,8 +5381,8 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der", - "spki", + "der 0.7.10", + "spki 0.7.3", ] [[package]] @@ -5269,13 +5459,38 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "primefield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c555a6e4eb7d4e158fcb028c835c3b8642206ddc279b5c6b202ef9a8bdb592f4" +dependencies = [ + "crypto-bigint 0.7.5", + "crypto-common 0.2.2", + "ff 0.14.0", + "rand_core 0.10.1", + "subtle", + "zeroize", +] + [[package]] name = "primeorder" version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" dependencies = [ - "elliptic-curve", + "elliptic-curve 0.13.8", +] + +[[package]] +name = "primeorder" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c9f42978c78a00e3d68f69fc03e57a234debae69da4020a4fb588fcdcd07b06" +dependencies = [ + "elliptic-curve 0.14.1", + "primefield", + "wnaf", ] [[package]] @@ -5627,7 +5842,7 @@ dependencies = [ "dcap-qvl", "dstack-attest", "dstack-types", - "elliptic-curve", + "elliptic-curve 0.13.8", "errify", "ez-hash", "flate2", @@ -5636,7 +5851,7 @@ dependencies = [ "hex_fmt", "hkdf", "or-panic", - "p256", + "p256 0.13.2", "parity-scale-codec", "rand 0.8.6", "rcgen", @@ -6004,6 +6219,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "rfc6979" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4a459cddafb3fe76b31fd8f1108007566c40301feb64dc7b54656eb7388172b" +dependencies = [ + "crypto-bigint 0.7.5", + "hmac 0.13.0", +] + [[package]] name = "ring" version = "0.17.14" @@ -6184,7 +6409,7 @@ version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ - "const-oid", + "const-oid 0.9.6", "digest 0.10.7", "num-bigint-dig", "num-integer", @@ -6193,8 +6418,8 @@ dependencies = [ "pkcs8", "rand_core 0.6.4", "sha2 0.10.9", - "signature", - "spki", + "signature 2.2.0", + "spki 0.7.3", "subtle", "zeroize", ] @@ -6635,14 +6860,28 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ - "base16ct", - "der", + "base16ct 0.2.0", + "der 0.7.10", "generic-array", "pkcs8", "subtle", "zeroize", ] +[[package]] +name = "sec1" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d" +dependencies = [ + "base16ct 1.0.0", + "ctutils", + "der 0.8.1", + "hybrid-array", + "subtle", + "zeroize", +] + [[package]] name = "secrecy" version = "0.8.0" @@ -6914,7 +7153,7 @@ dependencies = [ "sha2 0.10.9", "static_assertions", "uuid", - "x509-cert", + "x509-cert 0.2.5", ] [[package]] @@ -6979,6 +7218,17 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", +] + [[package]] name = "sha3" version = "0.10.9" @@ -7067,6 +7317,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "signature" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" +dependencies = [ + "digest 0.11.3", + "rand_core 0.10.1", +] + [[package]] name = "simd-adler32" version = "0.3.9" @@ -7191,7 +7451,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", - "der", + "der 0.7.10", +] + +[[package]] +name = "spki" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" +dependencies = [ + "base64ct", + "der 0.8.1", ] [[package]] @@ -7855,7 +8125,7 @@ dependencies = [ "dstack-types", "hex", "nom", - "p256", + "p256 0.13.2", "pem", "reqwest", "rsa", @@ -8031,7 +8301,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "subtle", ] @@ -8916,6 +9186,17 @@ dependencies = [ "wasmparser", ] +[[package]] +name = "wnaf" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab12e7090f27e2ffd9322651492942d50c2926094af30601e1964337db39daf1" +dependencies = [ + "ff 0.14.0", + "group 0.14.0", + "hybrid-array", +] + [[package]] name = "wrapcenum-derive" version = "0.4.1" @@ -8961,12 +9242,23 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94" dependencies = [ - "const-oid", - "der", - "spki", + "const-oid 0.9.6", + "der 0.7.10", + "spki 0.7.3", "tls_codec", ] +[[package]] +name = "x509-cert" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "105ef4642d9cb137ef83d623d0e4bf08b8adf69e9918ca904a174adb6d3d038b" +dependencies = [ + "const-oid 0.10.2", + "der 0.8.1", + "spki 0.8.0", +] + [[package]] name = "x509-parser" version = "0.16.0" diff --git a/dstack/Cargo.toml b/dstack/Cargo.toml index 05011b843..bb2f36fc9 100644 --- a/dstack/Cargo.toml +++ b/dstack/Cargo.toml @@ -214,7 +214,7 @@ url = "2.5" # Cryptography/Security aes-gcm = "0.10.3" curve25519-dalek = "4.1.3" -dcap-qvl = "0.5.2" +dcap-qvl = "0.6.0" dcap-qvl-webpki = "0.103.4" elliptic-curve = { version = "0.13.8", features = ["pkcs8"] } getrandom = "0.3.1" diff --git a/dstack/dstack-attest/src/attestation.rs b/dstack/dstack-attest/src/attestation.rs index 7e75d0ba6..b4eeb1d73 100644 --- a/dstack/dstack-attest/src/attestation.rs +++ b/dstack/dstack-attest/src/attestation.rs @@ -9,7 +9,10 @@ /// and REPORT_DATA occupies bytes 568..632 (64 bytes). pub const TDX_QUOTE_REPORT_DATA_RANGE: std::ops::Range = 568..632; -use std::{borrow::Cow, time::SystemTime}; +use std::{ + borrow::Cow, + time::{Duration, SystemTime}, +}; use anyhow::{anyhow, bail, Context, Result}; use cc_eventlog::{RuntimeEvent, TdxEvent}; @@ -17,6 +20,7 @@ use dcap_qvl::{ collateral::CollateralClient, quote::{EnclaveReport, Quote, Report, TDReport10, TDReport15}, verify::VerifiedReport as TdxVerifiedReport, + QuotePolicy, TcbStatus, TcbStatusWithAdvisory, }; pub use dstack_types::CollateralUrls; #[cfg(feature = "quote")] @@ -182,10 +186,42 @@ impl AttestationVerifier { .duration_since(SystemTime::UNIX_EPOCH) .context("system clock is before UNIX epoch")? .as_secs(); - self.tdx.verify(quote, &collateral, now) + let claims = self + .tdx + .verify_with_policy(quote, collateral, now, &tdx_quote_policy(now))?; + Ok(TdxVerifiedReport { + status: claims.tcb.status.to_string(), + advisory_ids: claims.tcb.advisory_ids, + report: claims.report, + ppid: claims.platform.pck.ppid, + platform_status: TcbStatusWithAdvisory::new( + claims.platform.tcb_level.tcb_status, + claims.platform.tcb_level.advisory_ids, + ), + qe_status: TcbStatusWithAdvisory::new( + claims.qe.tcb_level.tcb_status, + claims.qe.tcb_level.advisory_ids, + ), + }) } } +const TCB_OUT_OF_DATE_GRACE_PERIOD: Duration = Duration::from_secs(15 * 24 * 60 * 60); + +fn tdx_quote_policy(now: u64) -> QuotePolicy { + QuotePolicy::strict(now) + .allow_status(TcbStatus::SWHardeningNeeded) + .allow_status(TcbStatus::ConfigurationNeeded) + .allow_status(TcbStatus::ConfigurationAndSWHardeningNeeded) + .allow_status(TcbStatus::OutOfDate) + .allow_status(TcbStatus::OutOfDateConfigurationNeeded) + .platform_grace_period(TCB_OUT_OF_DATE_GRACE_PERIOD) + .qe_grace_period(TCB_OUT_OF_DATE_GRACE_PERIOD) + .allow_dynamic_platform(true) + .allow_cached_keys(true) + .allow_smt(true) +} + fn read_root_file(path: Option<&std::path::Path>, platform: &str) -> Result>> { let Some(path) = path else { return Ok(None); diff --git a/dstack/local-key-provider/src/provider.rs b/dstack/local-key-provider/src/provider.rs index 154200aea..4947d4351 100644 --- a/dstack/local-key-provider/src/provider.rs +++ b/dstack/local-key-provider/src/provider.rs @@ -5,8 +5,10 @@ use dcap_qvl::{ collateral::CollateralClient, quote::{Quote, TDReport10}, + QuotePolicy, TcbStatus, }; use sha2::{Digest, Sha256}; +use std::time::{Duration, SystemTime}; use tracing::{debug, info}; use crate::{ @@ -58,17 +60,25 @@ impl KeyProvider { } async fn verify_tdx_quote(&self, raw_quote: &[u8]) -> Result { - let report = self + let collateral = self .collateral - .fetch_and_verify(raw_quote) + .fetch(raw_quote) .await .map_err(|error| ProviderError::QuoteVerification(error.to_string()))?; - let tdx_report = report.report.as_td10().copied().ok_or_else(|| { + let now = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .map_err(|error| ProviderError::QuoteVerification(error.to_string()))? + .as_secs(); + let policy = tdx_quote_policy(now); + let claims = dcap_qvl::verify::QuoteVerifier::new_prod() + .verify_with_policy(raw_quote, collateral, now, &policy) + .map_err(|error| ProviderError::QuoteVerification(error.to_string()))?; + let tdx_report = claims.report.as_td10().copied().ok_or_else(|| { ProviderError::QuoteVerification("verified quote is not a TDX quote".into()) })?; debug!( - tcb_status = %report.status, - advisories = ?report.advisory_ids, + tcb_status = %claims.tcb.status, + advisories = ?claims.tcb.advisory_ids, "TDX quote verified" ); Ok(tdx_report) @@ -110,6 +120,22 @@ fn measurements(report: &TDReport10) -> Vec { output } +const TCB_OUT_OF_DATE_GRACE_PERIOD: Duration = Duration::from_secs(15 * 24 * 60 * 60); + +fn tdx_quote_policy(now: u64) -> QuotePolicy { + QuotePolicy::strict(now) + .allow_status(TcbStatus::SWHardeningNeeded) + .allow_status(TcbStatus::ConfigurationNeeded) + .allow_status(TcbStatus::ConfigurationAndSWHardeningNeeded) + .allow_status(TcbStatus::OutOfDate) + .allow_status(TcbStatus::OutOfDateConfigurationNeeded) + .platform_grace_period(TCB_OUT_OF_DATE_GRACE_PERIOD) + .qe_grace_period(TCB_OUT_OF_DATE_GRACE_PERIOD) + .allow_dynamic_platform(true) + .allow_cached_keys(true) + .allow_smt(true) +} + #[cfg(test)] mod tests { use super::*; From 231028355fc205c4ca66d11ee291fa3a371544a2 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Wed, 22 Jul 2026 06:01:38 -0700 Subject: [PATCH 2/3] fix(attestation): reject other non-current TCB statuses --- docs/security/security-model.md | 4 ++-- dstack/dstack-attest/src/attestation.rs | 20 ++++++++++++++++---- dstack/local-key-provider/src/provider.rs | 20 ++++++++++++++++---- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/docs/security/security-model.md b/docs/security/security-model.md index 6ba67f4cf..e4482d242 100644 --- a/docs/security/security-model.md +++ b/docs/security/security-model.md @@ -297,9 +297,9 @@ risk is already outside dstack's confidentiality/integrity guarantees. ### Out-of-date TCBs have a bounded grace period -dstack applies `dcap-qvl`'s quote policy after cryptographic verification. Platform and quoting-enclave TCB levels reported as `OutOfDate` are accepted for 15 days after the corresponding TCB level's publication date. Once that grace period expires, verification fails. `Revoked` TCBs are always rejected. +dstack applies `dcap-qvl`'s quote policy after cryptographic verification. `UpToDate` is accepted, while platform and quoting-enclave TCB levels reported as `OutOfDate` are accepted for 15 days after the corresponding TCB level's publication date. Once that grace period expires, verification fails. All other TCB statuses, including `ConfigurationNeeded`, `SWHardeningNeeded`, and `Revoked`, are rejected. -Other non-current statuses (`ConfigurationNeeded`, `SWHardeningNeeded`, and their combinations) remain accepted and are surfaced in the verified report for downstream policy decisions. `validate_tcb` separately enforces hard invariants: debug mode must be off, and the SEAM/service-TD measurements must be well-formed. +`validate_tcb` separately enforces hard invariants: debug mode must be off, and the SEAM/service-TD measurements must be well-formed. ### Development modes are auditable, not production-safe diff --git a/dstack/dstack-attest/src/attestation.rs b/dstack/dstack-attest/src/attestation.rs index b4eeb1d73..5babaa776 100644 --- a/dstack/dstack-attest/src/attestation.rs +++ b/dstack/dstack-attest/src/attestation.rs @@ -210,11 +210,7 @@ const TCB_OUT_OF_DATE_GRACE_PERIOD: Duration = Duration::from_secs(15 * 24 * 60 fn tdx_quote_policy(now: u64) -> QuotePolicy { QuotePolicy::strict(now) - .allow_status(TcbStatus::SWHardeningNeeded) - .allow_status(TcbStatus::ConfigurationNeeded) - .allow_status(TcbStatus::ConfigurationAndSWHardeningNeeded) .allow_status(TcbStatus::OutOfDate) - .allow_status(TcbStatus::OutOfDateConfigurationNeeded) .platform_grace_period(TCB_OUT_OF_DATE_GRACE_PERIOD) .qe_grace_period(TCB_OUT_OF_DATE_GRACE_PERIOD) .allow_dynamic_platform(true) @@ -2515,6 +2511,22 @@ pub struct AppInfo { mod tests { use super::*; + #[test] + fn tdx_policy_accepts_only_up_to_date_and_out_of_date() { + let policy = tdx_quote_policy(0); + assert!(policy.is_status_acceptable(TcbStatus::UpToDate)); + assert!(policy.is_status_acceptable(TcbStatus::OutOfDate)); + for status in [ + TcbStatus::OutOfDateConfigurationNeeded, + TcbStatus::ConfigurationAndSWHardeningNeeded, + TcbStatus::ConfigurationNeeded, + TcbStatus::SWHardeningNeeded, + TcbStatus::Revoked, + ] { + assert!(!policy.is_status_acceptable(status)); + } + } + #[test] fn external_trust_anchor_requires_explicit_insecure_opt_in() { let config = AttestationVerifierConfig { diff --git a/dstack/local-key-provider/src/provider.rs b/dstack/local-key-provider/src/provider.rs index 4947d4351..79dba6f25 100644 --- a/dstack/local-key-provider/src/provider.rs +++ b/dstack/local-key-provider/src/provider.rs @@ -124,11 +124,7 @@ const TCB_OUT_OF_DATE_GRACE_PERIOD: Duration = Duration::from_secs(15 * 24 * 60 fn tdx_quote_policy(now: u64) -> QuotePolicy { QuotePolicy::strict(now) - .allow_status(TcbStatus::SWHardeningNeeded) - .allow_status(TcbStatus::ConfigurationNeeded) - .allow_status(TcbStatus::ConfigurationAndSWHardeningNeeded) .allow_status(TcbStatus::OutOfDate) - .allow_status(TcbStatus::OutOfDateConfigurationNeeded) .platform_grace_period(TCB_OUT_OF_DATE_GRACE_PERIOD) .qe_grace_period(TCB_OUT_OF_DATE_GRACE_PERIOD) .allow_dynamic_platform(true) @@ -140,6 +136,22 @@ fn tdx_quote_policy(now: u64) -> QuotePolicy { mod tests { use super::*; + #[test] + fn tdx_policy_accepts_only_up_to_date_and_out_of_date() { + let policy = tdx_quote_policy(0); + assert!(policy.is_status_acceptable(TcbStatus::UpToDate)); + assert!(policy.is_status_acceptable(TcbStatus::OutOfDate)); + for status in [ + TcbStatus::OutOfDateConfigurationNeeded, + TcbStatus::ConfigurationAndSWHardeningNeeded, + TcbStatus::ConfigurationNeeded, + TcbStatus::SWHardeningNeeded, + TcbStatus::Revoked, + ] { + assert!(!policy.is_status_acceptable(status)); + } + } + #[test] fn extracts_all_key_derivation_measurements_in_wire_order() { let quote = Quote::parse(include_bytes!("../../ra-tls/assets/tdx_quote")).unwrap(); From 63b68d7d02aa8c8b0ea82ab2dba0f3d9794bf5d2 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Wed, 22 Jul 2026 06:45:09 -0700 Subject: [PATCH 3/3] chore(deps): update dcap-qvl to 0.6.1 --- dstack/Cargo.lock | 4 ++-- dstack/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dstack/Cargo.lock b/dstack/Cargo.lock index f41122a65..9eca2f574 100644 --- a/dstack/Cargo.lock +++ b/dstack/Cargo.lock @@ -1501,9 +1501,9 @@ checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] name = "dcap-qvl" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82af46ebeaf7d7aa0481f324bad8848b90b44f377d23787b2d8e79e479dd06b1" +checksum = "26bb988bf78cd9a44477ee4bbfa9768335c3b58df75860ea048aee3356c90d7c" dependencies = [ "anyhow", "asn1_der", diff --git a/dstack/Cargo.toml b/dstack/Cargo.toml index bb2f36fc9..033ee2c20 100644 --- a/dstack/Cargo.toml +++ b/dstack/Cargo.toml @@ -214,7 +214,7 @@ url = "2.5" # Cryptography/Security aes-gcm = "0.10.3" curve25519-dalek = "4.1.3" -dcap-qvl = "0.6.0" +dcap-qvl = "0.6.1" dcap-qvl-webpki = "0.103.4" elliptic-curve = { version = "0.13.8", features = ["pkcs8"] } getrandom = "0.3.1"