Skip to content
Draft
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 2 additions & 6 deletions attestation-provider-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,8 @@ async fn main() -> anyhow::Result<()> {
None => MeasurementPolicy::accept_anything(),
};

let attestation_verifier = AttestationVerifier {
measurement_policy,
pccs_url: None,
log_dcap_quote: cli.log_dcap_quote,
override_azure_outdated_tcb: false,
};
let attestation_verifier =
AttestationVerifier::new(measurement_policy, None, cli.log_dcap_quote, false);

let attestation_message =
attestation_provider_client(server_addr, attestation_verifier).await?;
Expand Down
3 changes: 2 additions & 1 deletion attested-tls/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ parity-scale-codec = "3.7.5"
openssl = "0.10.75"
num-bigint = "0.4.6"
webpki = { package = "rustls-webpki", version = "0.103.8" }
time = "0.3.44"
time = { version = "0.3.44", features = ["parsing", "formatting"] }
once_cell = "1.21.3"

# Used for azure vTPM attestation support
Expand All @@ -60,6 +60,7 @@ rcgen = { version = "0.14.5", optional = true }
tdx-quote = { version = "0.0.5", features = ["mock"], optional = true }

[dev-dependencies]
axum = "0.8.6"
rcgen = "0.14.5"
tempfile = "3.23.0"
tdx-quote = { version = "0.0.5", features = ["mock"] }
Expand Down
16 changes: 8 additions & 8 deletions attested-tls/src/attestation/azure/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use thiserror::Error;
use x509_parser::prelude::*;

use crate::attestation::{
dcap::verify_dcap_attestation_with_given_timestamp, measurements::MultiMeasurements,
Pccs, dcap::verify_dcap_attestation_with_given_timestamp, measurements::MultiMeasurements,
};

