Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 43 additions & 13 deletions crates/attestation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,39 @@ This crate provides:

## Runtime Requirements

Verification uses the [`pccs`](../pccs) crate for collateral caching and
background refresh. As a result, constructing an `AttestationVerifier` with
PCCS enabled and calling verification APIs is expected to happen from within a
Tokio runtime and might panic if called outside of one.

Note that although some of the verification API methods are synchronous (for
example `verify_attestation_sync`), still their functionality depends on
Tokio-backed background tasks such as PCCS pre-warm and cache refresh.
Verification uses the [`pccs`](../pccs) crate to fetch DCAP collateral and,
depending on the selected mode, cache and refresh it. Asynchronous
verification requires a Tokio runtime. Constructing an `AttestationVerifier`
in `Prewarmed` mode also requires an active runtime because pre-warming starts
immediately; constructing it in `Remote` or `Lazy` mode does not itself spawn
a task.

Synchronous verification requires a cached mode (`Lazy` or `Prewarmed`) with
the required collateral already cached. Cache misses and expired entries may
start Tokio-backed background refresh tasks. `Remote` mode cannot be used for
synchronous verification because fetching collateral requires asynchronous
I/O.

## DCAP collateral modes

Every `AttestationVerifier` has a PCCS collateral source configured through
`AttestationVerifierBuilder::with_pccs_mode`. The default is
`PccsMode::Remote`.

- `Remote` keeps no internal cache and fetches collateral from the configured
endpoint for every asynchronous verification.
- `Lazy` starts with an empty internal cache and fetches collateral on demand.
- `Prewarmed` immediately starts discovering and caching available TDX
collateral, then refreshes cached entries before expiry.

Use `with_pccs_url` to select an Intel PCS or PCCS-compatible endpoint. Without
an explicit URL, the endpoint defaults to Intel PCS.

`AttestationVerifier::ready()` waits for initial work only in `Prewarmed`
mode. It returns immediately for `Remote` and `Lazy`. A successful return in
`Remote` or `Lazy` does not mean later verification will avoid fetching
collateral. In `Prewarmed` mode it means pre-warm bootstrap completed, but
individual collateral fetches can still have failed.

## Feature flags

Expand Down Expand Up @@ -64,6 +89,10 @@ must be explicitly enabled via the `override_azure_outdated_tcb` flag on
Enables mock quote support via the local `mock-tdx` crate for tests and
development on non-TDX hardware.

In mock builds, `Remote` mode uses embedded mock collateral rather than making
an external request. Cached modes can be pointed at a local mock PCCS when
testing cache behavior.

Do not use in production. Disabled by default.

## Attestation Types
Expand All @@ -90,11 +119,12 @@ attempted.
Alternatively, an external 'attestation provider service' URL can be provided
which outsources the attestation generation to another process.

When verifying DCAP attestations, the Intel PCS is used to retrieve collateral
unless a PCCS URL is provided via a command line argument. If outdated TCB is
used, the quote will fail to verify. For special cases where outdated TCB
should be allowed, a custom override function can be passed when verifying which
may modify collateral before it is validated against the TCB.
When verifying DCAP attestations, collateral is retrieved according to the
configured PCCS mode. The endpoint defaults to Intel PCS unless a PCCS URL is
provided through the verifier builder. If outdated TCB is used, the quote will
fail to verify. For special cases where outdated TCB should be allowed, a
custom override function can be passed when verifying which may modify
collateral before it is validated against the TCB.

## Measurements File

