From 3dd42c24ab80009fc7202bc21c0609803b7a8ea5 Mon Sep 17 00:00:00 2001 From: peg Date: Mon, 20 Apr 2026 21:25:44 +0200 Subject: [PATCH 01/11] Add mock TDX fixture generator and quote verifier --- Cargo.lock | 109 ++-- Cargo.toml | 2 + crates/attestation/Cargo.toml | 9 +- crates/attestation/README.md | 2 +- crates/attestation/src/dcap.rs | 36 +- crates/attestation/src/lib.rs | 16 +- crates/attestation/src/measurements.rs | 33 +- crates/mock-tdx/Cargo.toml | 23 + crates/mock-tdx/README.md | 24 + crates/mock-tdx/src/lib.rs | 356 +++++++++++++ crates/mock-tdx/src/main.rs | 475 ++++++++++++++++++ .../generated-dcap/mock-dcap-collateral.yaml | 134 +++++ .../generated-dcap/mock-dcap-manifest.json | 28 ++ .../generated-dcap/mock-pck-chain.pem | 32 ++ .../generated-dcap/mock-pck-key.pem | 5 + .../generated-dcap/mock-pck.crl.der | Bin 0 -> 223 bytes .../generated-dcap/mock-qe-identity.json | 1 + .../generated-dcap/mock-root-ca-key.pem | 5 + .../generated-dcap/mock-root-ca.crl.der | Bin 0 -> 217 bytes .../generated-dcap/mock-root-ca.der | Bin 0 -> 400 bytes .../generated-dcap/mock-root-ca.pem | 11 + .../generated-dcap/mock-tcb-info.json | 1 + .../generated-dcap/mock-tcb-signer-chain.pem | 33 ++ readme.md | 5 +- 24 files changed, 1204 insertions(+), 136 deletions(-) create mode 100644 crates/mock-tdx/Cargo.toml create mode 100644 crates/mock-tdx/README.md create mode 100644 crates/mock-tdx/src/lib.rs create mode 100644 crates/mock-tdx/src/main.rs create mode 100644 crates/mock-tdx/test-assets/generated-dcap/mock-dcap-collateral.yaml create mode 100644 crates/mock-tdx/test-assets/generated-dcap/mock-dcap-manifest.json create mode 100644 crates/mock-tdx/test-assets/generated-dcap/mock-pck-chain.pem create mode 100644 crates/mock-tdx/test-assets/generated-dcap/mock-pck-key.pem create mode 100644 crates/mock-tdx/test-assets/generated-dcap/mock-pck.crl.der create mode 100644 crates/mock-tdx/test-assets/generated-dcap/mock-qe-identity.json create mode 100644 crates/mock-tdx/test-assets/generated-dcap/mock-root-ca-key.pem create mode 100644 crates/mock-tdx/test-assets/generated-dcap/mock-root-ca.crl.der create mode 100644 crates/mock-tdx/test-assets/generated-dcap/mock-root-ca.der create mode 100644 crates/mock-tdx/test-assets/generated-dcap/mock-root-ca.pem create mode 100644 crates/mock-tdx/test-assets/generated-dcap/mock-tcb-info.json create mode 100644 crates/mock-tdx/test-assets/generated-dcap/mock-tcb-signer-chain.pem diff --git a/Cargo.lock b/Cargo.lock index da26021..a18b1fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -301,19 +301,18 @@ dependencies = [ "dcap-qvl 0.3.12 (git+https://github.com/Phala-Network/dcap-qvl.git?rev=f1dcc65371e941a7b83e3234833d23a1fb232ab1)", "hex", "http 1.4.0", + "mock-tdx", "num-bigint", "once_cell", "openssl", "parity-scale-codec", "pccs", "pem-rfc7468", - "rand_core 0.6.4", "reqwest", "rustls-webpki", "serde", "serde-saphyr", "serde_json", - "tdx-quote", "tempfile", "thiserror 2.0.18", "time", @@ -356,6 +355,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" dependencies = [ "aws-lc-sys", + "untrusted 0.7.1", "zeroize", ] @@ -1178,7 +1178,7 @@ dependencies = [ "rustls-pki-types", "sha2", "signature", - "untrusted", + "untrusted 0.9.0", ] [[package]] @@ -2290,18 +2290,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "k256" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" -dependencies = [ - "cfg-if", - "ecdsa", - "elliptic-curve", - "sha2", -] - [[package]] name = "keccak" version = "0.1.6" @@ -2475,6 +2463,25 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "mock-tdx" +version = "0.0.1" +dependencies = [ + "dcap-qvl 0.3.12 (git+https://github.com/Phala-Network/dcap-qvl.git?rev=f1dcc65371e941a7b83e3234833d23a1fb232ab1)", + "hex", + "p256", + "parity-scale-codec", + "rcgen 0.14.7", + "serde", + "serde-saphyr", + "serde_bytes", + "serde_json", + "sha2", + "time", + "x509-parser 0.18.1", + "yasna 0.5.2", +] + [[package]] name = "moka" version = "0.12.14" @@ -2749,19 +2756,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "p521" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" -dependencies = [ - "base16ct", - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2", -] - [[package]] name = "parity-scale-codec" version = "3.7.5" @@ -3259,6 +3253,7 @@ version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e" dependencies = [ + "aws-lc-rs", "pem", "ring", "rustls-pki-types", @@ -3393,7 +3388,7 @@ dependencies = [ "cfg-if", "getrandom 0.2.17", "libc", - "untrusted", + "untrusted 0.9.0", "windows-sys 0.52.0", ] @@ -3511,7 +3506,7 @@ dependencies = [ "aws-lc-rs", "ring", "rustls-pki-types", - "untrusted", + "untrusted 0.9.0", ] [[package]] @@ -3969,20 +3964,6 @@ dependencies = [ "vsock", ] -[[package]] -name = "tdx-quote" -version = "0.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a2b5801f4d44185197dffb3a63147bb227e19987b5b93628269625676ab1635" -dependencies = [ - "nom", - "p256", - "pem", - "sha2", - "spki", - "x509-verify", -] - [[package]] name = "tempfile" version = "3.27.0" @@ -4369,6 +4350,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "untrusted" version = "0.9.0" @@ -4932,18 +4919,6 @@ dependencies = [ "spki", ] -[[package]] -name = "x509-ocsp" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e54e695a31f0fecb826cf59ae2093c941d7ef932a1f8508185dd23b29ce2e2e" -dependencies = [ - "const-oid", - "der", - "spki", - "x509-cert", -] - [[package]] name = "x509-parser" version = "0.16.0" @@ -4969,6 +4944,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" dependencies = [ "asn1-rs 0.7.1", + "aws-lc-rs", "data-encoding", "der-parser 10.0.0", "lazy_static", @@ -4980,27 +4956,6 @@ dependencies = [ "time", ] -[[package]] -name = "x509-verify" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43a49bf845cd2f3aff9603a4276409dbf2b8fa4454d3e9501bf5b0028342964" -dependencies = [ - "const-oid", - "der", - "ecdsa", - "ed25519-dalek", - "k256", - "p256", - "p384", - "p521", - "sha2", - "signature", - "spki", - "x509-cert", - "x509-ocsp", -] - [[package]] name = "yansi" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index d168e7d..4f699fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "crates/nested-tls", "crates/attestation", "crates/pccs", + "crates/mock-tdx", ] [workspace.lints.rust] @@ -19,6 +20,7 @@ unused_async = "warn" [workspace.dependencies] attestation = { path = "crates/attestation" } +mock-tdx = { path = "crates/mock-tdx" } rustls = { version = "0.23.37", default-features = false, features = ["brotli"] } tokio = { version = "1.50.0", features = ["default"] } tokio-rustls = { version = "0.26.4", default-features = false } diff --git a/crates/attestation/Cargo.toml b/crates/attestation/Cargo.toml index 81fcb85..e314cbf 100644 --- a/crates/attestation/Cargo.toml +++ b/crates/attestation/Cargo.toml @@ -9,6 +9,7 @@ keywords = ["attestation", "CVM", "TDX"] [dependencies] pccs = { workspace = true } +mock-tdx = { workspace = true, optional = true } tokio = { workspace = true, features = ["fs"] } tokio-rustls = { workspace = true, default-features = false } x509-parser = "0.18.0" @@ -16,7 +17,6 @@ thiserror = "2.0.17" anyhow = "1.0.100" pem-rfc7468 = { version = "0.7.0", features = ["std"] } configfs-tsm = "0.0.2" -rand_core = { version = "0.6.4", features = ["getrandom"] } dcap-qvl = { workspace = true, features = ["danger-allow-tcb-override"] } hex = "0.4.3" http = "1.3.1" @@ -38,12 +38,9 @@ az-tdx-vtpm = { version = "0.7.4", optional = true } tss-esapi = { version = "7.6.0", optional = true } openssl = { version = "0.10.75", optional = true } -# Used by test helpers -tdx-quote = { version = "0.0.5", features = ["mock"], optional = true } - [dev-dependencies] +mock-tdx = { workspace = true } tempfile = "3.23.0" -tdx-quote = { version = "0.0.5", features = ["mock"] } tokio-rustls = { workspace = true, default-features = true } serde-saphyr = "0.0.22" @@ -54,7 +51,7 @@ default = [] azure = ["tss-esapi", "az-tdx-vtpm", "openssl"] # Allows mock quotes used in tests and exposes related functions for testing -mock = ["tdx-quote"] +mock = ["dep:mock-tdx"] [lints] workspace = true diff --git a/crates/attestation/README.md b/crates/attestation/README.md index 6e0320c..acfa30d 100644 --- a/crates/attestation/README.md +++ b/crates/attestation/README.md @@ -37,7 +37,7 @@ must be explicitly enabled via the `override_azure_outdated_tcb` flag on ### `mock` -Enables mock quote support via `tdx-quote` for tests and development on non-TDX +Enables mock quote support via the local `mock-tdx` crate for tests and development on non-TDX hardware. Do not use in production. Disabled by default. ## Attestation Types diff --git a/crates/attestation/src/dcap.rs b/crates/attestation/src/dcap.rs index 36602c9..2aeb431 100644 --- a/crates/attestation/src/dcap.rs +++ b/crates/attestation/src/dcap.rs @@ -7,6 +7,8 @@ use dcap_qvl::{ quote::{Quote, Report}, tcb_info::TcbInfo, }; +#[cfg(any(test, feature = "mock"))] +use mock_tdx::{generate_mock_tdx_quote, load_mock_tdx_material}; use pccs::{Pccs, PccsError}; use thiserror::Error; @@ -130,26 +132,31 @@ pub async fn verify_dcap_attestation( expected_input_data: [u8; 64], _pccs: Option, ) -> Result { - // In tests we use mock quotes which will fail to verify - let quote = tdx_quote::Quote::from_bytes(&input)?; - if quote.report_input_data() != expected_input_data { + let quote = Quote::parse(&input)?; + let material = load_mock_tdx_material().map_err(|error| anyhow::anyhow!(error.to_string()))?; + let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)?.as_secs(); + let verifier = dcap_qvl::verify::QuoteVerifier::new( + material.root_ca_der, + dcap_qvl::verify::rustcrypto::backend(), + ); + verifier.verify(&input, &material.collateral, now)?; + + let measurements = MultiMeasurements::from_dcap_qvl_quote("e)?; + if get_quote_input_data(quote.report) != expected_input_data { return Err(DcapVerificationError::InputMismatch); } - Ok(MultiMeasurements::from_tdx_quote("e)) + + Ok(measurements) } /// Create a mock quote for testing on non-confidential hardware #[cfg(any(test, feature = "mock"))] fn generate_quote(input: [u8; 64]) -> Result, QuoteGenerationError> { - let attestation_key = tdx_quote::SigningKey::random(&mut rand_core::OsRng); - let provisioning_certification_key = tdx_quote::SigningKey::random(&mut rand_core::OsRng); - Ok(tdx_quote::Quote::mock( - attestation_key.clone(), - provisioning_certification_key.clone(), - input, - b"Mock cert chain".to_vec(), - ) - .as_bytes()) + generate_mock_tdx_quote(input).map_err(|error| { + QuoteGenerationError::IO(std::io::Error::other(format!( + "mock-tdx quote generation: {error}" + ))) + }) } /// Create a quote @@ -178,9 +185,6 @@ pub enum DcapVerificationError { SystemTime(#[from] std::time::SystemTimeError), #[error("DCAP quote verification: {0}")] DcapQvl(#[from] anyhow::Error), - #[cfg(any(test, feature = "mock"))] - #[error("Quote parse: {0}")] - QuoteParse(#[from] tdx_quote::QuoteParseError), #[error("PCCS: {0}")] Pccs(#[from] PccsError), #[error("Timestamp exceeds i64 range")] diff --git a/crates/attestation/src/lib.rs b/crates/attestation/src/lib.rs index efad45c..4208663 100644 --- a/crates/attestation/src/lib.rs +++ b/crates/attestation/src/lib.rs @@ -55,19 +55,9 @@ impl AttestationExchangeMessage { } } _ => { - #[cfg(any(test, feature = "mock"))] - { - let quote = tdx_quote::Quote::from_bytes(&self.attestation) - .map_err(DcapVerificationError::from)?; - Ok(Some(MultiMeasurements::from_tdx_quote("e))) - } - - #[cfg(not(any(test, feature = "mock")))] - { - let quote = dcap_qvl::verify::Quote::parse(&self.attestation) - .map_err(DcapVerificationError::from)?; - Ok(Some(MultiMeasurements::from_dcap_qvl_quote("e)?)) - } + let quote = dcap_qvl::verify::Quote::parse(&self.attestation) + .map_err(DcapVerificationError::from)?; + Ok(Some(MultiMeasurements::from_dcap_qvl_quote("e)?)) } } } diff --git a/crates/attestation/src/measurements.rs b/crates/attestation/src/measurements.rs index 97759f0..ae79ea7 100644 --- a/crates/attestation/src/measurements.rs +++ b/crates/attestation/src/measurements.rs @@ -217,31 +217,20 @@ impl MultiMeasurements { ]))) } - #[cfg(any(test, feature = "mock"))] - pub fn from_tdx_quote(quote: &tdx_quote::Quote) -> Self { - Self::Dcap(HashMap::from([ - (DcapMeasurementRegister::MRTD, quote.mrtd()), - (DcapMeasurementRegister::RTMR0, quote.rtmr0()), - (DcapMeasurementRegister::RTMR1, quote.rtmr1()), - (DcapMeasurementRegister::RTMR2, quote.rtmr2()), - (DcapMeasurementRegister::RTMR3, quote.rtmr3()), - ])) - } - pub fn from_pcrs<'a>(pcrs: impl Iterator) -> Self { Self::Azure(pcrs.copied().enumerate().map(|(index, value)| (index as u32, value)).collect()) } } -/// All-zero measurement values used in some tests +/// Mock TDX measurement values used in tests #[cfg(any(test, feature = "mock"))] pub fn mock_dcap_measurements() -> MultiMeasurements { MultiMeasurements::Dcap(HashMap::from([ - (DcapMeasurementRegister::MRTD, [0u8; 48]), - (DcapMeasurementRegister::RTMR0, [0u8; 48]), - (DcapMeasurementRegister::RTMR1, [0u8; 48]), - (DcapMeasurementRegister::RTMR2, [0u8; 48]), - (DcapMeasurementRegister::RTMR3, [0u8; 48]), + (DcapMeasurementRegister::MRTD, mock_tdx::MOCK_MRTD), + (DcapMeasurementRegister::RTMR0, mock_tdx::MOCK_RTMR0), + (DcapMeasurementRegister::RTMR1, mock_tdx::MOCK_RTMR1), + (DcapMeasurementRegister::RTMR2, mock_tdx::MOCK_RTMR2), + (DcapMeasurementRegister::RTMR3, mock_tdx::MOCK_RTMR3), ])) } @@ -369,11 +358,11 @@ impl MeasurementPolicy { accepted_measurements: vec![MeasurementRecord { measurement_id: "test".to_string(), measurements: ExpectedMeasurements::Dcap(HashMap::from([ - (DcapMeasurementRegister::MRTD, vec![[0; 48]]), - (DcapMeasurementRegister::RTMR0, vec![[0; 48]]), - (DcapMeasurementRegister::RTMR1, vec![[0; 48]]), - (DcapMeasurementRegister::RTMR2, vec![[0; 48]]), - (DcapMeasurementRegister::RTMR3, vec![[0; 48]]), + (DcapMeasurementRegister::MRTD, vec![mock_tdx::MOCK_MRTD]), + (DcapMeasurementRegister::RTMR0, vec![mock_tdx::MOCK_RTMR0]), + (DcapMeasurementRegister::RTMR1, vec![mock_tdx::MOCK_RTMR1]), + (DcapMeasurementRegister::RTMR2, vec![mock_tdx::MOCK_RTMR2]), + (DcapMeasurementRegister::RTMR3, vec![mock_tdx::MOCK_RTMR3]), ])), }], } diff --git a/crates/mock-tdx/Cargo.toml b/crates/mock-tdx/Cargo.toml new file mode 100644 index 0000000..53c5160 --- /dev/null +++ b/crates/mock-tdx/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "mock-tdx" +version = "0.0.1" +edition = "2024" +publish = false + +[dependencies] +dcap-qvl = { workspace = true } +hex = "0.4.3" +p256 = { version = "0.13.2", features = ["ecdsa", "pkcs8", "pem"] } +rcgen = { version = "0.14.7", features = ["pem", "crypto", "aws_lc_rs"] } +scale = { package = "parity-scale-codec", version = "3.7.5", features = ["derive"] } +serde = { version = "1.0.228", features = ["derive"] } +serde_bytes = "0.11.19" +serde_json = "1.0.145" +serde-saphyr = "0.0.22" +sha2 = "0.10.9" +time = { version = "0.3.47", features = ["formatting", "macros", "parsing"] } +yasna = "0.5.2" +x509-parser = "0.18.1" + +[lints] +workspace = true diff --git a/crates/mock-tdx/README.md b/crates/mock-tdx/README.md new file mode 100644 index 0000000..023c225 --- /dev/null +++ b/crates/mock-tdx/README.md @@ -0,0 +1,24 @@ +# mock-tdx + +`mock-tdx` generates deterministic mock TDX DCAP artifacts for tests and +development on non-TDX hardware. + +It provides: + +- a small fixture generator for a mock DCAP trust chain and collateral +- a quote generator that emits mock TDX DCAP quotes with caller-supplied + `report_data` +- checked-in mock collateral, root certificates, CRLs, and signing material + under `test-assets/generated-dcap` + +The generated quotes are shaped so they can be parsed and verified with +`dcap-qvl` using the mock root of trust bundled with this crate. + +This crate is intended for tests and local development. It does not provide +production attestation material. + +To refresh the checked-in fixtures: + +```bash +cargo run -p mock-tdx -- refresh-dcap-fixtures +``` diff --git a/crates/mock-tdx/src/lib.rs b/crates/mock-tdx/src/lib.rs new file mode 100644 index 0000000..3f9f81d --- /dev/null +++ b/crates/mock-tdx/src/lib.rs @@ -0,0 +1,356 @@ +use dcap_qvl::{ + QuoteCollateralV3, + quote::{ + AuthData, AuthDataV4, CertificationData, Data, EnclaveReport, Header, + QEReportCertificationData, Quote, Report, TDReport10, + }, +}; +use p256::{ + ecdsa::{Signature, SigningKey, signature::Signer}, + pkcs8::DecodePrivateKey, +}; +use scale::Encode; +use serde::Serialize; +use sha2::Digest; + +/// Embedded collateral fixture contents +const EMBEDDED_COLLATERAL_YAML: &str = + include_str!("../test-assets/generated-dcap/mock-dcap-collateral.yaml"); +/// Embedded manifest fixture contents +const EMBEDDED_MANIFEST_JSON: &str = + include_str!("../test-assets/generated-dcap/mock-dcap-manifest.json"); +/// Embedded root CA DER contents +const EMBEDDED_ROOT_CA_DER: &[u8] = + include_bytes!("../test-assets/generated-dcap/mock-root-ca.der"); +/// Embedded PCK private key PEM contents +const EMBEDDED_PCK_KEY_PEM: &str = include_str!("../test-assets/generated-dcap/mock-pck-key.pem"); +/// Embedded PCK chain PEM contents +const EMBEDDED_PCK_CHAIN_PEM: &str = + include_str!("../test-assets/generated-dcap/mock-pck-chain.pem"); + +/// Deterministic attestation secret key bytes +const ATTESTATION_SK: [u8; 32] = [0x55; 32]; +/// TDX quote version used by the mock builder +const QUOTE_VERSION: u16 = 4; +/// ECDSA P-256 attestation key type +const ATTESTATION_KEY_TYPE_ECDSA256_WITH_P256_CURVE: u16 = 2; +/// TDX tee type +const TEE_TYPE_TDX: u32 = 0x00000081; +/// Intel QE vendor ID +const INTEL_QE_VENDOR_ID: [u8; 16] = [ + 0x93, 0x9A, 0x72, 0x33, 0xF7, 0x9C, 0x4C, 0xA9, 0x94, 0x0A, 0x0D, 0xB3, 0x95, 0x7F, 0x06, 0x07, +]; +/// Certification data type for a PCK chain +const PCK_CERT_CHAIN: u16 = 5; +/// Outer certification data type for a QE report block +const QE_REPORT_CERT: u16 = 6; +/// Fixed quote signature length +const ECDSA_SIGNATURE_BYTE_LEN: usize = 64; +/// Fixed attestation public key length +const ECDSA_PUBKEY_BYTE_LEN: usize = 64; +/// Fixed QE report length +const ENCLAVE_REPORT_BYTE_LEN: usize = 384; + +/// Mock QE miscselect value +const QE_MISCSELECT: [u8; 4] = [0; 4]; +/// Mock QE attributes value +const QE_ATTRIBUTES: [u8; 16] = [0; 16]; +/// Mock QE mrsigner value +const QE_MRSIGNER: [u8; 32] = [0x5a; 32]; +/// Mock QE ISV product ID +const QE_ISVPRODID: u16 = 2; +/// Mock QE ISV SVN +const QE_ISVSVN: u16 = 11; +/// Mock QE auth data +const QE_AUTH_DATA: [u8; 32] = [0xa5; 32]; +/// Mock TD TCB SVN value for all components +const TDX_TCB_SVN: [u8; 16] = [1; 16]; +/// Mock TD attributes with SEPT_VE_DISABLE enabled +const TD_ATTRIBUTES: [u8; 8] = [0, 0, 0, 0x10, 0, 0, 0, 0]; +/// Mock MRTD value used in generated mock TDX quotes +pub const MOCK_MRTD: [u8; 48] = [0x10; 48]; +/// Mock RTMR0 value used in generated mock TDX quotes +pub const MOCK_RTMR0: [u8; 48] = [0x50; 48]; +/// Mock RTMR1 value used in generated mock TDX quotes +pub const MOCK_RTMR1: [u8; 48] = [0x60; 48]; +/// Mock RTMR2 value used in generated mock TDX quotes +pub const MOCK_RTMR2: [u8; 48] = [0x70; 48]; +/// Mock RTMR3 value used in generated mock TDX quotes +pub const MOCK_RTMR3: [u8; 48] = [0x80; 48]; + +/// Mock TDX material loaded from generated assets +pub struct MockTdxMaterial { + /// Quote collateral used to verify generated mock quotes + pub collateral: QuoteCollateralV3, + /// Mock root CA in DER form + pub root_ca_der: Vec, + /// Mock PCK signing key + pub pck_signing_key: SigningKey, + /// Mock PCK certificate chain in PEM form + pub pck_chain_pem: String, + /// Manifest describing the generated mock platform + pub manifest: FixtureManifest, +} + +/// Load the generated mock TDX material from the workspace fixture +/// directory +pub fn load_mock_tdx_material() -> Result> { + let collateral: QuoteCollateralV3 = serde_saphyr::from_str(EMBEDDED_COLLATERAL_YAML)?; + let root_ca_der = EMBEDDED_ROOT_CA_DER.to_vec(); + let pck_signing_key = SigningKey::from_pkcs8_pem(EMBEDDED_PCK_KEY_PEM)?; + let pck_chain_pem = EMBEDDED_PCK_CHAIN_PEM.to_string(); + let manifest: FixtureManifest = serde_json::from_str(EMBEDDED_MANIFEST_JSON)?; + + Ok(MockTdxMaterial { collateral, root_ca_der, pck_signing_key, pck_chain_pem, manifest }) +} + +/// Construct a p256 signing key from deterministic secret key bytes +pub(crate) fn signing_key_from_secret( + secret: [u8; 32], +) -> Result> { + Ok(SigningKey::from_bytes((&secret).into())?) +} + +/// Generate a mock TDX DCAP quote using the generated fixture material +pub fn generate_mock_tdx_quote( + report_data: [u8; 64], +) -> Result, Box> { + let material = load_mock_tdx_material()?; + generate_mock_tdx_quote_from_material(&material, report_data) +} + +/// Generate a mock TDX DCAP quote from a specific loaded material set +pub fn generate_mock_tdx_quote_from_material( + material: &MockTdxMaterial, + report_data: [u8; 64], +) -> Result, Box> { + let attestation_key = signing_key_from_secret(ATTESTATION_SK)?; + let attestation_pubkey = raw_public_key(&attestation_key); + + let qe_report = build_qe_report(&attestation_pubkey)?; + let qe_report_signature = sign_fixed_p256(&material.pck_signing_key, &qe_report); + + let outer_certification_data = + CertificationData { cert_type: QE_REPORT_CERT, body: Data::new(Vec::new()) }; + let inner_certification_data = CertificationData { + cert_type: PCK_CERT_CHAIN, + body: Data::new(material.pck_chain_pem.as_bytes().to_vec()), + }; + + let auth_data = AuthData::V4(AuthDataV4 { + ecdsa_signature: [0; ECDSA_SIGNATURE_BYTE_LEN], + ecdsa_attestation_key: attestation_pubkey, + certification_data: outer_certification_data, + qe_report_data: QEReportCertificationData { + qe_report, + qe_report_signature, + qe_auth_data: Data::new(QE_AUTH_DATA.to_vec()), + certification_data: inner_certification_data, + }, + }); + + let mut quote = Quote { + header: Header { + version: QUOTE_VERSION, + attestation_key_type: ATTESTATION_KEY_TYPE_ECDSA256_WITH_P256_CURVE, + tee_type: TEE_TYPE_TDX, + qe_svn: QE_ISVSVN, + pce_svn: material.manifest.pce_svn, + qe_vendor_id: INTEL_QE_VENDOR_ID, + user_data: [0; 20], + }, + report: Report::TD10(TDReport10 { + tee_tcb_svn: TDX_TCB_SVN, + mr_seam: [0; 48], + mr_signer_seam: [0; 48], + seam_attributes: [0; 8], + td_attributes: TD_ATTRIBUTES, + xfam: [0; 8], + mr_td: MOCK_MRTD, + mr_config_id: [0x20; 48], + mr_owner: [0x30; 48], + mr_owner_config: [0x40; 48], + rt_mr0: MOCK_RTMR0, + rt_mr1: MOCK_RTMR1, + rt_mr2: MOCK_RTMR2, + rt_mr3: MOCK_RTMR3, + report_data, + }), + auth_data, + }; + + let signed_scope = signed_quote_scope("e); + let auth_data = match &mut quote.auth_data { + AuthData::V4(auth_data) => auth_data, + AuthData::V3(_) => return Err("unexpected auth data version".into()), + }; + auth_data.ecdsa_signature = sign_fixed_p256(&attestation_key, &signed_scope); + + Ok(quote.encode()) +} + +/// Sign raw bytes with ECDSA P-256 and return a fixed-size compact +/// signature +fn sign_fixed_p256(signing_key: &SigningKey, bytes: &[u8]) -> [u8; ECDSA_SIGNATURE_BYTE_LEN] { + sign_raw_p256(signing_key, bytes).try_into().expect("p256 compact signature") +} + +/// Sign raw bytes with ECDSA P-256 and return the compact r||s form +pub(crate) fn sign_raw_p256(signing_key: &SigningKey, bytes: &[u8]) -> Vec { + let signature: Signature = signing_key.sign(bytes); + signature.to_bytes().to_vec() +} + +/// Build the QE report for the mock quote +fn build_qe_report( + attestation_pubkey: &[u8; ECDSA_PUBKEY_BYTE_LEN], +) -> Result<[u8; ENCLAVE_REPORT_BYTE_LEN], Box> { + let qe_hash = + sha2::Sha256::digest([attestation_pubkey.as_slice(), QE_AUTH_DATA.as_slice()].concat()); + let mut qe_report_data = [0u8; 64]; + qe_report_data[..32].copy_from_slice(&qe_hash); + + let qe_report = EnclaveReport { + cpu_svn: [0; 16], + misc_select: u32::from_le_bytes(QE_MISCSELECT), + reserved1: [0; 28], + attributes: QE_ATTRIBUTES, + mr_enclave: [0; 32], + reserved2: [0; 32], + mr_signer: QE_MRSIGNER, + reserved3: [0; 96], + isv_prod_id: QE_ISVPRODID, + isv_svn: QE_ISVSVN, + reserved4: [0; 60], + report_data: qe_report_data, + }; + + Ok(scale::Encode::encode(&qe_report).try_into().map_err(|_| "unexpected QE report length")?) +} + +/// Encode the raw uncompressed P-256 public key without the SEC1 prefix +/// byte +fn raw_public_key(signing_key: &SigningKey) -> [u8; ECDSA_PUBKEY_BYTE_LEN] { + let encoded = signing_key.verifying_key().to_encoded_point(false); + encoded.as_bytes()[1..].try_into().expect("uncompressed p256 public key") +} + +/// Return the quote bytes that are covered by the ISV report signature +fn signed_quote_scope(quote: &Quote) -> Vec { + let mut encoded = scale::Encode::encode(quote); + encoded.truncate(quote.signed_length()); + encoded +} + +/// Summary manifest written alongside generated fixture files +#[derive(Serialize, serde::Deserialize)] +pub struct FixtureManifest { + /// Mock platform FMSPC encoded as uppercase hex + pub fmspc: String, + /// Mock platform PCE ID encoded as uppercase hex + pub pce_id_hex: String, + /// Mock platform PCE SVN + pub pce_svn: u16, + /// Mock platform CPU SVN encoded as uppercase hex + pub cpu_svn_hex: String, + /// Mock platform PPID encoded as uppercase hex + pub ppid_hex: String, + /// Mock QE ISV SVN + pub qe_isvsvn: u16, + /// Human-readable issuer names embedded in the generated certificates + pub issuer_common_names: IssuerNames, + /// Generated fixture filenames + pub files: OutputFiles, +} + +/// Human-readable issuer names captured in the manifest +#[derive(Serialize, serde::Deserialize)] +pub struct IssuerNames { + /// Root CA common name + pub root_ca: String, + /// TCB signing CA common name + pub tcb_signing_ca: String, + /// TCB signer common name + pub tcb_signer: String, + /// PCK certificate common name + pub pck: String, +} + +/// File inventory for the generated fixture set +#[derive(Serialize, serde::Deserialize)] +pub struct OutputFiles { + /// Quote collateral fixture filename + pub collateral: String, + /// Manifest filename + pub manifest: String, + /// Root CA DER filename + pub root_ca_der: String, + /// Root CA PEM filename + pub root_ca_pem: String, + /// Root CA private key PEM filename + pub root_ca_key_pem: String, + /// TCB signer chain PEM filename + pub tcb_signer_chain_pem: String, + /// PCK chain PEM filename + pub pck_chain_pem: String, + /// PCK private key PEM filename + pub pck_key_pem: String, + /// Root CA CRL DER filename + pub root_crl_der: String, + /// PCK CRL DER filename + pub pck_crl_der: String, + /// TCB info JSON filename + pub tcb_info_json: String, + /// QE identity JSON filename + pub qe_identity_json: String, +} + +#[cfg(test)] +mod tests { + use super::*; + + const FIXTURE_TIME: u64 = 1_767_225_601; + + #[test] + fn builds_quote_that_parses_and_verifies() { + let material = load_mock_tdx_material().unwrap(); + let report_data = [0xAB; 64]; + + let quote_bytes = generate_mock_tdx_quote_from_material(&material, report_data).unwrap(); + let quote = Quote::parse("e_bytes).unwrap(); + assert_eq!(quote.header.version, QUOTE_VERSION); + assert_eq!(quote.header.tee_type, TEE_TYPE_TDX); + assert_eq!(hex::encode_upper(quote.fmspc().unwrap()), material.manifest.fmspc); + assert_eq!(quote.ca().unwrap(), "processor"); + + let verifier = dcap_qvl::verify::QuoteVerifier::new( + material.root_ca_der.clone(), + dcap_qvl::verify::rustcrypto::backend(), + ); + let verified = verifier.verify("e_bytes, &material.collateral, FIXTURE_TIME).unwrap(); + let dcap_qvl::quote::Report::TD10(report) = verified.report else { + panic!("expected TD10 report"); + }; + assert_eq!(report.report_data, report_data); + assert_eq!(report.mr_td, MOCK_MRTD); + assert_eq!(report.rt_mr0, MOCK_RTMR0); + } + + #[test] + fn tampered_quote_signature_fails_verification() { + const HEADER_BYTE_LEN: usize = 48; + const TD_REPORT10_BYTE_LEN: usize = 584; + const AUTH_DATA_SIZE_BYTE_LEN: usize = 4; + + let material = load_mock_tdx_material().unwrap(); + let mut quote_bytes = generate_mock_tdx_quote_from_material(&material, [0xCD; 64]).unwrap(); + let signature_offset = HEADER_BYTE_LEN + TD_REPORT10_BYTE_LEN + AUTH_DATA_SIZE_BYTE_LEN; + quote_bytes[signature_offset] ^= 0x01; + + let verifier = dcap_qvl::verify::QuoteVerifier::new( + material.root_ca_der.clone(), + dcap_qvl::verify::rustcrypto::backend(), + ); + assert!(verifier.verify("e_bytes, &material.collateral, FIXTURE_TIME).is_err()); + } +} diff --git a/crates/mock-tdx/src/main.rs b/crates/mock-tdx/src/main.rs new file mode 100644 index 0000000..df3f06c --- /dev/null +++ b/crates/mock-tdx/src/main.rs @@ -0,0 +1,475 @@ +use mock_tdx::{FixtureManifest, IssuerNames, OutputFiles}; + +use dcap_qvl::{ + QuoteCollateralV3, + intel::{PckExtension, parse_pck_extension}, + tcb_info::{Tcb, TcbComponents, TcbInfo, TcbLevel, TcbStatus}, +}; +use p256::{ + SecretKey, + ecdsa::{Signature, SigningKey, signature::Signer}, + pkcs8::EncodePrivateKey, +}; + +use rcgen::{ + BasicConstraints, CertificateParams, CertificateRevocationListParams, CertifiedIssuer, + CustomExtension, DnType, IsCa, KeyIdMethod, KeyPair, KeyUsagePurpose, SerialNumber, +}; +use std::{ + fs::{self, write}, + path::PathBuf, +}; +use time::{Date, Month, OffsetDateTime, PrimitiveDateTime, Time}; + +/// Default output directory for generated mock DCAP fixtures +const OUTPUT_DIR: &str = "crates/mock-tdx/test-assets/generated-dcap"; +/// Serialized collateral fixture filename +const COLLATERAL_BASENAME: &str = "mock-dcap-collateral.yaml"; +/// Mock platform manifest filename +const MANIFEST_BASENAME: &str = "mock-dcap-manifest.json"; +/// Root CA DER filename +const ROOT_CA_DER_BASENAME: &str = "mock-root-ca.der"; +/// Root CA PEM filename +const ROOT_CA_PEM_BASENAME: &str = "mock-root-ca.pem"; +/// Root CA private key PEM filename +const ROOT_CA_KEY_BASENAME: &str = "mock-root-ca-key.pem"; +/// TCB signer issuer chain PEM filename +const TCB_SIGNER_CHAIN_BASENAME: &str = "mock-tcb-signer-chain.pem"; +/// PCK chain PEM filename +const PCK_CHAIN_BASENAME: &str = "mock-pck-chain.pem"; +/// PCK private key PEM filename +const PCK_KEY_BASENAME: &str = "mock-pck-key.pem"; +/// Root CA CRL DER filename +const ROOT_CRL_DER_BASENAME: &str = "mock-root-ca.crl.der"; +/// PCK CRL DER filename +const PCK_CRL_DER_BASENAME: &str = "mock-pck.crl.der"; +/// TCB info JSON filename +const TCB_INFO_JSON_BASENAME: &str = "mock-tcb-info.json"; +/// QE identity JSON filename +const QE_IDENTITY_JSON_BASENAME: &str = "mock-qe-identity.json"; + +/// Deterministic PCK secret key bytes +const PCK_SK: [u8; 32] = [0x44; 32]; + +/// Deterministic root CA secret key bytes +const ROOT_CA_SK: [u8; 32] = [0x11; 32]; +/// Common name used for the mock root CA +const ROOT_CA_CN: &str = "Mock Intel Root CA"; +/// Common name used for the mock TCB signing CA +const TCB_CA_CN: &str = "Mock Intel TCB Signing CA"; +/// Common name used for the mock TCB signer +const TCB_SIGNER_CN: &str = "Mock Intel TCB Signer"; +/// Common name used for the mock PCK certificate +const PCK_CN: &str = "Mock Intel PCK"; + +/// Intel SGX extension OID for the mock PCK cert +const PCK_EXTENSION_OID: &[u64] = &[1, 2, 840, 113741, 1, 13, 1]; +/// Deterministic TCB CA secret key bytes +const TCB_CA_SK: [u8; 32] = [0x22; 32]; +/// Deterministic TCB signer secret key bytes +const TCB_SIGNER_SK: [u8; 32] = [0x33; 32]; + +/// Mock QE miscselect value +const QE_MISCSELECT: [u8; 4] = [0; 4]; +/// Mock QE miscselect mask +const QE_MISCSELECT_MASK: [u8; 4] = [0xff; 4]; +/// Mock QE attributes value +const QE_ATTRIBUTES: [u8; 16] = [0; 16]; +/// Mock QE attributes mask +const QE_ATTRIBUTES_MASK: [u8; 16] = [0xff; 16]; +/// Mock QE mrsigner value +const QE_MRSIGNER: [u8; 32] = [0x5a; 32]; +/// Mock QE ISV product ID +const QE_ISVPRODID: u16 = 2; +/// Mock QE ISV SVN +const QE_ISVSVN: u16 = 11; +/// Mock TD TCB SVN value for all components +const TDX_TCB_SVN: [u8; 16] = [1; 16]; + +/// Fixed validity window used for checked-in mock fixtures +struct ValidityWindow { + not_before: OffsetDateTime, + not_after: OffsetDateTime, +} + +/// Return the stable validity range used by generated mock fixtures +fn validity_window() -> ValidityWindow { + let not_before = PrimitiveDateTime::new( + Date::from_calendar_date(2025, Month::January, 1).expect("valid start date"), + Time::MIDNIGHT, + ) + .assume_utc(); + let not_after = PrimitiveDateTime::new( + Date::from_calendar_date(2045, Month::January, 1).expect("valid end date"), + Time::MIDNIGHT, + ) + .assume_utc(); + ValidityWindow { not_before, not_after } +} + +/// Construct an rcgen keypair from deterministic secret key bytes +fn key_pair_from_secret(secret: [u8; 32]) -> Result> { + let pkcs8 = SecretKey::from_slice(&secret)?.to_pkcs8_pem(Default::default())?; + Ok(KeyPair::from_pkcs8_pem_and_sign_algo(pkcs8.as_str(), &rcgen::PKCS_ECDSA_P256_SHA256)?) +} + +/// Construct a p256 signing key from deterministic secret key bytes +fn signing_key_from_secret(secret: [u8; 32]) -> Result> { + Ok(SigningKey::from_bytes((&secret).into())?) +} + +/// Sign raw bytes with ECDSA P-256 and return the compact r||s form +fn sign_raw_p256(signing_key: &SigningKey, bytes: &[u8]) -> Vec { + let signature: Signature = signing_key.sign(bytes); + signature.to_bytes().to_vec() +} + +/// Known-good Intel SGX extension DER reused for the mock PCK cert +/// +/// This blob was copied from `dcap-qvl`'s `tests/generate_test_certs.sh` +/// where it is embedded as the OpenSSL DER payload for OID +/// `1.2.840.113741.1.13.1` +const VALID_PCK_EXTENSION_HEX: &str = "308201C1301E060A2A864886F84D010D01010410D04EC06D4E6D92DC90D0AD3CF5EE2DDF30820164060A2A864886F84D010D0102308201543010060B2A864886F84D010D01020102010B3010060B2A864886F84D010D01020202010B3010060B2A864886F84D010D0102030201023010060B2A864886F84D010D0102040201023011060B2A864886F84D010D010205020200FF3010060B2A864886F84D010D0102060201013010060B2A864886F84D010D0102070201003010060B2A864886F84D010D0102080201003010060B2A864886F84D010D0102090201003010060B2A864886F84D010D01020A0201003010060B2A864886F84D010D01020B0201003010060B2A864886F84D010D01020C0201003010060B2A864886F84D010D01020D0201003010060B2A864886F84D010D01020E0201003010060B2A864886F84D010D01020F0201003010060B2A864886F84D010D0102100201003010060B2A864886F84D010D01021102010D301F060B2A864886F84D010D01021204100B0B0202FF01000000000000000000003010060A2A864886F84D010D0103040200003014060A2A864886F84D010D0104040600906EA10000300F060A2A864886F84D010D01050A0100"; +/// Return a known-good Intel SGX extension DER payload for the mock +/// PCK cert +fn intel_sgx_extension_der() -> Vec { + hex::decode(VALID_PCK_EXTENSION_HEX).expect("valid extension hex") +} + +/// Generate the full mock PKI and collateral fixture set into the +/// target directory +fn refresh_dcap_fixtures() -> Result<(), Box> { + let output_dir = &workspace_root().join(OUTPUT_DIR); + fs::create_dir_all(output_dir)?; + + let validity = validity_window(); + let root_params = ca_params(ROOT_CA_CN, validity.not_before, validity.not_after, 1)?; + let root_key = key_pair_from_secret(ROOT_CA_SK)?; + let root = CertifiedIssuer::self_signed(root_params, root_key)?; + + let tcb_ca_params = ca_params(TCB_CA_CN, validity.not_before, validity.not_after, 2)?; + let tcb_ca_key = key_pair_from_secret(TCB_CA_SK)?; + let tcb_ca = CertifiedIssuer::signed_by(tcb_ca_params, tcb_ca_key, &root)?; + + let tcb_signer_key = key_pair_from_secret(TCB_SIGNER_SK)?; + let tcb_signer_params = + end_entity_params(TCB_SIGNER_CN, validity.not_before, validity.not_after, 3)?; + let tcb_signer = tcb_signer_params.signed_by(&tcb_signer_key, &tcb_ca)?; + + let pck_key = key_pair_from_secret(PCK_SK)?; + let mut pck_params = end_entity_params(PCK_CN, validity.not_before, validity.not_after, 4)?; + pck_params + .custom_extensions + .push(CustomExtension::from_oid_content(PCK_EXTENSION_OID, intel_sgx_extension_der())); + let pck_cert = pck_params.signed_by(&pck_key, &root)?; + let pck_extension = parse_pck_extension(pck_cert.der().as_ref())?; + + let root_crl = CertificateRevocationListParams { + this_update: validity.not_before, + next_update: validity.not_after, + crl_number: SerialNumber::from(1_u64), + issuing_distribution_point: None, + revoked_certs: Vec::new(), + key_identifier_method: KeyIdMethod::Sha256, + } + .signed_by(&root)?; + let pck_crl = CertificateRevocationListParams { + this_update: validity.not_before, + next_update: validity.not_after, + crl_number: SerialNumber::from(2_u64), + issuing_distribution_point: None, + revoked_certs: Vec::new(), + key_identifier_method: KeyIdMethod::Sha256, + } + .signed_by(&tcb_ca)?; + + let tcb_info = mock_tcb_info(validity.not_before, validity.not_after, &pck_extension); + let qe_identity = mock_qe_identity(validity.not_before, validity.not_after); + let tcb_info_json = serde_json::to_string(&tcb_info)?; + let qe_identity_json = serde_json::to_string(&qe_identity)?; + let tcb_signing_key = signing_key_from_secret(TCB_SIGNER_SK)?; + let tcb_info_signature = sign_raw_p256(&tcb_signing_key, tcb_info_json.as_bytes()); + let qe_identity_signature = sign_raw_p256(&tcb_signing_key, qe_identity_json.as_bytes()); + + let pck_chain_pem = format!("{}{}", pck_cert.pem(), root.pem()); + let tcb_signer_chain_pem = format!("{}{}{}", tcb_signer.pem(), tcb_ca.pem(), root.pem()); + + let collateral = QuoteCollateralV3 { + pck_crl_issuer_chain: format!("{}{}", tcb_ca.pem(), root.pem()), + root_ca_crl: root_crl.der().to_vec(), + pck_crl: pck_crl.der().to_vec(), + tcb_info_issuer_chain: tcb_signer_chain_pem.clone(), + tcb_info: tcb_info_json.clone(), + tcb_info_signature, + qe_identity_issuer_chain: tcb_signer_chain_pem.clone(), + qe_identity: qe_identity_json.clone(), + qe_identity_signature, + pck_certificate_chain: Some(pck_chain_pem.clone()), + }; + + validate_fixture_set( + &collateral, + &pck_cert, + &tcb_signer, + &tcb_info, + &qe_identity, + &pck_extension, + )?; + + let manifest = FixtureManifest { + fmspc: hex::encode_upper(pck_extension.fmspc), + pce_id_hex: hex::encode_upper(&pck_extension.pce_id), + pce_svn: pck_extension.pce_svn, + cpu_svn_hex: hex::encode_upper(pck_extension.cpu_svn), + ppid_hex: hex::encode_upper(&pck_extension.ppid), + qe_isvsvn: QE_ISVSVN, + issuer_common_names: IssuerNames { + root_ca: ROOT_CA_CN.to_string(), + tcb_signing_ca: TCB_CA_CN.to_string(), + tcb_signer: TCB_SIGNER_CN.to_string(), + pck: PCK_CN.to_string(), + }, + files: OutputFiles { + collateral: COLLATERAL_BASENAME.to_string(), + manifest: MANIFEST_BASENAME.to_string(), + root_ca_der: ROOT_CA_DER_BASENAME.to_string(), + root_ca_pem: ROOT_CA_PEM_BASENAME.to_string(), + root_ca_key_pem: ROOT_CA_KEY_BASENAME.to_string(), + tcb_signer_chain_pem: TCB_SIGNER_CHAIN_BASENAME.to_string(), + pck_chain_pem: PCK_CHAIN_BASENAME.to_string(), + pck_key_pem: PCK_KEY_BASENAME.to_string(), + root_crl_der: ROOT_CRL_DER_BASENAME.to_string(), + pck_crl_der: PCK_CRL_DER_BASENAME.to_string(), + tcb_info_json: TCB_INFO_JSON_BASENAME.to_string(), + qe_identity_json: QE_IDENTITY_JSON_BASENAME.to_string(), + }, + }; + + write(output_dir.join(COLLATERAL_BASENAME), serde_saphyr::to_string(&collateral)?)?; + write(output_dir.join(MANIFEST_BASENAME), serde_json::to_string_pretty(&manifest)?)?; + write(output_dir.join(ROOT_CA_DER_BASENAME), root.der().as_ref())?; + write(output_dir.join(ROOT_CA_PEM_BASENAME), root.pem())?; + write( + output_dir.join(ROOT_CA_KEY_BASENAME), + key_pair_from_secret(ROOT_CA_SK)?.serialize_pem(), + )?; + write(output_dir.join(TCB_SIGNER_CHAIN_BASENAME), tcb_signer_chain_pem)?; + write(output_dir.join(PCK_CHAIN_BASENAME), pck_chain_pem)?; + write( + output_dir.join(PCK_KEY_BASENAME), + SecretKey::from_slice(&PCK_SK)?.to_pkcs8_pem(Default::default())?, + )?; + write(output_dir.join(ROOT_CRL_DER_BASENAME), root_crl.der().as_ref())?; + write(output_dir.join(PCK_CRL_DER_BASENAME), pck_crl.der().as_ref())?; + write(output_dir.join(TCB_INFO_JSON_BASENAME), format!("{tcb_info_json}\n"))?; + write(output_dir.join(QE_IDENTITY_JSON_BASENAME), format!("{qe_identity_json}\n"))?; + + Ok(()) +} + +/// Build mock TCB info aligned with the generated PCK extension values +fn mock_tcb_info( + not_before: OffsetDateTime, + not_after: OffsetDateTime, + pck_extension: &PckExtension, +) -> TcbInfo { + TcbInfo { + id: "TDX".to_string(), + version: 3, + issue_date: not_before.format(&time::format_description::well_known::Rfc3339).unwrap(), + next_update: not_after.format(&time::format_description::well_known::Rfc3339).unwrap(), + fmspc: hex::encode_upper(pck_extension.fmspc), + pce_id: hex::encode_upper(&pck_extension.pce_id), + tcb_type: 0, + tcb_evaluation_data_number: 1, + tcb_levels: vec![TcbLevel { + tcb: Tcb { + sgx_components: pck_extension + .cpu_svn + .into_iter() + .map(|svn| TcbComponents { svn }) + .collect(), + tdx_components: TDX_TCB_SVN.into_iter().map(|svn| TcbComponents { svn }).collect(), + pce_svn: pck_extension.pce_svn, + }, + tcb_date: not_before.format(&time::format_description::well_known::Rfc3339).unwrap(), + tcb_status: TcbStatus::UpToDate, + advisory_ids: Vec::new(), + }], + } +} + +/// Verify that the generated PKI and collateral set is internally +/// consistent +fn validate_fixture_set( + collateral: &QuoteCollateralV3, + pck_cert: &rcgen::Certificate, + tcb_signer: &rcgen::Certificate, + tcb_info: &TcbInfo, + qe_identity: &MockQeIdentity, + pck_extension: &PckExtension, +) -> Result<(), Box> { + let parsed_pck = parse_pck_extension(pck_cert.der().as_ref())?; + if parsed_pck.fmspc != pck_extension.fmspc { + return Err("generated PCK FMSPC mismatch".into()); + } + if parsed_pck.cpu_svn != pck_extension.cpu_svn { + return Err("generated PCK CPU SVN mismatch".into()); + } + if parsed_pck.pce_svn != pck_extension.pce_svn { + return Err("generated PCK PCE SVN mismatch".into()); + } + if parsed_pck.ppid != pck_extension.ppid { + return Err("generated PCK PPID mismatch".into()); + } + + let _parsed_tcb_signer = x509_parser::parse_x509_certificate(tcb_signer.der().as_ref()) + .map_err(|_| "failed to parse generated TCB signer certificate")?; + let parsed_collateral: QuoteCollateralV3 = + serde_saphyr::from_str(&serde_saphyr::to_string(collateral)?)?; + if parsed_collateral.tcb_info != collateral.tcb_info { + return Err("collateral serialization round-trip mismatch".into()); + } + + let parsed_tcb: TcbInfo = serde_json::from_str(&collateral.tcb_info)?; + let parsed_qe: MockQeIdentity = serde_json::from_str(&collateral.qe_identity)?; + if parsed_tcb.fmspc != tcb_info.fmspc || parsed_qe.id != qe_identity.id { + return Err("collateral JSON payload mismatch".into()); + } + + Ok(()) +} + +/// QE TCB payload carrying the mock ISV SVN +#[derive(Clone, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +struct MockQeTcb { + isvsvn: u16, +} + +/// One QE identity TCB level entry +#[derive(Clone, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +struct MockQeTcbLevel { + tcb: MockQeTcb, + tcb_date: String, + tcb_status: TcbStatus, + #[serde(rename = "advisoryIDs", default)] + advisory_ids: Vec, +} + +/// Minimal QE identity shape used for serialized mock collateral +#[derive(Clone, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +struct MockQeIdentity { + id: String, + version: u8, + issue_date: String, + next_update: String, + tcb_evaluation_data_number: u32, + miscselect: String, + #[serde(rename = "miscselectMask")] + miscselect_mask: String, + attributes: String, + #[serde(rename = "attributesMask")] + attributes_mask: String, + mrsigner: String, + isvprodid: u16, + tcb_levels: Vec, +} + +/// Build mock QE identity collateral with stable, permissive values +fn mock_qe_identity(not_before: OffsetDateTime, not_after: OffsetDateTime) -> MockQeIdentity { + MockQeIdentity { + id: "TD_QE".to_string(), + version: 2, + issue_date: not_before.format(&time::format_description::well_known::Rfc3339).unwrap(), + next_update: not_after.format(&time::format_description::well_known::Rfc3339).unwrap(), + tcb_evaluation_data_number: 1, + miscselect: hex::encode_upper(QE_MISCSELECT), + miscselect_mask: hex::encode_upper(QE_MISCSELECT_MASK), + attributes: hex::encode_upper(QE_ATTRIBUTES), + attributes_mask: hex::encode_upper(QE_ATTRIBUTES_MASK), + mrsigner: hex::encode_upper(QE_MRSIGNER), + isvprodid: QE_ISVPRODID, + tcb_levels: vec![MockQeTcbLevel { + tcb: MockQeTcb { isvsvn: QE_ISVSVN }, + tcb_date: not_before.format(&time::format_description::well_known::Rfc3339).unwrap(), + tcb_status: TcbStatus::UpToDate, + advisory_ids: Vec::new(), + }], + } +} + +/// Resolve the workspace root from this crate location +fn workspace_root() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .expect("crate dir") + .parent() + .expect("workspace root") + .to_path_buf() +} + +/// Build CA certificate parameters for one mock issuer +fn ca_params( + common_name: &str, + not_before: OffsetDateTime, + not_after: OffsetDateTime, + serial: u64, +) -> Result> { + let mut params = CertificateParams::new(Vec::::new())?; + params.not_before = not_before; + params.not_after = not_after; + params.serial_number = Some(SerialNumber::from(serial)); + params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); + params.key_usages = vec![ + KeyUsagePurpose::KeyCertSign, + KeyUsagePurpose::DigitalSignature, + KeyUsagePurpose::CrlSign, + ]; + params.use_authority_key_identifier_extension = true; + params.key_identifier_method = KeyIdMethod::Sha256; + params.distinguished_name.push(DnType::CommonName, common_name); + Ok(params) +} + +/// Build end-entity certificate parameters for the mock signer and PCK +/// certs +fn end_entity_params( + common_name: &str, + not_before: OffsetDateTime, + not_after: OffsetDateTime, + serial: u64, +) -> Result> { + let mut params = CertificateParams::new(Vec::::new())?; + params.not_before = not_before; + params.not_after = not_after; + params.serial_number = Some(SerialNumber::from(serial)); + params.is_ca = IsCa::ExplicitNoCa; + params.key_usages = vec![KeyUsagePurpose::DigitalSignature]; + params.use_authority_key_identifier_extension = true; + params.key_identifier_method = KeyIdMethod::Sha256; + params.distinguished_name.push(DnType::CommonName, common_name); + Ok(params) +} + +/// CLI entrypoint for refreshing deterministic mock DCAP fixtures +fn main() -> Result<(), Box> { + match std::env::args().nth(1).as_deref() { + Some("refresh-dcap-fixtures") => { + refresh_dcap_fixtures()?; + } + Some(other) => { + eprintln!("Unknown command: {other}"); + eprintln!("Usage: cargo run -p mock-tdx -- refresh-dcap-fixtures"); + std::process::exit(2); + } + None => { + eprintln!("Usage: cargo run -p mock-tdx -- refresh-dcap-fixtures"); + std::process::exit(2); + } + } + + Ok(()) +} diff --git a/crates/mock-tdx/test-assets/generated-dcap/mock-dcap-collateral.yaml b/crates/mock-tdx/test-assets/generated-dcap/mock-dcap-collateral.yaml new file mode 100644 index 0000000..9dfe155 --- /dev/null +++ b/crates/mock-tdx/test-assets/generated-dcap/mock-dcap-collateral.yaml @@ -0,0 +1,134 @@ +pck_crl_issuer_chain: | + -----BEGIN CERTIFICATE----- + MIIBkjCCATmgAwIBAgIBAjAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu + dGVsIFJvb3QgQ0EwHhcNMjUwMTAxMDAwMDAwWhcNNDUwMTAxMDAwMDAwWjAkMSIw + IAYDVQQDDBlNb2NrIEludGVsIFRDQiBTaWduaW5nIENBMFkwEwYHKoZIzj0CAQYI + KoZIzj0DAQcDQgAE1lqTl3yqPRsIGFL/V6eeRl8WYFdzBLrq1QXdOkhYnPNQGF6J + U3LfYiHqOhN1V+Rz/dtnVfBb1QfDxTP86ckShaNjMGEwHwYDVR0jBBgwFoAUdoBa + Y6aYDBgHVCShPzJ3LQLXxxswDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBS/y6K3 + QqgHu7crUi+kaUxGBP9o6zAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0cA + MEQCIGepVOEvTx2ic+Yrw8uX0CsslLDBz6WzlWvDqodzUstPAiBs+kxDr5Tcx3oL + +6UKISByeEmo2u0OQsow26ZIs3BbVw== + -----END CERTIFICATE----- + -----BEGIN CERTIFICATE----- + MIIBjDCCATKgAwIBAgIBATAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu + dGVsIFJvb3QgQ0EwHhcNMjUwMTAxMDAwMDAwWhcNNDUwMTAxMDAwMDAwWjAdMRsw + GQYDVQQDDBJNb2NrIEludGVsIFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMB + BwNCAAQCF+YX8LZEOSgnj5aZnmmiOk8sFSvfbWzfZuW4AoLU7RlKfevLl3EtLdo8 + qFqodlpW9F/HWFmWUvKJfGUwbleUo2MwYTAfBgNVHSMEGDAWgBR2gFpjppgMGAdU + JKE/MnctAtfHGzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFHaAWmOmmAwYB1Qk + oT8ydy0C18cbMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIhAIpY + Gp+NkZnyVdGHNmeqYkl15rfGN6XYAuh9981yICztAiBlHn+o9LfGvFCI6R7DA5FL + 7kGbJLyiIMrR+xVRhpLHxg== + -----END CERTIFICATE----- +root_ca_crl: >- + 3081d6307d020101300a06082a8648ce3d040302301d311b301906035504030c124d6f636b20496e74656c20526f6f74204341170d3235303130313030303030305a170d3435303130313030303030305aa02f302d301f0603551d2304183016801476805a63a6980c18075424a13f32772d02d7c71b300a0603551d140403020101300a06082a8648ce3d0403020349003046022100f3749c1f623ca9c5c948a3f6b421ed2636adba6023af7ba3cc80b55f577ffd970221009b92d3db4f6ecadd6636fb3350420820230e3e770e248aad13617325a267d06e +pck_crl: >- + 3081dc308184020101300a06082a8648ce3d04030230243122302006035504030c194d6f636b20496e74656c20544342205369676e696e67204341170d3235303130313030303030305a170d3435303130313030303030305aa02f302d301f0603551d23041830168014bfcba2b742a807bbb72b522fa4694c4604ff68eb300a0603551d140403020102300a06082a8648ce3d040302034700304402207b99e91bcdfcc1ec076103279955dc04b799074541cedeb10a8d74b380a6172302206c952af030073c711ca787abfdb01ca8dec13a10947a0d14d50ce275ada21240 +tcb_info_issuer_chain: | + -----BEGIN CERTIFICATE----- + MIIBlzCCATygAwIBAgIBAzAKBggqhkjOPQQDAjAkMSIwIAYDVQQDDBlNb2NrIElu + dGVsIFRDQiBTaWduaW5nIENBMB4XDTI1MDEwMTAwMDAwMFoXDTQ1MDEwMTAwMDAw + MFowIDEeMBwGA1UEAwwVTW9jayBJbnRlbCBUQ0IgU2lnbmVyMFkwEwYHKoZIzj0C + AQYIKoZIzj0DAQcDQgAEUadYCDOJjqGxg8vXNQpAmQeMbvHB4Y6XDNdoMDXyXn0B + EFInErC1p8/wgWhUhphKlOaDHtrEbnNg+p2DSnqBoaNjMGEwHwYDVR0jBBgwFoAU + v8uit0KoB7u3K1IvpGlMRgT/aOswDgYDVR0PAQH/BAQDAgeAMB0GA1UdDgQWBBST + 7j5t5QAoWFiJAKg4c8ROKT5hpTAPBgNVHRMBAf8EBTADAQEAMAoGCCqGSM49BAMC + A0kAMEYCIQC5u/Nxyy330a/pDEmvcNZNoaZSID6GiBhorkO1EQBtawIhAOUAd0z7 + di2OsiDXWFc5TiQ9+FxMuWTx9eo+DiUM+UnK + -----END CERTIFICATE----- + -----BEGIN CERTIFICATE----- + MIIBkjCCATmgAwIBAgIBAjAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu + dGVsIFJvb3QgQ0EwHhcNMjUwMTAxMDAwMDAwWhcNNDUwMTAxMDAwMDAwWjAkMSIw + IAYDVQQDDBlNb2NrIEludGVsIFRDQiBTaWduaW5nIENBMFkwEwYHKoZIzj0CAQYI + KoZIzj0DAQcDQgAE1lqTl3yqPRsIGFL/V6eeRl8WYFdzBLrq1QXdOkhYnPNQGF6J + U3LfYiHqOhN1V+Rz/dtnVfBb1QfDxTP86ckShaNjMGEwHwYDVR0jBBgwFoAUdoBa + Y6aYDBgHVCShPzJ3LQLXxxswDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBS/y6K3 + QqgHu7crUi+kaUxGBP9o6zAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0cA + MEQCIGepVOEvTx2ic+Yrw8uX0CsslLDBz6WzlWvDqodzUstPAiBs+kxDr5Tcx3oL + +6UKISByeEmo2u0OQsow26ZIs3BbVw== + -----END CERTIFICATE----- + -----BEGIN CERTIFICATE----- + MIIBjDCCATKgAwIBAgIBATAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu + dGVsIFJvb3QgQ0EwHhcNMjUwMTAxMDAwMDAwWhcNNDUwMTAxMDAwMDAwWjAdMRsw + GQYDVQQDDBJNb2NrIEludGVsIFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMB + BwNCAAQCF+YX8LZEOSgnj5aZnmmiOk8sFSvfbWzfZuW4AoLU7RlKfevLl3EtLdo8 + qFqodlpW9F/HWFmWUvKJfGUwbleUo2MwYTAfBgNVHSMEGDAWgBR2gFpjppgMGAdU + JKE/MnctAtfHGzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFHaAWmOmmAwYB1Qk + oT8ydy0C18cbMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIhAIpY + Gp+NkZnyVdGHNmeqYkl15rfGN6XYAuh9981yICztAiBlHn+o9LfGvFCI6R7DA5FL + 7kGbJLyiIMrR+xVRhpLHxg== + -----END CERTIFICATE----- +tcb_info: "{\"id\":\"TDX\",\"version\":3,\"issueDate\":\"2025-01-01T00:00:00Z\",\"nextUpdate\":\"2045-01-01T00:00:00Z\",\"fmspc\":\"00906EA10000\",\"pceId\":\"0000\",\"tcbType\":0,\"tcbEvaluationDataNumber\":1,\"tcbLevels\":[{\"tcb\":{\"sgxtcbcomponents\":[{\"svn\":11},{\"svn\":11},{\"svn\":2},{\"svn\":2},{\"svn\":255},{\"svn\":1},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0}],\"tdxtcbcomponents\":[{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1}],\"pcesvn\":13},\"tcbDate\":\"2025-01-01T00:00:00Z\",\"tcbStatus\":\"UpToDate\",\"advisoryIDs\":[]}]}" +tcb_info_signature: >- + 81deffe35b79b7d7cfa1b4f7a62cf2f661f7d47c1d53838f8c48199ebbd14af77605f8a9bf060aafc48624a5a70be20307bb9e622345fd59f40966967ff1bce1 +qe_identity_issuer_chain: | + -----BEGIN CERTIFICATE----- + MIIBlzCCATygAwIBAgIBAzAKBggqhkjOPQQDAjAkMSIwIAYDVQQDDBlNb2NrIElu + dGVsIFRDQiBTaWduaW5nIENBMB4XDTI1MDEwMTAwMDAwMFoXDTQ1MDEwMTAwMDAw + MFowIDEeMBwGA1UEAwwVTW9jayBJbnRlbCBUQ0IgU2lnbmVyMFkwEwYHKoZIzj0C + AQYIKoZIzj0DAQcDQgAEUadYCDOJjqGxg8vXNQpAmQeMbvHB4Y6XDNdoMDXyXn0B + EFInErC1p8/wgWhUhphKlOaDHtrEbnNg+p2DSnqBoaNjMGEwHwYDVR0jBBgwFoAU + v8uit0KoB7u3K1IvpGlMRgT/aOswDgYDVR0PAQH/BAQDAgeAMB0GA1UdDgQWBBST + 7j5t5QAoWFiJAKg4c8ROKT5hpTAPBgNVHRMBAf8EBTADAQEAMAoGCCqGSM49BAMC + A0kAMEYCIQC5u/Nxyy330a/pDEmvcNZNoaZSID6GiBhorkO1EQBtawIhAOUAd0z7 + di2OsiDXWFc5TiQ9+FxMuWTx9eo+DiUM+UnK + -----END CERTIFICATE----- + -----BEGIN CERTIFICATE----- + MIIBkjCCATmgAwIBAgIBAjAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu + dGVsIFJvb3QgQ0EwHhcNMjUwMTAxMDAwMDAwWhcNNDUwMTAxMDAwMDAwWjAkMSIw + IAYDVQQDDBlNb2NrIEludGVsIFRDQiBTaWduaW5nIENBMFkwEwYHKoZIzj0CAQYI + KoZIzj0DAQcDQgAE1lqTl3yqPRsIGFL/V6eeRl8WYFdzBLrq1QXdOkhYnPNQGF6J + U3LfYiHqOhN1V+Rz/dtnVfBb1QfDxTP86ckShaNjMGEwHwYDVR0jBBgwFoAUdoBa + Y6aYDBgHVCShPzJ3LQLXxxswDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBS/y6K3 + QqgHu7crUi+kaUxGBP9o6zAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0cA + MEQCIGepVOEvTx2ic+Yrw8uX0CsslLDBz6WzlWvDqodzUstPAiBs+kxDr5Tcx3oL + +6UKISByeEmo2u0OQsow26ZIs3BbVw== + -----END CERTIFICATE----- + -----BEGIN CERTIFICATE----- + MIIBjDCCATKgAwIBAgIBATAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu + dGVsIFJvb3QgQ0EwHhcNMjUwMTAxMDAwMDAwWhcNNDUwMTAxMDAwMDAwWjAdMRsw + GQYDVQQDDBJNb2NrIEludGVsIFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMB + BwNCAAQCF+YX8LZEOSgnj5aZnmmiOk8sFSvfbWzfZuW4AoLU7RlKfevLl3EtLdo8 + qFqodlpW9F/HWFmWUvKJfGUwbleUo2MwYTAfBgNVHSMEGDAWgBR2gFpjppgMGAdU + JKE/MnctAtfHGzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFHaAWmOmmAwYB1Qk + oT8ydy0C18cbMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIhAIpY + Gp+NkZnyVdGHNmeqYkl15rfGN6XYAuh9981yICztAiBlHn+o9LfGvFCI6R7DA5FL + 7kGbJLyiIMrR+xVRhpLHxg== + -----END CERTIFICATE----- +qe_identity: "{\"id\":\"TD_QE\",\"version\":2,\"issueDate\":\"2025-01-01T00:00:00Z\",\"nextUpdate\":\"2045-01-01T00:00:00Z\",\"tcbEvaluationDataNumber\":1,\"miscselect\":\"00000000\",\"miscselectMask\":\"FFFFFFFF\",\"attributes\":\"00000000000000000000000000000000\",\"attributesMask\":\"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\",\"mrsigner\":\"5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A\",\"isvprodid\":2,\"tcbLevels\":[{\"tcb\":{\"isvsvn\":11},\"tcbDate\":\"2025-01-01T00:00:00Z\",\"tcbStatus\":\"UpToDate\",\"advisoryIDs\":[]}]}" +qe_identity_signature: >- + 7394339f635a123e047fb29ead5ce6faac737b5d648fb28b7b27d3e71829454bac1aff1d3c70b37bdca5bf2571d9c097ee58cf1c48693d844f5c4699d3c4f85b +pck_certificate_chain: | + -----BEGIN CERTIFICATE----- + MIIDZTCCAwqgAwIBAgIBBDAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu + dGVsIFJvb3QgQ0EwHhcNMjUwMTAxMDAwMDAwWhcNNDUwMTAxMDAwMDAwWjAZMRcw + FQYDVQQDDA5Nb2NrIEludGVsIFBDSzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA + BFs2iQ2svXyalrt0oe4os9LXW3LgmiDvJc+Ob9ip8DUNDhS+2NRoKjTYNTi9/1uW + 6JpmZuwNtXRdAvoSEAct91qjggI9MIICOTAfBgNVHSMEGDAWgBR2gFpjppgMGAdU + JKE/MnctAtfHGzAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFMUlGyqYmt+cq9c1 + ZOIHYPVgQoShMA8GA1UdEwEB/wQFMAMBAQAwggHUBgkqhkiG+E0BDQEEggHFMIIB + wTAeBgoqhkiG+E0BDQEBBBDQTsBtTm2S3JDQrTz17i3fMIIBZAYKKoZIhvhNAQ0B + AjCCAVQwEAYLKoZIhvhNAQ0BAgECAQswEAYLKoZIhvhNAQ0BAgICAQswEAYLKoZI + hvhNAQ0BAgMCAQIwEAYLKoZIhvhNAQ0BAgQCAQIwEQYLKoZIhvhNAQ0BAgUCAgD/ + MBAGCyqGSIb4TQENAQIGAgEBMBAGCyqGSIb4TQENAQIHAgEAMBAGCyqGSIb4TQEN + AQIIAgEAMBAGCyqGSIb4TQENAQIJAgEAMBAGCyqGSIb4TQENAQIKAgEAMBAGCyqG + SIb4TQENAQILAgEAMBAGCyqGSIb4TQENAQIMAgEAMBAGCyqGSIb4TQENAQINAgEA + MBAGCyqGSIb4TQENAQIOAgEAMBAGCyqGSIb4TQENAQIPAgEAMBAGCyqGSIb4TQEN + AQIQAgEAMBAGCyqGSIb4TQENAQIRAgENMB8GCyqGSIb4TQENAQISBBALCwIC/wEA + AAAAAAAAAAAAMBAGCiqGSIb4TQENAQMEAgAAMBQGCiqGSIb4TQENAQQEBgCQbqEA + ADAPBgoqhkiG+E0BDQEFCgEAMAoGCCqGSM49BAMCA0kAMEYCIQDJDr0VCWIRXShw + nIjk4N59CqFlUIy1GCiZCzMEz/J31QIhAP8lY/k6nufijKcZFIJj6H5AUiihugQ0 + SjmQUfVPDXx+ + -----END CERTIFICATE----- + -----BEGIN CERTIFICATE----- + MIIBjDCCATKgAwIBAgIBATAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu + dGVsIFJvb3QgQ0EwHhcNMjUwMTAxMDAwMDAwWhcNNDUwMTAxMDAwMDAwWjAdMRsw + GQYDVQQDDBJNb2NrIEludGVsIFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMB + BwNCAAQCF+YX8LZEOSgnj5aZnmmiOk8sFSvfbWzfZuW4AoLU7RlKfevLl3EtLdo8 + qFqodlpW9F/HWFmWUvKJfGUwbleUo2MwYTAfBgNVHSMEGDAWgBR2gFpjppgMGAdU + JKE/MnctAtfHGzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFHaAWmOmmAwYB1Qk + oT8ydy0C18cbMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIhAIpY + Gp+NkZnyVdGHNmeqYkl15rfGN6XYAuh9981yICztAiBlHn+o9LfGvFCI6R7DA5FL + 7kGbJLyiIMrR+xVRhpLHxg== + -----END CERTIFICATE----- diff --git a/crates/mock-tdx/test-assets/generated-dcap/mock-dcap-manifest.json b/crates/mock-tdx/test-assets/generated-dcap/mock-dcap-manifest.json new file mode 100644 index 0000000..9b01f72 --- /dev/null +++ b/crates/mock-tdx/test-assets/generated-dcap/mock-dcap-manifest.json @@ -0,0 +1,28 @@ +{ + "fmspc": "00906EA10000", + "pce_id_hex": "0000", + "pce_svn": 13, + "cpu_svn_hex": "0B0B0202FF0100000000000000000000", + "ppid_hex": "D04EC06D4E6D92DC90D0AD3CF5EE2DDF", + "qe_isvsvn": 11, + "issuer_common_names": { + "root_ca": "Mock Intel Root CA", + "tcb_signing_ca": "Mock Intel TCB Signing CA", + "tcb_signer": "Mock Intel TCB Signer", + "pck": "Mock Intel PCK" + }, + "files": { + "collateral": "mock-dcap-collateral.yaml", + "manifest": "mock-dcap-manifest.json", + "root_ca_der": "mock-root-ca.der", + "root_ca_pem": "mock-root-ca.pem", + "root_ca_key_pem": "mock-root-ca-key.pem", + "tcb_signer_chain_pem": "mock-tcb-signer-chain.pem", + "pck_chain_pem": "mock-pck-chain.pem", + "pck_key_pem": "mock-pck-key.pem", + "root_crl_der": "mock-root-ca.crl.der", + "pck_crl_der": "mock-pck.crl.der", + "tcb_info_json": "mock-tcb-info.json", + "qe_identity_json": "mock-qe-identity.json" + } +} \ No newline at end of file diff --git a/crates/mock-tdx/test-assets/generated-dcap/mock-pck-chain.pem b/crates/mock-tdx/test-assets/generated-dcap/mock-pck-chain.pem new file mode 100644 index 0000000..f4fc34d --- /dev/null +++ b/crates/mock-tdx/test-assets/generated-dcap/mock-pck-chain.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIDZTCCAwqgAwIBAgIBBDAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu +dGVsIFJvb3QgQ0EwHhcNMjUwMTAxMDAwMDAwWhcNNDUwMTAxMDAwMDAwWjAZMRcw +FQYDVQQDDA5Nb2NrIEludGVsIFBDSzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA +BFs2iQ2svXyalrt0oe4os9LXW3LgmiDvJc+Ob9ip8DUNDhS+2NRoKjTYNTi9/1uW +6JpmZuwNtXRdAvoSEAct91qjggI9MIICOTAfBgNVHSMEGDAWgBR2gFpjppgMGAdU +JKE/MnctAtfHGzAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFMUlGyqYmt+cq9c1 +ZOIHYPVgQoShMA8GA1UdEwEB/wQFMAMBAQAwggHUBgkqhkiG+E0BDQEEggHFMIIB +wTAeBgoqhkiG+E0BDQEBBBDQTsBtTm2S3JDQrTz17i3fMIIBZAYKKoZIhvhNAQ0B +AjCCAVQwEAYLKoZIhvhNAQ0BAgECAQswEAYLKoZIhvhNAQ0BAgICAQswEAYLKoZI +hvhNAQ0BAgMCAQIwEAYLKoZIhvhNAQ0BAgQCAQIwEQYLKoZIhvhNAQ0BAgUCAgD/ +MBAGCyqGSIb4TQENAQIGAgEBMBAGCyqGSIb4TQENAQIHAgEAMBAGCyqGSIb4TQEN +AQIIAgEAMBAGCyqGSIb4TQENAQIJAgEAMBAGCyqGSIb4TQENAQIKAgEAMBAGCyqG +SIb4TQENAQILAgEAMBAGCyqGSIb4TQENAQIMAgEAMBAGCyqGSIb4TQENAQINAgEA +MBAGCyqGSIb4TQENAQIOAgEAMBAGCyqGSIb4TQENAQIPAgEAMBAGCyqGSIb4TQEN +AQIQAgEAMBAGCyqGSIb4TQENAQIRAgENMB8GCyqGSIb4TQENAQISBBALCwIC/wEA +AAAAAAAAAAAAMBAGCiqGSIb4TQENAQMEAgAAMBQGCiqGSIb4TQENAQQEBgCQbqEA +ADAPBgoqhkiG+E0BDQEFCgEAMAoGCCqGSM49BAMCA0kAMEYCIQDJDr0VCWIRXShw +nIjk4N59CqFlUIy1GCiZCzMEz/J31QIhAP8lY/k6nufijKcZFIJj6H5AUiihugQ0 +SjmQUfVPDXx+ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBjDCCATKgAwIBAgIBATAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu +dGVsIFJvb3QgQ0EwHhcNMjUwMTAxMDAwMDAwWhcNNDUwMTAxMDAwMDAwWjAdMRsw +GQYDVQQDDBJNb2NrIEludGVsIFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMB +BwNCAAQCF+YX8LZEOSgnj5aZnmmiOk8sFSvfbWzfZuW4AoLU7RlKfevLl3EtLdo8 +qFqodlpW9F/HWFmWUvKJfGUwbleUo2MwYTAfBgNVHSMEGDAWgBR2gFpjppgMGAdU +JKE/MnctAtfHGzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFHaAWmOmmAwYB1Qk +oT8ydy0C18cbMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIhAIpY +Gp+NkZnyVdGHNmeqYkl15rfGN6XYAuh9981yICztAiBlHn+o9LfGvFCI6R7DA5FL +7kGbJLyiIMrR+xVRhpLHxg== +-----END CERTIFICATE----- diff --git a/crates/mock-tdx/test-assets/generated-dcap/mock-pck-key.pem b/crates/mock-tdx/test-assets/generated-dcap/mock-pck-key.pem new file mode 100644 index 0000000..a56d01d --- /dev/null +++ b/crates/mock-tdx/test-assets/generated-dcap/mock-pck-key.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgRERERERERERERERE +REREREREREREREREREREREREREShRANCAARbNokNrL18mpa7dKHuKLPS11ty4Jog +7yXPjm/YqfA1DQ4UvtjUaCo02DU4vf9bluiaZmbsDbV0XQL6EhAHLfda +-----END PRIVATE KEY----- diff --git a/crates/mock-tdx/test-assets/generated-dcap/mock-pck.crl.der b/crates/mock-tdx/test-assets/generated-dcap/mock-pck.crl.der new file mode 100644 index 0000000000000000000000000000000000000000..156ab7b0feed0ceaf7d7e73aff850a195113ce28 GIT binary patch literal 223 zcmXqLykpSV!o9csC#m1r4=5fxJg_+4f)==6&l8rf(g_%dlH$ORB!85NUHAf*RKfgr5 z*-@O=$kf0P2*DsqoYw@IyFlMS*FYYqSyq`v!a%G+q^uz-dD#pe3HA_`h4x0}x=h!P zO9O2KDi8tMg=#aiCxd|-lOn_Ck~#88HY<;w^jQ3Di{e`~v$eYtl-E};KGU!@KD_?# obfA*ilP=%(&pUND&Fr^vfD?y;GM`;JpGwzS;lyIqMd=sv0D?$9umAu6 literal 0 HcmV?d00001 diff --git a/crates/mock-tdx/test-assets/generated-dcap/mock-root-ca.der b/crates/mock-tdx/test-assets/generated-dcap/mock-root-ca.der new file mode 100644 index 0000000000000000000000000000000000000000..446d1be8467f38b6af52915f253bc129c8560ecf GIT binary patch literal 400 zcmXqLV(c+!Vl-O7%*4pV#K>sC#m1r4=5fxJg_+4f)==6&l8rf(g_%dlH$ORB!85NU zHAf*RKfgr5+0j5woY%HQ172Mb6(~mD}NnP?fbbo_tT#4U~0PZR?@5X_37z_y1KV)Rz$5RiwgS^e>@^` zTF|G?npA_l@F|Ov4H6CHfex2dW|1%uYY-`Gh)P~IgGYirL}j79QMoSD_2be8d>{q< zjEw(TfPQ6cGmr)G`B=nQuq)>WNehFNvl=i1Dddo1_Fyn@Wm075ijbP$J8|Zx(2MP6 z>8p}FOP_5&X1??W(~H{gXNwec-ZCkq%GIy Date: Tue, 21 Apr 2026 10:06:43 +0200 Subject: [PATCH 02/11] Add mock TDX crate --- Cargo.lock | 5 +- crates/attestation/src/dcap.rs | 42 +++++++++- crates/mock-tdx/Cargo.toml | 3 + crates/mock-tdx/src/lib.rs | 6 +- crates/mock-tdx/src/main.rs | 24 ++++-- crates/{pccs => mock-tdx}/src/mock_pcs.rs | 99 +++++++---------------- crates/pccs/Cargo.toml | 2 +- crates/pccs/src/lib.rs | 57 ++++++------- 8 files changed, 121 insertions(+), 117 deletions(-) rename crates/{pccs => mock-tdx}/src/mock_pcs.rs (66%) diff --git a/Cargo.lock b/Cargo.lock index a18b1fe..8f1212b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2467,6 +2467,7 @@ dependencies = [ name = "mock-tdx" version = "0.0.1" dependencies = [ + "axum", "dcap-qvl 0.3.12 (git+https://github.com/Phala-Network/dcap-qvl.git?rev=f1dcc65371e941a7b83e3234833d23a1fb232ab1)", "hex", "p256", @@ -2478,6 +2479,8 @@ dependencies = [ "serde_json", "sha2", "time", + "tokio", + "urlencoding", "x509-parser 0.18.1", "yasna 0.5.2", ] @@ -2812,9 +2815,9 @@ name = "pccs" version = "0.0.1" dependencies = [ "anyhow", - "axum", "dcap-qvl 0.3.12 (git+https://github.com/Phala-Network/dcap-qvl.git?rev=f1dcc65371e941a7b83e3234833d23a1fb232ab1)", "hex", + "mock-tdx", "rcgen 0.14.7", "reqwest", "serde", diff --git a/crates/attestation/src/dcap.rs b/crates/attestation/src/dcap.rs index 2aeb431..9ca3dbc 100644 --- a/crates/attestation/src/dcap.rs +++ b/crates/attestation/src/dcap.rs @@ -126,20 +126,27 @@ pub async fn verify_dcap_attestation_with_given_timestamp( } #[cfg(any(test, feature = "mock"))] -#[allow(clippy::unused_async)] pub async fn verify_dcap_attestation( input: Vec, expected_input_data: [u8; 64], - _pccs: Option, + pccs: Option, ) -> Result { let quote = Quote::parse(&input)?; let material = load_mock_tdx_material().map_err(|error| anyhow::anyhow!(error.to_string()))?; + let ca = quote.ca()?; + let fmspc = hex::encode_upper(quote.fmspc()?); let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)?.as_secs(); + let collateral = if let Some(ref pccs) = pccs { + let (collateral, _is_fresh) = pccs.get_collateral(fmspc, ca, now).await?; + collateral + } else { + material.collateral + }; let verifier = dcap_qvl::verify::QuoteVerifier::new( material.root_ca_der, dcap_qvl::verify::rustcrypto::backend(), ); - verifier.verify(&input, &material.collateral, now)?; + verifier.verify(&input, &collateral, now)?; let measurements = MultiMeasurements::from_dcap_qvl_quote("e)?; if get_quote_input_data(quote.report) != expected_input_data { @@ -195,6 +202,8 @@ pub enum DcapVerificationError { mod tests { use super::*; use crate::measurements::MeasurementPolicy; + use mock_tdx::{MockPcsConfig, load_mock_tdx_material, spawn_mock_pcs_server}; + #[tokio::test] async fn test_dcap_verify() { let attestation_bytes: &'static [u8] = @@ -275,4 +284,31 @@ mod tests { .await .unwrap(); } + + #[tokio::test] + async fn test_mock_dcap_verify_uses_pccs_when_provided() { + let material = load_mock_tdx_material().unwrap(); + let tcb_info: serde_json::Value = serde_json::from_str(&material.collateral.tcb_info).unwrap(); + let qe_identity: serde_json::Value = + serde_json::from_str(&material.collateral.qe_identity).unwrap(); + let mock_pcs = spawn_mock_pcs_server(MockPcsConfig { + include_fmspcs_listing: false, + tcb_next_update: tcb_info["nextUpdate"].as_str().unwrap().to_string(), + qe_next_update: qe_identity["nextUpdate"].as_str().unwrap().to_string(), + refreshed_tcb_next_update: None, + refreshed_qe_next_update: None, + }) + .await + .unwrap(); + let pccs = Pccs::new(Some(mock_pcs.base_url.clone())); + let expected_input_data = [0xA5; 64]; + let attestation_bytes = create_dcap_attestation(expected_input_data).unwrap(); + + let measurements = + verify_dcap_attestation(attestation_bytes, expected_input_data, Some(pccs)).await.unwrap(); + + assert_eq!(measurements, crate::measurements::mock_dcap_measurements()); + assert_eq!(mock_pcs.tcb_call_count(), 1); + assert_eq!(mock_pcs.qe_call_count(), 1); + } } diff --git a/crates/mock-tdx/Cargo.toml b/crates/mock-tdx/Cargo.toml index 53c5160..f13c39f 100644 --- a/crates/mock-tdx/Cargo.toml +++ b/crates/mock-tdx/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" publish = false [dependencies] +axum = "0.8.8" dcap-qvl = { workspace = true } hex = "0.4.3" p256 = { version = "0.13.2", features = ["ecdsa", "pkcs8", "pem"] } @@ -16,6 +17,8 @@ serde_json = "1.0.145" serde-saphyr = "0.0.22" sha2 = "0.10.9" time = { version = "0.3.47", features = ["formatting", "macros", "parsing"] } +tokio = { workspace = true, features = ["rt", "net"] } +urlencoding = "2.1.3" yasna = "0.5.2" x509-parser = "0.18.1" diff --git a/crates/mock-tdx/src/lib.rs b/crates/mock-tdx/src/lib.rs index 3f9f81d..1482397 100644 --- a/crates/mock-tdx/src/lib.rs +++ b/crates/mock-tdx/src/lib.rs @@ -1,3 +1,5 @@ +mod mock_pcs; + use dcap_qvl::{ QuoteCollateralV3, quote::{ @@ -13,6 +15,8 @@ use scale::Encode; use serde::Serialize; use sha2::Digest; +pub use mock_pcs::{MockPcsConfig, MockPcsServer, spawn_mock_pcs_server}; + /// Embedded collateral fixture contents const EMBEDDED_COLLATERAL_YAML: &str = include_str!("../test-assets/generated-dcap/mock-dcap-collateral.yaml"); @@ -101,7 +105,7 @@ pub fn load_mock_tdx_material() -> Result, - pub(super) refreshed_qe_next_update: Option, +pub struct MockPcsConfig { + pub include_fmspcs_listing: bool, + pub tcb_next_update: String, + pub qe_next_update: String, + pub refreshed_tcb_next_update: Option, + pub refreshed_qe_next_update: Option, } -/// A mock PCS server so we can run tests without using the Intel PCS. -pub(super) struct MockPcsServer { - pub(super) base_url: String, +pub struct MockPcsServer { + pub base_url: String, _task: JoinHandle<()>, tcb_calls: Arc, qe_calls: Arc, @@ -55,11 +42,11 @@ impl Drop for MockPcsServer { } impl MockPcsServer { - pub(super) fn tcb_call_count(&self) -> usize { + pub fn tcb_call_count(&self) -> usize { self.tcb_calls.load(Ordering::SeqCst) } - pub(super) fn qe_call_count(&self) -> usize { + pub fn qe_call_count(&self) -> usize { self.qe_calls.load(Ordering::SeqCst) } } @@ -85,24 +72,22 @@ struct MockPcsState { qe_calls: Arc, } -pub(super) async fn spawn_mock_pcs_server(config: MockPcsConfig) -> MockPcsServer { - let base_collateral: QuoteCollateralV3 = serde_saphyr::from_slice(include_bytes!( - "../../attestation/test-assets/dcap-quote-collateral-00.yaml" - )) - .unwrap(); - let now = OffsetDateTime::now_utc(); - let fresh_crl = generate_mock_crl_der(now, now + Duration::days(365)); +pub async fn spawn_mock_pcs_server( + config: MockPcsConfig, +) -> Result> { + let material = load_mock_tdx_material()?; + let base_collateral: QuoteCollateralV3 = material.collateral; - let mut tcb_info: Value = serde_json::from_str(&base_collateral.tcb_info).unwrap(); + let mut tcb_info: Value = serde_json::from_str(&base_collateral.tcb_info)?; tcb_info["nextUpdate"] = Value::String(config.tcb_next_update.clone()); - let mut qe_identity: Value = serde_json::from_str(&base_collateral.qe_identity).unwrap(); + let mut qe_identity: Value = serde_json::from_str(&base_collateral.qe_identity)?; qe_identity["nextUpdate"] = Value::String(config.qe_next_update.clone()); let tcb_calls = Arc::new(AtomicUsize::new(0)); let qe_calls = Arc::new(AtomicUsize::new(0)); let state = Arc::new(MockPcsState { - fmspc: config.fmspc, + fmspc: material.manifest.fmspc, include_fmspcs_listing: config.include_fmspcs_listing, base_tcb_info: tcb_info, base_qe_identity: qe_identity, @@ -112,11 +97,11 @@ pub(super) async fn spawn_mock_pcs_server(config: MockPcsConfig) -> MockPcsServe qe_next_update: config.qe_next_update, refreshed_tcb_next_update: config.refreshed_tcb_next_update, refreshed_qe_next_update: config.refreshed_qe_next_update, - pck_crl: fresh_crl.clone(), - pck_crl_issuer_chain: "mock-pck-crl-issuer-chain".to_string(), - tcb_issuer_chain: "mock-tcb-info-issuer-chain".to_string(), - qe_issuer_chain: "mock-qe-issuer-chain".to_string(), - root_ca_crl_hex: hex::encode(fresh_crl), + pck_crl: base_collateral.pck_crl, + pck_crl_issuer_chain: urlencoding::encode(&base_collateral.pck_crl_issuer_chain).into(), + tcb_issuer_chain: urlencoding::encode(&base_collateral.tcb_info_issuer_chain).into(), + qe_issuer_chain: urlencoding::encode(&base_collateral.qe_identity_issuer_chain).into(), + root_ca_crl_hex: hex::encode(base_collateral.root_ca_crl), tcb_calls: tcb_calls.clone(), qe_calls: qe_calls.clone(), }); @@ -129,13 +114,13 @@ pub(super) async fn spawn_mock_pcs_server(config: MockPcsConfig) -> MockPcsServe .route("/sgx/certification/v4/rootcacrl", get(mock_root_ca_crl_handler)) .with_state(state); - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let addr: SocketAddr = listener.local_addr().unwrap(); + let listener = TcpListener::bind("127.0.0.1:0").await?; + let addr: SocketAddr = listener.local_addr()?; let task = tokio::spawn(async move { axum::serve(listener, app).await.unwrap(); }); - MockPcsServer { base_url: format!("http://{addr}"), _task: task, tcb_calls, qe_calls } + Ok(MockPcsServer { base_url: format!("http://{addr}"), _task: task, tcb_calls, qe_calls }) } async fn mock_pck_crl_handler( @@ -208,29 +193,3 @@ async fn mock_qe_identity_handler( async fn mock_root_ca_crl_handler(State(state): State>) -> impl IntoResponse { state.root_ca_crl_hex.clone() } - -/// Create a mock certificate revocation list -fn generate_mock_crl_der(this_update: OffsetDateTime, next_update: OffsetDateTime) -> Vec { - let mut issuer_params = CertificateParams::new(Vec::new()).unwrap(); - issuer_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); - issuer_params.key_usages = vec![ - KeyUsagePurpose::KeyCertSign, - KeyUsagePurpose::DigitalSignature, - KeyUsagePurpose::CrlSign, - ]; - let issuer_key = KeyPair::generate().unwrap(); - let issuer = Issuer::new(issuer_params, issuer_key); - - CertificateRevocationListParams { - this_update, - next_update, - crl_number: SerialNumber::from(1_u64), - issuing_distribution_point: None, - revoked_certs: Vec::new(), - key_identifier_method: rcgen::KeyIdMethod::Sha256, - } - .signed_by(&issuer) - .unwrap() - .der() - .to_vec() -} diff --git a/crates/pccs/Cargo.toml b/crates/pccs/Cargo.toml index 83a52c5..3e2020b 100644 --- a/crates/pccs/Cargo.toml +++ b/crates/pccs/Cargo.toml @@ -22,7 +22,7 @@ reqwest = { version = "0.12.23", default-features = false, features = [ x509-parser = "0.18.0" [dev-dependencies] -axum = "0.8.6" rcgen = "0.14.5" tracing-subscriber = { version = "0.3.20", features = ["env-filter", "fmt"] } serde-saphyr = "0.0.22" +mock-tdx = { workspace = true } diff --git a/crates/pccs/src/lib.rs b/crates/pccs/src/lib.rs index c539ad1..f5de451 100644 --- a/crates/pccs/src/lib.rs +++ b/crates/pccs/src/lib.rs @@ -597,43 +597,39 @@ pub enum PccsError { TimeStampExceedsI64, } -#[cfg(test)] -mod mock_pcs; - #[cfg(test)] mod tests { + use mock_tdx::{MockPcsConfig, load_mock_tdx_material, spawn_mock_pcs_server}; use tokio::time::Duration; - use super::{ - mock_pcs::{MockPcsConfig, spawn_mock_pcs_server}, - *, - }; + use super::*; + + fn mock_tdx_fmspc() -> String { + load_mock_tdx_material().unwrap().manifest.fmspc + } #[tokio::test] async fn test_mock_pcs_server_helper_with_get_collateral() { + let fmspc = mock_tdx_fmspc(); let mock = spawn_mock_pcs_server(MockPcsConfig { - fmspc: "00806F050000".to_string(), include_fmspcs_listing: false, tcb_next_update: "2999-01-01T00:00:00Z".to_string(), qe_next_update: "2999-01-01T00:00:00Z".to_string(), refreshed_tcb_next_update: None, refreshed_qe_next_update: None, }) - .await; + .await + .unwrap(); let pccs = Pccs::new(Some(mock.base_url.clone())); let now = 1_700_000_000_u64; - let (_, is_fresh) = - pccs.get_collateral("00806F050000".to_string(), "processor", now).await.unwrap(); + let (_, is_fresh) = pccs.get_collateral(fmspc, "processor", now).await.unwrap(); assert!(is_fresh); } #[test] fn test_extract_next_update_includes_crl_expiry() { - let mut collateral: QuoteCollateralV3 = serde_saphyr::from_slice(include_bytes!( - "../../attestation/test-assets/dcap-quote-collateral-00.yaml" - )) - .unwrap(); + let mut collateral: QuoteCollateralV3 = load_mock_tdx_material().unwrap().collateral; let mut tcb_info: serde_json::Value = serde_json::from_str(&collateral.tcb_info).unwrap(); tcb_info["nextUpdate"] = serde_json::Value::String("2999-01-01T00:00:00Z".to_string()); @@ -660,30 +656,27 @@ mod tests { .unwrap() .format(&Rfc3339) .unwrap(); + let fmspc = mock_tdx_fmspc(); let mock = spawn_mock_pcs_server(MockPcsConfig { - fmspc: "00806F050000".to_string(), include_fmspcs_listing: false, tcb_next_update: initial_next_update.clone(), qe_next_update: initial_next_update, refreshed_tcb_next_update: Some(refreshed_next_update.clone()), refreshed_qe_next_update: Some(refreshed_next_update), }) - .await; + .await + .unwrap(); let pccs = Pccs::new(Some(mock.base_url.clone())); - let (_, is_fresh) = pccs - .get_collateral("00806F050000".to_string(), "processor", initial_now as u64) - .await - .unwrap(); + let (_, is_fresh) = + pccs.get_collateral(fmspc.clone(), "processor", initial_now as u64).await.unwrap(); assert!(is_fresh); assert_eq!(mock.tcb_call_count(), 1); assert_eq!(mock.qe_call_count(), 1); - let (_, is_fresh_second) = pccs - .get_collateral("00806F050000".to_string(), "processor", initial_now as u64) - .await - .unwrap(); + let (_, is_fresh_second) = + pccs.get_collateral(fmspc.clone(), "processor", initial_now as u64).await.unwrap(); assert!(!is_fresh_second); assert_eq!(mock.tcb_call_count(), 1); assert_eq!(mock.qe_call_count(), 1); @@ -700,10 +693,8 @@ mod tests { let before_check_calls = mock.tcb_call_count(); let now_after_background = unix_now().unwrap(); - let (_, is_fresh_again) = pccs - .get_collateral("00806F050000".to_string(), "processor", now_after_background as u64) - .await - .unwrap(); + let (_, is_fresh_again) = + pccs.get_collateral(fmspc, "processor", now_after_background as u64).await.unwrap(); assert!(!is_fresh_again); assert_eq!(mock.tcb_call_count(), before_check_calls); } @@ -711,14 +702,14 @@ mod tests { #[tokio::test] async fn test_ready_waits_for_startup_prewarm() { let mock = spawn_mock_pcs_server(MockPcsConfig { - fmspc: "00806F050000".to_string(), include_fmspcs_listing: true, tcb_next_update: "2999-01-01T00:00:00Z".to_string(), qe_next_update: "2999-01-01T00:00:00Z".to_string(), refreshed_tcb_next_update: None, refreshed_qe_next_update: None, }) - .await; + .await + .unwrap(); let pccs = Pccs::new(Some(mock.base_url.clone())); let summary = tokio::time::timeout(Duration::from_secs(5), pccs.ready()).await.unwrap().unwrap(); @@ -746,14 +737,14 @@ mod tests { #[tokio::test] async fn test_ready_supports_multiple_waiters() { let mock = spawn_mock_pcs_server(MockPcsConfig { - fmspc: "00806F050000".to_string(), include_fmspcs_listing: true, tcb_next_update: "2999-01-01T00:00:00Z".to_string(), qe_next_update: "2999-01-01T00:00:00Z".to_string(), refreshed_tcb_next_update: None, refreshed_qe_next_update: None, }) - .await; + .await + .unwrap(); let pccs = Pccs::new(Some(mock.base_url.clone())); let pccs_clone = pccs.clone(); From b859d23c93026d08b418bbacd45ead8aa8dcab5c Mon Sep 17 00:00:00 2001 From: peg Date: Tue, 21 Apr 2026 10:07:01 +0200 Subject: [PATCH 03/11] Default mock PCS config --- crates/attestation/src/dcap.rs | 16 ++-------------- crates/mock-tdx/src/lib.rs | 2 +- crates/mock-tdx/src/mock_pcs.rs | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/crates/attestation/src/dcap.rs b/crates/attestation/src/dcap.rs index 9ca3dbc..3a6c682 100644 --- a/crates/attestation/src/dcap.rs +++ b/crates/attestation/src/dcap.rs @@ -202,7 +202,7 @@ pub enum DcapVerificationError { mod tests { use super::*; use crate::measurements::MeasurementPolicy; - use mock_tdx::{MockPcsConfig, load_mock_tdx_material, spawn_mock_pcs_server}; + use mock_tdx::{MockPcsConfig, spawn_mock_pcs_server}; #[tokio::test] async fn test_dcap_verify() { @@ -287,19 +287,7 @@ mod tests { #[tokio::test] async fn test_mock_dcap_verify_uses_pccs_when_provided() { - let material = load_mock_tdx_material().unwrap(); - let tcb_info: serde_json::Value = serde_json::from_str(&material.collateral.tcb_info).unwrap(); - let qe_identity: serde_json::Value = - serde_json::from_str(&material.collateral.qe_identity).unwrap(); - let mock_pcs = spawn_mock_pcs_server(MockPcsConfig { - include_fmspcs_listing: false, - tcb_next_update: tcb_info["nextUpdate"].as_str().unwrap().to_string(), - qe_next_update: qe_identity["nextUpdate"].as_str().unwrap().to_string(), - refreshed_tcb_next_update: None, - refreshed_qe_next_update: None, - }) - .await - .unwrap(); + let mock_pcs = spawn_mock_pcs_server(MockPcsConfig::default()).await.unwrap(); let pccs = Pccs::new(Some(mock_pcs.base_url.clone())); let expected_input_data = [0xA5; 64]; let attestation_bytes = create_dcap_attestation(expected_input_data).unwrap(); diff --git a/crates/mock-tdx/src/lib.rs b/crates/mock-tdx/src/lib.rs index 1482397..a6f6ae6 100644 --- a/crates/mock-tdx/src/lib.rs +++ b/crates/mock-tdx/src/lib.rs @@ -105,7 +105,7 @@ pub fn load_mock_tdx_material() -> Result, } +impl Default for MockPcsConfig { + fn default() -> Self { + let material = load_mock_tdx_material().unwrap(); + let tcb_info: Value = serde_json::from_str(&material.collateral.tcb_info).unwrap(); + let qe_identity: Value = serde_json::from_str(&material.collateral.qe_identity).unwrap(); + + Self { + include_fmspcs_listing: false, + tcb_next_update: tcb_info["nextUpdate"].as_str().unwrap().to_string(), + qe_next_update: qe_identity["nextUpdate"].as_str().unwrap().to_string(), + refreshed_tcb_next_update: None, + refreshed_qe_next_update: None, + } + } +} + pub struct MockPcsServer { pub base_url: String, _task: JoinHandle<()>, From f8d9b2e313db20c221c2d5a2adf5bcaf898835c9 Mon Sep 17 00:00:00 2001 From: peg Date: Tue, 21 Apr 2026 10:09:54 +0200 Subject: [PATCH 04/11] Readme --- crates/attestation/src/dcap.rs | 7 +++++-- crates/mock-tdx/README.md | 10 ++++++---- crates/mock-tdx/src/lib.rs | 15 +++++++++++---- crates/mock-tdx/src/mock_pcs.rs | 3 ++- 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/crates/attestation/src/dcap.rs b/crates/attestation/src/dcap.rs index 3a6c682..3f8953c 100644 --- a/crates/attestation/src/dcap.rs +++ b/crates/attestation/src/dcap.rs @@ -200,9 +200,10 @@ pub enum DcapVerificationError { #[cfg(test)] mod tests { + use mock_tdx::{MockPcsConfig, spawn_mock_pcs_server}; + use super::*; use crate::measurements::MeasurementPolicy; - use mock_tdx::{MockPcsConfig, spawn_mock_pcs_server}; #[tokio::test] async fn test_dcap_verify() { @@ -293,7 +294,9 @@ mod tests { let attestation_bytes = create_dcap_attestation(expected_input_data).unwrap(); let measurements = - verify_dcap_attestation(attestation_bytes, expected_input_data, Some(pccs)).await.unwrap(); + verify_dcap_attestation(attestation_bytes, expected_input_data, Some(pccs)) + .await + .unwrap(); assert_eq!(measurements, crate::measurements::mock_dcap_measurements()); assert_eq!(mock_pcs.tcb_call_count(), 1); diff --git a/crates/mock-tdx/README.md b/crates/mock-tdx/README.md index 023c225..2aeeeba 100644 --- a/crates/mock-tdx/README.md +++ b/crates/mock-tdx/README.md @@ -1,15 +1,17 @@ # mock-tdx `mock-tdx` generates deterministic mock TDX DCAP artifacts for tests and -development on non-TDX hardware. +development on non-TDX hardware. It plays the role of Intel so we can +mock the complete DCAP workflow. It provides: -- a small fixture generator for a mock DCAP trust chain and collateral -- a quote generator that emits mock TDX DCAP quotes with caller-supplied +- A small fixture generator for a mock DCAP trust chain and collateral +- A quote generator that emits mock TDX DCAP quotes with caller-supplied `report_data` -- checked-in mock collateral, root certificates, CRLs, and signing material +- Checked-in mock collateral, root certificates, CRLs, and signing material under `test-assets/generated-dcap` +- A mock PCS server The generated quotes are shaped so they can be parsed and verified with `dcap-qvl` using the mock root of trust bundled with this crate. diff --git a/crates/mock-tdx/src/lib.rs b/crates/mock-tdx/src/lib.rs index a6f6ae6..fcea7f6 100644 --- a/crates/mock-tdx/src/lib.rs +++ b/crates/mock-tdx/src/lib.rs @@ -3,10 +3,19 @@ mod mock_pcs; use dcap_qvl::{ QuoteCollateralV3, quote::{ - AuthData, AuthDataV4, CertificationData, Data, EnclaveReport, Header, - QEReportCertificationData, Quote, Report, TDReport10, + AuthData, + AuthDataV4, + CertificationData, + Data, + EnclaveReport, + Header, + QEReportCertificationData, + Quote, + Report, + TDReport10, }, }; +pub use mock_pcs::{MockPcsConfig, MockPcsServer, spawn_mock_pcs_server}; use p256::{ ecdsa::{Signature, SigningKey, signature::Signer}, pkcs8::DecodePrivateKey, @@ -15,8 +24,6 @@ use scale::Encode; use serde::Serialize; use sha2::Digest; -pub use mock_pcs::{MockPcsConfig, MockPcsServer, spawn_mock_pcs_server}; - /// Embedded collateral fixture contents const EMBEDDED_COLLATERAL_YAML: &str = include_str!("../test-assets/generated-dcap/mock-dcap-collateral.yaml"); diff --git a/crates/mock-tdx/src/mock_pcs.rs b/crates/mock-tdx/src/mock_pcs.rs index 391b310..be66c2f 100644 --- a/crates/mock-tdx/src/mock_pcs.rs +++ b/crates/mock-tdx/src/mock_pcs.rs @@ -8,7 +8,8 @@ use std::{ }; use axum::{ - Json, Router, + Json, + Router, extract::{Query, State}, response::IntoResponse, routing::get, From 727f867385ccf1264ccd41f79908ce773047f1c2 Mon Sep 17 00:00:00 2001 From: peg Date: Tue, 21 Apr 2026 11:33:41 +0200 Subject: [PATCH 05/11] Tidy --- crates/attestation/src/dcap.rs | 10 +- crates/mock-tdx/src/lib.rs | 145 ++++-------------- crates/mock-tdx/src/main.rs | 89 ++++------- crates/mock-tdx/src/mock_pcs.rs | 31 +++- .../generated-dcap/mock-dcap-collateral.yaml | 68 ++++---- .../generated-dcap/mock-dcap-manifest.json | 28 ---- .../generated-dcap/mock-pck-chain.pem | 16 +- .../generated-dcap/mock-pck.crl.der | Bin 223 -> 0 bytes .../generated-dcap/mock-qe-identity.json | 1 - .../generated-dcap/mock-root-ca-key.pem | 5 - .../generated-dcap/mock-root-ca.crl.der | Bin 217 -> 0 bytes .../generated-dcap/mock-root-ca.der | Bin 400 -> 399 bytes .../generated-dcap/mock-root-ca.pem | 11 -- .../generated-dcap/mock-tcb-info.json | 1 - .../generated-dcap/mock-tcb-signer-chain.pem | 33 ---- crates/pccs/src/lib.rs | 8 +- 16 files changed, 139 insertions(+), 307 deletions(-) delete mode 100644 crates/mock-tdx/test-assets/generated-dcap/mock-dcap-manifest.json delete mode 100644 crates/mock-tdx/test-assets/generated-dcap/mock-pck.crl.der delete mode 100644 crates/mock-tdx/test-assets/generated-dcap/mock-qe-identity.json delete mode 100644 crates/mock-tdx/test-assets/generated-dcap/mock-root-ca-key.pem delete mode 100644 crates/mock-tdx/test-assets/generated-dcap/mock-root-ca.crl.der delete mode 100644 crates/mock-tdx/test-assets/generated-dcap/mock-root-ca.pem delete mode 100644 crates/mock-tdx/test-assets/generated-dcap/mock-tcb-info.json delete mode 100644 crates/mock-tdx/test-assets/generated-dcap/mock-tcb-signer-chain.pem diff --git a/crates/attestation/src/dcap.rs b/crates/attestation/src/dcap.rs index 3f8953c..01cce8e 100644 --- a/crates/attestation/src/dcap.rs +++ b/crates/attestation/src/dcap.rs @@ -8,7 +8,7 @@ use dcap_qvl::{ tcb_info::TcbInfo, }; #[cfg(any(test, feature = "mock"))] -use mock_tdx::{generate_mock_tdx_quote, load_mock_tdx_material}; +use mock_tdx::generate_mock_tdx_quote; use pccs::{Pccs, PccsError}; use thiserror::Error; @@ -132,7 +132,6 @@ pub async fn verify_dcap_attestation( pccs: Option, ) -> Result { let quote = Quote::parse(&input)?; - let material = load_mock_tdx_material().map_err(|error| anyhow::anyhow!(error.to_string()))?; let ca = quote.ca()?; let fmspc = hex::encode_upper(quote.fmspc()?); let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)?.as_secs(); @@ -140,12 +139,9 @@ pub async fn verify_dcap_attestation( let (collateral, _is_fresh) = pccs.get_collateral(fmspc, ca, now).await?; collateral } else { - material.collateral + mock_tdx::mock_collateral() }; - let verifier = dcap_qvl::verify::QuoteVerifier::new( - material.root_ca_der, - dcap_qvl::verify::rustcrypto::backend(), - ); + let verifier = mock_tdx::mock_dcap_verifier(); verifier.verify(&input, &collateral, now)?; let measurements = MultiMeasurements::from_dcap_qvl_quote("e)?; diff --git a/crates/mock-tdx/src/lib.rs b/crates/mock-tdx/src/lib.rs index fcea7f6..bbdfd39 100644 --- a/crates/mock-tdx/src/lib.rs +++ b/crates/mock-tdx/src/lib.rs @@ -14,6 +14,7 @@ use dcap_qvl::{ Report, TDReport10, }, + tcb_info::TcbInfo, }; pub use mock_pcs::{MockPcsConfig, MockPcsServer, spawn_mock_pcs_server}; use p256::{ @@ -21,15 +22,11 @@ use p256::{ pkcs8::DecodePrivateKey, }; use scale::Encode; -use serde::Serialize; use sha2::Digest; /// Embedded collateral fixture contents const EMBEDDED_COLLATERAL_YAML: &str = include_str!("../test-assets/generated-dcap/mock-dcap-collateral.yaml"); -/// Embedded manifest fixture contents -const EMBEDDED_MANIFEST_JSON: &str = - include_str!("../test-assets/generated-dcap/mock-dcap-manifest.json"); /// Embedded root CA DER contents const EMBEDDED_ROOT_CA_DER: &[u8] = include_bytes!("../test-assets/generated-dcap/mock-root-ca.der"); @@ -89,30 +86,17 @@ pub const MOCK_RTMR2: [u8; 48] = [0x70; 48]; /// Mock RTMR3 value used in generated mock TDX quotes pub const MOCK_RTMR3: [u8; 48] = [0x80; 48]; -/// Mock TDX material loaded from generated assets -pub struct MockTdxMaterial { - /// Quote collateral used to verify generated mock quotes - pub collateral: QuoteCollateralV3, - /// Mock root CA in DER form - pub root_ca_der: Vec, - /// Mock PCK signing key - pub pck_signing_key: SigningKey, - /// Mock PCK certificate chain in PEM form - pub pck_chain_pem: String, - /// Manifest describing the generated mock platform - pub manifest: FixtureManifest, +/// Get a DCAP quote verifier with the mock PCK root-of-trust +pub fn mock_dcap_verifier() -> dcap_qvl::verify::QuoteVerifier { + dcap_qvl::verify::QuoteVerifier::new( + EMBEDDED_ROOT_CA_DER.to_vec(), + dcap_qvl::verify::rustcrypto::backend(), + ) } -/// Load the generated mock TDX material from the workspace fixture -/// directory -pub fn load_mock_tdx_material() -> Result> { - let collateral: QuoteCollateralV3 = serde_saphyr::from_str(EMBEDDED_COLLATERAL_YAML)?; - let root_ca_der = EMBEDDED_ROOT_CA_DER.to_vec(); - let pck_signing_key = SigningKey::from_pkcs8_pem(EMBEDDED_PCK_KEY_PEM)?; - let pck_chain_pem = EMBEDDED_PCK_CHAIN_PEM.to_string(); - let manifest: FixtureManifest = serde_json::from_str(EMBEDDED_MANIFEST_JSON)?; - - Ok(MockTdxMaterial { collateral, root_ca_der, pck_signing_key, pck_chain_pem, manifest }) +/// Get mock collateral for verifying generated mock quotes +pub fn mock_collateral() -> QuoteCollateralV3 { + serde_saphyr::from_str(EMBEDDED_COLLATERAL_YAML).unwrap() } /// Construct a p256 signing key from deterministic secret key bytes @@ -126,26 +110,29 @@ pub(crate) fn signing_key_from_secret( pub fn generate_mock_tdx_quote( report_data: [u8; 64], ) -> Result, Box> { - let material = load_mock_tdx_material()?; - generate_mock_tdx_quote_from_material(&material, report_data) + let collateral = mock_collateral(); + generate_mock_tdx_quote_with_collateral(&collateral, report_data) } /// Generate a mock TDX DCAP quote from a specific loaded material set -pub fn generate_mock_tdx_quote_from_material( - material: &MockTdxMaterial, +pub fn generate_mock_tdx_quote_with_collateral( + collateral: &QuoteCollateralV3, report_data: [u8; 64], ) -> Result, Box> { let attestation_key = signing_key_from_secret(ATTESTATION_SK)?; let attestation_pubkey = raw_public_key(&attestation_key); let qe_report = build_qe_report(&attestation_pubkey)?; - let qe_report_signature = sign_fixed_p256(&material.pck_signing_key, &qe_report); + + let pck_signing_key = SigningKey::from_pkcs8_pem(EMBEDDED_PCK_KEY_PEM)?; + let qe_report_signature = sign_fixed_p256(&pck_signing_key, &qe_report); let outer_certification_data = CertificationData { cert_type: QE_REPORT_CERT, body: Data::new(Vec::new()) }; + let inner_certification_data = CertificationData { cert_type: PCK_CERT_CHAIN, - body: Data::new(material.pck_chain_pem.as_bytes().to_vec()), + body: Data::new(EMBEDDED_PCK_CHAIN_PEM.as_bytes().to_vec()), }; let auth_data = AuthData::V4(AuthDataV4 { @@ -160,13 +147,16 @@ pub fn generate_mock_tdx_quote_from_material( }, }); + let tcb_info: TcbInfo = serde_json::from_str(&collateral.tcb_info)?; + let tcb_level = tcb_info.tcb_levels.first().ok_or("mock TDX collateral missing TCB level")?; + let mut quote = Quote { header: Header { version: QUOTE_VERSION, attestation_key_type: ATTESTATION_KEY_TYPE_ECDSA256_WITH_P256_CURVE, tee_type: TEE_TYPE_TDX, qe_svn: QE_ISVSVN, - pce_svn: material.manifest.pce_svn, + pce_svn: tcb_level.tcb.pce_svn, qe_vendor_id: INTEL_QE_VENDOR_ID, user_data: [0; 20], }, @@ -253,69 +243,6 @@ fn signed_quote_scope(quote: &Quote) -> Vec { encoded } -/// Summary manifest written alongside generated fixture files -#[derive(Serialize, serde::Deserialize)] -pub struct FixtureManifest { - /// Mock platform FMSPC encoded as uppercase hex - pub fmspc: String, - /// Mock platform PCE ID encoded as uppercase hex - pub pce_id_hex: String, - /// Mock platform PCE SVN - pub pce_svn: u16, - /// Mock platform CPU SVN encoded as uppercase hex - pub cpu_svn_hex: String, - /// Mock platform PPID encoded as uppercase hex - pub ppid_hex: String, - /// Mock QE ISV SVN - pub qe_isvsvn: u16, - /// Human-readable issuer names embedded in the generated certificates - pub issuer_common_names: IssuerNames, - /// Generated fixture filenames - pub files: OutputFiles, -} - -/// Human-readable issuer names captured in the manifest -#[derive(Serialize, serde::Deserialize)] -pub struct IssuerNames { - /// Root CA common name - pub root_ca: String, - /// TCB signing CA common name - pub tcb_signing_ca: String, - /// TCB signer common name - pub tcb_signer: String, - /// PCK certificate common name - pub pck: String, -} - -/// File inventory for the generated fixture set -#[derive(Serialize, serde::Deserialize)] -pub struct OutputFiles { - /// Quote collateral fixture filename - pub collateral: String, - /// Manifest filename - pub manifest: String, - /// Root CA DER filename - pub root_ca_der: String, - /// Root CA PEM filename - pub root_ca_pem: String, - /// Root CA private key PEM filename - pub root_ca_key_pem: String, - /// TCB signer chain PEM filename - pub tcb_signer_chain_pem: String, - /// PCK chain PEM filename - pub pck_chain_pem: String, - /// PCK private key PEM filename - pub pck_key_pem: String, - /// Root CA CRL DER filename - pub root_crl_der: String, - /// PCK CRL DER filename - pub pck_crl_der: String, - /// TCB info JSON filename - pub tcb_info_json: String, - /// QE identity JSON filename - pub qe_identity_json: String, -} - #[cfg(test)] mod tests { use super::*; @@ -324,21 +251,20 @@ mod tests { #[test] fn builds_quote_that_parses_and_verifies() { - let material = load_mock_tdx_material().unwrap(); let report_data = [0xAB; 64]; - let quote_bytes = generate_mock_tdx_quote_from_material(&material, report_data).unwrap(); + let quote_bytes = generate_mock_tdx_quote(report_data).unwrap(); let quote = Quote::parse("e_bytes).unwrap(); assert_eq!(quote.header.version, QUOTE_VERSION); assert_eq!(quote.header.tee_type, TEE_TYPE_TDX); - assert_eq!(hex::encode_upper(quote.fmspc().unwrap()), material.manifest.fmspc); + + let collateral = mock_collateral(); + let tcb_info: TcbInfo = serde_json::from_str(&collateral.tcb_info).unwrap(); + assert_eq!(hex::encode_upper(quote.fmspc().unwrap()), tcb_info.fmspc); assert_eq!(quote.ca().unwrap(), "processor"); - let verifier = dcap_qvl::verify::QuoteVerifier::new( - material.root_ca_der.clone(), - dcap_qvl::verify::rustcrypto::backend(), - ); - let verified = verifier.verify("e_bytes, &material.collateral, FIXTURE_TIME).unwrap(); + let verifier = mock_dcap_verifier(); + let verified = verifier.verify("e_bytes, &collateral, FIXTURE_TIME).unwrap(); let dcap_qvl::quote::Report::TD10(report) = verified.report else { panic!("expected TD10 report"); }; @@ -353,15 +279,12 @@ mod tests { const TD_REPORT10_BYTE_LEN: usize = 584; const AUTH_DATA_SIZE_BYTE_LEN: usize = 4; - let material = load_mock_tdx_material().unwrap(); - let mut quote_bytes = generate_mock_tdx_quote_from_material(&material, [0xCD; 64]).unwrap(); + let mut quote_bytes = generate_mock_tdx_quote([0xCD; 64]).unwrap(); let signature_offset = HEADER_BYTE_LEN + TD_REPORT10_BYTE_LEN + AUTH_DATA_SIZE_BYTE_LEN; quote_bytes[signature_offset] ^= 0x01; - let verifier = dcap_qvl::verify::QuoteVerifier::new( - material.root_ca_der.clone(), - dcap_qvl::verify::rustcrypto::backend(), - ); - assert!(verifier.verify("e_bytes, &material.collateral, FIXTURE_TIME).is_err()); + let verifier = mock_dcap_verifier(); + let collateral = mock_collateral(); + assert!(verifier.verify("e_bytes, &collateral, FIXTURE_TIME).is_err()); } } diff --git a/crates/mock-tdx/src/main.rs b/crates/mock-tdx/src/main.rs index 259d40f..432ed30 100644 --- a/crates/mock-tdx/src/main.rs +++ b/crates/mock-tdx/src/main.rs @@ -8,7 +8,6 @@ use dcap_qvl::{ intel::{PckExtension, parse_pck_extension}, tcb_info::{Tcb, TcbComponents, TcbInfo, TcbLevel, TcbStatus}, }; -use mock_tdx::{FixtureManifest, IssuerNames, OutputFiles}; use p256::{ SecretKey, ecdsa::{Signature, SigningKey, signature::Signer}, @@ -33,28 +32,12 @@ use time::{Date, Month, OffsetDateTime, PrimitiveDateTime, Time}; const OUTPUT_DIR: &str = "crates/mock-tdx/test-assets/generated-dcap"; /// Serialized collateral fixture filename const COLLATERAL_BASENAME: &str = "mock-dcap-collateral.yaml"; -/// Mock platform manifest filename -const MANIFEST_BASENAME: &str = "mock-dcap-manifest.json"; /// Root CA DER filename const ROOT_CA_DER_BASENAME: &str = "mock-root-ca.der"; -/// Root CA PEM filename -const ROOT_CA_PEM_BASENAME: &str = "mock-root-ca.pem"; -/// Root CA private key PEM filename -const ROOT_CA_KEY_BASENAME: &str = "mock-root-ca-key.pem"; -/// TCB signer issuer chain PEM filename -const TCB_SIGNER_CHAIN_BASENAME: &str = "mock-tcb-signer-chain.pem"; /// PCK chain PEM filename const PCK_CHAIN_BASENAME: &str = "mock-pck-chain.pem"; /// PCK private key PEM filename const PCK_KEY_BASENAME: &str = "mock-pck-key.pem"; -/// Root CA CRL DER filename -const ROOT_CRL_DER_BASENAME: &str = "mock-root-ca.crl.der"; -/// PCK CRL DER filename -const PCK_CRL_DER_BASENAME: &str = "mock-pck.crl.der"; -/// TCB info JSON filename -const TCB_INFO_JSON_BASENAME: &str = "mock-tcb-info.json"; -/// QE identity JSON filename -const QE_IDENTITY_JSON_BASENAME: &str = "mock-qe-identity.json"; /// Deterministic PCK secret key bytes const PCK_SK: [u8; 32] = [0x44; 32]; @@ -137,7 +120,22 @@ fn sign_raw_p256(signing_key: &SigningKey, bytes: &[u8]) -> Vec { /// This blob was copied from `dcap-qvl`'s `tests/generate_test_certs.sh` /// where it is embedded as the OpenSSL DER payload for OID /// `1.2.840.113741.1.13.1` -const VALID_PCK_EXTENSION_HEX: &str = "308201C1301E060A2A864886F84D010D01010410D04EC06D4E6D92DC90D0AD3CF5EE2DDF30820164060A2A864886F84D010D0102308201543010060B2A864886F84D010D01020102010B3010060B2A864886F84D010D01020202010B3010060B2A864886F84D010D0102030201023010060B2A864886F84D010D0102040201023011060B2A864886F84D010D010205020200FF3010060B2A864886F84D010D0102060201013010060B2A864886F84D010D0102070201003010060B2A864886F84D010D0102080201003010060B2A864886F84D010D0102090201003010060B2A864886F84D010D01020A0201003010060B2A864886F84D010D01020B0201003010060B2A864886F84D010D01020C0201003010060B2A864886F84D010D01020D0201003010060B2A864886F84D010D01020E0201003010060B2A864886F84D010D01020F0201003010060B2A864886F84D010D0102100201003010060B2A864886F84D010D01021102010D301F060B2A864886F84D010D01021204100B0B0202FF01000000000000000000003010060A2A864886F84D010D0103040200003014060A2A864886F84D010D0104040600906EA10000300F060A2A864886F84D010D01050A0100"; +const VALID_PCK_EXTENSION_HEX: &str = concat!( + "308201C1301E060A2A864886F84D010D01010410D04EC06D4E6D92DC90D0AD3CF5EE2DDF", + "30820164060A2A864886F84D010D0102308201543010060B2A864886F84D010D0102010201", + "0B3010060B2A864886F84D010D01020202010B3010060B2A864886F84D010D010203020102", + "3010060B2A864886F84D010D0102040201023011060B2A864886F84D010D010205020200FF", + "3010060B2A864886F84D010D0102060201013010060B2A864886F84D010D01020702010030", + "10060B2A864886F84D010D0102080201003010060B2A864886F84D010D0102090201003010", + "060B2A864886F84D010D01020A0201003010060B2A864886F84D010D01020B020100301006", + "0B2A864886F84D010D01020C0201003010060B2A864886F84D010D01020D0201003010060B", + "2A864886F84D010D01020E0201003010060B2A864886F84D010D01020F0201003010060B2A", + "864886F84D010D0102100201003010060B2A864886F84D010D01021102010D301F060B2A86", + "4886F84D010D01021204100B0B0202FF01000000000000000000003010060A2A864886F84D", + "010D0103040200003014060A2A864886F84D010D0104040600906EA10000300F060A2A8648", + "86F84D010D01050A0100", +); + /// Return a known-good Intel SGX extension DER payload for the mock /// PCK cert fn intel_sgx_extension_der() -> Vec { @@ -224,53 +222,28 @@ fn refresh_dcap_fixtures() -> Result<(), Box> { &pck_extension, )?; - let manifest = FixtureManifest { - fmspc: hex::encode_upper(pck_extension.fmspc), - pce_id_hex: hex::encode_upper(&pck_extension.pce_id), - pce_svn: pck_extension.pce_svn, - cpu_svn_hex: hex::encode_upper(pck_extension.cpu_svn), - ppid_hex: hex::encode_upper(&pck_extension.ppid), - qe_isvsvn: QE_ISVSVN, - issuer_common_names: IssuerNames { - root_ca: ROOT_CA_CN.to_string(), - tcb_signing_ca: TCB_CA_CN.to_string(), - tcb_signer: TCB_SIGNER_CN.to_string(), - pck: PCK_CN.to_string(), - }, - files: OutputFiles { - collateral: COLLATERAL_BASENAME.to_string(), - manifest: MANIFEST_BASENAME.to_string(), - root_ca_der: ROOT_CA_DER_BASENAME.to_string(), - root_ca_pem: ROOT_CA_PEM_BASENAME.to_string(), - root_ca_key_pem: ROOT_CA_KEY_BASENAME.to_string(), - tcb_signer_chain_pem: TCB_SIGNER_CHAIN_BASENAME.to_string(), - pck_chain_pem: PCK_CHAIN_BASENAME.to_string(), - pck_key_pem: PCK_KEY_BASENAME.to_string(), - root_crl_der: ROOT_CRL_DER_BASENAME.to_string(), - pck_crl_der: PCK_CRL_DER_BASENAME.to_string(), - tcb_info_json: TCB_INFO_JSON_BASENAME.to_string(), - qe_identity_json: QE_IDENTITY_JSON_BASENAME.to_string(), - }, - }; - write(output_dir.join(COLLATERAL_BASENAME), serde_saphyr::to_string(&collateral)?)?; - write(output_dir.join(MANIFEST_BASENAME), serde_json::to_string_pretty(&manifest)?)?; write(output_dir.join(ROOT_CA_DER_BASENAME), root.der().as_ref())?; - write(output_dir.join(ROOT_CA_PEM_BASENAME), root.pem())?; - write( - output_dir.join(ROOT_CA_KEY_BASENAME), - key_pair_from_secret(ROOT_CA_SK)?.serialize_pem(), - )?; - write(output_dir.join(TCB_SIGNER_CHAIN_BASENAME), tcb_signer_chain_pem)?; write(output_dir.join(PCK_CHAIN_BASENAME), pck_chain_pem)?; write( output_dir.join(PCK_KEY_BASENAME), SecretKey::from_slice(&PCK_SK)?.to_pkcs8_pem(Default::default())?, )?; - write(output_dir.join(ROOT_CRL_DER_BASENAME), root_crl.der().as_ref())?; - write(output_dir.join(PCK_CRL_DER_BASENAME), pck_crl.der().as_ref())?; - write(output_dir.join(TCB_INFO_JSON_BASENAME), format!("{tcb_info_json}\n"))?; - write(output_dir.join(QE_IDENTITY_JSON_BASENAME), format!("{qe_identity_json}\n"))?; + for basename in [ + "mock-root-ca.pem", + "mock-root-ca-key.pem", + "mock-tcb-signer-chain.pem", + "mock-root-ca.crl.der", + "mock-pck.crl.der", + "mock-tcb-info.json", + "mock-qe-identity.json", + ] { + match fs::remove_file(output_dir.join(basename)) { + Ok(()) => {} + Err(error) if error.kind() == std::io::ErrorKind::NotFound => {} + Err(error) => return Err(error.into()), + } + } Ok(()) } diff --git a/crates/mock-tdx/src/mock_pcs.rs b/crates/mock-tdx/src/mock_pcs.rs index be66c2f..77276c6 100644 --- a/crates/mock-tdx/src/mock_pcs.rs +++ b/crates/mock-tdx/src/mock_pcs.rs @@ -18,22 +18,29 @@ use dcap_qvl::QuoteCollateralV3; use serde_json::{Value, json}; use tokio::{net::TcpListener, task::JoinHandle}; -use crate::load_mock_tdx_material; +use crate::mock_collateral; +/// Configuration for a mock PCS server backed by `mock-tdx` collateral #[derive(Clone)] pub struct MockPcsConfig { + /// Whether the `/fmspcs` endpoint should advertise the mock FMSPC pub include_fmspcs_listing: bool, + /// `nextUpdate` value returned by the first TCB info response pub tcb_next_update: String, + /// `nextUpdate` value returned by the first QE identity response pub qe_next_update: String, + /// Optional `nextUpdate` value returned by later TCB info responses pub refreshed_tcb_next_update: Option, + /// Optional `nextUpdate` value returned by later QE identity responses pub refreshed_qe_next_update: Option, } impl Default for MockPcsConfig { + /// Builds a fixture-consistent config from the embedded mock collateral fn default() -> Self { - let material = load_mock_tdx_material().unwrap(); - let tcb_info: Value = serde_json::from_str(&material.collateral.tcb_info).unwrap(); - let qe_identity: Value = serde_json::from_str(&material.collateral.qe_identity).unwrap(); + let collateral = mock_collateral(); + let tcb_info: Value = serde_json::from_str(&collateral.tcb_info).unwrap(); + let qe_identity: Value = serde_json::from_str(&collateral.qe_identity).unwrap(); Self { include_fmspcs_listing: false, @@ -45,7 +52,9 @@ impl Default for MockPcsConfig { } } +/// Handle to a running mock PCS server pub struct MockPcsServer { + /// Base URL for the spawned server pub base_url: String, _task: JoinHandle<()>, tcb_calls: Arc, @@ -59,15 +68,18 @@ impl Drop for MockPcsServer { } impl MockPcsServer { + /// Returns how many times the TCB info endpoint has been called pub fn tcb_call_count(&self) -> usize { self.tcb_calls.load(Ordering::SeqCst) } + /// Returns how many times the QE identity endpoint has been called pub fn qe_call_count(&self) -> usize { self.qe_calls.load(Ordering::SeqCst) } } +/// Shared state served by the mock PCS routes #[derive(Clone)] struct MockPcsState { fmspc: String, @@ -89,11 +101,11 @@ struct MockPcsState { qe_calls: Arc, } +/// Spawns a local mock PCS server using the embedded `mock-tdx` collateral pub async fn spawn_mock_pcs_server( config: MockPcsConfig, ) -> Result> { - let material = load_mock_tdx_material()?; - let base_collateral: QuoteCollateralV3 = material.collateral; + let base_collateral: QuoteCollateralV3 = mock_collateral(); let mut tcb_info: Value = serde_json::from_str(&base_collateral.tcb_info)?; tcb_info["nextUpdate"] = Value::String(config.tcb_next_update.clone()); @@ -104,7 +116,7 @@ pub async fn spawn_mock_pcs_server( let tcb_calls = Arc::new(AtomicUsize::new(0)); let qe_calls = Arc::new(AtomicUsize::new(0)); let state = Arc::new(MockPcsState { - fmspc: material.manifest.fmspc, + fmspc: tcb_info["fmspc"].as_str().ok_or("mock collateral missing fmspc")?.to_string(), include_fmspcs_listing: config.include_fmspcs_listing, base_tcb_info: tcb_info, base_qe_identity: qe_identity, @@ -140,6 +152,7 @@ pub async fn spawn_mock_pcs_server( Ok(MockPcsServer { base_url: format!("http://{addr}"), _task: task, tcb_calls, qe_calls }) } +/// Serves the mock PCK CRL and issuer chain async fn mock_pck_crl_handler( State(state): State>, Query(params): Query>, @@ -152,6 +165,7 @@ async fn mock_pck_crl_handler( ([("SGX-PCK-CRL-Issuer-Chain", state.pck_crl_issuer_chain.clone())], state.pck_crl.clone()) } +/// Serves the optional FMSPC listing used by PCCS prewarm tests async fn mock_fmspcs_handler(State(state): State>) -> impl IntoResponse { if state.include_fmspcs_listing { Json(json!([{ @@ -163,6 +177,7 @@ async fn mock_fmspcs_handler(State(state): State>) -> impl Int } } +/// Serves signed TCB info with configurable refresh behavior async fn mock_tcb_handler( State(state): State>, Query(params): Query>, @@ -185,6 +200,7 @@ async fn mock_tcb_handler( ) } +/// Serves signed QE identity collateral with configurable refresh behavior async fn mock_qe_identity_handler( State(state): State>, Query(params): Query>, @@ -207,6 +223,7 @@ async fn mock_qe_identity_handler( ) } +/// Serves the root CA CRL expected by the PCS client async fn mock_root_ca_crl_handler(State(state): State>) -> impl IntoResponse { state.root_ca_crl_hex.clone() } diff --git a/crates/mock-tdx/test-assets/generated-dcap/mock-dcap-collateral.yaml b/crates/mock-tdx/test-assets/generated-dcap/mock-dcap-collateral.yaml index 9dfe155..433f2b4 100644 --- a/crates/mock-tdx/test-assets/generated-dcap/mock-dcap-collateral.yaml +++ b/crates/mock-tdx/test-assets/generated-dcap/mock-dcap-collateral.yaml @@ -7,35 +7,35 @@ pck_crl_issuer_chain: | U3LfYiHqOhN1V+Rz/dtnVfBb1QfDxTP86ckShaNjMGEwHwYDVR0jBBgwFoAUdoBa Y6aYDBgHVCShPzJ3LQLXxxswDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBS/y6K3 QqgHu7crUi+kaUxGBP9o6zAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0cA - MEQCIGepVOEvTx2ic+Yrw8uX0CsslLDBz6WzlWvDqodzUstPAiBs+kxDr5Tcx3oL - +6UKISByeEmo2u0OQsow26ZIs3BbVw== + MEQCIClT20U/lslrRF7D0fWejItCI36VUrYcB0fhdAqruQ8hAiB4dlI00d0G5V2P + GymjA8Mo0NJGUsxhaUzzNJkZtp+gtw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- - MIIBjDCCATKgAwIBAgIBATAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu + MIIBizCCATKgAwIBAgIBATAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu dGVsIFJvb3QgQ0EwHhcNMjUwMTAxMDAwMDAwWhcNNDUwMTAxMDAwMDAwWjAdMRsw GQYDVQQDDBJNb2NrIEludGVsIFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMB BwNCAAQCF+YX8LZEOSgnj5aZnmmiOk8sFSvfbWzfZuW4AoLU7RlKfevLl3EtLdo8 qFqodlpW9F/HWFmWUvKJfGUwbleUo2MwYTAfBgNVHSMEGDAWgBR2gFpjppgMGAdU JKE/MnctAtfHGzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFHaAWmOmmAwYB1Qk - oT8ydy0C18cbMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIhAIpY - Gp+NkZnyVdGHNmeqYkl15rfGN6XYAuh9981yICztAiBlHn+o9LfGvFCI6R7DA5FL - 7kGbJLyiIMrR+xVRhpLHxg== + oT8ydy0C18cbMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDRwAwRAIgcTi3 + m5UlXAYJLHc0VISnLUaiMcWt8EWkEUyMaTgR+NsCIE6+vLJEeQY972rDZjeoaQqm + 7ga04Ws35QJ/eC8l28ir -----END CERTIFICATE----- root_ca_crl: >- - 3081d6307d020101300a06082a8648ce3d040302301d311b301906035504030c124d6f636b20496e74656c20526f6f74204341170d3235303130313030303030305a170d3435303130313030303030305aa02f302d301f0603551d2304183016801476805a63a6980c18075424a13f32772d02d7c71b300a0603551d140403020101300a06082a8648ce3d0403020349003046022100f3749c1f623ca9c5c948a3f6b421ed2636adba6023af7ba3cc80b55f577ffd970221009b92d3db4f6ecadd6636fb3350420820230e3e770e248aad13617325a267d06e + 3081d6307d020101300a06082a8648ce3d040302301d311b301906035504030c124d6f636b20496e74656c20526f6f74204341170d3235303130313030303030305a170d3435303130313030303030305aa02f302d301f0603551d2304183016801476805a63a6980c18075424a13f32772d02d7c71b300a0603551d140403020101300a06082a8648ce3d0403020349003046022100c8e0953270d9430ebd630f516b9920d6fffdd8087c1dadd03a4988670d50398d022100b962161263717205102c23d8b16f9e094afaef0f1b4fdc480eb39c77ef0afa7e pck_crl: >- - 3081dc308184020101300a06082a8648ce3d04030230243122302006035504030c194d6f636b20496e74656c20544342205369676e696e67204341170d3235303130313030303030305a170d3435303130313030303030305aa02f302d301f0603551d23041830168014bfcba2b742a807bbb72b522fa4694c4604ff68eb300a0603551d140403020102300a06082a8648ce3d040302034700304402207b99e91bcdfcc1ec076103279955dc04b799074541cedeb10a8d74b380a6172302206c952af030073c711ca787abfdb01ca8dec13a10947a0d14d50ce275ada21240 + 3081dd308184020101300a06082a8648ce3d04030230243122302006035504030c194d6f636b20496e74656c20544342205369676e696e67204341170d3235303130313030303030305a170d3435303130313030303030305aa02f302d301f0603551d23041830168014bfcba2b742a807bbb72b522fa4694c4604ff68eb300a0603551d140403020102300a06082a8648ce3d0403020348003045022100894fabfa679970db9e448f4316aad96c58f00f0c7f9a115a2454715c3479091902200edab93163c2548870dda7f673d1671ad3912a0db1fe194ef2d4222acf3aeb75 tcb_info_issuer_chain: | -----BEGIN CERTIFICATE----- - MIIBlzCCATygAwIBAgIBAzAKBggqhkjOPQQDAjAkMSIwIAYDVQQDDBlNb2NrIElu + MIIBljCCATygAwIBAgIBAzAKBggqhkjOPQQDAjAkMSIwIAYDVQQDDBlNb2NrIElu dGVsIFRDQiBTaWduaW5nIENBMB4XDTI1MDEwMTAwMDAwMFoXDTQ1MDEwMTAwMDAw MFowIDEeMBwGA1UEAwwVTW9jayBJbnRlbCBUQ0IgU2lnbmVyMFkwEwYHKoZIzj0C AQYIKoZIzj0DAQcDQgAEUadYCDOJjqGxg8vXNQpAmQeMbvHB4Y6XDNdoMDXyXn0B EFInErC1p8/wgWhUhphKlOaDHtrEbnNg+p2DSnqBoaNjMGEwHwYDVR0jBBgwFoAU v8uit0KoB7u3K1IvpGlMRgT/aOswDgYDVR0PAQH/BAQDAgeAMB0GA1UdDgQWBBST 7j5t5QAoWFiJAKg4c8ROKT5hpTAPBgNVHRMBAf8EBTADAQEAMAoGCCqGSM49BAMC - A0kAMEYCIQC5u/Nxyy330a/pDEmvcNZNoaZSID6GiBhorkO1EQBtawIhAOUAd0z7 - di2OsiDXWFc5TiQ9+FxMuWTx9eo+DiUM+UnK + A0gAMEUCIFWN0plZ3iG2e6hI+nIQ4TDU7vR7WGE8jpr0QrJnSYjRAiEAiXy2lkPf + OXVtV9Ken5md/6AusUY+vwA1ZAcQcxlfPyA= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIBkjCCATmgAwIBAgIBAjAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu @@ -45,34 +45,34 @@ tcb_info_issuer_chain: | U3LfYiHqOhN1V+Rz/dtnVfBb1QfDxTP86ckShaNjMGEwHwYDVR0jBBgwFoAUdoBa Y6aYDBgHVCShPzJ3LQLXxxswDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBS/y6K3 QqgHu7crUi+kaUxGBP9o6zAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0cA - MEQCIGepVOEvTx2ic+Yrw8uX0CsslLDBz6WzlWvDqodzUstPAiBs+kxDr5Tcx3oL - +6UKISByeEmo2u0OQsow26ZIs3BbVw== + MEQCIClT20U/lslrRF7D0fWejItCI36VUrYcB0fhdAqruQ8hAiB4dlI00d0G5V2P + GymjA8Mo0NJGUsxhaUzzNJkZtp+gtw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- - MIIBjDCCATKgAwIBAgIBATAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu + MIIBizCCATKgAwIBAgIBATAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu dGVsIFJvb3QgQ0EwHhcNMjUwMTAxMDAwMDAwWhcNNDUwMTAxMDAwMDAwWjAdMRsw GQYDVQQDDBJNb2NrIEludGVsIFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMB BwNCAAQCF+YX8LZEOSgnj5aZnmmiOk8sFSvfbWzfZuW4AoLU7RlKfevLl3EtLdo8 qFqodlpW9F/HWFmWUvKJfGUwbleUo2MwYTAfBgNVHSMEGDAWgBR2gFpjppgMGAdU JKE/MnctAtfHGzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFHaAWmOmmAwYB1Qk - oT8ydy0C18cbMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIhAIpY - Gp+NkZnyVdGHNmeqYkl15rfGN6XYAuh9981yICztAiBlHn+o9LfGvFCI6R7DA5FL - 7kGbJLyiIMrR+xVRhpLHxg== + oT8ydy0C18cbMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDRwAwRAIgcTi3 + m5UlXAYJLHc0VISnLUaiMcWt8EWkEUyMaTgR+NsCIE6+vLJEeQY972rDZjeoaQqm + 7ga04Ws35QJ/eC8l28ir -----END CERTIFICATE----- tcb_info: "{\"id\":\"TDX\",\"version\":3,\"issueDate\":\"2025-01-01T00:00:00Z\",\"nextUpdate\":\"2045-01-01T00:00:00Z\",\"fmspc\":\"00906EA10000\",\"pceId\":\"0000\",\"tcbType\":0,\"tcbEvaluationDataNumber\":1,\"tcbLevels\":[{\"tcb\":{\"sgxtcbcomponents\":[{\"svn\":11},{\"svn\":11},{\"svn\":2},{\"svn\":2},{\"svn\":255},{\"svn\":1},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0}],\"tdxtcbcomponents\":[{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1}],\"pcesvn\":13},\"tcbDate\":\"2025-01-01T00:00:00Z\",\"tcbStatus\":\"UpToDate\",\"advisoryIDs\":[]}]}" tcb_info_signature: >- 81deffe35b79b7d7cfa1b4f7a62cf2f661f7d47c1d53838f8c48199ebbd14af77605f8a9bf060aafc48624a5a70be20307bb9e622345fd59f40966967ff1bce1 qe_identity_issuer_chain: | -----BEGIN CERTIFICATE----- - MIIBlzCCATygAwIBAgIBAzAKBggqhkjOPQQDAjAkMSIwIAYDVQQDDBlNb2NrIElu + MIIBljCCATygAwIBAgIBAzAKBggqhkjOPQQDAjAkMSIwIAYDVQQDDBlNb2NrIElu dGVsIFRDQiBTaWduaW5nIENBMB4XDTI1MDEwMTAwMDAwMFoXDTQ1MDEwMTAwMDAw MFowIDEeMBwGA1UEAwwVTW9jayBJbnRlbCBUQ0IgU2lnbmVyMFkwEwYHKoZIzj0C AQYIKoZIzj0DAQcDQgAEUadYCDOJjqGxg8vXNQpAmQeMbvHB4Y6XDNdoMDXyXn0B EFInErC1p8/wgWhUhphKlOaDHtrEbnNg+p2DSnqBoaNjMGEwHwYDVR0jBBgwFoAU v8uit0KoB7u3K1IvpGlMRgT/aOswDgYDVR0PAQH/BAQDAgeAMB0GA1UdDgQWBBST 7j5t5QAoWFiJAKg4c8ROKT5hpTAPBgNVHRMBAf8EBTADAQEAMAoGCCqGSM49BAMC - A0kAMEYCIQC5u/Nxyy330a/pDEmvcNZNoaZSID6GiBhorkO1EQBtawIhAOUAd0z7 - di2OsiDXWFc5TiQ9+FxMuWTx9eo+DiUM+UnK + A0gAMEUCIFWN0plZ3iG2e6hI+nIQ4TDU7vR7WGE8jpr0QrJnSYjRAiEAiXy2lkPf + OXVtV9Ken5md/6AusUY+vwA1ZAcQcxlfPyA= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIBkjCCATmgAwIBAgIBAjAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu @@ -82,26 +82,26 @@ qe_identity_issuer_chain: | U3LfYiHqOhN1V+Rz/dtnVfBb1QfDxTP86ckShaNjMGEwHwYDVR0jBBgwFoAUdoBa Y6aYDBgHVCShPzJ3LQLXxxswDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBS/y6K3 QqgHu7crUi+kaUxGBP9o6zAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0cA - MEQCIGepVOEvTx2ic+Yrw8uX0CsslLDBz6WzlWvDqodzUstPAiBs+kxDr5Tcx3oL - +6UKISByeEmo2u0OQsow26ZIs3BbVw== + MEQCIClT20U/lslrRF7D0fWejItCI36VUrYcB0fhdAqruQ8hAiB4dlI00d0G5V2P + GymjA8Mo0NJGUsxhaUzzNJkZtp+gtw== -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- - MIIBjDCCATKgAwIBAgIBATAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu + MIIBizCCATKgAwIBAgIBATAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu dGVsIFJvb3QgQ0EwHhcNMjUwMTAxMDAwMDAwWhcNNDUwMTAxMDAwMDAwWjAdMRsw GQYDVQQDDBJNb2NrIEludGVsIFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMB BwNCAAQCF+YX8LZEOSgnj5aZnmmiOk8sFSvfbWzfZuW4AoLU7RlKfevLl3EtLdo8 qFqodlpW9F/HWFmWUvKJfGUwbleUo2MwYTAfBgNVHSMEGDAWgBR2gFpjppgMGAdU JKE/MnctAtfHGzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFHaAWmOmmAwYB1Qk - oT8ydy0C18cbMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIhAIpY - Gp+NkZnyVdGHNmeqYkl15rfGN6XYAuh9981yICztAiBlHn+o9LfGvFCI6R7DA5FL - 7kGbJLyiIMrR+xVRhpLHxg== + oT8ydy0C18cbMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDRwAwRAIgcTi3 + m5UlXAYJLHc0VISnLUaiMcWt8EWkEUyMaTgR+NsCIE6+vLJEeQY972rDZjeoaQqm + 7ga04Ws35QJ/eC8l28ir -----END CERTIFICATE----- qe_identity: "{\"id\":\"TD_QE\",\"version\":2,\"issueDate\":\"2025-01-01T00:00:00Z\",\"nextUpdate\":\"2045-01-01T00:00:00Z\",\"tcbEvaluationDataNumber\":1,\"miscselect\":\"00000000\",\"miscselectMask\":\"FFFFFFFF\",\"attributes\":\"00000000000000000000000000000000\",\"attributesMask\":\"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\",\"mrsigner\":\"5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A\",\"isvprodid\":2,\"tcbLevels\":[{\"tcb\":{\"isvsvn\":11},\"tcbDate\":\"2025-01-01T00:00:00Z\",\"tcbStatus\":\"UpToDate\",\"advisoryIDs\":[]}]}" qe_identity_signature: >- 7394339f635a123e047fb29ead5ce6faac737b5d648fb28b7b27d3e71829454bac1aff1d3c70b37bdca5bf2571d9c097ee58cf1c48693d844f5c4699d3c4f85b pck_certificate_chain: | -----BEGIN CERTIFICATE----- - MIIDZTCCAwqgAwIBAgIBBDAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu + MIIDZDCCAwqgAwIBAgIBBDAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu dGVsIFJvb3QgQ0EwHhcNMjUwMTAxMDAwMDAwWhcNNDUwMTAxMDAwMDAwWjAZMRcw FQYDVQQDDA5Nb2NrIEludGVsIFBDSzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA BFs2iQ2svXyalrt0oe4os9LXW3LgmiDvJc+Ob9ip8DUNDhS+2NRoKjTYNTi9/1uW @@ -117,18 +117,18 @@ pck_certificate_chain: | MBAGCyqGSIb4TQENAQIOAgEAMBAGCyqGSIb4TQENAQIPAgEAMBAGCyqGSIb4TQEN AQIQAgEAMBAGCyqGSIb4TQENAQIRAgENMB8GCyqGSIb4TQENAQISBBALCwIC/wEA AAAAAAAAAAAAMBAGCiqGSIb4TQENAQMEAgAAMBQGCiqGSIb4TQENAQQEBgCQbqEA - ADAPBgoqhkiG+E0BDQEFCgEAMAoGCCqGSM49BAMCA0kAMEYCIQDJDr0VCWIRXShw - nIjk4N59CqFlUIy1GCiZCzMEz/J31QIhAP8lY/k6nufijKcZFIJj6H5AUiihugQ0 - SjmQUfVPDXx+ + ADAPBgoqhkiG+E0BDQEFCgEAMAoGCCqGSM49BAMCA0gAMEUCIAqDl+dxedPT/Olc + LXNwchpcs9/1tZ7CA0JBZS6TdC27AiEAg0Ke2IntwcEZTF557/AqQO8QNCD237J4 + LTglZz97CaI= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- - MIIBjDCCATKgAwIBAgIBATAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu + MIIBizCCATKgAwIBAgIBATAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu dGVsIFJvb3QgQ0EwHhcNMjUwMTAxMDAwMDAwWhcNNDUwMTAxMDAwMDAwWjAdMRsw GQYDVQQDDBJNb2NrIEludGVsIFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMB BwNCAAQCF+YX8LZEOSgnj5aZnmmiOk8sFSvfbWzfZuW4AoLU7RlKfevLl3EtLdo8 qFqodlpW9F/HWFmWUvKJfGUwbleUo2MwYTAfBgNVHSMEGDAWgBR2gFpjppgMGAdU JKE/MnctAtfHGzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFHaAWmOmmAwYB1Qk - oT8ydy0C18cbMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIhAIpY - Gp+NkZnyVdGHNmeqYkl15rfGN6XYAuh9981yICztAiBlHn+o9LfGvFCI6R7DA5FL - 7kGbJLyiIMrR+xVRhpLHxg== + oT8ydy0C18cbMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDRwAwRAIgcTi3 + m5UlXAYJLHc0VISnLUaiMcWt8EWkEUyMaTgR+NsCIE6+vLJEeQY972rDZjeoaQqm + 7ga04Ws35QJ/eC8l28ir -----END CERTIFICATE----- diff --git a/crates/mock-tdx/test-assets/generated-dcap/mock-dcap-manifest.json b/crates/mock-tdx/test-assets/generated-dcap/mock-dcap-manifest.json deleted file mode 100644 index 9b01f72..0000000 --- a/crates/mock-tdx/test-assets/generated-dcap/mock-dcap-manifest.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "fmspc": "00906EA10000", - "pce_id_hex": "0000", - "pce_svn": 13, - "cpu_svn_hex": "0B0B0202FF0100000000000000000000", - "ppid_hex": "D04EC06D4E6D92DC90D0AD3CF5EE2DDF", - "qe_isvsvn": 11, - "issuer_common_names": { - "root_ca": "Mock Intel Root CA", - "tcb_signing_ca": "Mock Intel TCB Signing CA", - "tcb_signer": "Mock Intel TCB Signer", - "pck": "Mock Intel PCK" - }, - "files": { - "collateral": "mock-dcap-collateral.yaml", - "manifest": "mock-dcap-manifest.json", - "root_ca_der": "mock-root-ca.der", - "root_ca_pem": "mock-root-ca.pem", - "root_ca_key_pem": "mock-root-ca-key.pem", - "tcb_signer_chain_pem": "mock-tcb-signer-chain.pem", - "pck_chain_pem": "mock-pck-chain.pem", - "pck_key_pem": "mock-pck-key.pem", - "root_crl_der": "mock-root-ca.crl.der", - "pck_crl_der": "mock-pck.crl.der", - "tcb_info_json": "mock-tcb-info.json", - "qe_identity_json": "mock-qe-identity.json" - } -} \ No newline at end of file diff --git a/crates/mock-tdx/test-assets/generated-dcap/mock-pck-chain.pem b/crates/mock-tdx/test-assets/generated-dcap/mock-pck-chain.pem index f4fc34d..dba2d40 100644 --- a/crates/mock-tdx/test-assets/generated-dcap/mock-pck-chain.pem +++ b/crates/mock-tdx/test-assets/generated-dcap/mock-pck-chain.pem @@ -1,5 +1,5 @@ -----BEGIN CERTIFICATE----- -MIIDZTCCAwqgAwIBAgIBBDAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu +MIIDZDCCAwqgAwIBAgIBBDAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu dGVsIFJvb3QgQ0EwHhcNMjUwMTAxMDAwMDAwWhcNNDUwMTAxMDAwMDAwWjAZMRcw FQYDVQQDDA5Nb2NrIEludGVsIFBDSzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA BFs2iQ2svXyalrt0oe4os9LXW3LgmiDvJc+Ob9ip8DUNDhS+2NRoKjTYNTi9/1uW @@ -15,18 +15,18 @@ SIb4TQENAQILAgEAMBAGCyqGSIb4TQENAQIMAgEAMBAGCyqGSIb4TQENAQINAgEA MBAGCyqGSIb4TQENAQIOAgEAMBAGCyqGSIb4TQENAQIPAgEAMBAGCyqGSIb4TQEN AQIQAgEAMBAGCyqGSIb4TQENAQIRAgENMB8GCyqGSIb4TQENAQISBBALCwIC/wEA AAAAAAAAAAAAMBAGCiqGSIb4TQENAQMEAgAAMBQGCiqGSIb4TQENAQQEBgCQbqEA -ADAPBgoqhkiG+E0BDQEFCgEAMAoGCCqGSM49BAMCA0kAMEYCIQDJDr0VCWIRXShw -nIjk4N59CqFlUIy1GCiZCzMEz/J31QIhAP8lY/k6nufijKcZFIJj6H5AUiihugQ0 -SjmQUfVPDXx+ +ADAPBgoqhkiG+E0BDQEFCgEAMAoGCCqGSM49BAMCA0gAMEUCIAqDl+dxedPT/Olc +LXNwchpcs9/1tZ7CA0JBZS6TdC27AiEAg0Ke2IntwcEZTF557/AqQO8QNCD237J4 +LTglZz97CaI= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIBjDCCATKgAwIBAgIBATAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu +MIIBizCCATKgAwIBAgIBATAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu dGVsIFJvb3QgQ0EwHhcNMjUwMTAxMDAwMDAwWhcNNDUwMTAxMDAwMDAwWjAdMRsw GQYDVQQDDBJNb2NrIEludGVsIFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMB BwNCAAQCF+YX8LZEOSgnj5aZnmmiOk8sFSvfbWzfZuW4AoLU7RlKfevLl3EtLdo8 qFqodlpW9F/HWFmWUvKJfGUwbleUo2MwYTAfBgNVHSMEGDAWgBR2gFpjppgMGAdU JKE/MnctAtfHGzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFHaAWmOmmAwYB1Qk -oT8ydy0C18cbMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIhAIpY -Gp+NkZnyVdGHNmeqYkl15rfGN6XYAuh9981yICztAiBlHn+o9LfGvFCI6R7DA5FL -7kGbJLyiIMrR+xVRhpLHxg== +oT8ydy0C18cbMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDRwAwRAIgcTi3 +m5UlXAYJLHc0VISnLUaiMcWt8EWkEUyMaTgR+NsCIE6+vLJEeQY972rDZjeoaQqm +7ga04Ws35QJ/eC8l28ir -----END CERTIFICATE----- diff --git a/crates/mock-tdx/test-assets/generated-dcap/mock-pck.crl.der b/crates/mock-tdx/test-assets/generated-dcap/mock-pck.crl.der deleted file mode 100644 index 156ab7b0feed0ceaf7d7e73aff850a195113ce28..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 223 zcmXqLykpSV!o9csC#m1r4=5fxJg_+4f)==6&l8rf(g_%dlH$ORB!85NUHAf*RKfgr5 z*-@O=$kf0P2*DsqoYw@IyFlMS*FYYqSyq`v!a%G+q^uz-dD#pe3HA_`h4x0}x=h!P zO9O2KDi8tMg=#aiCxd|-lOn_Ck~#88HY<;w^jQ3Di{e`~v$eYtl-E};KGU!@KD_?# obfA*ilP=%(&pUND&Fr^vfD?y;GM`;JpGwzS;lyIqMd=sv0D?$9umAu6 diff --git a/crates/mock-tdx/test-assets/generated-dcap/mock-root-ca.der b/crates/mock-tdx/test-assets/generated-dcap/mock-root-ca.der index 446d1be8467f38b6af52915f253bc129c8560ecf..a8d73869998211330d9c3bb9f6218076d6fb7b0b 100644 GIT binary patch delta 86 zcmV-c0IC0w1CIj)FoFS#u>?Z_NJjuLL;@gjIJcXXC0qsxEO#_igr_Y=qA|s-@I|B% sOpIwb5%}8zAWpu#vP5|XJ@0D6W;dv53a0J`wBc(v?Z_Nk{-NMFJrJidY(-jggu1RndnwXR2aJb>_FmH>KDD t=zaIiav&`20w84`f2j1g#=KC7=^n!akxTADn String { - load_mock_tdx_material().unwrap().manifest.fmspc + let collateral = mock_collateral(); + let tcb_info: TcbInfo = serde_json::from_str(&collateral.tcb_info).unwrap(); + tcb_info.fmspc } #[tokio::test] @@ -629,7 +631,7 @@ mod tests { #[test] fn test_extract_next_update_includes_crl_expiry() { - let mut collateral: QuoteCollateralV3 = load_mock_tdx_material().unwrap().collateral; + let mut collateral: QuoteCollateralV3 = mock_collateral(); let mut tcb_info: serde_json::Value = serde_json::from_str(&collateral.tcb_info).unwrap(); tcb_info["nextUpdate"] = serde_json::Value::String("2999-01-01T00:00:00Z".to_string()); From d74c266fa3e5c87b4fb469825a5c087cd6048d59 Mon Sep 17 00:00:00 2001 From: peg Date: Tue, 21 Apr 2026 12:37:04 +0200 Subject: [PATCH 06/11] Rename assets directory --- crates/mock-tdx/README.md | 6 +- .../mock-dcap-collateral.yaml | 78 +++++++++--------- .../mock-pck-chain.pem | 14 ++-- .../mock-pck-key.pem | 0 .../mock-root-ca.der | Bin 399 -> 400 bytes crates/mock-tdx/src/lib.rs | 11 +-- crates/mock-tdx/src/main.rs | 2 +- 7 files changed, 54 insertions(+), 57 deletions(-) rename crates/mock-tdx/{test-assets/generated-dcap => assets}/mock-dcap-collateral.yaml (74%) rename crates/mock-tdx/{test-assets/generated-dcap => assets}/mock-pck-chain.pem (80%) rename crates/mock-tdx/{test-assets/generated-dcap => assets}/mock-pck-key.pem (100%) rename crates/mock-tdx/{test-assets/generated-dcap => assets}/mock-root-ca.der (74%) diff --git a/crates/mock-tdx/README.md b/crates/mock-tdx/README.md index 2aeeeba..05239fa 100644 --- a/crates/mock-tdx/README.md +++ b/crates/mock-tdx/README.md @@ -7,10 +7,10 @@ mock the complete DCAP workflow. It provides: - A small fixture generator for a mock DCAP trust chain and collateral -- A quote generator that emits mock TDX DCAP quotes with caller-supplied +- A quote generator for mock TDX DCAP quotes with caller-supplied `report_data` -- Checked-in mock collateral, root certificates, CRLs, and signing material - under `test-assets/generated-dcap` +- Checked-in mock collateral, root certificate, and PCK certificate chain + and key under `assets` - A mock PCS server The generated quotes are shaped so they can be parsed and verified with diff --git a/crates/mock-tdx/test-assets/generated-dcap/mock-dcap-collateral.yaml b/crates/mock-tdx/assets/mock-dcap-collateral.yaml similarity index 74% rename from crates/mock-tdx/test-assets/generated-dcap/mock-dcap-collateral.yaml rename to crates/mock-tdx/assets/mock-dcap-collateral.yaml index 433f2b4..65edd7b 100644 --- a/crates/mock-tdx/test-assets/generated-dcap/mock-dcap-collateral.yaml +++ b/crates/mock-tdx/assets/mock-dcap-collateral.yaml @@ -1,100 +1,100 @@ pck_crl_issuer_chain: | -----BEGIN CERTIFICATE----- - MIIBkjCCATmgAwIBAgIBAjAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu + MIIBlDCCATmgAwIBAgIBAjAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu dGVsIFJvb3QgQ0EwHhcNMjUwMTAxMDAwMDAwWhcNNDUwMTAxMDAwMDAwWjAkMSIw IAYDVQQDDBlNb2NrIEludGVsIFRDQiBTaWduaW5nIENBMFkwEwYHKoZIzj0CAQYI KoZIzj0DAQcDQgAE1lqTl3yqPRsIGFL/V6eeRl8WYFdzBLrq1QXdOkhYnPNQGF6J U3LfYiHqOhN1V+Rz/dtnVfBb1QfDxTP86ckShaNjMGEwHwYDVR0jBBgwFoAUdoBa Y6aYDBgHVCShPzJ3LQLXxxswDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBS/y6K3 - QqgHu7crUi+kaUxGBP9o6zAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0cA - MEQCIClT20U/lslrRF7D0fWejItCI36VUrYcB0fhdAqruQ8hAiB4dlI00d0G5V2P - GymjA8Mo0NJGUsxhaUzzNJkZtp+gtw== + QqgHu7crUi+kaUxGBP9o6zAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0kA + MEYCIQCvhCZKzOyaNkad7y1vBE4SKtT8nRZqCx/Y82ugmDoAjgIhAIs/9uHaNmOD + Uip8B/h+JVgIm8FoNs5EOc5D/PkyoEKk -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- - MIIBizCCATKgAwIBAgIBATAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu + MIIBjDCCATKgAwIBAgIBATAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu dGVsIFJvb3QgQ0EwHhcNMjUwMTAxMDAwMDAwWhcNNDUwMTAxMDAwMDAwWjAdMRsw GQYDVQQDDBJNb2NrIEludGVsIFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMB BwNCAAQCF+YX8LZEOSgnj5aZnmmiOk8sFSvfbWzfZuW4AoLU7RlKfevLl3EtLdo8 qFqodlpW9F/HWFmWUvKJfGUwbleUo2MwYTAfBgNVHSMEGDAWgBR2gFpjppgMGAdU JKE/MnctAtfHGzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFHaAWmOmmAwYB1Qk - oT8ydy0C18cbMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDRwAwRAIgcTi3 - m5UlXAYJLHc0VISnLUaiMcWt8EWkEUyMaTgR+NsCIE6+vLJEeQY972rDZjeoaQqm - 7ga04Ws35QJ/eC8l28ir + oT8ydy0C18cbMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIhANOM + o5zM6NZ93Iewr2S2g0MiM+6mMJaJNDfY5pXp82amAiBXJ1pB709SgQCgRmICY6GJ + LsG1gRFnBX+0dG80hRXdPA== -----END CERTIFICATE----- root_ca_crl: >- - 3081d6307d020101300a06082a8648ce3d040302301d311b301906035504030c124d6f636b20496e74656c20526f6f74204341170d3235303130313030303030305a170d3435303130313030303030305aa02f302d301f0603551d2304183016801476805a63a6980c18075424a13f32772d02d7c71b300a0603551d140403020101300a06082a8648ce3d0403020349003046022100c8e0953270d9430ebd630f516b9920d6fffdd8087c1dadd03a4988670d50398d022100b962161263717205102c23d8b16f9e094afaef0f1b4fdc480eb39c77ef0afa7e + 3081d5307d020101300a06082a8648ce3d040302301d311b301906035504030c124d6f636b20496e74656c20526f6f74204341170d3235303130313030303030305a170d3435303130313030303030305aa02f302d301f0603551d2304183016801476805a63a6980c18075424a13f32772d02d7c71b300a0603551d140403020101300a06082a8648ce3d0403020348003045022034caddb53533343cde3c792b6a4457ce1685d07fda266591d276774ace219a3f022100a26423311d592db905ef49ab329ffce8b1ef4e0e0fd05b56f4085789038b035b pck_crl: >- - 3081dd308184020101300a06082a8648ce3d04030230243122302006035504030c194d6f636b20496e74656c20544342205369676e696e67204341170d3235303130313030303030305a170d3435303130313030303030305aa02f302d301f0603551d23041830168014bfcba2b742a807bbb72b522fa4694c4604ff68eb300a0603551d140403020102300a06082a8648ce3d0403020348003045022100894fabfa679970db9e448f4316aad96c58f00f0c7f9a115a2454715c3479091902200edab93163c2548870dda7f673d1671ad3912a0db1fe194ef2d4222acf3aeb75 + 3081dc308184020101300a06082a8648ce3d04030230243122302006035504030c194d6f636b20496e74656c20544342205369676e696e67204341170d3235303130313030303030305a170d3435303130313030303030305aa02f302d301f0603551d23041830168014bfcba2b742a807bbb72b522fa4694c4604ff68eb300a0603551d140403020102300a06082a8648ce3d040302034700304402205062b6aee1fea13dea47a816f419df3da4af7f71a2a98887d027c72d983366f2022030f8baae33ab09b7d9826ad238761e6e365079671d1e1cb31ee1e339d8da4249 tcb_info_issuer_chain: | -----BEGIN CERTIFICATE----- - MIIBljCCATygAwIBAgIBAzAKBggqhkjOPQQDAjAkMSIwIAYDVQQDDBlNb2NrIElu + MIIBlzCCATygAwIBAgIBAzAKBggqhkjOPQQDAjAkMSIwIAYDVQQDDBlNb2NrIElu dGVsIFRDQiBTaWduaW5nIENBMB4XDTI1MDEwMTAwMDAwMFoXDTQ1MDEwMTAwMDAw MFowIDEeMBwGA1UEAwwVTW9jayBJbnRlbCBUQ0IgU2lnbmVyMFkwEwYHKoZIzj0C AQYIKoZIzj0DAQcDQgAEUadYCDOJjqGxg8vXNQpAmQeMbvHB4Y6XDNdoMDXyXn0B EFInErC1p8/wgWhUhphKlOaDHtrEbnNg+p2DSnqBoaNjMGEwHwYDVR0jBBgwFoAU v8uit0KoB7u3K1IvpGlMRgT/aOswDgYDVR0PAQH/BAQDAgeAMB0GA1UdDgQWBBST 7j5t5QAoWFiJAKg4c8ROKT5hpTAPBgNVHRMBAf8EBTADAQEAMAoGCCqGSM49BAMC - A0gAMEUCIFWN0plZ3iG2e6hI+nIQ4TDU7vR7WGE8jpr0QrJnSYjRAiEAiXy2lkPf - OXVtV9Ken5md/6AusUY+vwA1ZAcQcxlfPyA= + A0kAMEYCIQCAhGx8v+2u1fXhC8xMtzeouG654iUvC684nd3q7TBHMwIhAKqvK38E + Mu8JWo589cyxCqsAErRhSodsqUcW/MyDC0hL -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- - MIIBkjCCATmgAwIBAgIBAjAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu + MIIBlDCCATmgAwIBAgIBAjAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu dGVsIFJvb3QgQ0EwHhcNMjUwMTAxMDAwMDAwWhcNNDUwMTAxMDAwMDAwWjAkMSIw IAYDVQQDDBlNb2NrIEludGVsIFRDQiBTaWduaW5nIENBMFkwEwYHKoZIzj0CAQYI KoZIzj0DAQcDQgAE1lqTl3yqPRsIGFL/V6eeRl8WYFdzBLrq1QXdOkhYnPNQGF6J U3LfYiHqOhN1V+Rz/dtnVfBb1QfDxTP86ckShaNjMGEwHwYDVR0jBBgwFoAUdoBa Y6aYDBgHVCShPzJ3LQLXxxswDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBS/y6K3 - QqgHu7crUi+kaUxGBP9o6zAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0cA - MEQCIClT20U/lslrRF7D0fWejItCI36VUrYcB0fhdAqruQ8hAiB4dlI00d0G5V2P - GymjA8Mo0NJGUsxhaUzzNJkZtp+gtw== + QqgHu7crUi+kaUxGBP9o6zAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0kA + MEYCIQCvhCZKzOyaNkad7y1vBE4SKtT8nRZqCx/Y82ugmDoAjgIhAIs/9uHaNmOD + Uip8B/h+JVgIm8FoNs5EOc5D/PkyoEKk -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- - MIIBizCCATKgAwIBAgIBATAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu + MIIBjDCCATKgAwIBAgIBATAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu dGVsIFJvb3QgQ0EwHhcNMjUwMTAxMDAwMDAwWhcNNDUwMTAxMDAwMDAwWjAdMRsw GQYDVQQDDBJNb2NrIEludGVsIFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMB BwNCAAQCF+YX8LZEOSgnj5aZnmmiOk8sFSvfbWzfZuW4AoLU7RlKfevLl3EtLdo8 qFqodlpW9F/HWFmWUvKJfGUwbleUo2MwYTAfBgNVHSMEGDAWgBR2gFpjppgMGAdU JKE/MnctAtfHGzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFHaAWmOmmAwYB1Qk - oT8ydy0C18cbMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDRwAwRAIgcTi3 - m5UlXAYJLHc0VISnLUaiMcWt8EWkEUyMaTgR+NsCIE6+vLJEeQY972rDZjeoaQqm - 7ga04Ws35QJ/eC8l28ir + oT8ydy0C18cbMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIhANOM + o5zM6NZ93Iewr2S2g0MiM+6mMJaJNDfY5pXp82amAiBXJ1pB709SgQCgRmICY6GJ + LsG1gRFnBX+0dG80hRXdPA== -----END CERTIFICATE----- tcb_info: "{\"id\":\"TDX\",\"version\":3,\"issueDate\":\"2025-01-01T00:00:00Z\",\"nextUpdate\":\"2045-01-01T00:00:00Z\",\"fmspc\":\"00906EA10000\",\"pceId\":\"0000\",\"tcbType\":0,\"tcbEvaluationDataNumber\":1,\"tcbLevels\":[{\"tcb\":{\"sgxtcbcomponents\":[{\"svn\":11},{\"svn\":11},{\"svn\":2},{\"svn\":2},{\"svn\":255},{\"svn\":1},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0},{\"svn\":0}],\"tdxtcbcomponents\":[{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1},{\"svn\":1}],\"pcesvn\":13},\"tcbDate\":\"2025-01-01T00:00:00Z\",\"tcbStatus\":\"UpToDate\",\"advisoryIDs\":[]}]}" tcb_info_signature: >- 81deffe35b79b7d7cfa1b4f7a62cf2f661f7d47c1d53838f8c48199ebbd14af77605f8a9bf060aafc48624a5a70be20307bb9e622345fd59f40966967ff1bce1 qe_identity_issuer_chain: | -----BEGIN CERTIFICATE----- - MIIBljCCATygAwIBAgIBAzAKBggqhkjOPQQDAjAkMSIwIAYDVQQDDBlNb2NrIElu + MIIBlzCCATygAwIBAgIBAzAKBggqhkjOPQQDAjAkMSIwIAYDVQQDDBlNb2NrIElu dGVsIFRDQiBTaWduaW5nIENBMB4XDTI1MDEwMTAwMDAwMFoXDTQ1MDEwMTAwMDAw MFowIDEeMBwGA1UEAwwVTW9jayBJbnRlbCBUQ0IgU2lnbmVyMFkwEwYHKoZIzj0C AQYIKoZIzj0DAQcDQgAEUadYCDOJjqGxg8vXNQpAmQeMbvHB4Y6XDNdoMDXyXn0B EFInErC1p8/wgWhUhphKlOaDHtrEbnNg+p2DSnqBoaNjMGEwHwYDVR0jBBgwFoAU v8uit0KoB7u3K1IvpGlMRgT/aOswDgYDVR0PAQH/BAQDAgeAMB0GA1UdDgQWBBST 7j5t5QAoWFiJAKg4c8ROKT5hpTAPBgNVHRMBAf8EBTADAQEAMAoGCCqGSM49BAMC - A0gAMEUCIFWN0plZ3iG2e6hI+nIQ4TDU7vR7WGE8jpr0QrJnSYjRAiEAiXy2lkPf - OXVtV9Ken5md/6AusUY+vwA1ZAcQcxlfPyA= + A0kAMEYCIQCAhGx8v+2u1fXhC8xMtzeouG654iUvC684nd3q7TBHMwIhAKqvK38E + Mu8JWo589cyxCqsAErRhSodsqUcW/MyDC0hL -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- - MIIBkjCCATmgAwIBAgIBAjAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu + MIIBlDCCATmgAwIBAgIBAjAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu dGVsIFJvb3QgQ0EwHhcNMjUwMTAxMDAwMDAwWhcNNDUwMTAxMDAwMDAwWjAkMSIw IAYDVQQDDBlNb2NrIEludGVsIFRDQiBTaWduaW5nIENBMFkwEwYHKoZIzj0CAQYI KoZIzj0DAQcDQgAE1lqTl3yqPRsIGFL/V6eeRl8WYFdzBLrq1QXdOkhYnPNQGF6J U3LfYiHqOhN1V+Rz/dtnVfBb1QfDxTP86ckShaNjMGEwHwYDVR0jBBgwFoAUdoBa Y6aYDBgHVCShPzJ3LQLXxxswDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBS/y6K3 - QqgHu7crUi+kaUxGBP9o6zAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0cA - MEQCIClT20U/lslrRF7D0fWejItCI36VUrYcB0fhdAqruQ8hAiB4dlI00d0G5V2P - GymjA8Mo0NJGUsxhaUzzNJkZtp+gtw== + QqgHu7crUi+kaUxGBP9o6zAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0kA + MEYCIQCvhCZKzOyaNkad7y1vBE4SKtT8nRZqCx/Y82ugmDoAjgIhAIs/9uHaNmOD + Uip8B/h+JVgIm8FoNs5EOc5D/PkyoEKk -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- - MIIBizCCATKgAwIBAgIBATAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu + MIIBjDCCATKgAwIBAgIBATAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu dGVsIFJvb3QgQ0EwHhcNMjUwMTAxMDAwMDAwWhcNNDUwMTAxMDAwMDAwWjAdMRsw GQYDVQQDDBJNb2NrIEludGVsIFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMB BwNCAAQCF+YX8LZEOSgnj5aZnmmiOk8sFSvfbWzfZuW4AoLU7RlKfevLl3EtLdo8 qFqodlpW9F/HWFmWUvKJfGUwbleUo2MwYTAfBgNVHSMEGDAWgBR2gFpjppgMGAdU JKE/MnctAtfHGzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFHaAWmOmmAwYB1Qk - oT8ydy0C18cbMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDRwAwRAIgcTi3 - m5UlXAYJLHc0VISnLUaiMcWt8EWkEUyMaTgR+NsCIE6+vLJEeQY972rDZjeoaQqm - 7ga04Ws35QJ/eC8l28ir + oT8ydy0C18cbMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIhANOM + o5zM6NZ93Iewr2S2g0MiM+6mMJaJNDfY5pXp82amAiBXJ1pB709SgQCgRmICY6GJ + LsG1gRFnBX+0dG80hRXdPA== -----END CERTIFICATE----- qe_identity: "{\"id\":\"TD_QE\",\"version\":2,\"issueDate\":\"2025-01-01T00:00:00Z\",\"nextUpdate\":\"2045-01-01T00:00:00Z\",\"tcbEvaluationDataNumber\":1,\"miscselect\":\"00000000\",\"miscselectMask\":\"FFFFFFFF\",\"attributes\":\"00000000000000000000000000000000\",\"attributesMask\":\"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\",\"mrsigner\":\"5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A\",\"isvprodid\":2,\"tcbLevels\":[{\"tcb\":{\"isvsvn\":11},\"tcbDate\":\"2025-01-01T00:00:00Z\",\"tcbStatus\":\"UpToDate\",\"advisoryIDs\":[]}]}" qe_identity_signature: >- @@ -117,18 +117,18 @@ pck_certificate_chain: | MBAGCyqGSIb4TQENAQIOAgEAMBAGCyqGSIb4TQENAQIPAgEAMBAGCyqGSIb4TQEN AQIQAgEAMBAGCyqGSIb4TQENAQIRAgENMB8GCyqGSIb4TQENAQISBBALCwIC/wEA AAAAAAAAAAAAMBAGCiqGSIb4TQENAQMEAgAAMBQGCiqGSIb4TQENAQQEBgCQbqEA - ADAPBgoqhkiG+E0BDQEFCgEAMAoGCCqGSM49BAMCA0gAMEUCIAqDl+dxedPT/Olc - LXNwchpcs9/1tZ7CA0JBZS6TdC27AiEAg0Ke2IntwcEZTF557/AqQO8QNCD237J4 - LTglZz97CaI= + ADAPBgoqhkiG+E0BDQEFCgEAMAoGCCqGSM49BAMCA0gAMEUCID830FZbEZLj3Zwv + +45GtB9pkIWKWgKXr/582kNwIagiAiEAttIFwEKZhgyjPIWgQsa0g31aUvKgtl31 + 9CfxzKBt/Qs= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- - MIIBizCCATKgAwIBAgIBATAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu + MIIBjDCCATKgAwIBAgIBATAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu dGVsIFJvb3QgQ0EwHhcNMjUwMTAxMDAwMDAwWhcNNDUwMTAxMDAwMDAwWjAdMRsw GQYDVQQDDBJNb2NrIEludGVsIFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMB BwNCAAQCF+YX8LZEOSgnj5aZnmmiOk8sFSvfbWzfZuW4AoLU7RlKfevLl3EtLdo8 qFqodlpW9F/HWFmWUvKJfGUwbleUo2MwYTAfBgNVHSMEGDAWgBR2gFpjppgMGAdU JKE/MnctAtfHGzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFHaAWmOmmAwYB1Qk - oT8ydy0C18cbMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDRwAwRAIgcTi3 - m5UlXAYJLHc0VISnLUaiMcWt8EWkEUyMaTgR+NsCIE6+vLJEeQY972rDZjeoaQqm - 7ga04Ws35QJ/eC8l28ir + oT8ydy0C18cbMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIhANOM + o5zM6NZ93Iewr2S2g0MiM+6mMJaJNDfY5pXp82amAiBXJ1pB709SgQCgRmICY6GJ + LsG1gRFnBX+0dG80hRXdPA== -----END CERTIFICATE----- diff --git a/crates/mock-tdx/test-assets/generated-dcap/mock-pck-chain.pem b/crates/mock-tdx/assets/mock-pck-chain.pem similarity index 80% rename from crates/mock-tdx/test-assets/generated-dcap/mock-pck-chain.pem rename to crates/mock-tdx/assets/mock-pck-chain.pem index dba2d40..b6b41c0 100644 --- a/crates/mock-tdx/test-assets/generated-dcap/mock-pck-chain.pem +++ b/crates/mock-tdx/assets/mock-pck-chain.pem @@ -15,18 +15,18 @@ SIb4TQENAQILAgEAMBAGCyqGSIb4TQENAQIMAgEAMBAGCyqGSIb4TQENAQINAgEA MBAGCyqGSIb4TQENAQIOAgEAMBAGCyqGSIb4TQENAQIPAgEAMBAGCyqGSIb4TQEN AQIQAgEAMBAGCyqGSIb4TQENAQIRAgENMB8GCyqGSIb4TQENAQISBBALCwIC/wEA AAAAAAAAAAAAMBAGCiqGSIb4TQENAQMEAgAAMBQGCiqGSIb4TQENAQQEBgCQbqEA -ADAPBgoqhkiG+E0BDQEFCgEAMAoGCCqGSM49BAMCA0gAMEUCIAqDl+dxedPT/Olc -LXNwchpcs9/1tZ7CA0JBZS6TdC27AiEAg0Ke2IntwcEZTF557/AqQO8QNCD237J4 -LTglZz97CaI= +ADAPBgoqhkiG+E0BDQEFCgEAMAoGCCqGSM49BAMCA0gAMEUCID830FZbEZLj3Zwv ++45GtB9pkIWKWgKXr/582kNwIagiAiEAttIFwEKZhgyjPIWgQsa0g31aUvKgtl31 +9CfxzKBt/Qs= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- -MIIBizCCATKgAwIBAgIBATAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu +MIIBjDCCATKgAwIBAgIBATAKBggqhkjOPQQDAjAdMRswGQYDVQQDDBJNb2NrIElu dGVsIFJvb3QgQ0EwHhcNMjUwMTAxMDAwMDAwWhcNNDUwMTAxMDAwMDAwWjAdMRsw GQYDVQQDDBJNb2NrIEludGVsIFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMB BwNCAAQCF+YX8LZEOSgnj5aZnmmiOk8sFSvfbWzfZuW4AoLU7RlKfevLl3EtLdo8 qFqodlpW9F/HWFmWUvKJfGUwbleUo2MwYTAfBgNVHSMEGDAWgBR2gFpjppgMGAdU JKE/MnctAtfHGzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFHaAWmOmmAwYB1Qk -oT8ydy0C18cbMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDRwAwRAIgcTi3 -m5UlXAYJLHc0VISnLUaiMcWt8EWkEUyMaTgR+NsCIE6+vLJEeQY972rDZjeoaQqm -7ga04Ws35QJ/eC8l28ir +oT8ydy0C18cbMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIhANOM +o5zM6NZ93Iewr2S2g0MiM+6mMJaJNDfY5pXp82amAiBXJ1pB709SgQCgRmICY6GJ +LsG1gRFnBX+0dG80hRXdPA== -----END CERTIFICATE----- diff --git a/crates/mock-tdx/test-assets/generated-dcap/mock-pck-key.pem b/crates/mock-tdx/assets/mock-pck-key.pem similarity index 100% rename from crates/mock-tdx/test-assets/generated-dcap/mock-pck-key.pem rename to crates/mock-tdx/assets/mock-pck-key.pem diff --git a/crates/mock-tdx/test-assets/generated-dcap/mock-root-ca.der b/crates/mock-tdx/assets/mock-root-ca.der similarity index 74% rename from crates/mock-tdx/test-assets/generated-dcap/mock-root-ca.der rename to crates/mock-tdx/assets/mock-root-ca.der index a8d73869998211330d9c3bb9f6218076d6fb7b0b..fa502522c2fb8953b34a49ec602a421b06188dab 100644 GIT binary patch delta 87 zcmV-d0I2_u1CRp*FoFS$u>?Z_Nk{-NMFJrJ(~P5>%;?sA+=sBQWVVAtA~Wu$FqVll tH`wNt>GNi$0w7l>T0!qmQh@-VMq&bEp@}ZRwSf_51%I@3Z#0D!-8=;KBys=% delta 86 zcmV-c0IC0w1CIj)FoFS#u>?Z_NJjuLL;@gjIJcXXC0qsxEO#_igr_Y=qA|s-@I|B% sOpIwb5%}8zAWpu#vP5|XJ@0D6W;dv53a0J`wBc(v Date: Tue, 21 Apr 2026 13:25:26 +0200 Subject: [PATCH 07/11] Additional test for fixtures --- crates/mock-tdx/src/lib.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/crates/mock-tdx/src/lib.rs b/crates/mock-tdx/src/lib.rs index f839b39..4ea4fbf 100644 --- a/crates/mock-tdx/src/lib.rs +++ b/crates/mock-tdx/src/lib.rs @@ -284,4 +284,21 @@ mod tests { let collateral = mock_collateral(); assert!(verifier.verify("e_bytes, &collateral, FIXTURE_TIME).is_err()); } + + #[test] + fn embedded_assets_are_semantically_self_consistent() { + let collateral = mock_collateral(); + let tcb_info: TcbInfo = serde_json::from_str(&collateral.tcb_info).unwrap(); + let verifier = mock_dcap_verifier(); + + assert!(!EMBEDDED_ROOT_CA_DER.is_empty()); + assert!(collateral.pck_certificate_chain.is_some()); + + let quote_bytes = generate_mock_tdx_quote([0xEF; 64]).unwrap(); + let quote = Quote::parse("e_bytes).unwrap(); + assert_eq!(hex::encode_upper(quote.fmspc().unwrap()), tcb_info.fmspc); + assert_eq!(quote.header.pce_svn, tcb_info.tcb_levels[0].tcb.pce_svn); + + verifier.verify("e_bytes, &collateral, FIXTURE_TIME).unwrap(); + } } From e789e426f5a18744385de353df5953ede442d7b8 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 22 Apr 2026 08:58:05 +0200 Subject: [PATCH 08/11] Tidy readme / comments --- crates/mock-tdx/README.md | 4 ++-- crates/pccs/src/lib.rs | 2 +- readme.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/mock-tdx/README.md b/crates/mock-tdx/README.md index 05239fa..604be1a 100644 --- a/crates/mock-tdx/README.md +++ b/crates/mock-tdx/README.md @@ -1,7 +1,7 @@ # mock-tdx `mock-tdx` generates deterministic mock TDX DCAP artifacts for tests and -development on non-TDX hardware. It plays the role of Intel so we can +development on non-TDX hardware. It plays the role of Intel so we can mock the complete DCAP workflow. It provides: @@ -9,7 +9,7 @@ It provides: - A small fixture generator for a mock DCAP trust chain and collateral - A quote generator for mock TDX DCAP quotes with caller-supplied `report_data` -- Checked-in mock collateral, root certificate, and PCK certificate chain +- Checked-in mock collateral, root certificate, and PCK certificate chain and key under `assets` - A mock PCS server diff --git a/crates/pccs/src/lib.rs b/crates/pccs/src/lib.rs index 53d5b88..81bcbe2 100644 --- a/crates/pccs/src/lib.rs +++ b/crates/pccs/src/lib.rs @@ -367,7 +367,7 @@ fn parse_next_update(field: &str, value: &str) -> Result { .map(|parsed| parsed.unix_timestamp()) } -/// Parse a certifcate revocation list and extract the timestamp for next +/// Parse a certificate revocation list and extract the timestamp for next /// update fn parse_crl_next_update(field: &str, crl_der: &[u8]) -> Result { let (_, crl) = CertificateRevocationList::from_der(crl_der).map_err(|e| { diff --git a/readme.md b/readme.md index 0f6cced..fad36f2 100644 --- a/readme.md +++ b/readme.md @@ -4,7 +4,7 @@ Primitives for attested tls channels. Provided crates: -- [`attested-tls`](./crates/attested-tls) - WIP - provides attested TLS via X509 +- [`attested-tls`](./crates/attested-tls) - provides attested TLS via X509 Certificate extensions and a custom certificate verifier. - [`nested-tls`](./crates/nested-tls) - provides two TLS sessions, such that that outer session can be used for a CA signed certificate and the inner From 8926494c1dfde8384af953eb752e186129853745 Mon Sep 17 00:00:00 2001 From: peg Date: Fri, 24 Apr 2026 08:31:25 +0200 Subject: [PATCH 09/11] Fixes following merge main --- crates/attestation/src/dcap.rs | 7 ++++--- crates/attestation/src/lib.rs | 6 +++++- crates/pccs/src/lib.rs | 27 +++++++++++++-------------- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/crates/attestation/src/dcap.rs b/crates/attestation/src/dcap.rs index 5a3c7a4..62310dc 100644 --- a/crates/attestation/src/dcap.rs +++ b/crates/attestation/src/dcap.rs @@ -239,11 +239,12 @@ pub fn verify_dcap_attestation_sync( _pccs: Pccs, ) -> Result { // In tests we use mock quotes which will fail to verify - let quote = tdx_quote::Quote::from_bytes(&input)?; - if quote.report_input_data() != expected_input_data { + let quote = Quote::parse(&input)?; + let measurements = MultiMeasurements::from_dcap_qvl_quote("e)?; + if get_quote_input_data(quote.report.clone()) != expected_input_data { return Err(DcapVerificationError::InputMismatch); } - Ok(MultiMeasurements::from_tdx_quote("e)) + Ok(measurements) } /// Create a mock quote for testing on non-confidential hardware diff --git a/crates/attestation/src/lib.rs b/crates/attestation/src/lib.rs index f20f3a4..6e8063e 100644 --- a/crates/attestation/src/lib.rs +++ b/crates/attestation/src/lib.rs @@ -317,7 +317,7 @@ impl AttestationVerifier { pccs_url: None, dump_dcap_quotes: false, override_azure_outdated_tcb: false, - internal_pccs: Some(Pccs::new_without_prewarm(None)), + internal_pccs: None, } } @@ -438,6 +438,10 @@ impl AttestationVerifier { } } _ => { + #[cfg(any(test, feature = "mock"))] + let pccs = + self.internal_pccs.clone().unwrap_or_else(|| Pccs::new_without_prewarm(None)); + #[cfg(not(any(test, feature = "mock")))] let pccs = self.internal_pccs.clone().ok_or(AttestationError::NoPccs)?; dcap::verify_dcap_attestation_sync( diff --git a/crates/pccs/src/lib.rs b/crates/pccs/src/lib.rs index b1f4779..774443b 100644 --- a/crates/pccs/src/lib.rs +++ b/crates/pccs/src/lib.rs @@ -900,30 +900,31 @@ mod tests { #[tokio::test] async fn test_get_collateral_sync_repairs_cache_miss_in_background() { + let fmspc = mock_tdx_fmspc(); let mock = spawn_mock_pcs_server(MockPcsConfig { - fmspc: "00806F050000".to_string(), include_fmspcs_listing: false, tcb_next_update: "2999-01-01T00:00:00Z".to_string(), qe_next_update: "2999-01-01T00:00:00Z".to_string(), refreshed_tcb_next_update: None, refreshed_qe_next_update: None, }) - .await; + .await + .unwrap(); let pccs = Pccs::new_without_prewarm(Some(mock.base_url.clone())); let now = unix_now().unwrap() as u64; - let err = pccs.get_collateral_sync("00806F050000".to_string(), "processor", now); + let err = pccs.get_collateral_sync(fmspc.clone(), "processor", now); assert!(matches!(err, Err(PccsError::NoCollateralForFmspc(_)))); for _ in 0..50 { - if pccs.get_collateral_sync("00806F050000".to_string(), "processor", now).is_ok() { + if pccs.get_collateral_sync(fmspc.clone(), "processor", now).is_ok() { break; } tokio::time::sleep(Duration::from_millis(20)).await; } - let collateral = pccs.get_collateral_sync("00806F050000".to_string(), "processor", now); + let collateral = pccs.get_collateral_sync(fmspc, "processor", now); assert!(collateral.is_ok(), "expected sync miss repair to populate cache"); assert_eq!(mock.tcb_call_count(), 1); assert_eq!(mock.qe_call_count(), 1); @@ -938,35 +939,33 @@ mod tests { .unwrap() .format(&Rfc3339) .unwrap(); + let fmspc = mock_tdx_fmspc(); let mock = spawn_mock_pcs_server(MockPcsConfig { - fmspc: "00806F050000".to_string(), include_fmspcs_listing: false, tcb_next_update: initial_next_update.clone(), qe_next_update: initial_next_update, refreshed_tcb_next_update: Some(refreshed_next_update.clone()), refreshed_qe_next_update: Some(refreshed_next_update), }) - .await; + .await + .unwrap(); let pccs = Pccs::new_without_prewarm(Some(mock.base_url.clone())); - let (_, is_fresh) = pccs - .get_collateral("00806F050000".to_string(), "processor", initial_now as u64) - .await - .unwrap(); + let (_, is_fresh) = + pccs.get_collateral(fmspc.clone(), "processor", initial_now as u64).await.unwrap(); assert!(is_fresh); { let mut cache = pccs.cache.write().unwrap(); let entry = cache - .get_mut(&PccsInput::new("00806F050000".to_string(), "processor")) + .get_mut(&PccsInput::new(fmspc.clone(), "processor")) .expect("expected cached collateral entry"); entry.next_update = initial_now - 1; entry.refresh_task = None; } - let stale_collateral = - pccs.get_collateral_sync("00806F050000".to_string(), "processor", initial_now as u64); + let stale_collateral = pccs.get_collateral_sync(fmspc, "processor", initial_now as u64); assert!(stale_collateral.is_ok(), "expected stale collateral to be returned"); for _ in 0..50 { From 53a41a6b4938571f9324056e95337166f77dd415 Mon Sep 17 00:00:00 2001 From: peg Date: Fri, 8 May 2026 10:46:34 +0200 Subject: [PATCH 10/11] Readme should be wrapped at 80 columns Co-authored-by: Anton --- crates/attestation/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/attestation/README.md b/crates/attestation/README.md index acfa30d..dd8e095 100644 --- a/crates/attestation/README.md +++ b/crates/attestation/README.md @@ -37,8 +37,10 @@ must be explicitly enabled via the `override_azure_outdated_tcb` flag on ### `mock` -Enables mock quote support via the local `mock-tdx` crate for tests and development on non-TDX -hardware. Do not use in production. Disabled by default. +Enables mock quote support via the local `mock-tdx` crate for tests and +development on non-TDX hardware. + +Do not use in production. Disabled by default. ## Attestation Types From 6695ee50865dfa68b4c1b3cb1f9f63f61aeeba41 Mon Sep 17 00:00:00 2001 From: peg Date: Fri, 8 May 2026 10:54:07 +0200 Subject: [PATCH 11/11] AttestationVerifier should use explicitly named variants when matching AttestationType --- crates/attestation/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/attestation/src/lib.rs b/crates/attestation/src/lib.rs index 5f8c712..b171e70 100644 --- a/crates/attestation/src/lib.rs +++ b/crates/attestation/src/lib.rs @@ -442,7 +442,7 @@ impl AttestationVerifier { return Err(AttestationError::AttestationTypeNotSupported); } } - _ => { + AttestationType::DcapTdx | AttestationType::QemuTdx | AttestationType::GcpTdx => { #[cfg(any(test, feature = "mock"))] let pccs = self.internal_pccs.clone().unwrap_or_else(|| Pccs::new_without_prewarm(None));