From fed1304b608c47c67b9ee03cdf0674fda8260a93 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 24 Jun 2026 10:57:23 +0200 Subject: [PATCH] Add shared collateral client --- crates/pccs/src/lib.rs | 76 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 62 insertions(+), 14 deletions(-) diff --git a/crates/pccs/src/lib.rs b/crates/pccs/src/lib.rs index ea63b05..01da7c0 100644 --- a/crates/pccs/src/lib.rs +++ b/crates/pccs/src/lib.rs @@ -9,7 +9,13 @@ use std::{ time::{SystemTime, UNIX_EPOCH}, }; -use dcap_qvl::{QuoteCollateralV3, collateral::CollateralClient, tcb_info::TcbInfo}; +use dcap_qvl::{ + QuoteCollateralV3, + collateral::CollateralClient, + configs::DefaultConfig, + http::{HttpClient as DcapHttpClient, HttpResponse}, + tcb_info::TcbInfo, +}; use thiserror::Error; use time::{OffsetDateTime, format_description::well_known::Rfc3339}; use tokio::{ @@ -26,15 +32,23 @@ pub const PCS_URL: &str = "https://api.trustedservices.intel.com"; const REFRESH_MARGIN_SECS: i64 = 300; /// How long to wait before retrying when failing to fetch collateral const REFRESH_RETRY_SECS: u64 = 60; +/// Timeout for PCCS HTTP requests. +const PCCS_HTTP_TIMEOUT_SECS: u64 = 180; /// How many collateral fetches to perform concurrently during initial /// pre-warm const STARTUP_PREWARM_CONCURRENCY: usize = 8; +type SharedCollateralClient = CollateralClient; + /// PCCS collateral cache with proactive background refresh #[derive(Clone)] pub struct Pccs { /// The URL of the service used to fetch collateral (PCS / PCCS) url: String, + /// HTTP client used for FMSPC fetches + http_client: reqwest::Client, + /// HTTP client for collateral fetches + collateral_client: SharedCollateralClient, /// The internal cache cache: Arc>>, /// Dedupes one-shot background refreshes for cache misses @@ -80,9 +94,14 @@ impl Pccs { .trim_end_matches("/sgx/certification/v4") .trim_end_matches("/tdx/certification/v4") .to_string(); + let http_client = reqwest::Client::new(); + let collateral_client = + CollateralClient::new(SharedReqwestHttp { client: http_client.clone() }, url.clone()); Self { url, + http_client, + collateral_client, cache: RwLock::new(HashMap::new()).into(), pending_refreshes: RwLock::new(HashSet::new()).into(), prewarm_stats: Arc::new(PrewarmStats::default()), @@ -138,7 +157,7 @@ impl Pccs { } } - let collateral = fetch_collateral(&self.url, fmspc.clone(), ca).await?; + let collateral = fetch_collateral(&self.collateral_client, fmspc.clone(), ca).await?; let next_update = extract_next_update(&collateral, now)?; { @@ -207,7 +226,7 @@ impl Pccs { ca: &'static str, ) -> Result { let now = unix_now()?; - let collateral = fetch_collateral(&self.url, fmspc.clone(), ca).await?; + let collateral = fetch_collateral(&self.collateral_client, fmspc.clone(), ca).await?; let next_update = extract_next_update(&collateral, now)?; let cache_key = PccsInput::new(fmspc, ca); @@ -235,9 +254,9 @@ impl Pccs { let weak_cache = Arc::downgrade(&self.cache); let key = cache_key.clone(); - let url = self.url.clone(); + let collateral_client = self.collateral_client.clone(); entry.refresh_task = Some(tokio::spawn(async move { - refresh_loop(weak_cache, url, key).await; + refresh_loop(weak_cache, collateral_client, key).await; })); } @@ -378,8 +397,12 @@ impl Pccs { /// Fetches available FMSPC entries from configured PCCS/PCS endpoint async fn fetch_fmspcs(&self) -> Result, PccsError> { let url = format!("{}/sgx/certification/v4/fmspcs", self.url); - let client = reqwest::Client::builder().timeout(Duration::from_secs(15)).build()?; - let response = client.get(&url).send().await?; + let response = self + .http_client + .get(&url) + .timeout(Duration::from_secs(PCCS_HTTP_TIMEOUT_SECS)) + .send() + .await?; if !response.status().is_success() { return Err(PccsError::FmspcFetch(response.status())); } @@ -389,6 +412,34 @@ impl Pccs { } } +/// An HTTP client shared used by [CollateralClient] and to fetch FMSPCs +/// during pre-warm +#[derive(Clone)] +struct SharedReqwestHttp { + client: reqwest::Client, +} + +impl DcapHttpClient for SharedReqwestHttp { + async fn get(&self, url: &str) -> anyhow::Result { + let resp = self + .client + .get(url) + .timeout(Duration::from_secs(PCCS_HTTP_TIMEOUT_SECS)) + .send() + .await?; + let status = resp.status().as_u16(); + let headers = resp + .headers() + .iter() + .filter_map(|(name, value)| { + value.to_str().ok().map(|v| (name.as_str().to_string(), v.to_string())) + }) + .collect::>(); + let body = resp.bytes().await?.to_vec(); + Ok(HttpResponse { status, headers, body }) + } +} + /// Final startup pre-warm status and counters. #[derive(Clone, Debug, PartialEq, Eq)] pub struct PrewarmSummary { @@ -420,14 +471,11 @@ impl PccsInput { /// Fetches collateral from PCCS for a given FMSPC and CA async fn fetch_collateral( - url: &str, + client: &SharedCollateralClient, fmspc: String, ca: &'static str, ) -> Result { - CollateralClient::with_default_http(url)? - .fetch_for_fmspc_without_pck_chain(&fmspc, ca, false) - .await - .map_err(Into::into) + client.fetch_for_fmspc_without_pck_chain(&fmspc, ca, false).await.map_err(Into::into) } /// Extracts the earliest next update timestamp from collateral metadata @@ -530,7 +578,7 @@ fn ca_as_static(ca: &str) -> Option<&'static str> { /// Background loop that refreshes collateral for a single cache key async fn refresh_loop( weak_cache: Weak>>, - pccs_url: String, + collateral_client: SharedCollateralClient, key: PccsInput, ) { let Some(ca_static) = ca_as_static(&key.ca) else { @@ -592,7 +640,7 @@ async fn refresh_loop( continue; } - match fetch_collateral(&pccs_url, key.fmspc.clone(), ca_static).await { + match fetch_collateral(&collateral_client, key.fmspc.clone(), ca_static).await { Ok(collateral) => { let validate_now = match unix_now() { Ok(timestamp) => timestamp,