Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 

README.md

attestation

Attestation generation and verification for confidential VMs, plus measurement policy handling.

This crate provides:

  • Attestation type detection (none, dcap-tdx, gcp-tdx, and azure-tdx when enabled)
  • Attestation generation and verification for DCAP and (optionally) Azure
  • Parsing and evaluation of measurement policies

Verification results

When an attestation is present, AttestationVerifier::verify_attestation and AttestationVerifier::verify_attestation_sync return a VerifiedAttestation containing the ExpectedMeasurements value from the policy record that accepted it. This is the matched policy value, not the raw register values extracted from the quote. For example, verification against a portable policy returns ExpectedMeasurements::Image, while an allow-any DCAP policy returns ExpectedMeasurements::Dcap with an empty register map. Successful verification without attestation returns None.

Matched expected measurements can be transported in an HTTP header using ExpectedMeasurements::to_header_format and reconstructed with ExpectedMeasurements::from_header_format. See Expected measurement header format.

Runtime Requirements

Verification uses the pccs crate to fetch DCAP collateral and, depending on the selected cache policy, cache and refresh it. Asynchronous verification requires a Tokio runtime. Constructing an AttestationVerifier with the Prewarmed policy also requires an active runtime because pre-warming starts immediately; constructing it with the Passthrough or OnDemand policy does not itself spawn a task.

Synchronous verification requires a cached policy (OnDemand or Prewarmed) with the required collateral already cached. Cache misses and expired entries may start Tokio-backed background refresh tasks. Passthrough cannot be used for synchronous verification because fetching collateral requires asynchronous I/O.

DCAP collateral configuration

Every AttestationVerifier has an independent collateral source and cache policy configured through AttestationVerifierBuilder::with_collateral_source and AttestationVerifierBuilder::with_cache_policy. The default is anonymous Intel PCS with CachePolicy::Passthrough.

  • Passthrough keeps no internal cache and fetches collateral from the configured endpoint for every asynchronous verification.
  • OnDemand starts with an empty internal cache and fetches collateral on demand.
  • Prewarmed immediately starts discovering and caching available TDX collateral, then refreshes cached entries before expiry.

Use CollateralSource::IntelPcs with an optional subscription key, or CollateralSource::Pccs with the URL of a compatible service. AttestationVerifierBuilder::with_intel_pcs_subscription_key and AttestationVerifierBuilder::with_pccs_url provide shortcuts for these common configurations.

AttestationVerifier::ready() waits for initial work only with Prewarmed. It returns immediately for Passthrough and OnDemand. A successful return with either policy does not mean later verification will avoid fetching collateral. With Prewarmed it means pre-warm bootstrap completed, but individual collateral fetches can still have failed.

Feature flags

azure-attester

Enables generation of Microsoft Azure vTPM attestation evidence on Azure TDX CVMs, through tss-esapi. Implies azure-verifier, giving full Azure attestation support.

This feature requires tpm2 and openssl to be installed. On Debian-based systems tpm2 is provided by libtss2-dev, and on nix 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 implementation of Azure attestation generation or verification ceases to work. Use at your own risk.

All Azure features are disabled by default. Without azure-verifier, verification of azure attestations is not possible and azure attestations will be rejected with an error.

azure-verifier

Enables verification of Microsoft Azure vTPM attestation evidence. Verification is pure computation over the evidence bytes; it requires openssl but no TPM stack, and builds and runs on any platform, including MacOS.

*** Note ***

Azure is known to use an outdated FMSPC 90C06F000000 which will cause verifications to fail. A workaround is provided which will allow this, but it must be explicitly enabled via the override_azure_outdated_tcb flag on AttestationVerifier.

mock

Enables mock quote support via the local mock-tdx crate for tests and development on non-TDX hardware.

In mock builds, CachePolicy::Passthrough uses embedded mock collateral rather than making an external request. CachePolicy::OnDemand and CachePolicy::Prewarmed can be pointed at a local mock PCCS when testing cache behavior.

Do not use in production. Disabled by default.

Attestation Types

These are the attestation type names used in the measurements file.

  • none - No attestation provided
  • gcp-tdx - DCAP TDX on Google Cloud Platform
  • azure-tdx - TDX on Azure, with vTPM attestation
  • dcap-tdx - DCAP TDX (platform not specified)

Local attestation types can be automatically detected. This works by initially attempting an Azure attestation, and if it fails attempting a DCAP attestation, and if that fails assume no CVM attestation. On detecting DCAP, a call to the Google Cloud metadata API is used to detect whether we are on Google Cloud.

In the case of attestation types dcap-tdx and gcp-tdx, a standard DCAP attestation is generated using the configfs-tsm linux filesystem interface. This means that the binary must be run with access to /sys/kernel/config/tsm/report which on many systems requires sudo. If configfs-tsm is unavailable, quote generation via vSOCK to the QGS will be attempted.

Alternatively, an external 'attestation provider service' URL can be provided which outsources the attestation generation to another process.

