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
32 changes: 0 additions & 32 deletions crates/attestation/assets/global-virtual-tpm-ca-03.pem

This file was deleted.

73 changes: 6 additions & 67 deletions crates/attestation/src/azure/ak_certificate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,30 +32,6 @@ const MICROSOFT_RSA_DEVICES_ROOT_2021: &str =
const AZURE_VIRTUAL_TPM_ROOT_2023: &str =
include_str!("../../assets/azure-virtual-tpm-root-2023.pem");

// globalVirtualTPMCA03 is the intermediate CA that issues TDX vTPM AK
// certificates Source: https://learn.microsoft.com/en-us/azure/virtual-machines/trusted-launch-faq
// Issuer: Azure Virtual TPM Root Certificate Authority 2023
// Valid: 2025-04-24 to 2027-04-24
const GLOBAL_VIRTUAL_TPMCA03_PEM: &str = include_str!("../../assets/global-virtual-tpm-ca-03.pem");

/// Legacy Azure intermediate certificates bundled with this crate.
///
/// Deprecated verification-only fallback: this is kept only for backwards
/// compatibility with older evidence that did not serialize AIA-fetched
/// intermediates. New Azure vTPM evidence should carry the AK issuer chain
/// fetched from AIA instead of relying on this bundled, eventually-stale
/// list.
///
/// Do not use this when generating new evidence or when deciding whether an
/// AIA-fetched issuer chain is complete. It should be removed once
/// supporting legacy evidence without `ak_intermediate_certificates_pem` is
/// no longer required.
static LEGACY_BUNDLED_AZURE_INTERMEDIATES: Lazy<Vec<CertificateDer<'static>>> = Lazy::new(|| {
let (_type_label, cert_der) =
pem_rfc7468::decode_vec(GLOBAL_VIRTUAL_TPMCA03_PEM.as_bytes()).expect("Cannot decode PEM");
vec![CertificateDer::from(cert_der)]
});

