From 4ca59c60d3b7adc9955562d0787907a9f9037a65 Mon Sep 17 00:00:00 2001 From: peg Date: Mon, 16 Mar 2026 10:45:03 +0100 Subject: [PATCH 01/10] Add sync method to get collateral from cache - switch to std rwlock in pccs crate --- crates/pccs/src/lib.rs | 59 +++++++++++++++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 9 deletions(-) diff --git a/crates/pccs/src/lib.rs b/crates/pccs/src/lib.rs index 983b4bc..7b81e5a 100644 --- a/crates/pccs/src/lib.rs +++ b/crates/pccs/src/lib.rs @@ -2,6 +2,7 @@ use std::{ collections::HashMap, sync::{ Arc, + RwLock, Weak, atomic::{AtomicBool, AtomicUsize, Ordering}, }, @@ -12,7 +13,7 @@ use dcap_qvl::{QuoteCollateralV3, collateral::get_collateral_for_fmspc, tcb_info use thiserror::Error; use time::{OffsetDateTime, format_description::well_known::Rfc3339}; use tokio::{ - sync::{RwLock, Semaphore, watch}, + sync::{Semaphore, watch}, task::{JoinHandle, JoinSet}, time::{Duration, sleep}, }; @@ -107,7 +108,7 @@ impl Pccs { let cache_key = PccsInput::new(fmspc.clone(), ca); { - let cache = self.cache.read().await; + let cache = self.cache.read().map_err(|_| PccsError::CachePoisoned)?; if let Some(entry) = cache.get(&cache_key) { if now < entry.next_update { return Ok((entry.collateral.clone(), false)); @@ -124,7 +125,7 @@ impl Pccs { let collateral = fetch_collateral(&self.pccs_url, fmspc.clone(), ca).await?; let next_update = extract_next_update(&collateral, now)?; - let mut cache = self.cache.write().await; + let mut cache = self.cache.write().map_err(|_| PccsError::CachePoisoned)?; if let Some(existing) = cache.get(&cache_key) && now < existing.next_update { @@ -137,6 +138,30 @@ impl Pccs { Ok((collateral, true)) } + pub fn get_collateral_sync( + &self, + fmspc: String, + ca: &'static str, + now: u64, + ) -> Result { + let now = i64::try_from(now).map_err(|_| PccsError::TimeStampExceedsI64)?; + let cache_key = PccsInput::new(fmspc.clone(), ca); + let cache = self.cache.read().map_err(|_| PccsError::CachePoisoned)?; + if let Some(entry) = cache.get(&cache_key) { + if now < entry.next_update { + tracing::warn!( + fmspc, + next_update = entry.next_update, + now, + "Cached collateral expired" + ); + } + Ok(entry.collateral.clone()) + } else { + Err(PccsError::NoCollateralForFmspc(format!("{cache_key:?}"))) + } + } + /// Fetches fresh collateral, overwrites cache, and ensures proactive /// refresh is scheduled async fn refresh_collateral( @@ -150,7 +175,7 @@ impl Pccs { let cache_key = PccsInput::new(fmspc, ca); { - let mut cache = self.cache.write().await; + let mut cache = self.cache.write().map_err(|_| PccsError::CachePoisoned)?; upsert_cache_entry(&mut cache, cache_key.clone(), collateral.clone(), next_update); } self.ensure_refresh_task(&cache_key).await; @@ -160,7 +185,10 @@ impl Pccs { /// Starts a background refresh loop for a cache key when no task is /// active async fn ensure_refresh_task(&self, cache_key: &PccsInput) { - let mut cache = self.cache.write().await; + let Ok(mut cache) = self.cache.write() else { + tracing::warn!("PCCS cache lock poisoned, cannot ensure refresh task"); + return; + }; let Some(entry) = cache.get_mut(cache_key) else { return; }; @@ -413,7 +441,10 @@ async fn refresh_loop( return; }; let next_update = { - let cache_guard = cache.read().await; + let Ok(cache_guard) = cache.read() else { + tracing::warn!("PCCS cache lock poisoned, refresh loop stopping"); + return; + }; let Some(entry) = cache_guard.get(&key) else { return; }; @@ -445,7 +476,10 @@ async fn refresh_loop( return; }; let should_refresh = { - let cache_guard = cache.read().await; + let Ok(cache_guard) = cache.read() else { + tracing::warn!("PCCS cache lock poisoned, refresh loop stopping"); + return; + }; let Some(entry) = cache_guard.get(&key) else { return; }; @@ -474,7 +508,10 @@ async fn refresh_loop( let Some(cache) = weak_cache.upgrade() else { return; }; - let mut cache_guard = cache.write().await; + let Ok(mut cache_guard) = cache.write() else { + tracing::warn!("PCCS cache lock poisoned, refresh loop stopping"); + return; + }; let Some(entry) = cache_guard.get_mut(&key) else { return; }; @@ -574,6 +611,10 @@ pub enum PccsError { PrewarmSignalClosed, #[error("Timestamp exceeds i64 range")] TimeStampExceedsI64, + #[error("PCCS cache lock poisoned")] + CachePoisoned, + #[error("No collateral in cache for FMSPC {0}")] + NoCollateralForFmspc(String), } #[cfg(test)] @@ -683,7 +724,7 @@ mod tests { assert_eq!(summary.successes, 2); assert_eq!(summary.failures, 0); - let cache_guard = pccs.cache.read().await; + let cache_guard = pccs.cache.read().unwrap(); let total_entries = cache_guard.len(); assert_eq!(total_entries, 2, "expected startup pre-provision to cache processor+platform"); From 95f84e69ce468777b597f387ad8d801172aa4d56 Mon Sep 17 00:00:00 2001 From: peg Date: Mon, 16 Mar 2026 11:37:00 +0100 Subject: [PATCH 02/10] Add sync methods for dcap and azure verification --- crates/attestation/src/azure/mod.rs | 160 ++++++++++++++++++++++++---- crates/attestation/src/dcap.rs | 117 +++++++++++++++++--- crates/attestation/src/lib.rs | 59 ++++++++++ 3 files changed, 299 insertions(+), 37 deletions(-) diff --git a/crates/attestation/src/azure/mod.rs b/crates/attestation/src/azure/mod.rs index c16e94e..1821ad4 100644 --- a/crates/attestation/src/azure/mod.rs +++ b/crates/attestation/src/azure/mod.rs @@ -12,7 +12,13 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; use x509_parser::prelude::*; -use crate::{dcap::verify_dcap_attestation_with_given_timestamp, measurements::MultiMeasurements}; +use crate::{ + dcap::{ + verify_dcap_attestation_with_given_timestamp, + verify_dcap_attestation_with_timestamp_sync, + }, + measurements::MultiMeasurements, +}; /// The attestation evidence payload that gets sent over the channel #[derive(Debug, Serialize, Deserialize)] @@ -42,6 +48,16 @@ struct TpmAttest { instance_info: Option>, } +/// Used during verification to support both sync and async verification +/// paths without duplicating code +struct PreparedAzureAttestation { + tdx_quote_bytes: Vec, + hcl_report: hcl::HclReport, + var_data_hash: [u8; 32], + expected_tdx_input_data: [u8; 64], + tpm_attestation: TpmAttest, +} + /// Generate a TDX attestation on Azure pub fn create_azure_attestation(input_data: [u8; 64]) -> Result, MaaError> { let hcl_report_bytes = vtpm::get_report_with_report_data(&input_data)?; @@ -100,6 +116,28 @@ pub async fn verify_azure_attestation( .await } +/// Verify a TDX attestation from Azure +pub fn verify_azure_attestation_sync( + input: Vec, + expected_input_data: [u8; 64], + pccs: Pccs, + override_azure_outdated_tcb: bool, +) -> Result { + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("Time went backwards") + .as_secs(); + + verify_azure_attestation_with_given_timestamp_sync( + input, + expected_input_data, + pccs, + None, + now, + override_azure_outdated_tcb, + ) +} + /// Do the verification, passing in the current time /// This allows us to test this function without time checks going out of /// date @@ -111,30 +149,101 @@ async fn verify_azure_attestation_with_given_timestamp( now: u64, override_azure_outdated_tcb: bool, ) -> Result { + let PreparedAzureAttestation { + tdx_quote_bytes, + hcl_report, + var_data_hash, + expected_tdx_input_data, + tpm_attestation, + } = prepare_azure_attestation(input)?; + + let _dcap_measurements = verify_dcap_attestation_with_given_timestamp( + tdx_quote_bytes, + expected_tdx_input_data, + pccs, + collateral, + now, + override_azure_outdated_tcb, + ) + .await?; + + finish_azure_attestation_verification( + hcl_report, + var_data_hash, + tpm_attestation, + expected_input_data, + now, + ) +} + +fn verify_azure_attestation_with_given_timestamp_sync( + input: Vec, + expected_input_data: [u8; 64], + pccs: Pccs, + collateral: Option, + now: u64, + override_azure_outdated_tcb: bool, +) -> Result { + let PreparedAzureAttestation { + tdx_quote_bytes, + hcl_report, + var_data_hash, + expected_tdx_input_data, + tpm_attestation, + } = prepare_azure_attestation(input)?; + + let _dcap_measurements = verify_dcap_attestation_with_timestamp_sync( + tdx_quote_bytes, + expected_tdx_input_data, + pccs, + collateral, + now, + override_azure_outdated_tcb, + )?; + + finish_azure_attestation_verification( + hcl_report, + var_data_hash, + tpm_attestation, + expected_input_data, + now, + ) +} + +/// Parses the attestation during verification +fn prepare_azure_attestation(input: Vec) -> Result { let attestation_document: AttestationDocument = serde_json::from_slice(&input)?; tracing::info!("Attempting to verifiy azure attestation: {attestation_document:?}"); - let hcl_report_bytes = BASE64_URL_SAFE.decode(attestation_document.hcl_report_base64)?; + let AttestationDocument { tdx_quote_base64, hcl_report_base64, tpm_attestation } = + attestation_document; + let hcl_report_bytes = BASE64_URL_SAFE.decode(hcl_report_base64)?; let hcl_report = hcl::HclReport::new(hcl_report_bytes)?; let var_data_hash = hcl_report.var_data_sha256(); - // Check that HCL var data hash matches TDX quote report data let mut expected_tdx_input_data = [0u8; 64]; expected_tdx_input_data[..32].copy_from_slice(&var_data_hash); - // Do DCAP verification - let tdx_quote_bytes = BASE64_URL_SAFE.decode(attestation_document.tdx_quote_base64)?; - let _dcap_measurements = verify_dcap_attestation_with_given_timestamp( + let tdx_quote_bytes = BASE64_URL_SAFE.decode(tdx_quote_base64)?; + + Ok(PreparedAzureAttestation { tdx_quote_bytes, + hcl_report, + var_data_hash, expected_tdx_input_data, - pccs, - collateral, - now, - override_azure_outdated_tcb, - ) - .await?; + tpm_attestation, + }) +} +/// The final part of vTPM verification, after verifying DCAP +fn finish_azure_attestation_verification( + hcl_report: hcl::HclReport, + var_data_hash: [u8; 32], + tpm_attestation: TpmAttest, + expected_input_data: [u8; 64], + now: u64, +) -> Result { let hcl_ak_pub = hcl_report.ak_pub()?; // Get attestation key from runtime claims @@ -166,7 +275,7 @@ async fn verify_azure_attestation_with_given_timestamp( } // Verify the vTPM quote - let vtpm_quote = attestation_document.tpm_attestation.quote; + let vtpm_quote = tpm_attestation.quote; let hcl_ak_pub_der = hcl_ak_pub.key.try_to_der().map_err(|_| MaaError::JwkConversion)?; let pub_key = PKey::public_key_from_der(&hcl_ak_pub_der)?; vtpm_quote.verify(&pub_key, &expected_input_data[..32])?; @@ -174,9 +283,8 @@ async fn verify_azure_attestation_with_given_timestamp( let pcrs = vtpm_quote.pcrs_sha256(); // Parse AK certificate - let (_type_label, ak_certificate_der) = pem_rfc7468::decode_vec( - attestation_document.tpm_attestation.ak_certificate_pem.as_bytes(), - )?; + let (_type_label, ak_certificate_der) = + pem_rfc7468::decode_vec(tpm_attestation.ak_certificate_pem.as_bytes())?; let (remaining_bytes, ak_certificate) = X509Certificate::from_der(&ak_certificate_der)?; @@ -394,20 +502,32 @@ mod tests { let collateral_bytes: &'static [u8] = include_bytes!("../../test-assets/azure-collateral02.json"); - let collateral = serde_json::from_slice(collateral_bytes).unwrap(); + let async_collateral = serde_json::from_slice(collateral_bytes).unwrap(); + let sync_collateral = serde_json::from_slice(collateral_bytes).unwrap(); - let measurements = verify_azure_attestation_with_given_timestamp( + let async_measurements = verify_azure_attestation_with_given_timestamp( attestation_bytes.to_vec(), [0; 64], // Input data None, - collateral, + async_collateral, now, false, ) .await .unwrap(); - measurement_policy.check_measurement(&measurements).unwrap(); + let sync_measurements = verify_azure_attestation_with_given_timestamp_sync( + attestation_bytes.to_vec(), + [0; 64], // Input data + Pccs::new(None), + sync_collateral, + now, + false, + ) + .unwrap(); + + assert_eq!(async_measurements, sync_measurements); + measurement_policy.check_measurement(&async_measurements).unwrap(); } #[tokio::test] diff --git a/crates/attestation/src/dcap.rs b/crates/attestation/src/dcap.rs index 5389e28..d14f1cb 100644 --- a/crates/attestation/src/dcap.rs +++ b/crates/attestation/src/dcap.rs @@ -46,6 +46,54 @@ pub async fn verify_dcap_attestation( .await } +/// Verify a DCAP TDX quote, and return the measurement values +#[cfg(not(any(test, feature = "mock")))] +pub fn verify_dcap_attestation_sync( + input: Vec, + expected_input_data: [u8; 64], + pccs: Pccs, +) -> Result { + let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)?.as_secs(); + let override_azure_outdated_tcb = false; + verify_dcap_attestation_with_timestamp_sync( + input, + expected_input_data, + pccs, + None, + now, + override_azure_outdated_tcb, + ) +} + +pub fn verify_dcap_attestation_with_timestamp_sync( + input: Vec, + expected_input_data: [u8; 64], + pccs: Pccs, + collateral: Option, + now: u64, + override_azure_outdated_tcb: bool, +) -> Result { + let quote = Quote::parse(&input)?; + + let ca = quote.ca()?; + let fmspc = hex::encode_upper(quote.fmspc()?); + + let collateral = if let Some(given_collateral) = collateral { + given_collateral + } else { + pccs.get_collateral_sync(fmspc.clone(), ca, now)? + }; + + verify_dcap_attestation_with_collateral_and_timestamp( + input, + quote, + expected_input_data, + collateral, + now, + override_azure_outdated_tcb, + ) +} + /// Allows the timestamp to be given, making it possible to test with /// existing attestations /// @@ -60,11 +108,47 @@ pub async fn verify_dcap_attestation_with_given_timestamp( override_azure_outdated_tcb: bool, ) -> Result { let quote = Quote::parse(&input)?; - tracing::info!("Verifying DCAP attestation: {quote:?}"); let ca = quote.ca()?; let fmspc = hex::encode_upper(quote.fmspc()?); + let collateral = if let Some(given_collateral) = collateral { + given_collateral + } else if let Some(ref pccs) = pccs_option { + let (collateral, _is_fresh) = pccs.get_collateral(fmspc.clone(), ca, now).await?; + collateral + } else { + get_collateral_for_fmspc( + PCS_URL, + fmspc.clone(), + ca, + false, // Indicates not SGX + ) + .await? + }; + + verify_dcap_attestation_with_collateral_and_timestamp( + input, + quote, + expected_input_data, + collateral, + now, + override_azure_outdated_tcb, + ) +} + +fn verify_dcap_attestation_with_collateral_and_timestamp( + raw_quote: Vec, + quote: Quote, + expected_input_data: [u8; 64], + collateral: QuoteCollateralV3, + now: u64, + override_azure_outdated_tcb: bool, +) -> Result { + tracing::info!("Verifying DCAP attestation: {quote:?}"); + + let fmspc = hex::encode_upper(quote.fmspc()?); + // Override outdated TCB only if we are on Azure and the FMSPC is known to // be outdated let override_outdated_tcb = if override_azure_outdated_tcb { @@ -82,23 +166,8 @@ pub async fn verify_dcap_attestation_with_given_timestamp( |tcb_info: TcbInfo| tcb_info }; - let collateral = if let Some(given_collateral) = collateral { - given_collateral - } else if let Some(ref pccs) = pccs_option { - let (collateral, _is_fresh) = pccs.get_collateral(fmspc.clone(), ca, now).await?; - collateral - } else { - get_collateral_for_fmspc( - PCS_URL, - fmspc.clone(), - ca, - false, // Indicates not SGX - ) - .await? - }; - let verified_report = dcap_qvl::verify::dangerous_verify_with_tcb_override( - &input, + &raw_quote, &collateral, now, override_outdated_tcb, @@ -136,6 +205,20 @@ pub async fn verify_dcap_attestation( Ok(MultiMeasurements::from_tdx_quote("e)) } +#[cfg(any(test, feature = "mock"))] +pub fn verify_dcap_attestation_sync( + input: Vec, + expected_input_data: [u8; 64], + _pccs: Pccs, +) -> Result { + // In tests we use mock quotes which will fail to verify + let quote = tdx_quote::Quote::from_bytes(&input)?; + if quote.report_input_data() != expected_input_data { + return Err(DcapVerificationError::InputMismatch); + } + Ok(MultiMeasurements::from_tdx_quote("e)) +} + /// Create a mock quote for testing on non-confidential hardware #[cfg(any(test, feature = "mock"))] fn generate_quote(input: [u8; 64]) -> Result, QuoteGenerationError> { diff --git a/crates/attestation/src/lib.rs b/crates/attestation/src/lib.rs index f472b02..f7cb5dd 100644 --- a/crates/attestation/src/lib.rs +++ b/crates/attestation/src/lib.rs @@ -348,6 +348,63 @@ impl AttestationVerifier { Ok(Some(measurements)) } + pub fn verify_attestation_sync( + &self, + attestation_exchange_message: AttestationExchangeMessage, + expected_input_data: [u8; 64], + ) -> Result, AttestationError> { + let attestation_type = attestation_exchange_message.attestation_type; + tracing::debug!("Verifing {attestation_type} attestation"); + + // TODO do fire-and-forget logging + // if self.log_dcap_quote { + // log_attestation(&attestation_exchange_message).await; + // } + + let measurements = match attestation_type { + AttestationType::None => { + if self.has_remote_attestion() { + return Err(AttestationError::AttestationTypeNotAccepted); + } + if attestation_exchange_message.attestation.is_empty() { + return Ok(None); + } else { + return Err(AttestationError::AttestationGivenWhenNoneExpected); + } + } + AttestationType::AzureTdx => { + #[cfg(feature = "azure")] + { + let pccs = self.internal_pccs.clone().ok_or(AttestationError::NoPccs)?; + azure::verify_azure_attestation_sync( + attestation_exchange_message.attestation, + expected_input_data, + pccs, + self.override_azure_outdated_tcb, + )? + } + #[cfg(not(feature = "azure"))] + { + return Err(AttestationError::AttestationTypeNotSupported); + } + } + _ => { + let pccs = self.internal_pccs.clone().ok_or(AttestationError::NoPccs)?; + dcap::verify_dcap_attestation_sync( + attestation_exchange_message.attestation, + expected_input_data, + pccs, + )? + } + }; + + // Do a measurement / attestation type policy check + self.measurement_policy.check_measurement(&measurements)?; + + tracing::debug!("Verification successful"); + Ok(Some(measurements)) + } + /// Whether we allow no remote attestation pub fn has_remote_attestion(&self) -> bool { self.measurement_policy.has_remote_attestion() @@ -472,6 +529,8 @@ pub enum AttestationError { SerdeJson(#[from] serde_json::Error), #[error("HTTP client: {0}")] Reqwest(#[from] reqwest::Error), + #[error("Sync verification requested but no PCCS configured")] + NoPccs, } #[cfg(test)] From 0e2a286a7e5f3556b14a4ca917eb67d9d3353f9e Mon Sep 17 00:00:00 2001 From: peg Date: Mon, 16 Mar 2026 11:56:48 +0100 Subject: [PATCH 03/10] Add sync methods for attestation verifier --- crates/attestation/src/azure/mod.rs | 2 +- crates/attestation/src/dcap.rs | 25 ++++++++-- crates/pccs/src/lib.rs | 73 +++++++++++++++++++---------- 3 files changed, 70 insertions(+), 30 deletions(-) diff --git a/crates/attestation/src/azure/mod.rs b/crates/attestation/src/azure/mod.rs index 1821ad4..09206c5 100644 --- a/crates/attestation/src/azure/mod.rs +++ b/crates/attestation/src/azure/mod.rs @@ -519,7 +519,7 @@ mod tests { let sync_measurements = verify_azure_attestation_with_given_timestamp_sync( attestation_bytes.to_vec(), [0; 64], // Input data - Pccs::new(None), + Pccs::new_without_prewarm(None), sync_collateral, now, false, diff --git a/crates/attestation/src/dcap.rs b/crates/attestation/src/dcap.rs index d14f1cb..a8d7641 100644 --- a/crates/attestation/src/dcap.rs +++ b/crates/attestation/src/dcap.rs @@ -301,9 +301,10 @@ mod tests { let collateral_bytes: &'static [u8] = include_bytes!("../test-assets/dcap-quote-collateral-00.json"); - let collateral = serde_json::from_slice(collateral_bytes).unwrap(); + let async_collateral = serde_json::from_slice(collateral_bytes).unwrap(); + let sync_collateral = serde_json::from_slice(collateral_bytes).unwrap(); - let measurements = verify_dcap_attestation_with_given_timestamp( + let async_measurements = verify_dcap_attestation_with_given_timestamp( attestation_bytes.to_vec(), [ 116, 39, 106, 100, 143, 31, 212, 145, 244, 116, 162, 213, 44, 114, 216, 80, 227, @@ -312,14 +313,30 @@ mod tests { 173, 129, 180, 32, 247, 70, 250, 141, 176, 248, 99, 125, ], None, - Some(collateral), + Some(async_collateral), now, false, ) .await .unwrap(); - measurement_policy.check_measurement(&measurements).unwrap(); + let sync_measurements = verify_dcap_attestation_with_timestamp_sync( + attestation_bytes.to_vec(), + [ + 116, 39, 106, 100, 143, 31, 212, 145, 244, 116, 162, 213, 44, 114, 216, 80, 227, + 118, 129, 87, 180, 62, 194, 151, 169, 145, 116, 130, 189, 119, 39, 139, 161, 136, + 37, 136, 57, 29, 25, 86, 182, 246, 70, 106, 216, 184, 220, 205, 85, 245, 114, 33, + 173, 129, 180, 32, 247, 70, 250, 141, 176, 248, 99, 125, + ], + Pccs::new_without_prewarm(None), + Some(sync_collateral), + now, + false, + ) + .unwrap(); + + assert_eq!(async_measurements, sync_measurements); + measurement_policy.check_measurement(&async_measurements).unwrap(); } // This specifically tests a quote which has outdated TCB level from Azure diff --git a/crates/pccs/src/lib.rs b/crates/pccs/src/lib.rs index 7b81e5a..8d90251 100644 --- a/crates/pccs/src/lib.rs +++ b/crates/pccs/src/lib.rs @@ -39,7 +39,7 @@ pub struct Pccs { /// The state of the initial pre-warm fetch prewarm_stats: Arc, /// Completion signal for startup pre-warm, shared across all clones - prewarm_outcome_tx: watch::Sender>, + prewarm_outcome_tx: Option>>, } impl std::fmt::Debug for Pccs { @@ -53,20 +53,10 @@ impl std::fmt::Debug for Pccs { impl Pccs { /// Creates a new PCCS cache using the provided URL or Intel PCS default pub fn new(pccs_url: Option) -> Self { - let pccs_url = pccs_url - .unwrap_or(PCS_URL.to_string()) - .trim_end_matches('/') - .trim_end_matches("/sgx/certification/v4") - .trim_end_matches("/tdx/certification/v4") - .to_string(); + let mut pccs = Self::new_without_prewarm(pccs_url); let (prewarm_outcome_tx, _) = watch::channel(None); - let pccs = Self { - pccs_url, - cache: RwLock::new(HashMap::new()).into(), - prewarm_stats: Arc::new(PrewarmStats::default()), - prewarm_outcome_tx, - }; + pccs.prewarm_outcome_tx = Some(prewarm_outcome_tx); // Start filling the cache right away let pccs_for_prewarm = pccs.clone(); @@ -78,19 +68,41 @@ impl Pccs { pccs } + /// Creates a new PCCS cache using the provided URL or Intel PCS default + /// and does not pre-warm by proactively fetching collateral + pub fn new_without_prewarm(pccs_url: Option) -> Self { + let pccs_url = pccs_url + .unwrap_or(PCS_URL.to_string()) + .trim_end_matches('/') + .trim_end_matches("/sgx/certification/v4") + .trim_end_matches("/tdx/certification/v4") + .to_string(); + + Self { + pccs_url, + cache: RwLock::new(HashMap::new()).into(), + prewarm_stats: Arc::new(PrewarmStats::default()), + prewarm_outcome_tx: None, + } + } + /// Resolves when cache is pre-warmed with all available collateral pub async fn ready(&self) -> Result { - let mut outcome_rx = self.prewarm_outcome_tx.subscribe(); - loop { - if let Some(outcome) = outcome_rx.borrow_and_update().clone() { - return match outcome { - PrewarmOutcome::Ready(summary) => Ok(summary), - PrewarmOutcome::Failed(message) => Err(PccsError::PrewarmFailed(message)), - }; - } - if outcome_rx.changed().await.is_err() { - return Err(PccsError::PrewarmSignalClosed); + if let Some(prewarm_outcome_tx) = &self.prewarm_outcome_tx { + let mut outcome_rx = prewarm_outcome_tx.subscribe(); + loop { + if let Some(outcome) = outcome_rx.borrow_and_update().clone() { + return match outcome { + PrewarmOutcome::Ready(summary) => Ok(summary), + PrewarmOutcome::Failed(message) => Err(PccsError::PrewarmFailed(message)), + }; + } + if outcome_rx.changed().await.is_err() { + return Err(PccsError::PrewarmSignalClosed); + } } + } else { + Err(PccsError::PrewarmDisabled) } } @@ -293,8 +305,10 @@ impl Pccs { } fn finish_prewarm(&self, outcome: PrewarmOutcome) { - self.prewarm_stats.completed.store(true, Ordering::SeqCst); - let _ = self.prewarm_outcome_tx.send(Some(outcome)); + if let Some(prewarm_outcome_tx) = &self.prewarm_outcome_tx { + self.prewarm_stats.completed.store(true, Ordering::SeqCst); + let _ = prewarm_outcome_tx.send(Some(outcome)); + } } /// Fetches available FMSPC entries from configured PCCS/PCS endpoint @@ -609,6 +623,8 @@ pub enum PccsError { PrewarmFailed(String), #[error("PCCS prewarm signal channel closed before completion")] PrewarmSignalClosed, + #[error("PCCS prewarm is disabled for this instance")] + PrewarmDisabled, #[error("Timestamp exceeds i64 range")] TimeStampExceedsI64, #[error("PCCS cache lock poisoned")] @@ -768,4 +784,11 @@ mod tests { tokio::time::timeout(Duration::from_secs(2), pccs.ready()).await.unwrap(); assert!(matches!(ready_result, Err(PccsError::PrewarmFailed(_)))); } + + #[tokio::test] + async fn test_ready_returns_error_when_prewarm_disabled() { + let pccs = Pccs::new_without_prewarm(None); + let ready_result = pccs.ready().await; + assert!(matches!(ready_result, Err(PccsError::PrewarmDisabled))); + } } From 43bfe424cdcef44cf8b8fa7362ad7df05893bb82 Mon Sep 17 00:00:00 2001 From: peg Date: Mon, 16 Mar 2026 12:06:06 +0100 Subject: [PATCH 04/10] Clippy, improve logging --- crates/attestation/src/azure/mod.rs | 2 +- crates/attestation/src/dcap.rs | 1 + crates/pccs/src/lib.rs | 36 +++++++++++++++-------------- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/crates/attestation/src/azure/mod.rs b/crates/attestation/src/azure/mod.rs index 09206c5..7c8fdd0 100644 --- a/crates/attestation/src/azure/mod.rs +++ b/crates/attestation/src/azure/mod.rs @@ -462,7 +462,7 @@ mod tests { let hcl_report = hcl::HclReport::new(hcl_bytes.to_vec()).unwrap(); let hcl_var_data = hcl_report.var_data(); - let var_data_values: serde_json::Value = serde_json::from_slice(&hcl_var_data).unwrap(); + let var_data_values: serde_json::Value = serde_json::from_slice(hcl_var_data).unwrap(); // Check that it contains 64 byte user data assert_eq!(hex::decode(var_data_values["user-data"].as_str().unwrap()).unwrap().len(), 64); diff --git a/crates/attestation/src/dcap.rs b/crates/attestation/src/dcap.rs index a8d7641..0df6a0a 100644 --- a/crates/attestation/src/dcap.rs +++ b/crates/attestation/src/dcap.rs @@ -192,6 +192,7 @@ fn verify_dcap_attestation_with_collateral_and_timestamp( } #[cfg(any(test, feature = "mock"))] +#[allow(clippy::unused_async)] pub async fn verify_dcap_attestation( input: Vec, expected_input_data: [u8; 64], diff --git a/crates/pccs/src/lib.rs b/crates/pccs/src/lib.rs index 8d90251..e43affb 100644 --- a/crates/pccs/src/lib.rs +++ b/crates/pccs/src/lib.rs @@ -137,15 +137,16 @@ impl Pccs { let collateral = fetch_collateral(&self.pccs_url, fmspc.clone(), ca).await?; let next_update = extract_next_update(&collateral, now)?; - let mut cache = self.cache.write().map_err(|_| PccsError::CachePoisoned)?; - if let Some(existing) = cache.get(&cache_key) && - now < existing.next_update { - return Ok((existing.collateral.clone(), false)); - } + let mut cache = self.cache.write().map_err(|_| PccsError::CachePoisoned)?; + if let Some(existing) = cache.get(&cache_key) && + now < existing.next_update + { + return Ok((existing.collateral.clone(), false)); + } - upsert_cache_entry(&mut cache, cache_key.clone(), collateral.clone(), next_update); - drop(cache); + upsert_cache_entry(&mut cache, cache_key.clone(), collateral.clone(), next_update); + } self.ensure_refresh_task(&cache_key).await; Ok((collateral, true)) } @@ -160,7 +161,7 @@ impl Pccs { let cache_key = PccsInput::new(fmspc.clone(), ca); let cache = self.cache.read().map_err(|_| PccsError::CachePoisoned)?; if let Some(entry) = cache.get(&cache_key) { - if now < entry.next_update { + if now >= entry.next_update { tracing::warn!( fmspc, next_update = entry.next_update, @@ -740,16 +741,17 @@ mod tests { assert_eq!(summary.successes, 2); assert_eq!(summary.failures, 0); - let cache_guard = pccs.cache.read().unwrap(); - let total_entries = cache_guard.len(); + let (total_entries, fmspc, ca) = { + let cache_guard = pccs.cache.read().unwrap(); + let total_entries = cache_guard.len(); + let (fmspc, ca) = cache_guard + .keys() + .next() + .map(|k| (k.fmspc.clone(), k.ca.clone())) + .expect("expected startup pre-provision to populate PCCS cache"); + (total_entries, fmspc, ca) + }; assert_eq!(total_entries, 2, "expected startup pre-provision to cache processor+platform"); - - let (fmspc, ca) = cache_guard - .keys() - .next() - .map(|k| (k.fmspc.clone(), k.ca.clone())) - .expect("expected startup pre-provision to populate PCCS cache"); - drop(cache_guard); let ca_static = ca_as_static(&ca).expect("unexpected CA value in warmed cache entry"); let now = unix_now().unwrap(); let (_, is_fresh) = pccs.get_collateral(fmspc, ca_static, now as u64).await.unwrap(); From 8f4869edd7dad76800e669a0a2de3905170862ef Mon Sep 17 00:00:00 2001 From: peg Date: Mon, 16 Mar 2026 12:25:33 +0100 Subject: [PATCH 05/10] Improve quote logging by making it fire-and-forget to not slow things down --- crates/attestation/src/lib.rs | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/crates/attestation/src/lib.rs b/crates/attestation/src/lib.rs index f7cb5dd..dc2ccf4 100644 --- a/crates/attestation/src/lib.rs +++ b/crates/attestation/src/lib.rs @@ -301,7 +301,7 @@ impl AttestationVerifier { tracing::debug!("Verifing {attestation_type} attestation"); if self.log_dcap_quote { - log_attestation(&attestation_exchange_message).await; + log_attestation(&attestation_exchange_message); } let measurements = match attestation_type { @@ -356,10 +356,9 @@ impl AttestationVerifier { let attestation_type = attestation_exchange_message.attestation_type; tracing::debug!("Verifing {attestation_type} attestation"); - // TODO do fire-and-forget logging - // if self.log_dcap_quote { - // log_attestation(&attestation_exchange_message).await; - // } + if self.log_dcap_quote { + log_attestation(&attestation_exchange_message); + } let measurements = match attestation_type { AttestationType::None => { @@ -412,14 +411,25 @@ impl AttestationVerifier { } /// Write attestation data to a log file -async fn log_attestation(attestation: &AttestationExchangeMessage) { +fn log_attestation(attestation: &AttestationExchangeMessage) { if attestation.attestation_type != AttestationType::None { let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards").as_nanos(); let filename = format!("quotes/{}-{}", attestation.attestation_type, timestamp); - if let Err(err) = tokio::fs::write(&filename, attestation.attestation.clone()).await { - tracing::warn!("Failed to write {filename}: {err}"); + let attestation_bytes = attestation.attestation.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 { + tracing::warn!("Failed to write {filename}: {err}"); + } + }); + } else { + std::thread::spawn(move || { + if let Err(err) = std::fs::write(&filename, attestation_bytes) { + tracing::warn!("Failed to write {filename}: {err}"); + } + }); } } } From d6e68bc086047e9ab69b4e8a36e8e4ae78e3276b Mon Sep 17 00:00:00 2001 From: peg Date: Mon, 16 Mar 2026 12:55:32 +0100 Subject: [PATCH 06/10] Add re-freshing following cache misses --- crates/pccs/src/lib.rs | 144 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 140 insertions(+), 4 deletions(-) diff --git a/crates/pccs/src/lib.rs b/crates/pccs/src/lib.rs index e43affb..7aa9d8f 100644 --- a/crates/pccs/src/lib.rs +++ b/crates/pccs/src/lib.rs @@ -1,5 +1,5 @@ use std::{ - collections::HashMap, + collections::{HashMap, HashSet}, sync::{ Arc, RwLock, @@ -36,6 +36,8 @@ pub struct Pccs { pccs_url: String, /// The internal cache cache: Arc>>, + /// Dedupes one-shot background refreshes for cache misses + pending_refreshes: Arc>>, /// The state of the initial pre-warm fetch prewarm_stats: Arc, /// Completion signal for startup pre-warm, shared across all clones @@ -81,6 +83,7 @@ impl Pccs { Self { pccs_url, cache: RwLock::new(HashMap::new()).into(), + pending_refreshes: RwLock::new(HashSet::new()).into(), prewarm_stats: Arc::new(PrewarmStats::default()), prewarm_outcome_tx: None, } @@ -162,15 +165,27 @@ impl Pccs { let cache = self.cache.read().map_err(|_| PccsError::CachePoisoned)?; if let Some(entry) = cache.get(&cache_key) { if now >= entry.next_update { + let collateral = entry.collateral.clone(); tracing::warn!( fmspc, next_update = entry.next_update, now, "Cached collateral expired" ); + drop(cache); + + // Start a background task to renew + let pccs = self.clone(); + tokio::spawn(async move { + pccs.ensure_refresh_task(&cache_key).await; + }); + + return Ok(collateral); } Ok(entry.collateral.clone()) } else { + drop(cache); + self.spawn_background_refresh_for_cache_miss(cache_key.clone()); Err(PccsError::NoCollateralForFmspc(format!("{cache_key:?}"))) } } @@ -181,9 +196,9 @@ impl Pccs { &self, fmspc: String, ca: &'static str, - now: i64, ) -> Result { let collateral = fetch_collateral(&self.pccs_url, fmspc.clone(), ca).await?; + let now = unix_now()?; let next_update = extract_next_update(&collateral, now)?; let cache_key = PccsInput::new(fmspc, ca); @@ -217,6 +232,46 @@ impl Pccs { })); } + /// Starts a one-shot background fetch to populate a missing cache entry + fn spawn_background_refresh_for_cache_miss(&self, cache_key: PccsInput) { + { + let Ok(mut pending_refreshes) = self.pending_refreshes.write() else { + tracing::warn!("PCCS pending-refresh lock poisoned, cannot start sync refresh"); + return; + }; + if !pending_refreshes.insert(cache_key.clone()) { + return; + } + } + + let pccs = self.clone(); + tokio::spawn(async move { + let result = pccs + .refresh_collateral( + cache_key.fmspc.clone(), + ca_as_static(&cache_key.ca).expect("unsupported CA in pending refresh"), + ) + .await; + + if let Err(err) = result { + tracing::warn!( + fmspc = cache_key.fmspc, + ca = cache_key.ca, + error = %err, + "Sync-triggered PCCS cache repair failed" + ); + } + + // Always clear the dedupe marker so a later sync miss can + // retry if this repair attempt failed. + if let Ok(mut pending_refreshes) = pccs.pending_refreshes.write() { + pending_refreshes.remove(&cache_key); + } else { + tracing::warn!("PCCS pending-refresh lock poisoned during cleanup"); + } + }); + } + /// Pre-provisions TDX collateral for discovered FMSPC values to reduce /// hot-path fetches async fn startup_prewarm_all_tdx(&self) -> PrewarmOutcome { @@ -252,8 +307,7 @@ impl Pccs { let fmspc = entry.fmspc.clone(); join_set.spawn(async move { let _permit = permit; - let now = unix_now()?; - let result = pccs.refresh_collateral(fmspc.clone(), ca, now).await; + let result = pccs.refresh_collateral(fmspc.clone(), ca).await; Ok::<(String, &'static str, Result<(), PccsError>), PccsError>(( fmspc, ca, @@ -793,4 +847,86 @@ mod tests { let ready_result = pccs.ready().await; assert!(matches!(ready_result, Err(PccsError::PrewarmDisabled))); } + + #[tokio::test] + async fn test_get_collateral_sync_repairs_cache_miss_in_background() { + let mock = spawn_mock_pcs_server(MockPcsConfig { + fmspc: "00806F050000".to_string(), + include_fmspcs_listing: false, + tcb_next_update: "2999-01-01T00:00:00Z".to_string(), + qe_next_update: "2999-01-01T00:00:00Z".to_string(), + refreshed_tcb_next_update: None, + refreshed_qe_next_update: None, + }) + .await; + + let pccs = Pccs::new_without_prewarm(Some(mock.base_url.clone())); + let now = unix_now().unwrap() as u64; + + let err = pccs.get_collateral_sync("00806F050000".to_string(), "processor", now); + assert!(matches!(err, Err(PccsError::NoCollateralForFmspc(_)))); + + for _ in 0..50 { + if pccs.get_collateral_sync("00806F050000".to_string(), "processor", now).is_ok() { + break; + } + tokio::time::sleep(Duration::from_millis(20)).await; + } + + let collateral = pccs.get_collateral_sync("00806F050000".to_string(), "processor", now); + assert!(collateral.is_ok(), "expected sync miss repair to populate cache"); + assert_eq!(mock.tcb_call_count(), 1); + assert_eq!(mock.qe_call_count(), 1); + } + + #[tokio::test] + async fn test_get_collateral_sync_repairs_expired_cache_entry_in_background() { + let initial_now = unix_now().unwrap(); + let initial_next_update = + OffsetDateTime::from_unix_timestamp(initial_now + 1).unwrap().format(&Rfc3339).unwrap(); + let refreshed_next_update = OffsetDateTime::from_unix_timestamp(initial_now + 3600) + .unwrap() + .format(&Rfc3339) + .unwrap(); + + let mock = spawn_mock_pcs_server(MockPcsConfig { + fmspc: "00806F050000".to_string(), + include_fmspcs_listing: false, + tcb_next_update: initial_next_update.clone(), + qe_next_update: initial_next_update, + refreshed_tcb_next_update: Some(refreshed_next_update.clone()), + refreshed_qe_next_update: Some(refreshed_next_update), + }) + .await; + + let pccs = Pccs::new_without_prewarm(Some(mock.base_url.clone())); + let (_, is_fresh) = pccs + .get_collateral("00806F050000".to_string(), "processor", initial_now as u64) + .await + .unwrap(); + assert!(is_fresh); + + { + let mut cache = pccs.cache.write().unwrap(); + let entry = cache + .get_mut(&PccsInput::new("00806F050000".to_string(), "processor")) + .expect("expected cached collateral entry"); + entry.next_update = initial_now - 1; + entry.refresh_task = None; + } + + let stale_collateral = + pccs.get_collateral_sync("00806F050000".to_string(), "processor", initial_now as u64); + assert!(stale_collateral.is_ok(), "expected stale collateral to be returned"); + + for _ in 0..50 { + if mock.tcb_call_count() >= 2 && mock.qe_call_count() >= 2 { + break; + } + tokio::time::sleep(Duration::from_millis(20)).await; + } + + assert!(mock.tcb_call_count() >= 2, "expected background refresh after sync expired hit"); + assert!(mock.qe_call_count() >= 2, "expected background refresh after sync expired hit"); + } } From ee8a6afe8fc9b8a022177ce38068dcbe08e75c89 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 18 Mar 2026 09:44:31 +0100 Subject: [PATCH 07/10] Allow verifier sync fn to be used with mock attestations --- crates/attestation/src/lib.rs | 20 +++++++++++++++++++- crates/pccs/src/lib.rs | 8 ++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/crates/attestation/src/lib.rs b/crates/attestation/src/lib.rs index dc2ccf4..07703bf 100644 --- a/crates/attestation/src/lib.rs +++ b/crates/attestation/src/lib.rs @@ -286,7 +286,7 @@ impl AttestationVerifier { measurement_policy: MeasurementPolicy::mock(), log_dcap_quote: false, override_azure_outdated_tcb: false, - internal_pccs: None, + internal_pccs: Some(Pccs::new_without_prewarm(None)), } } @@ -389,6 +389,7 @@ impl AttestationVerifier { } _ => { let pccs = self.internal_pccs.clone().ok_or(AttestationError::NoPccs)?; + dcap::verify_dcap_attestation_sync( attestation_exchange_message.attestation, expected_input_data, @@ -620,4 +621,21 @@ mod tests { assert_eq!(wrapped.attestation_type, AttestationType::DcapTdx); assert_eq!(wrapped.attestation, vec![9, 8]); } + + #[test] + fn mock_verifier_supports_sync_verification() { + let input_data = [7u8; 64]; + let attestation = dcap::create_dcap_attestation(input_data).unwrap(); + let verifier = AttestationVerifier::mock(); + + let result = verifier.verify_attestation_sync( + AttestationExchangeMessage { + attestation_type: AttestationType::DcapTdx, + attestation, + }, + input_data, + ); + + assert!(result.is_ok(), "expected sync mock verification to succeed: {result:?}"); + } } diff --git a/crates/pccs/src/lib.rs b/crates/pccs/src/lib.rs index 7aa9d8f..1b23d8a 100644 --- a/crates/pccs/src/lib.rs +++ b/crates/pccs/src/lib.rs @@ -154,6 +154,14 @@ impl Pccs { Ok((collateral, true)) } + /// A synchronous method to get collateral from the cache. + /// + /// If the requested collateral is not present in the cache, this will + /// return an error rather than waiting to fetch it. But it does + /// begin fetching it in a background task. + /// + /// If the collateral is out of date, this will log a warning and return + /// it anyway on a best-effort basis. pub fn get_collateral_sync( &self, fmspc: String, From 7ca10990af34451e186566357e7e9c5678ac5a09 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 18 Mar 2026 09:46:06 +0100 Subject: [PATCH 08/10] Fmt --- crates/attestation/src/lib.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/attestation/src/lib.rs b/crates/attestation/src/lib.rs index 07703bf..f9ff877 100644 --- a/crates/attestation/src/lib.rs +++ b/crates/attestation/src/lib.rs @@ -629,10 +629,7 @@ mod tests { let verifier = AttestationVerifier::mock(); let result = verifier.verify_attestation_sync( - AttestationExchangeMessage { - attestation_type: AttestationType::DcapTdx, - attestation, - }, + AttestationExchangeMessage { attestation_type: AttestationType::DcapTdx, attestation }, input_data, ); From 49de272ec72ee8b91b4452ada0944cd569819dad Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 18 Mar 2026 10:27:10 +0100 Subject: [PATCH 09/10] Add method to check that AttestationVerifier is ready --- crates/attestation/src/lib.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/crates/attestation/src/lib.rs b/crates/attestation/src/lib.rs index f9ff877..e6c8297 100644 --- a/crates/attestation/src/lib.rs +++ b/crates/attestation/src/lib.rs @@ -13,7 +13,7 @@ use std::{ use measurements::MultiMeasurements; use parity_scale_codec::{Decode, Encode}; -use pccs::Pccs; +use pccs::{Pccs, PccsError}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -290,6 +290,25 @@ impl AttestationVerifier { } } + /// Resolves once the internal PCCS cache is ready to verify + /// attestations + /// + /// Calling this is optional - it is only really needed when you want to + /// gaurantee that collateral will not be fetched during + /// verification + pub async fn ready(&self) -> Result<(), AttestationError> { + // If we have no PCCS then we are ready + let Some(pccs) = &self.internal_pccs else { + return Ok(()); + }; + + // If we have pccs, and pre-warm is disabled we are also ready + match pccs.ready().await { + Ok(_) | Err(PccsError::PrewarmDisabled) => Ok(()), + Err(err) => Err(err.into()), + } + } + /// Verify an attestation, and ensure the measurements match one of our /// accepted measurements pub async fn verify_attestation( @@ -540,6 +559,8 @@ pub enum AttestationError { SerdeJson(#[from] serde_json::Error), #[error("HTTP client: {0}")] Reqwest(#[from] reqwest::Error), + #[error("PCCS: {0}")] + Pccs(#[from] PccsError), #[error("Sync verification requested but no PCCS configured")] NoPccs, } From 8ee899068abff11fbe508a5a39242d164dff9136 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 18 Mar 2026 10:35:19 +0100 Subject: [PATCH 10/10] Doccomments --- crates/attestation/src/azure/mod.rs | 7 ++++++- crates/attestation/src/dcap.rs | 13 ++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/crates/attestation/src/azure/mod.rs b/crates/attestation/src/azure/mod.rs index 7c8fdd0..228b7c7 100644 --- a/crates/attestation/src/azure/mod.rs +++ b/crates/attestation/src/azure/mod.rs @@ -116,7 +116,11 @@ pub async fn verify_azure_attestation( .await } -/// Verify a TDX attestation from Azure +/// Verify a TDX attestation from Azure - synchronous version +/// +/// This relies on having DCAP collateral already present in the cache +/// +/// If possible, prefer the async version pub fn verify_azure_attestation_sync( input: Vec, expected_input_data: [u8; 64], @@ -176,6 +180,7 @@ async fn verify_azure_attestation_with_given_timestamp( ) } +/// Synchronous version of the verifier fn verify_azure_attestation_with_given_timestamp_sync( input: Vec, expected_input_data: [u8; 64], diff --git a/crates/attestation/src/dcap.rs b/crates/attestation/src/dcap.rs index 0df6a0a..b649649 100644 --- a/crates/attestation/src/dcap.rs +++ b/crates/attestation/src/dcap.rs @@ -46,7 +46,12 @@ pub async fn verify_dcap_attestation( .await } -/// Verify a DCAP TDX quote, and return the measurement values +/// Synchonous version - Verify a DCAP TDX quote, and return the measurement +/// values +/// +/// This relies on having DCAP collateral already present in the cache +/// +/// If possible, prefer the async version #[cfg(not(any(test, feature = "mock")))] pub fn verify_dcap_attestation_sync( input: Vec, @@ -65,6 +70,12 @@ pub fn verify_dcap_attestation_sync( ) } +/// Verify a DCAP TDX quote, and return the measurement values, providing a +/// timestamp an optional pre-fetched collateral +/// +/// This relies on having DCAP collateral already present in the cache +/// +/// If possible, prefer the async version pub fn verify_dcap_attestation_with_timestamp_sync( input: Vec, expected_input_data: [u8; 64],