All checks were successful
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 6s
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 41s
standards workspaces build + test / joule-ui-rs (joule-ui/joule-ui-rs, joule-ui-rs, linux) (pull_request) Successful in 1m14s
standards workspaces build + test / jcp-rs (jcp-conformance, jcp/jcp-rs, -p jcp-energy --features nvml, jcp-rs, linux) (pull_request) Successful in 1m39s
standards workspaces build + test / joulecontract-rs (joulecontract/joulecontract-rs, joulecontract-rs, linux-arm64, true) (pull_request) Successful in 1m8s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 3m53s
standards workspaces build + test / sandbox-rs (sandbox-conformance, sandbox/sandbox-rs, -p sandbox-tee --features amd-kds,intel-pcs, sandbox-rs, linux) (pull_request) Successful in 2m39s
standards workspaces build + test / proof-rs (proof-conformance, proof/proof-rs, proof-rs, linux, true) (pull_request) Successful in 3m54s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 7m37s
standards workspaces build + test / jouledesk-rs (jouledesk/jouledesk-rs, jouledesk-rs, macos) (pull_request) Successful in 2m49s
The TDX path (#399) and its PCK chain check (#415) had only met quotes this crate built itself. This runs the reference host inside a TDX guest and records the result as evidence anyone can re-check offline. sandbox-runtime examples: - confidential_run_tdx, run inside the guest. It takes quotes through configfs-tsm and derives the PCK anchor from a probe quote's chain, checked to Intel's root, which the verifier's side supplies. It seals an S6 run against a challenge, then checks the attestation: verified against the challenge, refused against another and against none. - verify_run now reads the evidence kind. For TDX its anchor argument is Intel's root, and the PCK is derived from the quote's chain checked to it. SEV-SNP is unchanged, and the 2026-10-21 SEV-SNP evidence still verifies. - sandbox-runtime's dev-dependencies enable sandbox-tee's intel-pcs feature for these examples. sandbox/proof/hardware/2026-10-21-tdx: the attestation (with the DCAP v4 quote), the challenge, Intel's published root certificate, and a README saying what it shows and what it does not. The unit is a placeholder and revocation lists were not consulted. Checked on our side, away from the guest: - The quote's chain is PCK Certificate, then PCK Platform CA, then Root CA, and its root is byte-identical to Intel's published root (SHA-256 44:A0:19:6B:…:74:D3). - verify_run on the build host verifies the run, and refuses another challenge and no challenge. - An attestation with its outcome changed is refused (report-data binding mismatch). - Another certificate given as the root yields no anchor. Co-Authored-By: Claude Opus 5.5 <[email protected]>
60 lines
3.1 KiB
Rust
60 lines
3.1 KiB
Rust
//! Verify a sealed run's TEE evidence as a third party (SPEC §7.6): from the
|
|
//! attestation, the challenge the verifier issued, and the platform's anchor,
|
|
//! with nothing from the host that sealed it.
|
|
//!
|
|
//! ```text
|
|
//! verify_run <attestation.json> <challenge-hex> <anchor.der>
|
|
//! ```
|
|
//!
|
|
//! 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, TeeKind};
|
|
use sandbox_runtime::verify_run_evidence;
|
|
use sandbox_tee::policy::{TeeAnchor, TeePolicy};
|
|
|
|
fn main() {
|
|
let args: Vec<String> = std::env::args().collect();
|
|
let (Some(path), Some(challenge_hex), Some(vcek_path)) = (args.get(1), args.get(2), args.get(3)) else {
|
|
eprintln!("usage: verify_run <attestation.json> <challenge-hex> <vcek.der>");
|
|
std::process::exit(2);
|
|
};
|
|
let attestation: Attestation =
|
|
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 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("e, &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;
|
|
other[31] ^= 0x80;
|
|
let refused_other = verify_run_evidence(&attestation, Some(&other), &policy, &anchor).is_err();
|
|
let refused_none = verify_run_evidence(&attestation, None, &policy, &anchor).is_err();
|
|
match &verified {
|
|
Ok(v) => println!("verified: {:?} run, measurement {}", v.kind, hex::encode(v.measurement)),
|
|
Err(e) => println!("NOT verified: {e}"),
|
|
}
|
|
println!("refused for another challenge: {refused_other}");
|
|
println!("refused without a challenge: {refused_none}");
|
|
if verified.is_err() || !refused_other || !refused_none {
|
|
std::process::exit(1);
|
|
}
|
|
}
|