Expand Down
2 changes: 1 addition & 1 deletion crates/attestation/src/azure/attester/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,9 +286,9 @@ fn fetch_certificate_der(url: &str) -> Result<Vec<u8>, MaaError> {
#[cfg(test)]
mod test_utils {
use base64::{Engine as _, engine::general_purpose::URL_SAFE as BASE64_URL_SAFE};
use pccs::PCS_URL;

use super::{super::AttestationDocument, create_azure_attestation};
use crate::dcap::PCS_URL;

/// Capture a complete Azure TDX attestation fixture from inside an
/// Azure TDX CVM.
Expand Down
28 changes: 19 additions & 9 deletions crates/attestation/src/azure/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ struct PreparedAzureAttestation {
pub async fn verify_azure_attestation(
input: Vec<u8>,
expected_input_data: [u8; 64],
pccs: Option<Pccs>,
pccs: Pccs,
override_azure_outdated_tcb: bool,
) -> Result<MultiMeasurements, MaaError> {
let now = unix_time_now_secs()?;
Expand All @@ -61,6 +61,9 @@ pub async fn verify_azure_attestation(
///
/// This relies on having DCAP collateral already present in the cache
///
/// [`PccsMode::Remote`](pccs::PccsMode::Remote) is not supported because
/// fetching collateral requires asynchronous I/O.
///
/// If possible, prefer the async version
pub fn verify_azure_attestation_sync(
input: Vec<u8>,
Expand All @@ -86,7 +89,7 @@ pub fn verify_azure_attestation_sync(
async fn verify_azure_attestation_with_given_timestamp(
input: Vec<u8>,
expected_input_data: [u8; 64],
pccs: Option<Pccs>,
pccs: Pccs,
collateral: Option<QuoteCollateralV3>,
now: u64,
override_azure_outdated_tcb: bool,
Expand Down Expand Up @@ -385,13 +388,20 @@ mod tests {
let actual = MAX_AZURE_ATTESTATION_PAYLOAD_SIZE + 1;
let input = vec![b'{'; actual];

let err = verify_azure_attestation(input.clone(), [0; 64], None, false).await.unwrap_err();
let err = verify_azure_attestation(
input.clone(),
[0; 64],
Pccs::new(None, pccs::PccsMode::Remote),
false,
)
.await
.unwrap_err();
assert_payload_too_large(err, actual);

let err = verify_azure_attestation_sync(
input.clone(),
[0; 64],
Pccs::new_without_prewarm(None),
Pccs::new(None, pccs::PccsMode::Lazy),
false,
)
.unwrap_err();
Expand All @@ -400,7 +410,7 @@ mod tests {
let err = verify_azure_attestation_with_given_timestamp(
input.clone(),
[0; 64],
None,
Pccs::new(None, pccs::PccsMode::Remote),
None,
0,
false,
Expand All @@ -412,7 +422,7 @@ mod tests {
let err = verify_azure_attestation_with_given_timestamp_sync(
input,
[0; 64],
Pccs::new_without_prewarm(None),
Pccs::new(None, pccs::PccsMode::Lazy),
None,
0,
false,
Expand Down Expand Up @@ -462,7 +472,7 @@ mod tests {
let async_measurements = verify_azure_attestation_with_given_timestamp(
attestation_json.clone(),
[0; 64],
None,
Pccs::new(None, pccs::PccsMode::Remote),
Some(async_collateral),
now,
false,
Expand All @@ -473,7 +483,7 @@ mod tests {
let sync_measurements = verify_azure_attestation_with_given_timestamp_sync(
attestation_json,
[0; 64],
Pccs::new_without_prewarm(None),
Pccs::new(None, pccs::PccsMode::Lazy),
Some(sync_collateral),
now,
false,
Expand Down Expand Up @@ -503,7 +513,7 @@ mod tests {
let err = verify_azure_attestation_with_given_timestamp(
attestation_json,
expected_input_data,
None,
Pccs::new(None, pccs::PccsMode::Remote),
Some(collateral),
now,
false,
Expand Down
49 changes: 28 additions & 21 deletions crates/attestation/src/dcap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
//! verification
use dcap_qvl::{
QuoteCollateralV3,
collateral::CollateralClient,
intel::{quote_ca, quote_fmspc},
quote::{Quote, Report},
tcb_info::TcbInfo,
};
#[cfg(any(test, feature = "mock"))]
use mock_tdx::generate_mock_tdx_quote;
#[cfg(test)]
use pccs::PccsMode;
use pccs::{Pccs, PccsError};
use thiserror::Error;

Expand All @@ -18,9 +19,6 @@ use crate::{AttestationError, measurements::MultiMeasurements};
/// or other platforms)
const AZURE_BAD_FMSPC: &str = "90C06F000000";

/// For fetching collateral directly from Intel, if no PCCS is specified
pub const PCS_URL: &str = "https://api.trustedservices.intel.com";

/// Generate a TDX quote
pub fn create_dcap_attestation(input_data: [u8; 64]) -> Result<Vec<u8>, AttestationError> {
let quote = generate_quote(input_data)?;
Expand All @@ -33,7 +31,7 @@ pub fn create_dcap_attestation(input_data: [u8; 64]) -> Result<Vec<u8>, Attestat
pub async fn verify_dcap_attestation(
input: Vec<u8>,
expected_input_data: [u8; 64],
pccs: Option<Pccs>,
pccs: Pccs,
) -> Result<(MultiMeasurements, Quote), DcapVerificationError> {
let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)?.as_secs();
let override_azure_outdated_tcb = false;
Expand All @@ -53,6 +51,9 @@ pub async fn verify_dcap_attestation(
///
/// This relies on having DCAP collateral already present in the cache
///
/// [`PccsMode::Remote`](pccs::PccsMode::Remote) is not supported because
/// fetching collateral requires asynchronous I/O.
///
/// If possible, prefer the async version
#[cfg(not(any(test, feature = "mock")))]
pub fn verify_dcap_attestation_sync(
Expand All @@ -77,6 +78,9 @@ pub fn verify_dcap_attestation_sync(
///
/// This relies on having DCAP collateral already present in the cache
///
/// [`PccsMode::Remote`](pccs::PccsMode::Remote) is not supported unless
/// `collateral` is provided.
///
/// If possible, prefer the async version
pub fn verify_dcap_attestation_with_timestamp_sync(
input: Vec<u8>,
Expand Down Expand Up @@ -115,7 +119,7 @@ pub fn verify_dcap_attestation_with_timestamp_sync(
pub async fn verify_dcap_attestation_with_given_timestamp(
input: Vec<u8>,
expected_input_data: [u8; 64],
pccs_option: Option<Pccs>,
pccs: Pccs,
collateral: Option<QuoteCollateralV3>,
now: u64,
override_azure_outdated_tcb: bool,
Expand All @@ -127,13 +131,9 @@ pub async fn verify_dcap_attestation_with_given_timestamp(

let collateral = if let Some(given_collateral) = collateral {
given_collateral
} else if let Some(ref pccs) = pccs_option {
} else {
let (collateral, _is_fresh) = pccs.get_collateral(fmspc.clone(), ca, now).await?;
collateral
} else {
CollateralClient::with_default_http(PCS_URL)?
.fetch_for_fmspc_without_pck_chain(&fmspc, ca, false)
.await?
};

verify_dcap_attestation_with_collateral_and_timestamp(
Expand Down Expand Up @@ -205,17 +205,18 @@ fn verify_dcap_attestation_with_collateral_and_timestamp(
pub async fn verify_dcap_attestation(
input: Vec<u8>,
expected_input_data: [u8; 64],
pccs: Option<Pccs>,
pccs: Pccs,
) -> Result<(MultiMeasurements, Quote), DcapVerificationError> {
let quote = Quote::parse(&input)?;
let ca = quote_ca(&quote)?.as_id_str();
let fmspc = hex::encode_upper(quote_fmspc(&quote)?);
let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)?.as_secs();
let collateral = if let Some(ref pccs) = pccs {

let collateral = if pccs.is_remote() {
mock_tdx::mock_collateral()
} else {
let (collateral, _is_fresh) = pccs.get_collateral(fmspc, ca, now).await?;
collateral
} else {
mock_tdx::mock_collateral()
};
let verifier = mock_tdx::mock_dcap_verifier();
verifier.verify(&input, &collateral, now)?;
Expand All @@ -238,7 +239,13 @@ pub fn verify_dcap_attestation_sync(
let ca = quote_ca(&quote)?.as_id_str();
let fmspc = hex::encode_upper(quote_fmspc(&quote)?);
let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)?.as_secs();
let collateral = pccs.get_collateral_sync(fmspc, ca, now)?;

let collateral = if pccs.is_remote() {
mock_tdx::mock_collateral()
} else {
pccs.get_collateral_sync(fmspc, ca, now)?
};

let verifier = mock_tdx::mock_dcap_verifier();
verifier.verify(&input, &collateral, now)?;

Expand Down Expand Up @@ -334,7 +341,7 @@ mod tests {
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,
],
None,
Pccs::new(None, PccsMode::Remote),
Some(async_collateral),
now,
false,
Expand All @@ -350,7 +357,7 @@ mod tests {
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),
Pccs::new(None, PccsMode::Lazy),
Some(sync_collateral),
now,
false,
Expand Down Expand Up @@ -383,7 +390,7 @@ mod tests {
248, 104, 204, 187, 101, 49, 203, 40, 218, 185, 220, 228, 119, 40, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
],
None,
Pccs::new(None, PccsMode::Remote),
Some(collateral),
now,
true,
Expand All @@ -400,12 +407,12 @@ mod tests {
})
.await
.unwrap();
let pccs = Pccs::new(Some(mock_pcs.base_url.clone()));
let pccs = Pccs::new(Some(mock_pcs.base_url.clone()), PccsMode::Lazy);
let expected_input_data = [0xA5; 64];
let quote = create_dcap_attestation(expected_input_data).unwrap();

let (measurements, _) =
verify_dcap_attestation(quote, expected_input_data, Some(pccs)).await.unwrap();
verify_dcap_attestation(quote, expected_input_data, pccs).await.unwrap();

assert_eq!(measurements, crate::measurements::mock_dcap_measurements());
assert_eq!(mock_pcs.tcb_call_count(), 1);
Expand Down
2 changes: 1 addition & 1 deletion crates/attestation/src/gcp/firmware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ mod tests {
let (measurements, _) = verify_dcap_attestation_with_given_timestamp(
attestation_bytes.to_vec(),
expected_input_data,
None,
pccs::Pccs::new(None, pccs::PccsMode::Remote),
Some(collateral),
GCP_TDX_PORTABLE_FIXTURE_TIMESTAMP,
false,
Expand Down
Loading
Loading