Dstack-TEE / Dstack-TEE/dstack

Attestation: certificate validity and revocation checks differ per platform, and SEV-SNP has none

オープン
#1,140 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る
主要言語
Rust
スター
544
フォーク
96
平均マージ
23時間 40分
マージ済み PR(30日)
126

説明

## Summary

Temporal validation of attestation collateral differs by platform, and one platform has none at all. An expired VCEK verifies on AMD SEV-SNP exactly as a fresh one does; a revoked AWS Nitro certificate is never checked against a CRL because the production call site disables collateral; GCP TPM checks revocation only if a CRL happened to download. Only Intel TDX is complete.

None of this is written down anywhere, so a reader of one platform's path reasonably assumes the others behave the same way.

## What each platform actually does

| Platform | Cert `notBefore`/`notAfter` | Collateral expiry | Clock injectable | CRL |
|---|---|---|---|---|
| Intel TDX / GCP TDX | yes | yes (TCBInfo + QEIdentity `nextUpdate`) | yes (`now_secs`) | yes, `ExpirationPolicy::Enforce`, `UnknownStatusPolicy::Deny` |
| GCP TPM | yes | only when a CRL was downloaded | no | fail-open if the list is empty |
| AWS Nitro Enclave | yes (+1h doc freshness) | guard exists, unreachable in prod | yes | disabled at call site |
| AWS NitroTPM | yes (+1h doc freshness) | guard exists, unreachable in prod | yes | disabled at call site |
| **AMD SEV-SNP** | **no** | **no** | **no** | **no** |

## Detail

**AMD SEV-SNP — no temporal validation anywhere.** Both chain branches in `sev-snp-qvl/src/lib.rs:540-553` are signature-only:

- external-root branch, `verify_x509_chain` (`lib.rs:572-595`): checks `is_ca()`, issuer/subject linkage, three signatures. No `Validity` access exists in the file.
- default branch: `sev` 6.0.0's `Verifiable` impls bottom out at `X509::verify(&key)` (`sev-6.0.0/src/certs/snp/cert.rs:81-100`), which is a signature check.

`QuoteVerifier::verify` (`lib.rs:118-133`) takes no clock argument, so a caller cannot supply one either. AMD's `/vcek/v1/{product}/crl` is never fetched. Observed on real collateral: a VCEK is issued with a 7-year window (ASK/ARK run to 2045), so nothing expires soon — but nothing would be noticed if it did, and revocation is permanently invisible.

**AWS Nitro Enclave and NitroTPM — revocation disabled by the caller, not by the library.** `nsm-qvl` bails when a certificate has CRL distribution points but no CRL was supplied (`nsm-qvl/src/verify.rs:196-202`). That guard sits after the `let Some(collateral) = collateral else { ... return Ok(()) }` early return at `verify.rs:184-195`, and dstack passes `None`:

```rust
// dstack-attest/src/attestation.rs:2461
// CRL fetch is unreliable (e.g. 403 from S3), so keep it disabled here by default.
let verified_report = verifier
.aws_nitro_enclave
.verify(&nsm_quote.nsm_quote, None, now)
```

Same for NitroTPM at `attestation.rs:1830-1832`. The reason given is real — AWS's S3 CRL endpoint returns 403 — but the result is that a fail-closed guard reads as active while never running.

**GCP TPM — fail-open on an empty CRL set.** `tpm-qvl/src/verify.rs:609` gates all revocation checking on `if !collateral.crls.is_empty()`, and the `else` branch verifies with `None` revocation options. Unlike `nsm-qvl` there is no bail when a chain advertises CRL distribution points but none were downloaded: `collateral.rs:94-105` `continue`s past certs without a CRL DP, and `download_first_available_crl` only fails when *every* URL for a cert fails. Certificate dates are checked (`verify.rs:591-594`, `640-667`).

**The `now` parameter is mostly decorative.** `AttestationV1::verify_with_time` / `DstackQuote::verify_with_time` accept a `now`, but it reaches only `nsm-qvl`. TDX hardcodes `SystemTime::now()` inside `verify_tdx_quote` (`attestation.rs:181-188`); GCP TPM and SEV-SNP have no hook at all. Every production path passes `None`; the only non-`None` caller is `dstack-attest/tests/nitro_verify.rs:47`. Verifying a quote as-of a past instant is therefore impossible for three of five platforms, which also makes deterministic tests of expiry behaviour impossible to write.

**Operator-supplied root CAs are not date-validated either.** `validate_x509_certificate` (`attestation.rs:205-222`) parses the DER, rejects trailing bytes, and asserts `is_ca()`. Nothing looks at the validity window.

## Why it is worth fixing

Individually each of these is arguably defensible — SNP's rollback protection comes from the TCB fields in the report rather than from certificate expiry, and AWS's CRL endpoint really is unreliable. Collectively they mean the answer to "does dstack reject stale or revoked attestation collateral?" is "depends which platform, and not in a way the code makes visible."

The gap also compounds with caching. Pointing `[core.attestation.urls] amd_kds` at a caching mirror is a reasonable way to survive a KDS outage, but because nothing checks dates or revocation on that path, an arbitrarily old cached VCEK is indistinguishable from a fresh one.

## Suggested direction

Not a fix proposal, just what seems worth deciding:

1. **Decide and document the intended policy per platform**, including where fail-open is deliberate. Today the intent is only inferable from what the code omits.
2. **Thread one clock through every verifier.** `now` already exists in the public API; making it reach all five platforms costs little and makes expiry testable.
3. **Add certificate validity checks to `sev-snp-qvl`**, or record explicitly why the report's TCB fields make them redundant.
4. **Re-examine the Nitro CRL call site.** If S3 403s make CRLs impractical, a comment at the call site is weaker than a typed "revocation not checked" signal that surfaces in `VerifiedReport`.
5. **Consider surfacing what was checked** in the verification result, so a caller can tell a fully-validated chain from a signature-only one.

---

Found while testing 0.6.0-rc0 on real TDX and SEV-SNP hardware. Happy to take any of these if there is a preferred direction.

コントリビューションガイド

コントリビューションガイドを開く

調査の方向性

Start by comparing the verifier entry points in sev-snp-qvl/src/lib.rs, nsm-qvl/src/verify.rs, tpm-qvl/src/verify.rs, and dstack-attest/src/attestation.rs, including the nitro_verify test. First establish the intended per-platform policy and clock behavior; done means the policy is decided and documented, the chosen validation gaps are addressed consistently, and expiry or revocation behavior is covered by deterministic tests.

索引モデルが issue の本文から書いたものです。

評価

技術スタック
aws, gcp, rust
領域
cryptography, security
issue の種類
バグ
難易度
5/5
見積もり時間
1週間以上
活発さ
活発
明瞭さ
説明が足りない
初心者へのやさしさ
35/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。