diff --git a/Cargo.lock b/Cargo.lock index f9a940e..2038088 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -153,6 +153,15 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anes" version = "0.1.6" @@ -290,11 +299,43 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "attest-measure" +version = "0.0.1" +source = "git+https://github.com/easy-tee/attest.git?branch=ah%2Fbetter-interface#9436797ccde6354d54e1b912bb116cb66678c33d" +dependencies = [ + "anyhow", + "attest-types", + "authenticode", + "crc32fast", + "hex", + "hex-literal", + "object", + "serde", + "serde_json", + "serde_with", + "sha2", + "thiserror 2.0.18", +] + +[[package]] +name = "attest-types" +version = "0.0.1" +source = "git+https://github.com/easy-tee/attest.git?branch=ah%2Fbetter-interface#9436797ccde6354d54e1b912bb116cb66678c33d" +dependencies = [ + "parity-scale-codec", + "serde", + "serde_json", + "serde_with", +] + [[package]] name = "attestation" version = "0.0.1" dependencies = [ "anyhow", + "attest-measure", + "attest-types", "az-tdx-vtpm", "base64 0.22.1", "dcap-qvl 0.3.12 (git+https://github.com/Phala-Network/dcap-qvl.git?rev=f1dcc65371e941a7b83e3234833d23a1fb232ab1)", @@ -346,6 +387,24 @@ dependencies = [ "yasna 0.5.2", ] +[[package]] +name = "authenticode" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86c421a87e3dd1a3024c86e0787106b6ba40d9b434fe0ebeffbd24a242dc144d" +dependencies = [ + "cms", + "const-oid", + "der", + "digest", + "object", + "rsa", + "sha1", + "sha2", + "spki", + "x509-cert", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -682,6 +741,15 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + [[package]] name = "bumpalo" version = "3.20.2" @@ -785,8 +853,10 @@ version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ + "iana-time-zone", "num-traits", "serde", + "windows-link", ] [[package]] @@ -850,6 +920,18 @@ dependencies = [ "cc", ] +[[package]] +name = "cms" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b77c319abfd5219629c45c34c89ba945ed3c5e49fcde9d16b6c3885f118a730" +dependencies = [ + "const-oid", + "der", + "spki", + "x509-cert", +] + [[package]] name = "codicon" version = "3.0.0" @@ -908,6 +990,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1255,6 +1343,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", + "serde_core", ] [[package]] @@ -1389,6 +1478,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "ecdsa" version = "0.16.9" @@ -1821,6 +1916,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.15.5" @@ -1854,6 +1955,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-literal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" + [[package]] name = "hex_fmt" version = "0.3.0" @@ -2048,6 +2155,30 @@ dependencies = [ "tracing", ] +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "2.1.1" @@ -2185,6 +2316,17 @@ dependencies = [ "syn", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.13.0" @@ -2483,6 +2625,7 @@ dependencies = [ name = "mock-tdx" version = "0.0.1" dependencies = [ + "attest-types", "axum", "dcap-qvl 0.3.12 (git+https://github.com/Phala-Network/dcap-qvl.git?rev=f1dcc65371e941a7b83e3234833d23a1fb232ab1)", "hex", @@ -2648,6 +2791,15 @@ dependencies = [ "libm", ] +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + [[package]] name = "oid" version = "0.2.1" @@ -3309,6 +3461,26 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "regex" version = "1.12.3" @@ -3584,6 +3756,30 @@ dependencies = [ "syn", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -3696,7 +3892,7 @@ version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "indexmap", + "indexmap 2.13.0", "itoa", "memchr", "serde", @@ -3727,6 +3923,38 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e72c1c2cb7b223fafb600a619537a871c2818583d619401b785e7c0b746ccde2" +dependencies = [ + "base64 0.22.1", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.13.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b90c488738ecb4fb0262f41f43bc40efc5868d9fb744319ddf5f5317f417bfac" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sev" version = "6.3.1" @@ -4195,7 +4423,7 @@ version = "0.25.4+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" dependencies = [ - "indexmap", + "indexmap 2.13.0", "toml_datetime", "toml_parser", "winnow", @@ -4612,7 +4840,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap", + "indexmap 2.13.0", "wasm-encoder", "wasmparser", ] @@ -4625,7 +4853,7 @@ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags 2.11.0", "hashbrown 0.15.5", - "indexmap", + "indexmap 2.13.0", "semver", ] @@ -4682,12 +4910,65 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -4883,7 +5164,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck", - "indexmap", + "indexmap 2.13.0", "prettyplease", "syn", "wasm-metadata", @@ -4914,7 +5195,7 @@ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", "bitflags 2.11.0", - "indexmap", + "indexmap 2.13.0", "log", "serde", "serde_derive", @@ -4933,7 +5214,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap", + "indexmap 2.13.0", "log", "semver", "serde", diff --git a/crates/attestation/Cargo.toml b/crates/attestation/Cargo.toml index f27bcb5..866efb7 100644 --- a/crates/attestation/Cargo.toml +++ b/crates/attestation/Cargo.toml @@ -13,6 +13,8 @@ pccs = { workspace = true } mock-tdx = { workspace = true, optional = true } tokio = { workspace = true, features = ["fs"] } tokio-rustls = { workspace = true, default-features = false } +attest-types = { git = "https://github.com/easy-tee/attest.git", branch = "ah/better-interface" } +attest-measure = {git = "https://github.com/easy-tee/attest.git", branch = "ah/better-interface" } anyhow = "1.0.100" pem-rfc7468 = { version = "0.7.0", features = ["std"] } diff --git a/crates/attestation/src/azure/mod.rs b/crates/attestation/src/azure/mod.rs index 45d4e4c..08272d1 100644 --- a/crates/attestation/src/azure/mod.rs +++ b/crates/attestation/src/azure/mod.rs @@ -599,7 +599,7 @@ mod tests { .unwrap(); assert_eq!(async_measurements, sync_measurements); - measurement_policy.check_measurement(&async_measurements).unwrap(); + measurement_policy.check_measurement(&async_measurements, None).unwrap(); } #[tokio::test] diff --git a/crates/attestation/src/dcap.rs b/crates/attestation/src/dcap.rs index dca2c10..516e30c 100644 --- a/crates/attestation/src/dcap.rs +++ b/crates/attestation/src/dcap.rs @@ -1,5 +1,6 @@ //! Data Center Attestation Primitives (DCAP) evidence generation and //! verification +use attest_types::AttestationEvidence; use dcap_qvl::{ QuoteCollateralV3, collateral::get_collateral_for_fmspc, @@ -21,10 +22,12 @@ const AZURE_BAD_FMSPC: &str = "90C06F000000"; pub const PCS_URL: &str = "https://api.trustedservices.intel.com"; /// Generate a TDX quote -pub fn create_dcap_attestation(input_data: [u8; 64]) -> Result, AttestationError> { - let quote = generate_quote(input_data)?; - tracing::info!("Generated TDX quote of {} bytes", quote.len()); - Ok(quote) +pub fn create_dcap_attestation( + input_data: [u8; 64], +) -> Result { + let attestation_evidence = generate_quote(input_data)?; + tracing::info!("Generated TDX quote of {} bytes", attestation_evidence.quote.len()); + Ok(attestation_evidence) } /// Verify a DCAP TDX quote, and return the measurement values @@ -254,16 +257,19 @@ pub fn verify_dcap_attestation_sync( /// Create a mock quote for testing on non-confidential hardware #[cfg(any(test, feature = "mock"))] -fn generate_quote(input: [u8; 64]) -> Result, tdx_attest::TdxAttestError> { - generate_mock_tdx_quote(input).map_err(|error| { - tdx_attest::TdxAttestError::QuoteFailure(format!("mock-tdx quote generation: {error}")) - }) +fn generate_quote(input: [u8; 64]) -> Result { + generate_mock_tdx_quote(input).map_err(|error| AttestationError::Mock(format!("{error}"))) } /// Create a quote #[cfg(not(any(test, feature = "mock")))] -fn generate_quote(input: [u8; 64]) -> Result, tdx_attest::TdxAttestError> { - tdx_attest::get_quote(&input) +fn generate_quote(input: [u8; 64]) -> Result { + use attest_measure::platform; + + Ok(AttestationEvidence { + quote: tdx_attest::get_quote(&input)?, + platform: platform::metadata_for(attest_types::AttestationType::GcpTdx)?, + }) } /// Given a [Report] get the input data regardless of report type @@ -363,7 +369,7 @@ mod tests { .unwrap(); assert_eq!(async_measurements, sync_measurements); - measurement_policy.check_measurement(&async_measurements).unwrap(); + measurement_policy.check_measurement(&async_measurements, None).unwrap(); } // This specifically tests a quote which has outdated TCB level from Azure @@ -407,10 +413,10 @@ mod tests { .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 attestation = create_dcap_attestation(expected_input_data).unwrap(); let measurements = - verify_dcap_attestation(attestation_bytes, expected_input_data, Some(pccs)) + verify_dcap_attestation(attestation.quote, expected_input_data, Some(pccs)) .await .unwrap(); diff --git a/crates/attestation/src/lib.rs b/crates/attestation/src/lib.rs index 73bc5ba..294f046 100644 --- a/crates/attestation/src/lib.rs +++ b/crates/attestation/src/lib.rs @@ -12,6 +12,8 @@ use std::{ time::{Duration, SystemTime, UNIX_EPOCH}, }; +use attest_measure::platform::PlatformError; +pub use attest_types::{AttestationEvidence, PlatformMetadata}; use measurements::MultiMeasurements; use parity_scale_codec::{Decode, Encode}; use pccs::{Pccs, PccsError}; @@ -26,29 +28,31 @@ const GCP_METADATA_API: &str = "http://metadata.google.internal"; /// An attestation payload together with its type #[derive(Clone, Debug, Serialize, Deserialize, Encode, Decode)] pub struct AttestationExchangeMessage { - /// What CVM platform is used (including none) - pub attestation_type: AttestationType, - /// The attestation evidence as bytes - in the case of DCAP this is a - /// quote - pub attestation: Vec, + /// Attestation payload with platform metadata, if present. + /// `None` means no evidence presented. + pub attestation_evidence: Option, } impl AttestationExchangeMessage { /// Create an empty attestation payload for the case that we are running /// in a non-confidential environment pub fn without_attestation() -> Self { - Self { attestation_type: AttestationType::None, attestation: Vec::new() } + Self { attestation_evidence: None } } /// Extract the measurements from the attestation, if present, but do /// not verify pub fn get_measurements(&self) -> Result, AttestationError> { - match self.attestation_type { + let Some(attestation_evidence) = &self.attestation_evidence else { + return Ok(None); + }; + + match self.attestation_type() { AttestationType::None => Ok(None), AttestationType::AzureTdx => { #[cfg(feature = "azure")] { - Ok(Some(azure::get_measurements(&self.attestation)?)) + Ok(Some(azure::get_measurements(&attestation_evidence.quote)?)) } #[cfg(not(feature = "azure"))] { @@ -56,12 +60,57 @@ impl AttestationExchangeMessage { } } AttestationType::DcapTdx | AttestationType::GcpTdx | AttestationType::QemuTdx => { - let quote = dcap_qvl::verify::Quote::parse(&self.attestation) + let quote = dcap_qvl::verify::Quote::parse(&attestation_evidence.quote) .map_err(DcapVerificationError::from)?; Ok(Some(MultiMeasurements::from_dcap_qvl_quote("e)?)) } } } + + pub fn attestation_type(&self) -> AttestationType { + self.attestation_evidence + .as_ref() + .map(|evidence| evidence.platform.attestation_type.into()) + .unwrap_or(AttestationType::None) + } +} + +impl From for AttestationExchangeMessage { + fn from(attestation_evidence: AttestationEvidence) -> Self { + Self { attestation_evidence: Some(attestation_evidence) } + } +} + +impl From for AttestationType { + fn from(attestation_type: attest_types::AttestationType) -> Self { + match attestation_type { + attest_types::AttestationType::GcpTdx => AttestationType::GcpTdx, + attest_types::AttestationType::AzureTdx => AttestationType::AzureTdx, + attest_types::AttestationType::SelfHostedTdx => AttestationType::QemuTdx, + } + } +} + +impl From for attest_types::AttestationType { + fn from(attestation_type: AttestationType) -> Self { + match attestation_type { + AttestationType::None => attest_types::AttestationType::SelfHostedTdx, + AttestationType::AzureTdx => attest_types::AttestationType::AzureTdx, + AttestationType::GcpTdx => attest_types::AttestationType::GcpTdx, + AttestationType::DcapTdx | AttestationType::QemuTdx => { + attest_types::AttestationType::SelfHostedTdx + } + } + } +} + +fn placeholder_platform_metadata(attestation_type: AttestationType) -> PlatformMetadata { + PlatformMetadata { + attestation_type: attestation_type.into(), + ram_bytes: 0, + num_disks: 0, + acpi: None, + } } /// Type of attestation used @@ -177,7 +226,7 @@ impl AttestationGenerator { attestation_provider_url: Option, ) -> Result { if attestation_provider_url.is_some() { - // If a remote provide is used, dont do detection + // If a remote provider is used, dont do detection let attestation_type = serde_json::from_value(serde_json::Value::String( attestation_type_string.ok_or(AttestationError::AttestationTypeNotGiven)?, ))?; @@ -204,37 +253,34 @@ impl AttestationGenerator { if let Some(url) = &self.attestation_provider_url { Self::use_attestation_provider(url, self.attestation_type, input_data) } else { - Ok(AttestationExchangeMessage { - attestation_type: self.attestation_type, - attestation: self.generate_attestation_bytes(input_data)?, - }) - } - } - - /// Generate attestation evidence bytes based on attestation type, with - /// given input data - fn generate_attestation_bytes( - &self, - input_data: [u8; 64], - ) -> Result, AttestationError> { - match self.attestation_type { - AttestationType::None => Ok(Vec::new()), - AttestationType::AzureTdx => { - #[cfg(feature = "azure")] - { - Ok(azure::create_azure_attestation(input_data)?) + match self.attestation_type { + AttestationType::None => Ok(AttestationExchangeMessage::without_attestation()), + AttestationType::AzureTdx => { + #[cfg(feature = "azure")] + { + let platform = + attest_measure::platform::metadata_for(self.attestation_type.into())?; + Ok(AttestationExchangeMessage { + attestation_evidence: Some(AttestationEvidence { + quote: azure::create_azure_attestation(input_data)?, + platform, + }), + }) + } + #[cfg(not(feature = "azure"))] + { + tracing::error!( + "Attempted to generate an azure attestation but the `azure` feature not enabled" + ); + Err(AttestationError::AttestationTypeNotSupported) + } } - #[cfg(not(feature = "azure"))] - { - tracing::error!( - "Attempted to generate an azure attestation but the `azure` feature not enabled" - ); - Err(AttestationError::AttestationTypeNotSupported) + AttestationType::DcapTdx | AttestationType::GcpTdx | AttestationType::QemuTdx => { + let mut attestation_evidence = dcap::create_dcap_attestation(input_data)?; + attestation_evidence.platform.attestation_type = self.attestation_type.into(); + Ok(attestation_evidence.into()) } } - AttestationType::DcapTdx | AttestationType::GcpTdx | AttestationType::QemuTdx => { - dcap::create_dcap_attestation(input_data) - } } } @@ -262,7 +308,14 @@ impl AttestationGenerator { if let Ok(message) = AttestationExchangeMessage::decode(&mut &body[..]) { Ok(message) } else { - Ok(AttestationExchangeMessage { attestation_type, attestation: body }) + if attestation_type == AttestationType::None { + return Ok(AttestationExchangeMessage::without_attestation()); + } + + let platform = placeholder_platform_metadata(attestation_type); + Ok(AttestationExchangeMessage { + attestation_evidence: Some(AttestationEvidence { quote: body, platform }), + }) } } } @@ -364,7 +417,7 @@ impl AttestationVerifier { attestation_exchange_message: AttestationExchangeMessage, expected_input_data: [u8; 64], ) -> Result, AttestationError> { - let attestation_type = attestation_exchange_message.attestation_type; + let attestation_type = attestation_exchange_message.attestation_type(); tracing::debug!("Verifying {attestation_type} attestation"); if self.dump_dcap_quotes { @@ -376,7 +429,7 @@ impl AttestationVerifier { if self.has_remote_attestation() { return Err(AttestationError::AttestationTypeNotAccepted); } - if attestation_exchange_message.attestation.is_empty() { + if attestation_exchange_message.attestation_evidence.is_none() { return Ok(None); } else { return Err(AttestationError::AttestationGivenWhenNoneExpected); @@ -385,8 +438,12 @@ impl AttestationVerifier { AttestationType::AzureTdx => { #[cfg(feature = "azure")] { + let attestation_evidence = attestation_exchange_message + .attestation_evidence + .as_ref() + .ok_or(AttestationError::AttestationTypeNotAccepted)?; azure::verify_azure_attestation( - attestation_exchange_message.attestation, + attestation_evidence.quote.clone(), expected_input_data, self.internal_pccs.clone(), self.override_azure_outdated_tcb, @@ -399,8 +456,12 @@ impl AttestationVerifier { } } AttestationType::DcapTdx | AttestationType::GcpTdx | AttestationType::QemuTdx => { + let attestation_evidence = attestation_exchange_message + .attestation_evidence + .as_ref() + .ok_or(AttestationError::AttestationTypeNotAccepted)?; dcap::verify_dcap_attestation( - attestation_exchange_message.attestation, + attestation_evidence.quote.clone(), expected_input_data, self.internal_pccs.clone(), ) @@ -409,7 +470,11 @@ impl AttestationVerifier { }; // Do a measurement / attestation type policy check - self.measurement_policy.check_measurement(&measurements)?; + let platform_metadata = attestation_exchange_message + .attestation_evidence + .as_ref() + .map(|evidence| evidence.platform.clone()); + self.measurement_policy.check_measurement(&measurements, platform_metadata)?; tracing::debug!("Verification successful"); Ok(Some(measurements)) @@ -420,7 +485,7 @@ impl AttestationVerifier { attestation_exchange_message: AttestationExchangeMessage, expected_input_data: [u8; 64], ) -> Result, AttestationError> { - let attestation_type = attestation_exchange_message.attestation_type; + let attestation_type = attestation_exchange_message.attestation_type(); tracing::debug!("Verifying {attestation_type} attestation"); if self.dump_dcap_quotes { @@ -432,7 +497,7 @@ impl AttestationVerifier { if self.has_remote_attestation() { return Err(AttestationError::AttestationTypeNotAccepted); } - if attestation_exchange_message.attestation.is_empty() { + if attestation_exchange_message.attestation_evidence.is_none() { return Ok(None); } else { return Err(AttestationError::AttestationGivenWhenNoneExpected); @@ -441,9 +506,13 @@ impl AttestationVerifier { AttestationType::AzureTdx => { #[cfg(feature = "azure")] { + let attestation_evidence = attestation_exchange_message + .attestation_evidence + .as_ref() + .ok_or(AttestationError::AttestationTypeNotAccepted)?; let pccs = self.internal_pccs.clone().ok_or(AttestationError::NoPccs)?; azure::verify_azure_attestation_sync( - attestation_exchange_message.attestation, + attestation_evidence.quote.clone(), expected_input_data, pccs, self.override_azure_outdated_tcb, @@ -455,6 +524,10 @@ impl AttestationVerifier { } } AttestationType::DcapTdx | AttestationType::QemuTdx | AttestationType::GcpTdx => { + let attestation_evidence = attestation_exchange_message + .attestation_evidence + .as_ref() + .ok_or(AttestationError::AttestationTypeNotAccepted)?; #[cfg(any(test, feature = "mock"))] let pccs = self.internal_pccs.clone().unwrap_or_else(|| Pccs::new_without_prewarm(None)); @@ -462,7 +535,7 @@ impl AttestationVerifier { let pccs = self.internal_pccs.clone().ok_or(AttestationError::NoPccs)?; dcap::verify_dcap_attestation_sync( - attestation_exchange_message.attestation, + attestation_evidence.quote.clone(), expected_input_data, pccs, )? @@ -470,7 +543,11 @@ impl AttestationVerifier { }; // Do a measurement / attestation type policy check - self.measurement_policy.check_measurement(&measurements)?; + let platform_metadata = attestation_exchange_message + .attestation_evidence + .as_ref() + .map(|evidence| evidence.platform.clone()); + self.measurement_policy.check_measurement(&measurements, platform_metadata)?; tracing::debug!("Verification successful"); Ok(Some(measurements)) @@ -484,12 +561,13 @@ impl AttestationVerifier { /// Write attestation data to a log file fn log_attestation(attestation: &AttestationExchangeMessage) { - if attestation.attestation_type != AttestationType::None { + if let Some(attestation_evidence) = &attestation.attestation_evidence { let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards").as_nanos(); - let filename = format!("quotes/{}-{}", attestation.attestation_type, timestamp); - let attestation_bytes = attestation.attestation.clone(); + let attestation_type = attestation.attestation_type(); + let filename = format!("quotes/{attestation_type}-{timestamp}"); + let attestation_bytes = attestation_evidence.quote.clone(); if let Ok(handle) = tokio::runtime::Handle::try_current() { handle.spawn(async move { if let Err(err) = tokio::fs::write(&filename, attestation_bytes).await { @@ -513,8 +591,8 @@ fn running_on_gcp() -> Result { let resp = agent.get(GCP_METADATA_API).call(); if let Ok(r) = resp { - return Ok(r.status() == 200 && - r.header("Metadata-Flavor").map(|v| v == "Google").unwrap_or(false)); + return Ok(r.status() == 200 + && r.header("Metadata-Flavor").map(|v| v == "Google").unwrap_or(false)); } Ok(false) @@ -609,6 +687,11 @@ pub enum AttestationError { Pccs(#[from] PccsError), #[error("Sync verification requested but no PCCS configured")] NoPccs, + #[cfg(any(test, feature = "mock"))] + #[error("Cannot create mock attestation: {0}")] + Mock(String), + #[error("Cannot retrieve platform metadata: {0}")] + PlatformMetadata(#[from] PlatformError), } #[cfg(test)] @@ -660,8 +743,10 @@ mod tests { let input_data = [0u8; 64]; let encoded_message = AttestationExchangeMessage { - attestation_type: AttestationType::None, - attestation: vec![1, 2, 3], + attestation_evidence: Some(AttestationEvidence { + quote: vec![1, 2, 3], + platform: placeholder_platform_metadata(AttestationType::GcpTdx), + }), } .encode(); @@ -673,8 +758,8 @@ mod tests { input_data, ) .unwrap(); - assert_eq!(decoded.attestation_type, AttestationType::None); - assert_eq!(decoded.attestation, vec![1, 2, 3]); + assert_eq!(decoded.attestation_type(), AttestationType::GcpTdx); + assert_eq!(decoded.attestation_evidence.unwrap().quote, vec![1, 2, 3]); let raw_addr = spawn_test_attestation_provider_server(vec![9, 8]).await; let raw_url = format!("http://{raw_addr}"); @@ -684,8 +769,8 @@ mod tests { input_data, ) .unwrap(); - assert_eq!(wrapped.attestation_type, AttestationType::DcapTdx); - assert_eq!(wrapped.attestation, vec![9, 8]); + assert_eq!(wrapped.attestation_type(), AttestationType::QemuTdx); + assert_eq!(wrapped.attestation_evidence.unwrap().quote, vec![9, 8]); } #[tokio::test] @@ -700,10 +785,7 @@ mod tests { pccs.ready().await.unwrap(); } - let result = verifier.verify_attestation_sync( - AttestationExchangeMessage { attestation_type: AttestationType::DcapTdx, attestation }, - input_data, - ); + let result = verifier.verify_attestation_sync(attestation.into(), input_data); assert!(result.is_ok(), "expected sync mock verification to succeed: {result:?}"); } diff --git a/crates/attestation/src/measurements.rs b/crates/attestation/src/measurements.rs index db8c2c9..1cf841c 100644 --- a/crates/attestation/src/measurements.rs +++ b/crates/attestation/src/measurements.rs @@ -2,6 +2,8 @@ //! attestation use std::{collections::HashMap, fmt, fmt::Formatter, net::IpAddr, path::PathBuf}; +use attest_measure::dcap::{DcapFirmware, expected_dcap_registers}; +use attest_types::{AttestationType as ImageAttestationType, DcapImageHashes, PlatformMetadata}; use dcap_qvl::quote::Report; use http::{HeaderValue, header::InvalidHeaderValue, uri::InvalidUri}; use serde::Deserialize; @@ -135,6 +137,7 @@ impl fmt::Debug for AzureHexDebug<'_> { /// Expected measurement values for policy enforcement #[derive(Debug, Clone, PartialEq)] pub enum ExpectedMeasurements { + Image(DcapImageHashes), Dcap(HashMap>), Azure(HashMap>), NoAttestation, @@ -375,20 +378,69 @@ impl MeasurementPolicy { pub fn check_measurement( &self, measurements: &MultiMeasurements, + platform_metadata: Option, ) -> Result<(), AttestationError> { if self.accepted_measurements.iter().any(|measurement_record| match measurements { MultiMeasurements::Dcap(dcap_measurements) => { - if let ExpectedMeasurements::Dcap(expected) = &measurement_record.measurements { - // All measurements in our policy must be given and must match - for (k, v) in expected.iter() { - match dcap_measurements.get(k) { - Some(actual_value) if v.iter().any(|v| actual_value == v) => {} - _ => return false, + match &measurement_record.measurements { + ExpectedMeasurements::Dcap(expected) => { + // All measurements in our policy must be given and must match + for (k, v) in expected.iter() { + match dcap_measurements.get(k) { + Some(actual_value) if v.iter().any(|v| actual_value == v) => {} + _ => return false, + } } + true } - return true; + ExpectedMeasurements::Image(image_hashes) => { + let Some(platform_metadata) = &platform_metadata else { + return false; + }; + let firmware = match platform_metadata.attestation_type { + ImageAttestationType::GcpTdx => Some(DcapFirmware::gcp_hardcoded()), + ImageAttestationType::SelfHostedTdx => None, + ImageAttestationType::AzureTdx => return false, + }; + + let Ok(expected_measurements) = expected_dcap_registers( + image_hashes, + platform_metadata, + firmware.as_ref(), + ) else { + return false; // TODO should we bail here + }; + + if let Some(expected_mrtd) = expected_measurements.mrtd { + match dcap_measurements.get(&DcapMeasurementRegister::MRTD) { + Some(mrtd) if mrtd == &expected_mrtd => {} + _ => return false, + } + } + + if let Some(expected_rtmr0) = expected_measurements.rtmr0 { + match dcap_measurements.get(&DcapMeasurementRegister::RTMR0) { + Some(rtmr0) if rtmr0 == &expected_rtmr0 => {} + _ => return false, + } + } + + if let Some(rtmr1) = dcap_measurements.get(&DcapMeasurementRegister::RTMR1) + && rtmr1 != &expected_measurements.rtmr1 + { + return false; + } + + if let Some(rtmr2) = dcap_measurements.get(&DcapMeasurementRegister::RTMR2) + && rtmr2 != &expected_measurements.rtmr2 + { + return false; + } + + true + } + _ => false, } - false } MultiMeasurements::Azure(azure_measurements) => { if let ExpectedMeasurements::Azure(expected) = &measurement_record.measurements { @@ -576,6 +628,9 @@ impl MeasurementPolicy { mod tests { use std::collections::HashSet; + use attest_measure::dcap::expected_dcap_registers; + use attest_types::AcpiHashes; + use super::*; #[tokio::test] @@ -612,20 +667,22 @@ mod tests { // Will not match mock measurements assert!(matches!( - specific_measurements.check_measurement(&mock_dcap_measurements()).unwrap_err(), + specific_measurements.check_measurement(&mock_dcap_measurements(), None).unwrap_err(), AttestationError::MeasurementsNotAccepted )); // Will not match another attestation type assert!(matches!( - specific_measurements.check_measurement(&MultiMeasurements::NoAttestation).unwrap_err(), + specific_measurements + .check_measurement(&MultiMeasurements::NoAttestation, None) + .unwrap_err(), AttestationError::MeasurementsNotAccepted )); // A non-specific measurement fails assert!(matches!( specific_measurements - .check_measurement(&MultiMeasurements::Azure(HashMap::new())) + .check_measurement(&MultiMeasurements::Azure(HashMap::new()), None) .unwrap_err(), AttestationError::MeasurementsNotAccepted )); @@ -638,17 +695,68 @@ mod tests { let allowed_attestation_type = MeasurementPolicy::from_file("test-assets/measurements_2.json".into()).await.unwrap(); - allowed_attestation_type.check_measurement(&mock_dcap_measurements()).unwrap(); + allowed_attestation_type.check_measurement(&mock_dcap_measurements(), None).unwrap(); // Will not match another attestation type assert!(matches!( allowed_attestation_type - .check_measurement(&MultiMeasurements::NoAttestation) + .check_measurement(&MultiMeasurements::NoAttestation, None) .unwrap_err(), AttestationError::MeasurementsNotAccepted )); } + #[test] + fn test_gcp_image_hash_measurement_policy_accepts_matching_measurements() { + fn decode_hash(input: &str) -> [u8; 48] { + hex::decode(input).unwrap().try_into().unwrap() + } + + // Result of measuring a flashbox-l1 image + let image_hashes = DcapImageHashes { + uki_authenticode: decode_hash( + "fcaceb6d87694746ba2d93a87ef4209f2a7629b7f400097b93241e80b9ec3e1e80f9a4cd8028e6a83f297ea5de8d9abc", + ), + kernel_authenticode: decode_hash( + "b6c5133268aa8b440509f3d53ee855a5cd3aeb6441eb109a9f27f14c43bce3e2383856df4af876501ceeb4c9a3b15f0c", + ), + cmdline_hash: decode_hash( + "e03b89abf354a38976537b7a9138fd312e4cbf73b61eebc44086491701b1d167b9f6cb97a922325866c93e0834723d87", + ), + initrd_hash: decode_hash( + "a5b3d4742045e7d08aa19953c35098e784826b01a84f60568fa69f1a848dafd96ec98b8df616d6142779c9b97318166b", + ), + gpt_disk_guid_hash: decode_hash( + "180bac1af9c35cc15e909623c005289539b4da2840d9c9b658fd4968ea4f03e0159402d03da1afc9035e0db30804e282", + ), + }; + let policy = MeasurementPolicy { + accepted_measurements: vec![MeasurementRecord { + measurement_id: "image-hash-policy".to_string(), + measurements: ExpectedMeasurements::Image(image_hashes.clone()), + }], + }; + let platform_metadata = PlatformMetadata { + attestation_type: attest_types::AttestationType::GcpTdx, + ram_bytes: 4 * 1024 * 1024 * 1024, + num_disks: 1, + acpi: Some(AcpiHashes { loader: [0x11; 48], rsdp: [0x22; 48], tables: [0x33; 48] }), + }; + let firmware = DcapFirmware::gcp_hardcoded(); + let expected_measurements = + expected_dcap_registers(&image_hashes, &platform_metadata, Some(&firmware)).unwrap(); + + let measurements = MultiMeasurements::Dcap(HashMap::from([ + (DcapMeasurementRegister::MRTD, expected_measurements.mrtd.unwrap()), + (DcapMeasurementRegister::RTMR0, expected_measurements.rtmr0.unwrap()), + (DcapMeasurementRegister::RTMR1, expected_measurements.rtmr1), + (DcapMeasurementRegister::RTMR2, expected_measurements.rtmr2), + (DcapMeasurementRegister::RTMR3, mock_tdx::MOCK_RTMR3), + ])); + + policy.check_measurement(&measurements, Some(platform_metadata)).unwrap(); + } + #[tokio::test] async fn test_buildernet_measurements() { // Refresh this fixture explicitly with: @@ -662,13 +770,13 @@ mod tests { assert!(!policy.accepted_measurements.is_empty()); assert!(matches!( - policy.check_measurement(&MultiMeasurements::NoAttestation).unwrap_err(), + policy.check_measurement(&MultiMeasurements::NoAttestation, None).unwrap_err(), AttestationError::MeasurementsNotAccepted )); // A non-specific measurement fails assert!(matches!( - policy.check_measurement(&MultiMeasurements::Azure(HashMap::new())).unwrap_err(), + policy.check_measurement(&MultiMeasurements::Azure(HashMap::new()), None).unwrap_err(), AttestationError::MeasurementsNotAccepted )); } @@ -724,17 +832,17 @@ mod tests { // First value should match let measurements1 = MultiMeasurements::Dcap(HashMap::from([(DcapMeasurementRegister::MRTD, [0u8; 48])])); - assert!(policy.check_measurement(&measurements1).is_ok()); + assert!(policy.check_measurement(&measurements1, None).is_ok()); // Second value should also match let measurements2 = MultiMeasurements::Dcap(HashMap::from([(DcapMeasurementRegister::MRTD, [0x11u8; 48])])); - assert!(policy.check_measurement(&measurements2).is_ok()); + assert!(policy.check_measurement(&measurements2, None).is_ok()); // Different value should not match let measurements3 = MultiMeasurements::Dcap(HashMap::from([(DcapMeasurementRegister::MRTD, [0x22u8; 48])])); - assert!(policy.check_measurement(&measurements3).is_err()); + assert!(policy.check_measurement(&measurements3, None).is_err()); } #[tokio::test] @@ -814,21 +922,21 @@ mod tests { (DcapMeasurementRegister::MRTD, [0u8; 48]), (DcapMeasurementRegister::RTMR0, [0x11u8; 48]), ])); - assert!(policy.check_measurement(&measurements1).is_ok()); + assert!(policy.check_measurement(&measurements1, None).is_ok()); // Both match (single + second of any) let measurements2 = MultiMeasurements::Dcap(HashMap::from([ (DcapMeasurementRegister::MRTD, [0u8; 48]), (DcapMeasurementRegister::RTMR0, [0x22u8; 48]), ])); - assert!(policy.check_measurement(&measurements2).is_ok()); + assert!(policy.check_measurement(&measurements2, None).is_ok()); // Single matches but any doesn't let measurements3 = MultiMeasurements::Dcap(HashMap::from([ (DcapMeasurementRegister::MRTD, [0u8; 48]), (DcapMeasurementRegister::RTMR0, [0x33u8; 48]), ])); - assert!(policy.check_measurement(&measurements3).is_err()); + assert!(policy.check_measurement(&measurements3, None).is_err()); } #[tokio::test] diff --git a/crates/attested-tls/src/lib.rs b/crates/attested-tls/src/lib.rs index 1495611..d1f99cd 100644 --- a/crates/attested-tls/src/lib.rs +++ b/crates/attested-tls/src/lib.rs @@ -7,10 +7,12 @@ use std::{ }; pub use attestation::{ + AttestationEvidence, AttestationExchangeMessage, AttestationGenerator, AttestationType, AttestationVerifier, + PlatformMetadata, }; use ra_tls::{ attestation::{Attestation, AttestationQuote, VersionedAttestation}, @@ -540,9 +542,18 @@ impl AttestedCertificateVerifier { return Ok(message); } + // TODO maybe we have to drop support for the VersionedAttestation::V0 + // format as it cannot encode platform metadata return Ok(AttestationExchangeMessage { - attestation_type: AttestationType::DcapTdx, - attestation: tdx_quote.quote, + attestation_evidence: Some(AttestationEvidence { + quote: tdx_quote.quote, + platform: PlatformMetadata { + attestation_type: AttestationType::DcapTdx.into(), + ram_bytes: 0, + num_disks: 0, + acpi: None, + }, + }), }); } diff --git a/crates/mock-tdx/Cargo.toml b/crates/mock-tdx/Cargo.toml index f13c39f..720005b 100644 --- a/crates/mock-tdx/Cargo.toml +++ b/crates/mock-tdx/Cargo.toml @@ -21,6 +21,7 @@ tokio = { workspace = true, features = ["rt", "net"] } urlencoding = "2.1.3" yasna = "0.5.2" x509-parser = "0.18.1" +attest-types = { git = "https://github.com/easy-tee/attest.git", branch = "ah/better-interface" } [lints] workspace = true diff --git a/crates/mock-tdx/src/lib.rs b/crates/mock-tdx/src/lib.rs index 50a2989..ba021d9 100644 --- a/crates/mock-tdx/src/lib.rs +++ b/crates/mock-tdx/src/lib.rs @@ -1,5 +1,6 @@ pub mod mock_pcs; +use attest_types::{AttestationEvidence, PlatformMetadata}; use dcap_qvl::{ QuoteCollateralV3, quote::{ @@ -106,9 +107,17 @@ pub(crate) fn signing_key_from_secret( /// Generate a mock TDX DCAP quote using the generated fixture material pub fn generate_mock_tdx_quote( report_data: [u8; 64], -) -> Result, Box> { +) -> Result> { let collateral = mock_collateral(); - generate_mock_tdx_quote_with_collateral(&collateral, report_data) + Ok(AttestationEvidence { + quote: generate_mock_tdx_quote_with_collateral(&collateral, report_data)?, + platform: PlatformMetadata { + attestation_type: attest_types::AttestationType::GcpTdx, + num_disks: 0, + ram_bytes: 0, + acpi: None, + }, + }) } /// Generate a mock TDX DCAP quote from a specific loaded material set @@ -250,8 +259,8 @@ mod tests { fn builds_quote_that_parses_and_verifies() { let report_data = [0xAB; 64]; - let quote_bytes = generate_mock_tdx_quote(report_data).unwrap(); - let quote = Quote::parse("e_bytes).unwrap(); + let attestation_evidence = generate_mock_tdx_quote(report_data).unwrap(); + let quote = Quote::parse(&attestation_evidence.quote).unwrap(); assert_eq!(quote.header.version, QUOTE_VERSION); assert_eq!(quote.header.tee_type, TEE_TYPE_TDX); @@ -261,7 +270,8 @@ mod tests { assert_eq!(quote.ca().unwrap(), "processor"); let verifier = mock_dcap_verifier(); - let verified = verifier.verify("e_bytes, &collateral, FIXTURE_TIME).unwrap(); + let verified = + verifier.verify(&attestation_evidence.quote, &collateral, FIXTURE_TIME).unwrap(); let dcap_qvl::quote::Report::TD10(report) = verified.report else { panic!("expected TD10 report"); }; @@ -276,13 +286,13 @@ mod tests { const TD_REPORT10_BYTE_LEN: usize = 584; const AUTH_DATA_SIZE_BYTE_LEN: usize = 4; - let mut quote_bytes = generate_mock_tdx_quote([0xCD; 64]).unwrap(); + let mut attestation_evidence = 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; + attestation_evidence.quote[signature_offset] ^= 0x01; let verifier = mock_dcap_verifier(); let collateral = mock_collateral(); - assert!(verifier.verify("e_bytes, &collateral, FIXTURE_TIME).is_err()); + assert!(verifier.verify(&attestation_evidence.quote, &collateral, FIXTURE_TIME).is_err()); } #[test] @@ -294,11 +304,11 @@ mod tests { 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(); + let attestation_evidence = generate_mock_tdx_quote([0xEF; 64]).unwrap(); + let quote = Quote::parse(&attestation_evidence.quote).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(); + verifier.verify(&attestation_evidence.quote, &collateral, FIXTURE_TIME).unwrap(); } }