fix(attestation): include Azure vTPM AK intermediates#49
Conversation
Fetch Azure vTPM AK issuer certificates from the leaf certificate's AIA CA Issuers URLs during attestation generation and serialize them into the Azure evidence document. During verification, treat evidence-supplied intermediates as untrusted chain material, combine them with the legacy bundled intermediate for backwards compatibility, and continue pinning Azure vTPM roots. Bound the number of serialized intermediates during deserialization and keep stripping TPM NV zero padding before WebPKI verification. Add offline AK-chain fixtures for the observed Azure Cloud Virtual TPM CA - 24 -> Azure Cloud Virtual TPM CA 2025 chain.
ameba23
left a comment
There was a problem hiding this comment.
Thank you for the PR! We have let support for Azure slip, because we currently have no production Azure instances. So wonderful to have this fix contributed.
Very good idea to do the intermediary certificate fetching on the 'attester' side rather than the verifier side. One thing we could consider for a followup would be to cache these to avoid re-fetching on subsequent attestation generation.
Great to have the test assets with observed intermediaries. I think it could be useful to have a complete observed attestation payload to test against. We could spin up an Azure instance and get one if you don't want to add this yourself.
We use stable rust but nightly for formatting / clippy, which is why CI fails. This is unconventional and not documented (sorry). Could you please run cargo +nightly fmt && cargo +nightly clippy. Thanks.
| pub(crate) fn fetch_ak_intermediates_from_aia( | ||
| ak_cert: &X509Certificate<'_>, | ||
| ) -> Result<Vec<Vec<u8>>, MaaError> { | ||
| const MAX_AIA_DEPTH: usize = 6; |
There was a problem hiding this comment.
This seems to be too arbitrary. Why hardcode the depth? why not fetch all intermediates to verify the full chain? what happens if there were slightly more than 6 for example? would the full chain verification up to the Azure Root CA anchor still succeed?
| }; | ||
|
|
||
| aia.iter().find_map(|desc| { | ||
| if desc.access_method.to_id_string() != "1.3.6.1.5.5.7.48.2" { |
There was a problem hiding this comment.
small nit:
what is this hardcoded "1.3.6.1.5.5.7.48.2"? is this some kind of OID? could you please put it behind a const variable with identifiable name to know what it represents?
| if bytes.starts_with(b"-----BEGIN") { | ||
| let (_type_label, der) = pem_rfc7468::decode_vec(&bytes)?; | ||
| Ok(der) | ||
| } else { | ||
| Ok(bytes) | ||
| } |
There was a problem hiding this comment.
small nit: (no need to change but just curious)
I wonder if this "if condition" is necessary and is not already handled by the pem_rfc7468::decode_vec function instead.
|
@samlaf , thank you for surfacing this new change from Azure vTPM attestation process side as well as for your contribution to solve the issue. |
This fixes Azure TDX/vTPM attestation verification failures caused by newer Azure vTPM AK certificate chains that are not covered by the currently bundled
Global Virtual TPM CA - 03intermediate.On a current Azure TDX CVM, the AK leaf certificate chain can look like:
The TPM NV AK cert index on this VM contained only:
so parsing additional intermediates from the TPM AK cert payload is not sufficient for this chain.
This PR changes Azure attestation generation to fetch the AK issuer chain from the leaf certificate’s AIA
CA IssuersURLs and serialize those intermediates into the attestation evidence. Verification remains offline: the verifier treats these intermediates as untrusted chain material and still pins the Azure vTPM roots.Microsoft guidance indicates that AIA should be used to discover Azure vTPM intermediate CAs, since public docs can lag behind production intermediate rotation:
https://learn.microsoft.com/en-us/answers/questions/5897616/download-intermediate-ca-cert-for-azure-cloud-virt
Changes
Adds optional evidence field:
During Azure attestation generation:
CA IssuersURLs up to a bounded depth;During verification:
Keeps verification network-free/deterministic.
Keeps
#[serde(default)]on the new field so old evidence still deserializes.Adds offline fixture tests for the observed Azure chain:
Azure Cloud Virtual TPM CA - 24Azure Cloud Virtual TPM CA 2025Security notes
The fetched intermediates are not trusted anchors. They are serialized as untrusted evidence only. Verification still requires the AK certificate chain to terminate at a pinned Azure vTPM root.
The number of evidence-supplied intermediates is capped to avoid unbounded peer-controlled chain material.
Behavior change
Azure attestation generation now makes outbound HTTP(S) requests to Microsoft PKI/AIA endpoints in order to fetch issuer certificates.
Azure attestation verification does not make network requests.
Validation
Tested with this code on an Azure Standard_DC4eds_v6 TDX box on westus3. Before this change, verification failed with:
After this change:
The generated evidence includes two serialized AK intermediates:
This subsumes #48.
PR #48 improves handling for cases where the TPM AK cert payload contains concatenated DER intermediates. However, on the Azure TDX VM tested here, the TPM AK cert NV index contained only:
and no intermediate certificates. Therefore #48 does not fix the observed
UnknownIssuerfailure for this chain.This PR handles that case by following the AIA issuer URLs from the AK leaf certificate and including the resulting issuer chain in the evidence.