Skip to content

security(tee): source and pin the Global Virtual TPM CA - 03 root, or Azure provenance is host-dependent #453

Description

@imran-siddique

Split out of #431, which stays closed: its code deliverable shipped and works. This is the gap that code cannot close on its own.

The problem

Azure Trusted Launch presents two vTPM attestation-key certificate hierarchies concurrently at NV index 0x01C101D0. Measured on hardware:

D2s_v5, eastus, 2026-07-31 D2s_v7, eastus2, 2026-08-01
Certificate size 1596 bytes 994 bytes
Issuer Azure Cloud Virtual TPM CA - 11 Global Virtual TPM CA - 03
AIA extension present, 4 caIssuers URIs absent entirely
Chain to a pinnable root yes, reaches Azure Virtual TPM Root Certificate Authority 2023 no

cmcp_verify/tpm_roots.py pins only the 2023 root, which the CA-11 hierarchy reaches over AIA. On a host presenting Global Virtual TPM CA - 03 there is no AIA to walk, and tpm2_getcap handles-nv-index confirmed the intermediates are not stored elsewhere in NV as a fallback. So the chain cannot be assembled at all and verify_tpm_quote fails with "AK chain root is not among the supplied trusted TPM roots".

That failure is correct: key provenance genuinely cannot be established on such a host with what we have. But the consequence is that cMCP's TPM provenance story works on some Azure VMs and not others, with no way for an operator to tell in advance. Details and the corrected doc are in #452.

What is needed

  1. Obtain the Global Virtual TPM CA - 03 certificate and its chain up to a self-signed root, from a Microsoft-published source that can be cited. It is not fetchable from the AK certificate itself, since there is no AIA.
  2. Pin that root alongside AZURE_VTPM_ROOT_2023_PEM, keyed by platform so trusted_roots_for can return both for Azure.
  3. Confirm on hardware that a host presenting this hierarchy then verifies end to end. That means provisioning until a VM of that generation is drawn, which is not directly selectable; D2s_v7 in eastus2 produced one on 2026-08-01.
  4. Decide what a deployment does when neither root matches: fail closed is the current behaviour and is probably right, but the error should name the unrecognised issuer so an operator can act on it rather than reading a generic chain failure.

Why not just walk AIA

There is nothing to walk. This is the constraint that makes the issue a sourcing problem rather than a code problem, and it is why it cannot be fixed the way #449 and #450 fixed the CA-11 path.

Open question for whoever picks this up

Whether Microsoft publishes the Global Virtual TPM CA - 03 intermediate and root in a durable, linkable location. If not, this becomes the same shape as the client firmware TPM vendor-root problem in the RFC's section 6: a dependency only the platform vendor can resolve. In that case the honest outcome is to document Azure TPM provenance as hierarchy-dependent rather than to keep implying it is universal.

Related: #431 (closed, shipped the chain-carrying code), #452 (the hardware finding), #439 section 6.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions