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..72c7be7 100644 --- a/crates/attestation/Cargo.toml +++ b/crates/attestation/Cargo.toml @@ -36,12 +36,21 @@ 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] +# Azure vTPM evidence generation: reads the vTPM through tss-esapi, attests Intel TDX. +# An Azure TDX CVM is x86_64 linux, and tss-esapi-sys has no bindings for most other +# tuples in any case. Keeping these per-target rather than behind the feature alone +# leaves `azure-attester` (and --all-features) buildable everywhere: elsewhere the +# feature stays on but pulls in neither dep, so nothing extra compiles. build.rs +# mirrors the condition as the `azure_attester_x86_64_linux` cfg; keep the two in sync. 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 +67,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..7f3bf65 100644 --- a/crates/attestation/README.md +++ b/crates/attestation/README.md @@ -48,9 +48,20 @@ 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 for x86_64 Linux targets only — where an Azure +TDX CVM actually runs. **Elsewhere, MacOS and aarch64 Linux included, +`azure-attester` stays enabled but contributes nothing:** its generation +dependencies are target-gated out, so the crate behaves as if only +`azure-verifier` were enabled. `--all-features` still builds everywhere, but +`AttestationType::detect` will never report `AzureTdx` and generation will fail +with `AttestationTypeNotSupported`. Verification is unaffected. `build.rs` emits +a `cargo::warning` when this happens. + +The condition is on the target, not the build host, so any machine can produce a +build with generation in it via `--target x86_64-unknown-linux-gnu`, given a +cross toolchain and tpm2-tss and openssl for that target. **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..e5c0305 100644 --- a/crates/attestation/build.rs +++ b/crates/attestation/build.rs @@ -8,6 +8,37 @@ 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: any + // host targeting x86_64 linux gets the generation code. 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() { + let os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); + let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default(); + if os == "linux" && arch == "x86_64" { + println!("cargo::rustc-cfg=azure_attester_x86_64_linux"); + } else { + // Warn rather than fail: --all-features has to stay usable + // everywhere, so the feature is a no-op off this + // target instead of an error. + println!( + "cargo::warning=`azure-attester` is enabled but the target is {arch}-{os}. \ + The Azure evidence generation code is only compiled for x86_64 linux, so this \ + build will behave as if only `azure-verifier` were enabled: `detect` will \ + never return `AzureTdx`, and `generate_attestation` for `AzureTdx` will fail \ + with `AttestationTypeNotSupported`. Verifying Azure evidence still works. If \ + you need generation, target x86_64 linux — any host can, with \ + `--target x86_64-unknown-linux-gnu` and a cross toolchain." + ); + } + } + 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.