When verifying DCAP attestations, collateral is retrieved according to the configured collateral source and cache policy. The source defaults to Intel PCS unless a PCCS URL is provided through the verifier builder. If outdated TCB is used, the quote will fail to verify. For special cases where outdated TCB should be allowed, a custom override function can be passed when verifying which may modify collateral before it is validated against the TCB.

Measurements File

Accepted measurements for the remote party can be specified in a JSON file containing an array of objects, each of which specifies an accepted attestation type and set of measurements.

This aims to match the formatting used by cvm-reverse-proxy.

These objects have the following fields:

  • measurement_id - a name used to describe the entry. For example the name and version of the CVM OS image that these measurements correspond to.
  • attestation_type - a string containing one of the attestation types (confidential computing platforms) described below.
  • measurements - an object with fields referring to the five measurement registers. See Measurement field names.
  • dcap_image_hashes - an alternative to measurements that pins the hashes of the boot components (UKI, kernel, initrd, cmdline, GPT disk GUID) rather than raw register values. The verifier reconstructs the expected RTMRs at check time from these hashes and platform-fetched firmware. See Portable measurement policies below.

A record may set either measurements or dcap_image_hashes, not both.

Each measurement register entry supports two mutually exclusive fields:

  • expected_any - (recommended) an array of hex-encoded measurement values. The attestation is accepted if the actual measurement matches any value in the list (OR semantics).
  • expected - (deprecated) a single hex-encoded measurement value. Retained for backwards compatibility but expected_any should be preferred.

Example using expected_any (recommended):

[
  {
    "measurement_id": "dcap-tdx-example",
    "attestation_type": "dcap-tdx",
    "measurements": {
      "mrtd": {
        "expected_any": [
          "47a1cc074b914df8596bad0ed13d50d561ad1effc7f7cc530ab86da7ea49ffc03e57e7da829f8cba9c629c3970505323"
        ]
      },
      "rtmr0": {
        "expected_any": [
          "da6e07866635cb34a9ffcdc26ec6622f289e625c42c39b320f29cdf1dc84390b4f89dd0b073be52ac38ca7b0a0f375bb"
        ]
      },
      "rtmr1": {
        "expected_any": [
          "a7157e7c5f932e9babac9209d4527ec9ed837b8e335a931517677fa746db51ee56062e3324e266e3f39ec26a516f4f71"
        ]
      },
      "rtmr2": {
        "expected_any": [
          "e63560e50830e22fbc9b06cdce8afe784bf111e4251256cf104050f1347cd4ad9f30da408475066575145da0b098a124"
        ]
      },
      "rtmr3": {
        "expected_any": [
          "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
        ]
      }
    }
  }
]

The expected_any field is useful when multiple measurement values should be accepted for a register (e.g., for different versions of the firmware):

{
  "mrtd": {
    "expected_any": [
      "47a1cc074b914df8596bad0ed13d50d561ad1effc7f7cc530ab86da7ea49ffc03e57e7da829f8cba9c629c3970505323",
      "abc123def456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
    ]
  }
}
Legacy format using deprecated expected field

The expected field is deprecated but still supported for backwards compatibility:

[
  {
    "measurement_id": "dcap-tdx-example",
    "attestation_type": "dcap-tdx",
    "measurements": {
      "mrtd": {
        "expected": "47a1cc074b914df8596bad0ed13d50d561ad1effc7f7cc530ab86da7ea49ffc03e57e7da829f8cba9c629c3970505323"
      }
    }
  }
]

The only mandatory field is attestation_type. If an attestation type is specified with neither measurements nor dcap_image_hashes, any measurements will be accepted for this attestation type. The measurements can still be checked up-stream by the source client or target service using header injection for example. But it is then up to external programs to reject unacceptable measurements.

Measurement field names

For Azure vTMP attestations, the preferred field names are PCR register indexes prefixed with pcr or PCR. For example the following specifies PCRs 4 and 9:

{
    "measurement_id": "cvm-image-azure-tdx.rootfs-20241107200854.wic.vhd",
    "attestation_type": "azure-tdx",
    "measurements": {
        "pcr4": {
            "expected_any": ["1b8cd655f5ebdf50bedabfb5db6b896a0a7c56de54f318103a2de1e7cea57b6b"]
        },
        "pcr9": {
            "expected_any": ["992465f922102234c196f596fdaba86ea16eaa4c264dc425ec26bc2d1c364472"]
        }
    }
}

Legacy numeric field names are still supported for backwards compatibility:

  • "4" - PCR 4
  • "9" - PCR 9
  • "11" - PCR 11
  • and so on for valid PCR indices 0 through 23

All other attestation types are DCAP based. In measurement-policy JSON, the preferred field names are the register names and they are matched case-insensitively:

  • mrtd - MRTD
  • rtmr0 - RTMR0
  • rtmr1 - RTMR1
  • rtmr2 - RTMR2
  • rtmr3 - RTMR3

Legacy numeric field names are still supported for backwards compatibility:

  • "0" - MRTD
  • "1" - RTMR0
  • "2" - RTMR1
  • "3" - RTMR2
  • "4" - RTMR3

Expected measurement header format

ExpectedMeasurements::to_header_format serializes the matched policy value as self-describing JSON suitable for an HTTP HeaderValue. Hashes are lowercase hexadecimal strings. Both SHA-384 DCAP values and SHA-256 Azure values are represented as hex.

