From b30c8292029ce5fc6845fc53dd7c1d0c2b1caec0 Mon Sep 17 00:00:00 2001 From: peg Date: Tue, 31 Mar 2026 09:40:08 +0200 Subject: [PATCH 1/4] Improve Azure platform detection --- crates/attestation/src/azure/mod.rs | 23 +++++++++++++++++++++++ crates/attestation/src/lib.rs | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/crates/attestation/src/azure/mod.rs b/crates/attestation/src/azure/mod.rs index 610ae26..dfb4c32 100644 --- a/crates/attestation/src/azure/mod.rs +++ b/crates/attestation/src/azure/mod.rs @@ -1,6 +1,8 @@ //! Microsoft Azure vTPM attestation evidence generation and verification mod ak_certificate; mod nv_index; +use std::time::Duration; + use ak_certificate::{read_ak_certificate_from_tpm, verify_ak_cert_with_azure_roots}; use az_tdx_vtpm::{hcl, imds, vtpm}; use base64::{Engine as _, engine::general_purpose::URL_SAFE as BASE64_URL_SAFE}; @@ -13,6 +15,9 @@ use x509_parser::prelude::*; use crate::{dcap::verify_dcap_attestation_with_given_timestamp, measurements::MultiMeasurements}; +/// Used in attestation type detection to check if we are on Azure +const AZURE_METADATA_API: &str = "http://169.254.169.254/metadata/instance"; + /// The attestation evidence payload that gets sent over the channel #[derive(Debug, Serialize, Deserialize)] struct AttestationDocument { @@ -266,6 +271,24 @@ impl RsaPubKey { } } +/// Detect whether we are on Azure and can make an Azure vTPM attestation +pub async fn detect_azure_cvm() -> Result { + let client = reqwest::Client::builder().no_proxy().timeout(Duration::from_secs(2)).build()?; + + let resp = client.get(AZURE_METADATA_API).header("Metadata", "true").send().await; + + if let Ok(r) = resp && + r.status().is_success() && + r.headers() + .get(reqwest::header::CONTENT_TYPE) + .and_then(|value| value.to_str().ok()) + .is_some_and(|value| value.starts_with("application/json")) + { + return Ok(az_tdx_vtpm::is_tdx_cvm().unwrap_or(false)); + } + Ok(false) +} + /// An error when generating or verifying a Microsoft Azure vTPM attestation #[derive(Error, Debug)] pub enum MaaError { diff --git a/crates/attestation/src/lib.rs b/crates/attestation/src/lib.rs index 1978e18..0524eed 100644 --- a/crates/attestation/src/lib.rs +++ b/crates/attestation/src/lib.rs @@ -73,7 +73,7 @@ impl AttestationType { // First attempt azure, if the feature is present #[cfg(feature = "azure")] { - if azure::create_azure_attestation([0; 64]).is_ok() { + if azure::detect_azure_cvm().await? { return Ok(AttestationType::AzureTdx); } } From c36efc6d083418ab07fb3147a88dcd3c349556ad Mon Sep 17 00:00:00 2001 From: peg Date: Mon, 13 Apr 2026 10:57:48 +0200 Subject: [PATCH 2/4] Improve Azure detection function error handling and logging --- crates/attestation/src/azure/mod.rs | 56 +++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/crates/attestation/src/azure/mod.rs b/crates/attestation/src/azure/mod.rs index dfb4c32..e994374 100644 --- a/crates/attestation/src/azure/mod.rs +++ b/crates/attestation/src/azure/mod.rs @@ -9,6 +9,7 @@ use base64::{Engine as _, engine::general_purpose::URL_SAFE as BASE64_URL_SAFE}; use dcap_qvl::QuoteCollateralV3; use num_bigint::BigUint; use openssl::{error::ErrorStack, pkey::PKey}; +use reqwest::header::CONTENT_TYPE; use serde::{Deserialize, Serialize}; use thiserror::Error; use x509_parser::prelude::*; @@ -272,21 +273,48 @@ impl RsaPubKey { } /// Detect whether we are on Azure and can make an Azure vTPM attestation -pub async fn detect_azure_cvm() -> Result { +pub async fn detect_azure_cvm() -> Result { let client = reqwest::Client::builder().no_proxy().timeout(Duration::from_secs(2)).build()?; - let resp = client.get(AZURE_METADATA_API).header("Metadata", "true").send().await; + let response = match client.get(AZURE_METADATA_API).header("Metadata", "true").send().await { + Ok(response) => response, + Err(err) => { + tracing::debug!("Azure CVM detection failed: Azure metadata API request failed: {err}"); + return Ok(false); + } + }; + + if !response.status().is_success() { + tracing::debug!( + "Azure CVM detection failed: metadata API returned non-success status: {}", + response.status() + ); + return Ok(false); + } + + // Ensure the response has a JSON content type + let content_type = response + .headers() + .get(CONTENT_TYPE) + .map(|value| value.to_str().map(str::to_owned)) + .transpose() + .map_err(|_| MaaError::AzureMetadataApiNonJsonResponse { content_type: None })?; - if let Ok(r) = resp && - r.status().is_success() && - r.headers() - .get(reqwest::header::CONTENT_TYPE) - .and_then(|value| value.to_str().ok()) - .is_some_and(|value| value.starts_with("application/json")) - { - return Ok(az_tdx_vtpm::is_tdx_cvm().unwrap_or(false)); + if !content_type.as_deref().is_some_and(|value| value.starts_with("application/json")) { + return Err(MaaError::AzureMetadataApiNonJsonResponse { content_type }); + } + + match az_tdx_vtpm::is_tdx_cvm() { + Ok(true) => Ok(true), + Ok(false) => { + tracing::debug!("Azure CVM detection failed: platform is not an Azure TDX CVM"); + Ok(false) + } + Err(err) => { + tracing::debug!("Azure CVM detection failed: Azure TDX CVM probe failed: {err}"); + Ok(false) + } } - Ok(false) } /// An error when generating or verifying a Microsoft Azure vTPM attestation @@ -302,6 +330,8 @@ pub enum MaaError { Hcl(#[from] hcl::HclError), #[error("JSON: {0}")] Json(#[from] serde_json::Error), + #[error("HTTP client: {0}")] + Reqwest(#[from] reqwest::Error), #[error("vTPM quote: {0}")] VtpmQuote(#[from] vtpm::QuoteError), #[error("AK public key: {0}")] @@ -350,6 +380,10 @@ pub enum MaaError { ClaimsUserDataInputMismatch, #[error("DCAP verification: {0}")] DcapVerification(#[from] crate::dcap::DcapVerificationError), + #[error( + "Azure metadata API returned a successful response with non-JSON content-type: {content_type:?}" + )] + AzureMetadataApiNonJsonResponse { content_type: Option }, } #[cfg(test)] From bcb7b42c973757975a93294193ead975e730befd Mon Sep 17 00:00:00 2001 From: peg Date: Fri, 17 Apr 2026 17:29:38 +0200 Subject: [PATCH 3/4] Case insensitve header value comparison Co-authored-by: Anton --- crates/attestation/src/azure/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/attestation/src/azure/mod.rs b/crates/attestation/src/azure/mod.rs index b557737..31ebdac 100644 --- a/crates/attestation/src/azure/mod.rs +++ b/crates/attestation/src/azure/mod.rs @@ -310,7 +310,7 @@ pub async fn detect_azure_cvm() -> Result { .transpose() .map_err(|_| MaaError::AzureMetadataApiNonJsonResponse { content_type: None })?; - if !content_type.as_deref().is_some_and(|value| value.starts_with("application/json")) { + if !content_type.as_deref().is_some_and(|value| value.to_lowercase().starts_with("application/json")) { return Err(MaaError::AzureMetadataApiNonJsonResponse { content_type }); } From 48b30cbbfa54a10a9d1b7709b4c20443873e0189 Mon Sep 17 00:00:00 2001 From: peg Date: Fri, 17 Apr 2026 17:55:34 +0200 Subject: [PATCH 4/4] Fmt --- crates/attestation/src/azure/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/attestation/src/azure/mod.rs b/crates/attestation/src/azure/mod.rs index 31ebdac..d48254a 100644 --- a/crates/attestation/src/azure/mod.rs +++ b/crates/attestation/src/azure/mod.rs @@ -310,7 +310,10 @@ pub async fn detect_azure_cvm() -> Result { .transpose() .map_err(|_| MaaError::AzureMetadataApiNonJsonResponse { content_type: None })?; - if !content_type.as_deref().is_some_and(|value| value.to_lowercase().starts_with("application/json")) { + if !content_type + .as_deref() + .is_some_and(|value| value.to_lowercase().starts_with("application/json")) + { return Err(MaaError::AzureMetadataApiNonJsonResponse { content_type }); }