/// The attestation evidence payload that gets sent over the channel
Expand Down Expand Up @@ -81,7 +81,7 @@ pub async fn create_azure_attestation(input_data: [u8; 64]) -> Result<Vec<u8>, M
pub async fn verify_azure_attestation(
input: Vec<u8>,
expected_input_data: [u8; 64],
pccs_url: Option<String>,
pccs: Pccs,
override_azure_outdated_tcb: bool,
) -> Result<super::measurements::MultiMeasurements, MaaError> {
let now = std::time::SystemTime::now()
Expand All @@ -92,7 +92,7 @@ pub async fn verify_azure_attestation(
verify_azure_attestation_with_given_timestamp(
input,
expected_input_data,
pccs_url,
pccs,
None,
now,
override_azure_outdated_tcb,
Expand All @@ -105,7 +105,7 @@ pub async fn verify_azure_attestation(
async fn verify_azure_attestation_with_given_timestamp(
input: Vec<u8>,
expected_input_data: [u8; 64],
pccs_url: Option<String>,
pccs: Pccs,
collateral: Option<QuoteCollateralV3>,
now: u64,
override_azure_outdated_tcb: bool,
Expand All @@ -127,7 +127,7 @@ async fn verify_azure_attestation_with_given_timestamp(
let _dcap_measurements = verify_dcap_attestation_with_given_timestamp(
tdx_quote_bytes,
expected_tdx_input_data,
pccs_url,
pccs,
collateral,
now,
override_azure_outdated_tcb,
Expand Down Expand Up @@ -418,8 +418,8 @@ mod tests {
let measurements = verify_azure_attestation_with_given_timestamp(
attestation_bytes.to_vec(),
[0; 64], // Input data
None,
collateral,
Pccs::new(None),
Some(collateral),
now,
false,
)
Expand All @@ -445,7 +445,7 @@ mod tests {
let err = verify_azure_attestation_with_given_timestamp(
attestation_bytes.to_vec(),
expected_input_data,
None,
Pccs::new(None),
Some(collateral),
now,
false,
Expand Down
118 changes: 95 additions & 23 deletions attested-tls/src/attestation/dcap.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
//! Data Center Attestation Primitives (DCAP) evidence generation and verification
use crate::attestation::pccs::Pccs;
use crate::attestation::{AttestationError, measurements::MultiMeasurements};

use configfs_tsm::QuoteGenerationError;
use dcap_qvl::{
QuoteCollateralV3,
collateral::get_collateral_for_fmspc,
quote::{Quote, Report},
tcb_info::TcbInfo,
verify::VerifiedReport,
};
use thiserror::Error;

Expand All @@ -28,7 +29,7 @@ pub async fn create_dcap_attestation(input_data: [u8; 64]) -> Result<Vec<u8>, At
pub async fn verify_dcap_attestation(
input: Vec<u8>,
expected_input_data: [u8; 64],
pccs_url: Option<String>,
pccs: Pccs,
) -> Result<MultiMeasurements, DcapVerificationError> {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
Expand All @@ -37,7 +38,7 @@ pub async fn verify_dcap_attestation(
verify_dcap_attestation_with_given_timestamp(
input,
expected_input_data,
pccs_url,
pccs,
None,
now,
override_azure_outdated_tcb,
Expand All @@ -51,13 +52,16 @@ pub async fn verify_dcap_attestation(
pub async fn verify_dcap_attestation_with_given_timestamp(
input: Vec<u8>,
expected_input_data: [u8; 64],
pccs_url: Option<String>,
pccs: Pccs,
collateral: Option<QuoteCollateralV3>,
now: u64,
override_azure_outdated_tcb: bool,
) -> Result<MultiMeasurements, DcapVerificationError> {
let quote = Quote::parse(&input)?;
tracing::info!("Verifying DCAP attestation: {quote:?}");
let now_i64 = i64::try_from(now).map_err(|_| {
DcapVerificationError::PccsCollateralParse(format!("Timestamp {now} exceeds i64 range"))
})?;

let ca = quote.ca()?;
let fmspc = hex::encode_upper(quote.fmspc()?);
Expand All @@ -78,26 +82,56 @@ pub async fn verify_dcap_attestation_with_given_timestamp(
|tcb_info: TcbInfo| tcb_info
};

let collateral = match collateral {
Some(c) => c,
match collateral {
Some(given_collateral) => {
let verified_report = dcap_qvl::verify::verify_with_tcb_override(
&input,
&given_collateral,
now,
override_outdated_tcb,
)?;
warn_if_non_uptodate(&verified_report, &fmspc, CollateralSource::Provided);
}
None => {
get_collateral_for_fmspc(
&pccs_url.clone().unwrap_or(PCS_URL.to_string()),
fmspc,
ca,
false, // Indicates not SGX
)
.await?
let (collateral, is_fresh) = pccs.get_collateral(fmspc.clone(), ca, now_i64).await?;
let initial_source = if is_fresh {
CollateralSource::Fresh
} else {
CollateralSource::Cached
};
let initial_verification = dcap_qvl::verify::verify_with_tcb_override(
&input,
&collateral,
now,
override_outdated_tcb,
);

match initial_verification {
Ok(verified_report) => {
warn_if_non_uptodate(&verified_report, &fmspc, initial_source);
}
Err(e) => {
if is_fresh {
return Err(e.into());
}
tracing::warn!("Verification failed - trying with fresh collateral: {e}");
let collateral = pccs.refresh_collateral(fmspc.clone(), ca, now_i64).await?;
let verified_report = dcap_qvl::verify::verify_with_tcb_override(
&input,
&collateral,
now,
override_outdated_tcb,
)?;
warn_if_non_uptodate(
&verified_report,
&fmspc,
CollateralSource::RefreshedAfterFailure,
);
}
}
}
};

let _verified_report = dcap_qvl::verify::verify_with_tcb_override(
&input,
&collateral,
now,
override_outdated_tcb,
)?;

let measurements = MultiMeasurements::from_dcap_qvl_quote(&quote)?;

if get_quote_input_data(quote.report) != expected_input_data {
Expand All @@ -111,7 +145,7 @@ pub async fn verify_dcap_attestation_with_given_timestamp(
pub async fn verify_dcap_attestation(
input: Vec<u8>,
expected_input_data: [u8; 64],
_pccs_url: Option<String>,
_pccs: Pccs,
) -> Result<MultiMeasurements, DcapVerificationError> {
// In tests we use mock quotes which will fail to verify
let quote = tdx_quote::Quote::from_bytes(&input)?;
Expand Down Expand Up @@ -161,11 +195,49 @@ pub enum DcapVerificationError {
SystemTime(#[from] std::time::SystemTimeError),
#[error("DCAP quote verification: {0}")]
DcapQvl(#[from] anyhow::Error),
#[error("PCCS collateral parse error: {0}")]
PccsCollateralParse(String),
#[error("PCCS collateral expired: {0}")]
PccsCollateralExpired(String),
#[cfg(any(test, feature = "mock"))]
#[error("Quote parse: {0}")]
QuoteParse(#[from] tdx_quote::QuoteParseError),
}

/// Origin of collateral used for a verification attempt
#[derive(Clone, Copy, Debug)]
enum CollateralSource {
Provided,
Cached,
Fresh,
RefreshedAfterFailure,
}

impl CollateralSource {
/// Returns a stable source label for structured logs
fn as_str(self) -> &'static str {
match self {
Self::Provided => "provided",
Self::Cached => "cached",
Self::Fresh => "fresh",
Self::RefreshedAfterFailure => "refreshed_after_failure",
}
}
}

/// Logs a warning when verification succeeds with a non-UpToDate TCB status
fn warn_if_non_uptodate(report: &VerifiedReport, fmspc: &str, source: CollateralSource) {
if report.status != "UpToDate" {
tracing::warn!(
status = %report.status,
advisory_ids = ?report.advisory_ids,
fmspc,
collateral_source = source.as_str(),
"DCAP verification succeeded with non-UpToDate TCB status"
);
}
}

#[cfg(test)]
mod tests {
use crate::attestation::measurements::MeasurementPolicy;
Expand Down Expand Up @@ -211,7 +283,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),
Some(collateral),
now,
false,
Expand Down Expand Up @@ -244,7 +316,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),
Some(collateral),
now,
true,
Expand Down
33 changes: 24 additions & 9 deletions attested-tls/src/attestation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
pub mod azure;
pub mod dcap;
pub mod measurements;
pub(crate) mod pccs;

use measurements::MultiMeasurements;
use parity_scale_codec::{Decode, Encode};
Expand All @@ -16,7 +17,9 @@ use std::{

use thiserror::Error;

use crate::attestation::{dcap::DcapVerificationError, measurements::MeasurementPolicy};
use crate::attestation::{
dcap::DcapVerificationError, measurements::MeasurementPolicy, pccs::Pccs,
};

const GCP_METADATA_API: &str =
"http://metadata.google.internal/computeMetadata/v1/project/project-id";
Expand Down Expand Up @@ -250,24 +253,36 @@ impl AttestationGenerator {
pub struct AttestationVerifier {
/// The measurement policy with accepted values and attestation types
pub measurement_policy: MeasurementPolicy,
/// If this is empty, anything will be accepted - but measurements are always injected into HTTP
/// headers, so that they can be verified upstream
/// A PCCS service to use - defaults to Intel PCS
pub pccs_url: Option<String>,
/// Whether to log quotes to a file
pub log_dcap_quote: bool,
/// Whether to override outdated TCB when on Azure
pub override_azure_outdated_tcb: bool,
/// Internal cache for collateral
internal_pccs: Pccs,
}

impl AttestationVerifier {
pub fn new(
measurement_policy: MeasurementPolicy,
pccs_url: Option<String>,
log_dcap_quote: bool,
override_azure_outdated_tcb: bool,
) -> Self {
Self {
measurement_policy,
log_dcap_quote,
override_azure_outdated_tcb,
internal_pccs: Pccs::new(pccs_url),
}
}

/// Create an [AttestationVerifier] which will allow no remote attestation
pub fn expect_none() -> Self {
Self {
measurement_policy: MeasurementPolicy::expect_none(),
pccs_url: None,
log_dcap_quote: false,
override_azure_outdated_tcb: false,
internal_pccs: Pccs::new(None),
}
}

Expand All @@ -276,9 +291,9 @@ impl AttestationVerifier {
pub fn mock() -> Self {
Self {
measurement_policy: MeasurementPolicy::mock(),
pccs_url: None,
log_dcap_quote: false,
override_azure_outdated_tcb: false,
internal_pccs: Pccs::new(None),
}
}

Expand Down Expand Up @@ -312,7 +327,7 @@ impl AttestationVerifier {
azure::verify_azure_attestation(
attestation_exchange_message.attestation,
expected_input_data,
self.pccs_url.clone(),
self.internal_pccs.clone(),
self.override_azure_outdated_tcb,
)
.await?
Expand All @@ -326,7 +341,7 @@ impl AttestationVerifier {
dcap::verify_dcap_attestation(
attestation_exchange_message.attestation,
expected_input_data,
self.pccs_url.clone(),
self.internal_pccs.clone(),
)
.await?
}
Expand Down
Loading