DCAP and Azure register values are arrays because a policy can accept more than one value for each register. Header JSON uses numeric register keys:

  • DCAP: "0" is MRTD, followed by "1" through "4" for RTMR0 through RTMR3.
  • Azure: the key is the PCR index.

DCAP example:

{
  "type": "dcap",
  "measurements": {
    "0": ["<96 hex characters>"],
    "3": ["<96 hex characters>", "<96 hex characters>"]
  }
}

Azure example:

{
  "type": "azure",
  "measurements": {
    "4": ["<64 hex characters>"],
    "11": ["<64 hex characters>"]
  }
}

Portable image-hash example:

{
  "type": "image",
  "measurements": {
    "uki_authenticode": "<96 hex characters>",
    "kernel_authenticode": "<96 hex characters>",
    "cmdline_hash": "<96 hex characters>",
    "initrd_hash": "<96 hex characters>",
    "gpt_disk_guid_hash": "<96 hex characters>"
  }
}

No-attestation example:

{"type":"no_attestation"}

The actual header value is compact JSON on one line. The decoder preserves partial register policies and every alternative value in a register's expected_any list.

Portable measurement policies

The measurements format above specifies register values, so any change to platform-injected values (firmware, RAM size, disk count, ACPI tables) changes the expected register values even when the OS image is unchanged.

The dcap_image_hashes alternative allows you to specify the OS image's boot-component hashes instead, and the verifier reconstructs the expected register values from those hashes plus platform metadata available at attestation verification time. The same policy record then matches the same OS images across platform variants.

This can be done with the attest measure CLI from Easy-TEE/attest which outputs five hex-encoded SHA-384 values and, for images using a recent systemd EFI stub, one additional optional value:

  • uki_authenticode - authenticode hash of the UKI (unified kernel image)
  • kernel_authenticode - authenticode hash of the kernel binary
  • cmdline_hash - hash of the kernel command line
  • initrd_hash - hash of the initramfs
  • gpt_disk_guid_hash - hash derived from GPT partition GUIDs
  • pe_sections - optional accumulated hash of the UKI PE sections measured by recent systemd EFI stubs

Example:

[
  {
    "measurement_id": "flashbox-l1-v1.0.0",
    "attestation_type": "dcap-tdx",
    "dcap_image_hashes": {
      "uki_authenticode": "fcaceb6d87694746ba2d93a87ef4209f2a7629b7f400097b93241e80b9ec3e1e80f9a4cd8028e6a83f297ea5de8d9abc",
      "kernel_authenticode": "b6c5133268aa8b440509f3d53ee855a5cd3aeb6441eb109a9f27f14c43bce3e2383856df4af876501ceeb4c9a3b15f0c",
      "cmdline_hash": "e03b89abf354a38976537b7a9138fd312e4cbf73b61eebc44086491701b1d167b9f6cb97a922325866c93e0834723d87",
      "initrd_hash": "a5b3d4742045e7d08aa19953c35098e784826b01a84f60568fa69f1a848dafd96ec98b8df616d6142779c9b97318166b",
      "gpt_disk_guid_hash": "180bac1af9c35cc15e909623c005289539b4da2840d9c9b658fd4968ea4f03e0159402d03da1afc9035e0db30804e282"
    }
  }
]

Supported attestation types for portable measurements

Portable policies work with the "dcap-tdx" and "gcp-tdx" attestation types. "dcap-tdx" accepts DCAP evidence from any platform, including GCP and bare-metal TDX, while "gcp-tdx" restricts the record to GCP. For bare-metal DCAP TDX, MRTD selects a bundled trusted OVMF firmware blob. The verifier uses that blob and the platform metadata to reconstruct and check MRTD and RTMR0, in addition to the image-dependent RTMR1 and RTMR2. An unknown bare-metal MRTD is rejected. The accepted firmware assets and their provenance are documented in assets/ovmf. For GCP, the verifier instead fetches a Google-endorsed platform firmware blob keyed by MRTD.

The JSON object emitted directly by attest measure portable is also accepted as a measurement policy. Its optional azure PCR values and its dcap image hashes are converted into an Azure TDX record and a generic DCAP record respectively. It can be supplied on its own or as an element of a policy array, including an array mixed with records in the policy format described above:

{
  "kind": "portable",
  "azure": {
    "pcr4": "<64 hex characters>",
    "pcr9": "<64 hex characters>",
    "pcr11": "<64 hex characters>"
  },
  "dcap": {
    "uki_authenticode": "<96 hex characters>",
    "kernel_authenticode": "<96 hex characters>",
    "cmdline_hash": "<96 hex characters>",
    "initrd_hash": "<96 hex characters>",
    "gpt_disk_guid_hash": "<96 hex characters>"
  }
}

The non-portable "kind": "azure" output from attest measure is accepted in the same places and pins PCRs 4, 9, and 11. The "kind": "dcap" output is rejected because it only pins RTMR1 and RTMR2, leaving the other DCAP registers unconstrained. Use the portable output or an explicit measurement policy instead.