sandbox: a confidential run sealed on TDX hardware, its PCK checked to Intel's root #436

Merged
dcharlot merged 1 commit from feat/tdx-hardware-check into main 2026-10-22 09:27:04 -04:00
7 changed files with 214 additions and 7 deletions

View file

@ -0,0 +1,65 @@
# A confidential run on TDX hardware, 2026-10-21
The report-data binding of SPEC §7.6 on genuine Intel silicon, with the PCK
anchor derived from the quote's own certificate chain checked to Intel's
root, and the run verified by a party other than the host.
## What ran
- **Platform:** a confidential VM with Intel TDX (`c3-standard-4`, kernel
`7.0.0-1014-gcp`), created for the run and deleted after it.
- **Inside the guest:** the reference host
([`confidential_run_tdx`](../../../sandbox-rs/crates/sandbox-runtime/examples/confidential_run_tdx.rs)).
- It took quotes through `configfs-tsm` (provider `tdx_guest`).
- It derived the anchor from a probe quote's PCK chain, checked to Intel's
root, which the verifier's side uploaded rather than the guest fetching it.
- It sealed an S6 run whose `report_data` binds the unit, the receipt, the
outcome and a 32-byte challenge ([`challenge.hex`](challenge.hex)).
- **The quote:** DCAP version 4, TEE type `0x81`. The interface returns it in
an 8000-byte buffer, 4935 bytes of quote followed by padding. It travels in
the attestation as `tee.report_hex`.
The quote's certification data carries three certificates:
```text
CN=Intel SGX PCK Certificate issued by CN=Intel SGX PCK Platform CA
CN=Intel SGX PCK Platform CA issued by CN=Intel SGX Root CA
CN=Intel SGX Root CA self-signed
```
The root it carries is byte-identical to Intel's published root
([`intel-root.der`](intel-root.der)), whose SHA-256 fingerprint is
`44:A0:19:6B:2B:99:F8:89:B8:E1:49:E9:5B:80:7A:35:0E:74:24:96:43:99:E8:85:A7:CB:B8:CC:FA:B6:74:D3`.
Compare it with the one Intel publishes, not with this file.
## What it shows
Verified away from the guest, from these files alone:
```text
cd ../../../sandbox-rs
cargo run -p sandbox-runtime --example verify_run -- \
../proof/hardware/2026-10-21-tdx/attestation.json \
"$(cat ../proof/hardware/2026-10-21-tdx/challenge.hex)" \
../proof/hardware/2026-10-21-tdx/intel-root.der
```
- The quote's PCK chain checks to Intel's root (`sandbox-tee::intel_pcs`), and
the quote verifies through that PCK: the quote signature, the QE report's
endorsement of the attestation key, and the PCK's signature over the QE
report.
- Its `report_data` is the binding of this attestation's unit, receipt and
outcome, and of the challenge issued. It is refused for any other
challenge, and without one.
- An attestation whose outcome is changed is refused.
- Given another certificate in place of Intel's root, no anchor is derived.
## What it does not show
- The unit is a fixed placeholder. This exercises the binding, the chain
check and the verifier on real hardware, not an executor's isolation of a
workload.
- Revocation was not consulted: Intel publishes CRLs for the PCK CAs, and
checking them is the deployment's step.
- The receipt and Supervisor keys are fixed development keys. The trust here
rests on the quote, not on those signatures.

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
8a7e3b2032714567827ad2e65ae0739125dad921db483725436d3af51d8cd4dc

Binary file not shown.

View file

@ -21,4 +21,6 @@ thiserror.workspace = true
[dev-dependencies]
p256.workspace = true
# The examples check a TDX quote's PCK chain to Intel's root.
sandbox-tee = { workspace = true, features = ["intel-pcs"] }
serde_json.workspace = true

View file