/// The root anchors for azure
static AZURE_ROOT_ANCHORS: Lazy<Vec<TrustAnchor<'static>>> = Lazy::new(|| {
vec![
Expand All @@ -68,50 +44,17 @@ static AZURE_ROOT_ANCHORS: Lazy<Vec<TrustAnchor<'static>>> = Lazy::new(|| {

/// Verify an AK certificate against pinned Azure root CAs.
///
/// This includes `LEGACY_BUNDLED_AZURE_INTERMEDIATES` as a
/// verification-only fallback so older evidence captured before AIA-fetched
/// intermediates were serialized continues to verify.
/// `intermediate_cert_ders` are untrusted evidence from the attestation (or
/// fetched from AIA during generation); verification pins the Azure roots.
pub(crate) fn verify_ak_cert_with_azure_roots(
ak_cert_der: &[u8],
intermediate_cert_ders: &[Vec<u8>],
now_secs: u64,
) -> Result<(), MaaError> {
verify_ak_cert_with_azure_roots_inner(ak_cert_der, intermediate_cert_ders, now_secs, true)
}

/// Verify an AK certificate against pinned Azure root CAs using only the
/// intermediates supplied by the caller.
///
/// This is used while following AIA URLs during evidence generation. After
/// each fetched issuer is appended, we call this to check whether the
/// fetched chain is already complete. It intentionally excludes the legacy
/// bundled intermediates, otherwise generation could stop early because a
/// hardcoded intermediate completed the path, and the serialized evidence
/// would still depend on that legacy fallback.
fn verify_ak_cert_with_provided_intermediates_only(
ak_cert_der: &[u8],
intermediate_cert_ders: &[Vec<u8>],
now_secs: u64,
) -> Result<(), MaaError> {
verify_ak_cert_with_azure_roots_inner(ak_cert_der, intermediate_cert_ders, now_secs, false)
}

fn verify_ak_cert_with_azure_roots_inner(
ak_cert_der: &[u8],
intermediate_cert_ders: &[Vec<u8>],
now_secs: u64,
include_legacy_bundled_intermediates: bool,
) -> Result<(), MaaError> {
let ak_cert_der: CertificateDer = ak_cert_der.into();
let end_entity_cert = EndEntityCert::try_from(&ak_cert_der)?;

let mut intermediates = if include_legacy_bundled_intermediates {
LEGACY_BUNDLED_AZURE_INTERMEDIATES.clone()
} else {
Vec::new()
};
intermediates.extend(intermediate_cert_ders.iter().cloned().map(CertificateDer::from));

let intermediates: Vec<_> =
intermediate_cert_ders.iter().cloned().map(CertificateDer::from).collect();
let now = UnixTime::since_unix_epoch(Duration::from_secs(now_secs));

end_entity_cert.verify_for_usage(
Expand Down Expand Up @@ -145,9 +88,7 @@ pub(crate) fn fetch_ak_intermediates_from_aia(
now_secs: u64,
) -> Result<Vec<Vec<u8>>, MaaError> {
let mut intermediates = Vec::new();
if verify_ak_cert_with_provided_intermediates_only(ak_cert_der, &intermediates, now_secs)
.is_ok()
{
if verify_ak_cert_with_azure_roots(ak_cert_der, &intermediates, now_secs).is_ok() {
return Ok(intermediates);
}

Expand All @@ -159,9 +100,7 @@ pub(crate) fn fetch_ak_intermediates_from_aia(
issuer_urls = fetched_issuer.ca_issuers_urls;
intermediates.push(fetched_issuer.der);

if verify_ak_cert_with_provided_intermediates_only(ak_cert_der, &intermediates, now_secs)
.is_ok()
{
if verify_ak_cert_with_azure_roots(ak_cert_der, &intermediates, now_secs).is_ok() {
return Ok(intermediates);
} else if intermediates.len() == MAX_EVIDENCE_AK_INTERMEDIATE_CERTIFICATES {
return Err(MaaError::AkIssuerChainTooDeep {
Expand Down
70 changes: 2 additions & 68 deletions crates/attestation/src/azure/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,6 @@ mod test_utils {
#[cfg(test)]
mod tests {
use super::*;
use crate::measurements::MeasurementPolicy;

fn input_data_from_attestation(attestation_bytes: &[u8]) -> [u8; 64] {
let attestation_document: AttestationDocument =
Expand All @@ -695,76 +694,11 @@ mod tests {
assert_eq!(hex::decode(var_data_values["user-data"].as_str().unwrap()).unwrap().len(), 64);
}

/// Verify a stored attestation from a test-deployment on azure
#[tokio::test]
async fn test_verify() {
let attestation_bytes: &'static [u8] =
include_bytes!("../../test-assets/azure-tdx-1764662251380464271.yaml");

// To avoid this test stopping working when the certificate is no longer
// valid we pass in a timestamp
let now = 1771423480;

let measurements_json = br#"
[{
"measurement_id": "cvm-image-azure-tdx.rootfs-20241107200854.wic.vhd",
"attestation_type": "azure-tdx",
"measurements": {
"4": {
"expected": "c4a25a6d7704629f63db84d20ea8db0e9ce002b2801be9a340091fe7ac588699"
},
"9": {
"expected": "9f4a5775122ca4703e135a9ae6041edead0064262e399df11ca85182b0f1541d"
},
"11": {
"expected": "abd7c695ffdb6081e99636ee016d1322919c68d049b698b399d22ae215a121bf"
}
}
}]
"#;

let measurement_policy =
MeasurementPolicy::from_json_bytes(measurements_json.to_vec()).unwrap();

let collateral_bytes: &'static [u8] =
include_bytes!("../../test-assets/azure-collateral02.yaml");

let async_collateral = serde_saphyr::from_slice(collateral_bytes).unwrap();
let sync_collateral = serde_saphyr::from_slice(collateral_bytes).unwrap();
let attestation_json = serde_json::to_vec(
&serde_saphyr::from_slice::<AttestationDocument>(attestation_bytes).unwrap(),
)
.unwrap();

let async_measurements = verify_azure_attestation_with_given_timestamp(
attestation_json.clone(),
[0; 64], // Input data
None,
async_collateral,
now,
false,
)
.await
.unwrap();

let sync_measurements = verify_azure_attestation_with_given_timestamp_sync(
attestation_json,
[0; 64], // Input data
Pccs::new_without_prewarm(None),
sync_collateral,
now,
false,
)
.unwrap();

assert_eq!(async_measurements, sync_measurements);
measurement_policy.check_measurement(&async_measurements).unwrap();
}

/// Verify a complete observed Azure attestation payload that includes
/// AK intermediates fetched from the leaf certificate's AIA URLs.
#[tokio::test]
async fn test_verify_with_observed_ak_intermediates() {
async fn test_verify() {
// generated using [capture_azure_fixture] above.
let attestation_bytes: &'static [u8] =
include_bytes!("../../test-assets/azure-tdx-with-ak-intermediates-1780922561.yaml");
let collateral_bytes: &'static [u8] = include_bytes!(
Expand Down
Loading