diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 81bbb78..b956f64 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -73,5 +73,12 @@ jobs: - name: Check azure-verifier builds without the TPM stack run: cargo check -p attestation --no-default-features --features azure-verifier + # `azure-attester` carries its generation dependencies per-target, so off + # x86_64 linux it has to stay enabled and contribute nothing rather than + # fail to build. macos-latest is aarch64-darwin, the target + # tss-esapi-sys has no bindings for. + - name: Check azure-attester builds off x86_64 linux + run: cargo check -p attestation --all-features + - name: Test azure-verifier run: cargo test -p attestation --no-default-features --features azure-verifier diff --git a/crates/attestation/Cargo.toml b/crates/attestation/Cargo.toml index 764df23..46faf90 100644 --- a/crates/attestation/Cargo.toml +++ b/crates/attestation/Cargo.toml @@ -36,12 +36,23 @@ ureq = "2.12.1" webpki = { package = "rustls-webpki", version = "0.103.8" } x509-parser = "0.18.0" -# Used for azure vTPM attestation support. minimal cvm crate for verification -# and full tdx crate for generation (linux only because links tpm2-tss). +# Needed for verification of azure vTPM evidence. No features on purpose since az-cvm-vtpm's +# verifier feature implies its `tpm` one, which pulls tss-esapi and the native TPM libraries. +# See https://github.com/kinvolk/azure-cvm-tooling/issues/95 az-cvm-vtpm = { version = "0.7.4", default-features = false, optional = true } +openssl = { version = "0.10.79", optional = true } + +[target.'cfg(all(target_os = "linux", target_arch = "x86_64"))'.dependencies] +# Needed for generation of azure vTPM evidence, which reads the vTPM through tss-esapi and +# attests Intel TDX. An Azure TDX CVM is x86_64 linux, so the generation code is only +# meaningful there, and neither of these builds elsewhere in any case. +# Declaring them per-target rather than gating on the feature alone keeps `azure-attester` +# (and so --all-features) resolvable on every platform; off this target the feature stays +# enabled but contributes nothing, as the crate readme describes. build.rs derives the matching +# `azure_attester_x86_64_linux` cfg, and must keep the same condition as this table. +# See https://github.com/virtee/sev/issues/369 az-tdx-vtpm = { version = "0.7.4", optional = true } tss-esapi = { version = "7.6.0", optional = true } -openssl = { version = "0.10.79", optional = true } [dev-dependencies] mock-tdx = { workspace = true } @@ -58,9 +69,10 @@ serde_json = "1.0.145" [features] default = [] -# Generation of Microsoft Azure attestation evidence on an Azure TDX CVM. -# Implies `azure-verifier`. Reads the vTPM through tss-esapi, so it builds -# only where the native tpm2-tss libraries are available (Linux). +# Opts into generating Azure evidence, and so into the native tpm2-tss stack +# that tss-esapi links. Code should be gated on the `azure_attester_x86_64_linux` cfg +# injected by build.rs rather than on this feature, which says nothing about the target. +# The crate readme covers what enabling it does off x86_64 linux. azure-attester = ["azure-verifier", "dep:az-tdx-vtpm", "dep:tss-esapi"] # Verification of Microsoft Azure attestation evidence. Pure computation diff --git a/crates/attestation/README.md b/crates/attestation/README.md index c7df79e..068f15d 100644 --- a/crates/attestation/README.md +++ b/crates/attestation/README.md @@ -48,9 +48,19 @@ attestation support. This feature requires [tpm2](https://tpm2-software.github.io) and `openssl` to be installed. On Debian-based systems tpm2 is provided by [`libtss2-dev`](https://packages.debian.org/trixie/libtss2-dev), and on nix -`tpm2-tss`. This dependency is currently not packaged for MacOS, meaning -currently it is not possible to compile or run with the `azure-attester` -feature on MacOS. +`tpm2-tss`. + +The generation code is compiled on x86_64 Linux targets only — the platform +an Azure TDX CVM actually runs on, and the only one where the vTPM and the +native TPM stack exist. **On every other target, including MacOS and +aarch64 Linux, `azure-attester` stays enabled but contributes nothing.** +Cargo still reports the feature as on; what changes is that its generation +dependencies are target-gated out of the dependency graph and the +generation code is not compiled, leaving the same compiled surface as +`azure-verifier` on its own. So the crate builds (`--all-features` works +everywhere), but `AttestationType::detect` will not report `AzureTdx` and +generating Azure evidence fails with `AttestationTypeNotSupported`. Verifying +Azure evidence is unaffected. If you need generation, build on x86_64 Linux. **Note:** Azure support is currently **not actively maintained** as we do not have production CVMs deployed on Azure and so are unlikely to notice when this diff --git a/crates/attestation/build.rs b/crates/attestation/build.rs index 82362d1..c3d18df 100644 --- a/crates/attestation/build.rs +++ b/crates/attestation/build.rs @@ -8,6 +8,22 @@ const FIRMWARE_DIR: &str = "assets/ovmf"; const GENERATED_FIRMWARE: &str = "trusted-firmware.json"; fn main() { + // Gate for the Azure evidence generation code. It takes a cfg rather + // than the `azure-attester` feature alone because it only compiles + // where az-tdx-vtpm and tss-esapi resolve, so this condition has to + // stay identical to their target table in Cargo.toml. The + // CARGO_CFG_TARGET_* vars describe the target rather than the build + // host, which keeps the two in agreement when cross-compiling. The + // check-cfg goes outside the branch: the name is expected on every + // target, including those where the code it gates is switched off. + println!("cargo::rustc-check-cfg=cfg(azure_attester_x86_64_linux)"); + if env::var_os("CARGO_FEATURE_AZURE_ATTESTER").is_some() && + env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("linux") && + env::var("CARGO_CFG_TARGET_ARCH").as_deref() == Ok("x86_64") + { + println!("cargo::rustc-cfg=azure_attester_x86_64_linux"); + } + println!("cargo:rerun-if-changed={FIRMWARE_DIR}"); let mut paths = fs::read_dir(FIRMWARE_DIR) diff --git a/crates/attestation/src/azure/mod.rs b/crates/attestation/src/azure/mod.rs index d0cfd10..fd0e778 100644 --- a/crates/attestation/src/azure/mod.rs +++ b/crates/attestation/src/azure/mod.rs @@ -1,12 +1,12 @@ //! Microsoft Azure vTPM attestation evidence generation and verification mod ak_certificate; -#[cfg(feature = "azure-attester")] +#[cfg(azure_attester_x86_64_linux)] mod attester; mod tpm_quote; mod tpms_attest; mod verify; -#[cfg(feature = "azure-attester")] +#[cfg(azure_attester_x86_64_linux)] pub use attester::{create_azure_attestation, detect_azure_cvm}; use az_cvm_vtpm::hcl; use openssl::error::ErrorStack; @@ -165,39 +165,39 @@ pub enum MaaError { DcapVerification(#[from] crate::dcap::DcapVerificationError), // Errors that can only occur during evidence generation on an Azure CVM - #[cfg(feature = "azure-attester")] + #[cfg(azure_attester_x86_64_linux)] #[error("Report: {0}")] Report(#[from] az_tdx_vtpm::report::ReportError), - #[cfg(feature = "azure-attester")] + #[cfg(azure_attester_x86_64_linux)] #[error("IMDS: {0}")] Imds(#[from] az_tdx_vtpm::imds::ImdsError), - #[cfg(feature = "azure-attester")] + #[cfg(azure_attester_x86_64_linux)] #[error("vTPM report: {0}")] VtpmReport(#[from] az_tdx_vtpm::vtpm::ReportError), - #[cfg(feature = "azure-attester")] + #[cfg(azure_attester_x86_64_linux)] #[error("vTPM quote: {0}")] VtpmQuote(#[from] az_tdx_vtpm::vtpm::QuoteError), - #[cfg(feature = "azure-attester")] + #[cfg(azure_attester_x86_64_linux)] #[error("vTPM read: {0}")] TssEsapi(#[from] tss_esapi::Error), - #[cfg(feature = "azure-attester")] + #[cfg(azure_attester_x86_64_linux)] #[error("IO: {0}")] Io(#[from] std::io::Error), - #[cfg(feature = "azure-attester")] + #[cfg(azure_attester_x86_64_linux)] #[error("AIA URL is not HTTP(S): {url}")] UnsupportedAiaUrl { url: String }, - #[cfg(feature = "azure-attester")] + #[cfg(azure_attester_x86_64_linux)] #[error("Failed to fetch AIA issuer certificate from {url}: {source}")] AiaFetch { url: String, source: Box }, - #[cfg(feature = "azure-attester")] + #[cfg(azure_attester_x86_64_linux)] #[error( "Azure vTPM AK issuer chain exceeded maximum intermediate certificate count: {max_depth}" )] AkIssuerChainTooDeep { max_depth: usize }, - #[cfg(feature = "azure-attester")] + #[cfg(azure_attester_x86_64_linux)] #[error("Azure vTPM AK issuer chain could not be built to a pinned Azure root certificate")] AkIssuerChainIncomplete, - #[cfg(feature = "azure-attester")] + #[cfg(azure_attester_x86_64_linux)] #[error( "Azure metadata API returned a successful response with non-JSON content-type: {content_type:?}" )] diff --git a/crates/attestation/src/azure/tpm_quote.rs b/crates/attestation/src/azure/tpm_quote.rs index 8cefaab..16b08bb 100644 --- a/crates/attestation/src/azure/tpm_quote.rs +++ b/crates/attestation/src/azure/tpm_quote.rs @@ -14,9 +14,11 @@ //! extraction done by the `tpms_attest` parser instead of tss-esapi. It is //! vendored because az-cvm-vtpm's verifier feature currently requires its //! TPM device support (tss-esapi links the native tpm2-tss libraries, -//! making such builds Linux-only). If upstream decouples verification -//! from the TPM stack, this module can be retired in favour of depending -//! on az-cvm-vtpm's verifier again. +//! making such builds Linux-only). Tracked upstream as +//! ; if upstream +//! decouples verification from the TPM stack, this module and +//! [`super::tpms_attest`] can be retired in favour of depending on +//! az-cvm-vtpm's verifier again. use openssl::{ hash::MessageDigest, diff --git a/crates/attestation/src/lib.rs b/crates/attestation/src/lib.rs index b5706c4..0d432a0 100644 --- a/crates/attestation/src/lib.rs +++ b/crates/attestation/src/lib.rs @@ -2,7 +2,8 @@ // `azure-verifier` is the base Azure feature: it gates the whole module, // and `azure-attester` (which implies it) additionally enables the -// generation code inside. +// generation code inside, on the x86_64 linux targets where the vTPM it +// reads exists. #[cfg(feature = "azure-verifier")] pub mod azure; pub mod dcap; @@ -171,7 +172,7 @@ impl AttestationType { /// Detect what platform we are on by attempting an attestation pub fn detect() -> Result { // First attempt azure, if the feature is present - #[cfg(feature = "azure-attester")] + #[cfg(azure_attester_x86_64_linux)] { if azure::detect_azure_cvm()? { return Ok(AttestationType::AzureTdx); @@ -282,7 +283,7 @@ impl AttestationGenerator { match self.attestation_type { AttestationType::None => Ok(AttestationExchangeMessage::without_attestation()), AttestationType::AzureTdx => { - #[cfg(feature = "azure-attester")] + #[cfg(azure_attester_x86_64_linux)] { let platform = attest_measure::platform::metadata_for( self.attestation_type.try_into()?, @@ -294,10 +295,10 @@ impl AttestationGenerator { }), }) } - #[cfg(not(feature = "azure-attester"))] + #[cfg(not(azure_attester_x86_64_linux))] { tracing::error!( - "Attempted to generate an azure attestation but the `azure-attester` feature not enabled" + "Azure attestation generation requires the `azure-attester` feature on an x86_64 linux host" ); Err(AttestationError::AttestationTypeNotSupported) } diff --git a/readme.md b/readme.md index 2416f3d..ff2fbc9 100644 --- a/readme.md +++ b/readme.md @@ -51,6 +51,7 @@ More details in the individual READMEs of the provided crates: The included `shell.nix` file can be used with `nix-shell`, `direnv`, or `nix develop` to add the dependencies needed by the optional `azure-attester` -feature of the `attestation` crate on Linux. The `azure-verifier` feature -needs no extra system dependencies and builds on any platform. See the +feature of the `attestation` crate on x86_64 Linux, the only target its +generation code is compiled for. The `azure-verifier` feature needs no extra +system dependencies and builds on any platform. See the [`attestation` crate readme](./crates/attestation) for details.