@ -0,0 +1,96 @@
//! Seal a confidential run on TDX hardware and verify it as a third party
//! would (SPEC §7.6): the TDX counterpart of `confidential_run`.
//!
//! Run as root inside a TDX guest with `configfs-tsm`:
//!
//! ```text
//! confidential_run_tdx <challenge-hex> <intel-root.der>
//! ```
//!
//! `intel-root.der` is Intel's published root CA certificate, which the
//! verifier pins. The PCK anchor is derived from the chain a quote carries,
//! checked to that root, so the trust comes from the root and not from the
//! quote. The host seals an S6 run whose quote the platform makes over the
//! run's binding and the challenge, and the attestation is verified against
//! the challenge, against another, and against none. It prints the verdicts
//! and the attestation as JSON.
use ed25519_dalek::SigningKey;
use sandbox_attest::{DeviceIdentity, Outcome, TeeKind};
use sandbox_capability::Grants;
use sandbox_meter::{DeviceEnergy, Provenance};
use sandbox_runtime::{verify_run_evidence, Host, RunReport, TeeConfig};
use sandbox_schema::{ContentId, DeviceClass, Tier};
use sandbox_tee::policy::{TeeAnchor, TeePolicy};
use sandbox_tee::provider::{AttestationProvider, ConfigfsTsmProvider};
fn main() {
let args: Vec<String> = std::env::args().collect();
let (Some(challenge_hex), Some(root_path)) = (args.get(1), args.get(2)) else {
eprintln!("usage: confidential_run_tdx <challenge-hex> <intel-root.der>");
std::process::exit(2);
};
let challenge: [u8; 32] =
hex::decode(challenge_hex).ok().and_then(|v| v.try_into().ok()).expect("a 32-byte hex challenge");
let root = std::fs::read(root_path).expect("read Intel's root certificate");
let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).expect("a clock after 1970");
// A probe quote carries the platform's PCK chain; check it to the root.
let probe = ConfigfsTsmProvider::for_kind(TeeKind::Tdx).report(&[0; 64]).expect("a TDX quote");
let pck = sandbox_tee::intel_pcs::pck_from_quote(&probe, &root, now.as_secs()).expect("a PCK chain to Intel's root");
let anchor = TeeAnchor::Tdx(pck);
let policy = TeePolicy::tdx_baseline();
let host = Host::new("confidential-vm", "steward.hardware-check", SigningKey::from_bytes(&[1; 32]), SigningKey::from_bytes(&[2; 32]))
.with_tee(TeeConfig {
provider: Box::new(ConfigfsTsmProvider::for_kind(TeeKind::Tdx)),
policy: policy.clone(),
anchor: anchor.clone(),
});
let report = RunReport {
device_energy: vec![DeviceEnergy { class: DeviceClass::Cpu, measured_uj: None, estimated_uj: 1, provenance: Provenance::Estimator }],
device_identity: vec![DeviceIdentity { class: DeviceClass::Cpu, model: "confidential guest".into(), driver: "host".into() }],
outcome: Outcome::Completed,
journal: None,
storage: None,
};
let now_us = now.as_micros() as u64;
let sealed = match host.seal_run_with_mechanism(
ContentId::of_bytes(b"hardware check unit"),
ContentId::of_bytes(b"hardware check image"),
&Grants::default(),
Tier::S6,
report,
0,
[0; 8],
now_us,
now_us + 1,
None,
Some(challenge),
) {
Ok(s) => s,
Err(e) => {
println!("{}", serde_json::json!({ "sealed": false, "error": e.to_string(), "probe_hex": hex::encode(&probe) }));
std::process::exit(1);
}
};
let a = &sealed.attestation;
let tee = a.tee.as_ref().expect("an S6 run carries TEE evidence");
let verified = verify_run_evidence(a, Some(&challenge), &policy, &anchor);
let mut other = challenge;
other[0] ^= 1;
println!(
"{}",
serde_json::json!({
"sealed": true,
"verified": verified.is_ok(),
"verify_error": verified.err().map(|e| e.to_string()),
"refused_another_challenge": verify_run_evidence(a, Some(&other), &policy, &anchor).is_err(),
"refused_no_challenge": verify_run_evidence(a, None, &policy, &anchor).is_err(),
"kind": format!("{:?}", tee.kind),
"measurement_hex": tee.measurement_hex,
"tcb": tee.tcb,
"attestation": a,
})
);
}

View file

@ -3,14 +3,16 @@
//! with nothing from the host that sealed it.
//!
//! ```text
//! verify_run <attestation.json> <challenge-hex> <vcek.der>
//! verify_run <attestation.json> <challenge-hex> <anchor.der>
//! ```
//!
//! `vcek.der` is the chip's VCEK, as AMD's KDS issues it. The run is verified
//! For SEV-SNP evidence, `anchor.der` is the chip's VCEK, as AMD's KDS issues
//! it. For TDX evidence, it is Intel's root CA certificate: the PCK anchor is
//! derived from the chain the quote carries, checked to that root. The run is verified
//! against the challenge, and must be refused against any other challenge and
//! against none. Exits non-zero unless all three hold.
use sandbox_attest::Attestation;
use sandbox_attest::{Attestation, TeeKind};
use sandbox_runtime::verify_run_evidence;
use sandbox_tee::policy::{TeeAnchor, TeePolicy};
@ -24,10 +26,22 @@ fn main() {
serde_json::from_str(&std::fs::read_to_string(path).expect("read the attestation")).expect("an attestation");
let challenge: [u8; 32] =
hex::decode(challenge_hex.trim()).ok().and_then(|v| v.try_into().ok()).expect("a 32-byte hex challenge");
let der = std::fs::read(vcek_path).expect("read the VCEK");
let at = der.windows(4).position(|w| w == [0x03, 0x62, 0x00, 0x04]).expect("a P-384 point in the VCEK");
let anchor = TeeAnchor::SevSnp(p384::ecdsa::VerifyingKey::from_sec1_bytes(&der[at + 3..at + 3 + 97]).expect("a P-384 key"));
let policy = TeePolicy::sev_snp_baseline();
let der = std::fs::read(vcek_path).expect("read the anchor");
let tee = attestation.tee.as_ref().expect("TEE evidence to verify");
let (anchor, policy) = match tee.kind {
TeeKind::SevSnp => {
let at = der.windows(4).position(|w| w == [0x03, 0x62, 0x00, 0x04]).expect("a P-384 point in the VCEK");
let vcek = p384::ecdsa::VerifyingKey::from_sec1_bytes(&der[at + 3..at + 3 + 97]).expect("a P-384 key");
(TeeAnchor::SevSnp(vcek), TeePolicy::sev_snp_baseline())
}
TeeKind::Tdx => {
let quote = hex::decode(tee.report_hex.as_ref().expect("the quote")).expect("a hex quote");
let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).expect("a clock").as_secs();
let pck = sandbox_tee::intel_pcs::pck_from_quote(&quote, &der, now).expect("a PCK chain to Intel's root");
(TeeAnchor::Tdx(pck), TeePolicy::tdx_baseline())
}
TeeKind::ArmCca => panic!("no ArmCca verifier"),
};
let verified = verify_run_evidence(&attestation, Some(&challenge), &policy, &anchor);
let mut other = challenge;