From 38e80bbee5de39462ed5c40a1349efa2300a541e Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 3 Jun 2026 10:26:47 +0200 Subject: [PATCH 1/9] First step towards integrating easy-tee/attest --- Cargo.lock | 295 ++++++++++++++++++++++++++++++++- crates/attestation/Cargo.toml | 2 + crates/attestation/src/dcap.rs | 31 ++-- crates/attestation/src/lib.rs | 62 +++++-- crates/attested-tls/src/lib.rs | 1 + crates/mock-tdx/Cargo.toml | 1 + crates/mock-tdx/src/lib.rs | 32 ++-- 7 files changed, 381 insertions(+), 43 deletions(-) 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/dcap.rs b/crates/attestation/src/dcap.rs index dca2c10..10b52f8 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,21 +22,24 @@ 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 #[cfg(not(any(test, feature = "mock")))] pub async fn verify_dcap_attestation( - input: Vec, + attestation_evidence: AttestationEvidence, expected_input_data: [u8; 64], pccs: Option, ) -> Result { let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)?.as_secs(); let override_azure_outdated_tcb = false; + let input = attestation_evidence.quote; verify_dcap_attestation_with_given_timestamp( input, expected_input_data, @@ -206,11 +210,11 @@ fn verify_dcap_attestation_with_collateral_and_timestamp( #[cfg(any(test, feature = "mock"))] pub async fn verify_dcap_attestation( - input: Vec, + attestation_evidence: AttestationEvidence, expected_input_data: [u8; 64], pccs: Option, ) -> Result { - let quote = Quote::parse(&input)?; + let quote = Quote::parse(&attestation_evidence.quote)?; 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(); @@ -221,7 +225,7 @@ pub async fn verify_dcap_attestation( mock_tdx::mock_collateral() }; let verifier = mock_tdx::mock_dcap_verifier(); - verifier.verify(&input, &collateral, now)?; + verifier.verify(&attestation_evidence.quote, &collateral, now)?; let measurements = MultiMeasurements::from_dcap_qvl_quote("e)?; if get_quote_input_data(quote.report) != expected_input_data { @@ -254,7 +258,7 @@ 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> { +fn generate_quote(input: [u8; 64]) -> Result { generate_mock_tdx_quote(input).map_err(|error| { tdx_attest::TdxAttestError::QuoteFailure(format!("mock-tdx quote generation: {error}")) }) @@ -262,8 +266,13 @@ fn generate_quote(input: [u8; 64]) -> Result, tdx_attest::TdxAttestError /// 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).unwrap(), + }) } /// Given a [Report] get the input data regardless of report type diff --git a/crates/attestation/src/lib.rs b/crates/attestation/src/lib.rs index 73bc5ba..ceb1643 100644 --- a/crates/attestation/src/lib.rs +++ b/crates/attestation/src/lib.rs @@ -12,6 +12,7 @@ use std::{ time::{Duration, SystemTime, UNIX_EPOCH}, }; +use attest_types::{AttestationEvidence, PlatformMetadata}; use measurements::MultiMeasurements; use parity_scale_codec::{Decode, Encode}; use pccs::{Pccs, PccsError}; @@ -31,13 +32,18 @@ pub struct AttestationExchangeMessage { /// The attestation evidence as bytes - in the case of DCAP this is a /// quote pub attestation: Vec, + pub platform_metadata: 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_type: AttestationType::None, + attestation: Vec::new(), + platform_metadata: None, + } } /// Extract the measurements from the attestation, if present, but do @@ -64,6 +70,21 @@ impl AttestationExchangeMessage { } } +impl From for AttestationExchangeMessage { + fn from(attestation_evidence: AttestationEvidence) -> Self { + let attestation_type = match attestation_evidence.platform.attestation_type { + attest_types::AttestationType::GcpTdx => AttestationType::GcpTdx, + attest_types::AttestationType::AzureTdx => AttestationType::AzureTdx, + attest_types::AttestationType::SelfHostedTdx => AttestationType::QemuTdx, + }; + Self { + attestation_type, + attestation: attestation_evidence.quote, + platform_metadata: Some(attestation_evidence.platform), + } + } +} + /// Type of attestation used /// Only supported (or soon-to-be supported) types are given #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -204,10 +225,7 @@ 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)?, - }) + self.generate_attestation_bytes(input_data) } } @@ -216,9 +234,13 @@ impl AttestationGenerator { fn generate_attestation_bytes( &self, input_data: [u8; 64], - ) -> Result, AttestationError> { + ) -> Result { match self.attestation_type { - AttestationType::None => Ok(Vec::new()), + AttestationType::None => Ok(AttestationExchangeMessage { + attestation_type: AttestationType::None, + attestation: Vec::new(), + platform_metadata: None, + }), AttestationType::AzureTdx => { #[cfg(feature = "azure")] { @@ -233,7 +255,12 @@ impl AttestationGenerator { } } AttestationType::DcapTdx | AttestationType::GcpTdx | AttestationType::QemuTdx => { - dcap::create_dcap_attestation(input_data) + let attestaton_evidence = dcap::create_dcap_attestation(input_data)?; + Ok(AttestationExchangeMessage { + attestation_type: self.attestation_type, + attestation: attestaton_evidence.quote, + platform_metadata: Some(attestaton_evidence.platform), + }) } } } @@ -262,7 +289,11 @@ impl AttestationGenerator { if let Ok(message) = AttestationExchangeMessage::decode(&mut &body[..]) { Ok(message) } else { - Ok(AttestationExchangeMessage { attestation_type, attestation: body }) + Ok(AttestationExchangeMessage { + attestation_type, + attestation: body, + platform_metadata: None, + }) } } } @@ -399,8 +430,13 @@ impl AttestationVerifier { } } AttestationType::DcapTdx | AttestationType::GcpTdx | AttestationType::QemuTdx => { + let attesation_evidence = AttestationEvidence { + quote: attestation_exchange_message.attestation, + platform: attestation_exchange_message.platform_metadata.unwrap(), + }; + dcap::verify_dcap_attestation( - attestation_exchange_message.attestation, + attesation_evidence, expected_input_data, self.internal_pccs.clone(), ) @@ -662,6 +698,7 @@ mod tests { let encoded_message = AttestationExchangeMessage { attestation_type: AttestationType::None, attestation: vec![1, 2, 3], + platform_metadata: None, } .encode(); @@ -700,10 +737,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/attested-tls/src/lib.rs b/crates/attested-tls/src/lib.rs index 1495611..f2c2a3e 100644 --- a/crates/attested-tls/src/lib.rs +++ b/crates/attested-tls/src/lib.rs @@ -543,6 +543,7 @@ impl AttestedCertificateVerifier { return Ok(AttestationExchangeMessage { attestation_type: AttestationType::DcapTdx, attestation: tdx_quote.quote, + platform_metadata: 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(); } } From 3227a167231ee0b3454688d0e83d2b157429c42e Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 3 Jun 2026 12:12:05 +0200 Subject: [PATCH 2/9] Add test for measurement policy --- crates/attestation/src/dcap.rs | 2 +- crates/attestation/src/lib.rs | 12 ++- crates/attestation/src/measurements.rs | 139 ++++++++++++++++++++----- 3 files changed, 121 insertions(+), 32 deletions(-) diff --git a/crates/attestation/src/dcap.rs b/crates/attestation/src/dcap.rs index 10b52f8..5b19e57 100644 --- a/crates/attestation/src/dcap.rs +++ b/crates/attestation/src/dcap.rs @@ -372,7 +372,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 diff --git a/crates/attestation/src/lib.rs b/crates/attestation/src/lib.rs index ceb1643..7ded854 100644 --- a/crates/attestation/src/lib.rs +++ b/crates/attestation/src/lib.rs @@ -432,7 +432,7 @@ impl AttestationVerifier { AttestationType::DcapTdx | AttestationType::GcpTdx | AttestationType::QemuTdx => { let attesation_evidence = AttestationEvidence { quote: attestation_exchange_message.attestation, - platform: attestation_exchange_message.platform_metadata.unwrap(), + platform: attestation_exchange_message.platform_metadata.clone().unwrap(), }; dcap::verify_dcap_attestation( @@ -445,7 +445,8 @@ impl AttestationVerifier { }; // Do a measurement / attestation type policy check - self.measurement_policy.check_measurement(&measurements)?; + self.measurement_policy + .check_measurement(&measurements, attestation_exchange_message.platform_metadata)?; tracing::debug!("Verification successful"); Ok(Some(measurements)) @@ -506,7 +507,8 @@ impl AttestationVerifier { }; // Do a measurement / attestation type policy check - self.measurement_policy.check_measurement(&measurements)?; + self.measurement_policy + .check_measurement(&measurements, attestation_exchange_message.platform_metadata)?; tracing::debug!("Verification successful"); Ok(Some(measurements)) @@ -549,8 +551,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) diff --git a/crates/attestation/src/measurements.rs b/crates/attestation/src/measurements.rs index db8c2c9..8ef85e4 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::{build_rtmr2, gcp, self_hosted}; +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,53 @@ 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, + } } + return true; } - return true; + ExpectedMeasurements::Image(image_hashes) => { + let Some(platform_metadata) = &platform_metadata else { + return false; + }; + let expected_rtmr1 = match platform_metadata.attestation_type { + ImageAttestationType::GcpTdx => gcp::build_rtmr1(image_hashes).value(), + ImageAttestationType::SelfHostedTdx => { + self_hosted::build_rtmr1(image_hashes).value() + } + ImageAttestationType::AzureTdx => return false, + }; + let expected_rtmr2 = build_rtmr2(image_hashes).value(); + + if let Some(rtmr1) = dcap_measurements.get(&DcapMeasurementRegister::RTMR1) + { + if rtmr1 != &expected_rtmr1 { + return false; + } + } + + if let Some(rtmr2) = dcap_measurements.get(&DcapMeasurementRegister::RTMR2) + { + if rtmr2 != &expected_rtmr2 { + return false; + } + } + + // TODO how to handle mrtd and rtmr0 since they are Option + return true; + } + _ => false, } - false } MultiMeasurements::Azure(azure_measurements) => { if let ExpectedMeasurements::Azure(expected) = &measurement_record.measurements { @@ -527,9 +563,9 @@ impl MeasurementPolicy { )?; ExpectedMeasurements::Azure(azure_measurements) } - AttestationType::DcapTdx | - AttestationType::GcpTdx | - AttestationType::QemuTdx => ExpectedMeasurements::Dcap( + AttestationType::DcapTdx + | AttestationType::GcpTdx + | AttestationType::QemuTdx => ExpectedMeasurements::Dcap( measurements .iter() .map(|(index_str, entry)| { @@ -567,8 +603,8 @@ impl MeasurementPolicy { }; let normalized_host = host.trim_start_matches('[').trim_end_matches(']'); - Ok(normalized_host.eq_ignore_ascii_case("localhost") || - normalized_host.parse::().is_ok_and(|address| address.is_loopback())) + Ok(normalized_host.eq_ignore_ascii_case("localhost") + || normalized_host.parse::().is_ok_and(|address| address.is_loopback())) } } @@ -576,6 +612,8 @@ impl MeasurementPolicy { mod tests { use std::collections::HashSet; + use attest_measure::dcap::{build_rtmr2, gcp::build_rtmr1}; + use super::*; #[tokio::test] @@ -612,20 +650,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 +678,64 @@ 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: 0, + num_disks: 2, + acpi: None, + }; + let measurements = MultiMeasurements::Dcap(HashMap::from([ + (DcapMeasurementRegister::MRTD, mock_tdx::MOCK_MRTD), + (DcapMeasurementRegister::RTMR0, mock_tdx::MOCK_RTMR0), + (DcapMeasurementRegister::RTMR1, build_rtmr1(&image_hashes).value()), + (DcapMeasurementRegister::RTMR2, build_rtmr2(&image_hashes).value()), + (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 +749,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 +811,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 +901,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] From 25ea83ffa25aa07e98fe69a059224801ce786d46 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 3 Jun 2026 12:32:27 +0200 Subject: [PATCH 3/9] Also check rtmr0 --- crates/attestation/src/measurements.rs | 49 +++++++++++++++++--------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/crates/attestation/src/measurements.rs b/crates/attestation/src/measurements.rs index 8ef85e4..f73480b 100644 --- a/crates/attestation/src/measurements.rs +++ b/crates/attestation/src/measurements.rs @@ -2,7 +2,7 @@ //! attestation use std::{collections::HashMap, fmt, fmt::Formatter, net::IpAddr, path::PathBuf}; -use attest_measure::dcap::{build_rtmr2, gcp, self_hosted}; +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}; @@ -397,25 +397,37 @@ impl MeasurementPolicy { let Some(platform_metadata) = &platform_metadata else { return false; }; - let expected_rtmr1 = match platform_metadata.attestation_type { - ImageAttestationType::GcpTdx => gcp::build_rtmr1(image_hashes).value(), - ImageAttestationType::SelfHostedTdx => { - self_hosted::build_rtmr1(image_hashes).value() - } + let firmware = match platform_metadata.attestation_type { + ImageAttestationType::GcpTdx => Some(DcapFirmware::gcp_hardcoded()), + ImageAttestationType::SelfHostedTdx => None, ImageAttestationType::AzureTdx => return false, }; - let expected_rtmr2 = build_rtmr2(image_hashes).value(); + let expected_measurements = match expected_dcap_registers( + image_hashes, + platform_metadata, + firmware.as_ref(), + ) { + Ok(expected_measurements) => expected_measurements, + Err(_) => return false, // TODO should we bail here + }; + + 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) { - if rtmr1 != &expected_rtmr1 { + if rtmr1 != &expected_measurements.rtmr1 { return false; } } if let Some(rtmr2) = dcap_measurements.get(&DcapMeasurementRegister::RTMR2) { - if rtmr2 != &expected_rtmr2 { + if rtmr2 != &expected_measurements.rtmr2 { return false; } } @@ -612,7 +624,8 @@ impl MeasurementPolicy { mod tests { use std::collections::HashSet; - use attest_measure::dcap::{build_rtmr2, gcp::build_rtmr1}; + use attest_measure::dcap::expected_dcap_registers; + use attest_types::AcpiHashes; use super::*; @@ -721,15 +734,19 @@ mod tests { }; let platform_metadata = PlatformMetadata { attestation_type: attest_types::AttestationType::GcpTdx, - ram_bytes: 0, - num_disks: 2, - acpi: None, + 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, mock_tdx::MOCK_MRTD), - (DcapMeasurementRegister::RTMR0, mock_tdx::MOCK_RTMR0), - (DcapMeasurementRegister::RTMR1, build_rtmr1(&image_hashes).value()), - (DcapMeasurementRegister::RTMR2, build_rtmr2(&image_hashes).value()), + (DcapMeasurementRegister::RTMR0, expected_measurements.rtmr0.unwrap()), + (DcapMeasurementRegister::RTMR1, expected_measurements.rtmr1), + (DcapMeasurementRegister::RTMR2, expected_measurements.rtmr2), (DcapMeasurementRegister::RTMR3, mock_tdx::MOCK_RTMR3), ])); From bced75d8ccc5cde8b43f5e0e30465d717be48aeb Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 3 Jun 2026 12:37:50 +0200 Subject: [PATCH 4/9] Also check mrtd --- crates/attestation/src/measurements.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/attestation/src/measurements.rs b/crates/attestation/src/measurements.rs index f73480b..49f9252 100644 --- a/crates/attestation/src/measurements.rs +++ b/crates/attestation/src/measurements.rs @@ -411,6 +411,13 @@ impl MeasurementPolicy { Err(_) => 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 => {} @@ -432,7 +439,6 @@ impl MeasurementPolicy { } } - // TODO how to handle mrtd and rtmr0 since they are Option return true; } _ => false, @@ -743,7 +749,7 @@ mod tests { expected_dcap_registers(&image_hashes, &platform_metadata, Some(&firmware)).unwrap(); let measurements = MultiMeasurements::Dcap(HashMap::from([ - (DcapMeasurementRegister::MRTD, mock_tdx::MOCK_MRTD), + (DcapMeasurementRegister::MRTD, expected_measurements.mrtd.unwrap()), (DcapMeasurementRegister::RTMR0, expected_measurements.rtmr0.unwrap()), (DcapMeasurementRegister::RTMR1, expected_measurements.rtmr1), (DcapMeasurementRegister::RTMR2, expected_measurements.rtmr2), From b25a42f568d34c10d624370442bff75295575282 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 3 Jun 2026 12:42:49 +0200 Subject: [PATCH 5/9] Fix to compile for azure feature --- crates/attestation/src/azure/mod.rs | 5 +-- crates/attestation/src/lib.rs | 63 +++++++++++++---------------- 2 files changed, 31 insertions(+), 37 deletions(-) diff --git a/crates/attestation/src/azure/mod.rs b/crates/attestation/src/azure/mod.rs index 45d4e4c..296199f 100644 --- a/crates/attestation/src/azure/mod.rs +++ b/crates/attestation/src/azure/mod.rs @@ -17,8 +17,7 @@ use x509_parser::prelude::*; use crate::{ dcap::{ - verify_dcap_attestation_with_given_timestamp, - verify_dcap_attestation_with_timestamp_sync, + verify_dcap_attestation_with_given_timestamp, verify_dcap_attestation_with_timestamp_sync, }, measurements::MultiMeasurements, }; @@ -599,7 +598,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/lib.rs b/crates/attestation/src/lib.rs index 7ded854..6049f48 100644 --- a/crates/attestation/src/lib.rs +++ b/crates/attestation/src/lib.rs @@ -225,43 +225,38 @@ impl AttestationGenerator { if let Some(url) = &self.attestation_provider_url { Self::use_attestation_provider(url, self.attestation_type, input_data) } else { - 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 { - match self.attestation_type { - AttestationType::None => Ok(AttestationExchangeMessage { - attestation_type: AttestationType::None, - attestation: Vec::new(), - platform_metadata: None, - }), - AttestationType::AzureTdx => { - #[cfg(feature = "azure")] - { - Ok(azure::create_azure_attestation(input_data)?) + match self.attestation_type { + AttestationType::None => Ok(AttestationExchangeMessage { + attestation_type: AttestationType::None, + attestation: Vec::new(), + platform_metadata: None, + }), + AttestationType::AzureTdx => { + #[cfg(feature = "azure")] + { + Ok(AttestationExchangeMessage { + attestation_type: AttestationType::AzureTdx, + attestation: azure::create_azure_attestation(input_data)?, + platform_metadata: None, + }) + } + #[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 attestaton_evidence = dcap::create_dcap_attestation(input_data)?; + Ok(AttestationExchangeMessage { + attestation_type: self.attestation_type, + attestation: attestaton_evidence.quote, + platform_metadata: Some(attestaton_evidence.platform), + }) } } - AttestationType::DcapTdx | AttestationType::GcpTdx | AttestationType::QemuTdx => { - let attestaton_evidence = dcap::create_dcap_attestation(input_data)?; - Ok(AttestationExchangeMessage { - attestation_type: self.attestation_type, - attestation: attestaton_evidence.quote, - platform_metadata: Some(attestaton_evidence.platform), - }) - } } } From 9e0c26325f21b62ea1b96f06b5fad12edeef67fa Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 3 Jun 2026 12:47:02 +0200 Subject: [PATCH 6/9] Clippy --- crates/attestation/src/azure/mod.rs | 3 ++- crates/attestation/src/lib.rs | 4 ++-- crates/attestation/src/measurements.rs | 32 ++++++++++++-------------- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/crates/attestation/src/azure/mod.rs b/crates/attestation/src/azure/mod.rs index 296199f..08272d1 100644 --- a/crates/attestation/src/azure/mod.rs +++ b/crates/attestation/src/azure/mod.rs @@ -17,7 +17,8 @@ use x509_parser::prelude::*; use crate::{ dcap::{ - verify_dcap_attestation_with_given_timestamp, verify_dcap_attestation_with_timestamp_sync, + verify_dcap_attestation_with_given_timestamp, + verify_dcap_attestation_with_timestamp_sync, }, measurements::MultiMeasurements, }; diff --git a/crates/attestation/src/lib.rs b/crates/attestation/src/lib.rs index 6049f48..15ab8de 100644 --- a/crates/attestation/src/lib.rs +++ b/crates/attestation/src/lib.rs @@ -546,8 +546,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) diff --git a/crates/attestation/src/measurements.rs b/crates/attestation/src/measurements.rs index 49f9252..1cf841c 100644 --- a/crates/attestation/src/measurements.rs +++ b/crates/attestation/src/measurements.rs @@ -391,7 +391,7 @@ impl MeasurementPolicy { _ => return false, } } - return true; + true } ExpectedMeasurements::Image(image_hashes) => { let Some(platform_metadata) = &platform_metadata else { @@ -402,13 +402,13 @@ impl MeasurementPolicy { ImageAttestationType::SelfHostedTdx => None, ImageAttestationType::AzureTdx => return false, }; - let expected_measurements = match expected_dcap_registers( + + let Ok(expected_measurements) = expected_dcap_registers( image_hashes, platform_metadata, firmware.as_ref(), - ) { - Ok(expected_measurements) => expected_measurements, - Err(_) => return false, // TODO should we bail here + ) else { + return false; // TODO should we bail here }; if let Some(expected_mrtd) = expected_measurements.mrtd { @@ -426,20 +426,18 @@ impl MeasurementPolicy { } if let Some(rtmr1) = dcap_measurements.get(&DcapMeasurementRegister::RTMR1) + && rtmr1 != &expected_measurements.rtmr1 { - if rtmr1 != &expected_measurements.rtmr1 { - return false; - } + return false; } if let Some(rtmr2) = dcap_measurements.get(&DcapMeasurementRegister::RTMR2) + && rtmr2 != &expected_measurements.rtmr2 { - if rtmr2 != &expected_measurements.rtmr2 { - return false; - } + return false; } - return true; + true } _ => false, } @@ -581,9 +579,9 @@ impl MeasurementPolicy { )?; ExpectedMeasurements::Azure(azure_measurements) } - AttestationType::DcapTdx - | AttestationType::GcpTdx - | AttestationType::QemuTdx => ExpectedMeasurements::Dcap( + AttestationType::DcapTdx | + AttestationType::GcpTdx | + AttestationType::QemuTdx => ExpectedMeasurements::Dcap( measurements .iter() .map(|(index_str, entry)| { @@ -621,8 +619,8 @@ impl MeasurementPolicy { }; let normalized_host = host.trim_start_matches('[').trim_end_matches(']'); - Ok(normalized_host.eq_ignore_ascii_case("localhost") - || normalized_host.parse::().is_ok_and(|address| address.is_loopback())) + Ok(normalized_host.eq_ignore_ascii_case("localhost") || + normalized_host.parse::().is_ok_and(|address| address.is_loopback())) } } From 737d790d5d9ff5c082241034cfca350a136b164c Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 3 Jun 2026 13:47:54 +0200 Subject: [PATCH 7/9] Error handling --- crates/attestation/src/dcap.rs | 23 ++++++++++------------- crates/attestation/src/lib.rs | 13 +++++++------ 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/crates/attestation/src/dcap.rs b/crates/attestation/src/dcap.rs index 5b19e57..516e30c 100644 --- a/crates/attestation/src/dcap.rs +++ b/crates/attestation/src/dcap.rs @@ -33,13 +33,12 @@ pub fn create_dcap_attestation( /// Verify a DCAP TDX quote, and return the measurement values #[cfg(not(any(test, feature = "mock")))] pub async fn verify_dcap_attestation( - attestation_evidence: AttestationEvidence, + input: Vec, expected_input_data: [u8; 64], pccs: Option, ) -> Result { let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)?.as_secs(); let override_azure_outdated_tcb = false; - let input = attestation_evidence.quote; verify_dcap_attestation_with_given_timestamp( input, expected_input_data, @@ -210,11 +209,11 @@ fn verify_dcap_attestation_with_collateral_and_timestamp( #[cfg(any(test, feature = "mock"))] pub async fn verify_dcap_attestation( - attestation_evidence: AttestationEvidence, + input: Vec, expected_input_data: [u8; 64], pccs: Option, ) -> Result { - let quote = Quote::parse(&attestation_evidence.quote)?; + let quote = Quote::parse(&input)?; 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(); @@ -225,7 +224,7 @@ pub async fn verify_dcap_attestation( mock_tdx::mock_collateral() }; let verifier = mock_tdx::mock_dcap_verifier(); - verifier.verify(&attestation_evidence.quote, &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 { @@ -258,20 +257,18 @@ 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 { - 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 { +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).unwrap(), + platform: platform::metadata_for(attest_types::AttestationType::GcpTdx)?, }) } @@ -416,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 15ab8de..97b8dc5 100644 --- a/crates/attestation/src/lib.rs +++ b/crates/attestation/src/lib.rs @@ -12,6 +12,7 @@ use std::{ time::{Duration, SystemTime, UNIX_EPOCH}, }; +use attest_measure::platform::PlatformError; use attest_types::{AttestationEvidence, PlatformMetadata}; use measurements::MultiMeasurements; use parity_scale_codec::{Decode, Encode}; @@ -425,13 +426,8 @@ impl AttestationVerifier { } } AttestationType::DcapTdx | AttestationType::GcpTdx | AttestationType::QemuTdx => { - let attesation_evidence = AttestationEvidence { - quote: attestation_exchange_message.attestation, - platform: attestation_exchange_message.platform_metadata.clone().unwrap(), - }; - dcap::verify_dcap_attestation( - attesation_evidence, + attestation_exchange_message.attestation, expected_input_data, self.internal_pccs.clone(), ) @@ -642,6 +638,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)] From 5d2091397d912425cb6e8bd801c745ed73559c39 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 3 Jun 2026 14:15:25 +0200 Subject: [PATCH 8/9] Typo, comments --- crates/attestation/src/lib.rs | 6 +++--- crates/attested-tls/src/lib.rs | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/attestation/src/lib.rs b/crates/attestation/src/lib.rs index 97b8dc5..d979bea 100644 --- a/crates/attestation/src/lib.rs +++ b/crates/attestation/src/lib.rs @@ -250,11 +250,11 @@ impl AttestationGenerator { } } AttestationType::DcapTdx | AttestationType::GcpTdx | AttestationType::QemuTdx => { - let attestaton_evidence = dcap::create_dcap_attestation(input_data)?; + let attestation_evidence = dcap::create_dcap_attestation(input_data)?; Ok(AttestationExchangeMessage { attestation_type: self.attestation_type, - attestation: attestaton_evidence.quote, - platform_metadata: Some(attestaton_evidence.platform), + attestation: attestation_evidence.quote, + platform_metadata: Some(attestation_evidence.platform), }) } } diff --git a/crates/attested-tls/src/lib.rs b/crates/attested-tls/src/lib.rs index f2c2a3e..d4319ad 100644 --- a/crates/attested-tls/src/lib.rs +++ b/crates/attested-tls/src/lib.rs @@ -540,6 +540,8 @@ 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, From f245588ecc979b5d45e20094a3c4245ce217130f Mon Sep 17 00:00:00 2001 From: peg Date: Thu, 4 Jun 2026 17:27:26 +0200 Subject: [PATCH 9/9] Use AttestationEvidence directly in AttestationExchangeMessage --- crates/attestation/src/lib.rs | 176 +++++++++++++++++++++------------ crates/attested-tls/src/lib.rs | 14 ++- 2 files changed, 124 insertions(+), 66 deletions(-) diff --git a/crates/attestation/src/lib.rs b/crates/attestation/src/lib.rs index d979bea..294f046 100644 --- a/crates/attestation/src/lib.rs +++ b/crates/attestation/src/lib.rs @@ -13,7 +13,7 @@ use std::{ }; use attest_measure::platform::PlatformError; -use attest_types::{AttestationEvidence, PlatformMetadata}; +pub use attest_types::{AttestationEvidence, PlatformMetadata}; use measurements::MultiMeasurements; use parity_scale_codec::{Decode, Encode}; use pccs::{Pccs, PccsError}; @@ -28,34 +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, - pub platform_metadata: Option, + /// 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(), - platform_metadata: None, - } + 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"))] { @@ -63,29 +60,59 @@ 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 { - let attestation_type = match attestation_evidence.platform.attestation_type { + 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, - }; - Self { - attestation_type, - attestation: attestation_evidence.quote, - platform_metadata: Some(attestation_evidence.platform), } } } +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 /// Only supported (or soon-to-be supported) types are given #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -199,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)?, ))?; @@ -227,18 +254,17 @@ impl AttestationGenerator { Self::use_attestation_provider(url, self.attestation_type, input_data) } else { match self.attestation_type { - AttestationType::None => Ok(AttestationExchangeMessage { - attestation_type: AttestationType::None, - attestation: Vec::new(), - platform_metadata: None, - }), + 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_type: AttestationType::AzureTdx, - attestation: azure::create_azure_attestation(input_data)?, - platform_metadata: None, + attestation_evidence: Some(AttestationEvidence { + quote: azure::create_azure_attestation(input_data)?, + platform, + }), }) } #[cfg(not(feature = "azure"))] @@ -250,12 +276,9 @@ impl AttestationGenerator { } } AttestationType::DcapTdx | AttestationType::GcpTdx | AttestationType::QemuTdx => { - let attestation_evidence = dcap::create_dcap_attestation(input_data)?; - Ok(AttestationExchangeMessage { - attestation_type: self.attestation_type, - attestation: attestation_evidence.quote, - platform_metadata: Some(attestation_evidence.platform), - }) + let mut attestation_evidence = dcap::create_dcap_attestation(input_data)?; + attestation_evidence.platform.attestation_type = self.attestation_type.into(); + Ok(attestation_evidence.into()) } } } @@ -285,10 +308,13 @@ impl AttestationGenerator { if let Ok(message) = AttestationExchangeMessage::decode(&mut &body[..]) { Ok(message) } else { + if attestation_type == AttestationType::None { + return Ok(AttestationExchangeMessage::without_attestation()); + } + + let platform = placeholder_platform_metadata(attestation_type); Ok(AttestationExchangeMessage { - attestation_type, - attestation: body, - platform_metadata: None, + attestation_evidence: Some(AttestationEvidence { quote: body, platform }), }) } } @@ -391,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 { @@ -403,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); @@ -412,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, @@ -426,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(), ) @@ -436,8 +470,11 @@ impl AttestationVerifier { }; // Do a measurement / attestation type policy check - self.measurement_policy - .check_measurement(&measurements, attestation_exchange_message.platform_metadata)?; + 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)) @@ -448,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 { @@ -460,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); @@ -469,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, @@ -483,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)); @@ -490,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, )? @@ -498,8 +543,11 @@ impl AttestationVerifier { }; // Do a measurement / attestation type policy check - self.measurement_policy - .check_measurement(&measurements, attestation_exchange_message.platform_metadata)?; + 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)) @@ -513,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 { @@ -542,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) @@ -694,9 +743,10 @@ mod tests { let input_data = [0u8; 64]; let encoded_message = AttestationExchangeMessage { - attestation_type: AttestationType::None, - attestation: vec![1, 2, 3], - platform_metadata: None, + attestation_evidence: Some(AttestationEvidence { + quote: vec![1, 2, 3], + platform: placeholder_platform_metadata(AttestationType::GcpTdx), + }), } .encode(); @@ -708,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}"); @@ -719,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] diff --git a/crates/attested-tls/src/lib.rs b/crates/attested-tls/src/lib.rs index d4319ad..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}, @@ -543,9 +545,15 @@ impl AttestedCertificateVerifier { // 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, - platform_metadata: None, + attestation_evidence: Some(AttestationEvidence { + quote: tdx_quote.quote, + platform: PlatformMetadata { + attestation_type: AttestationType::DcapTdx.into(), + ram_bytes: 0, + num_disks: 0, + acpi: None, + }, + }), }); }