Skip to content

tee: TPM key provenance is host-dependent on Azure, and the local TPMT_SIGNATURE parser is a duplicate #77

Description

@imran-siddique

Two residues from #74 and #75, tracked so they do not live only in LIMITATIONS.md.

1. Chained verification cannot succeed on some Azure hosts

ca2a_verify/tpm_roots.py ships AZURE_VTPM_ROOT_2023_PEM as an opt-in anchor. Measured on hardware 2026-08-01: Azure Trusted Launch runs two vTPM CA hierarchies concurrently, and which one a VM presents is not selectable.

D2s_v5, eastus D2s_v7, eastus2
AK certificate 1596 bytes 994 bytes
Issuer Azure Cloud Virtual TPM CA - 11 Global Virtual TPM CA - 03
AIA extension present absent entirely
Chain to the shipped root yes no

On the second, there is nothing to walk and the intermediates are not in NV either, so verify_tpm_report fails closed with "AK chain root is not among the supplied trusted TPM roots". That is the correct outcome, because key provenance genuinely cannot be established there.

What this means for cA2A specifically: verify_offer reaches assurance="hardware" only where the peer's host happens to present the chainable hierarchy. A caller cannot know in advance whether a peer can meet that bar. The sealed-channel property that depends on it is therefore host-dependent, which is a stronger caveat than the one LIMITATIONS.md carried before #75.

Blocked on: obtaining the Global Virtual TPM CA - 03 root from a citable Microsoft source. That is a sourcing problem, not a code problem, and it is tracked on the cmcp side in agentrust-io/cmcp#453. When that root exists, pin both here.

Also still unproven: a collect-then-verify pass on a host that does present the chainable hierarchy. The 2026-08-01 run drew the no-AIA variant, so chained verification has never completed against a real vendor root in cA2A. Worth deliberately provisioning until a D2s_v5-class host appears.

2. parse_tpmt_signature is a duplicate

ca2a_verify/tpm.py carries its own TPMT_SIGNATURE parser because agent-manifest does not model that wire format, and its verify_tpm_quote takes a bare signature. cmcp carries the same parser for the same reason. This was a deliberate call when #74 landed (ship now, consolidate later) rather than an oversight.

Tracked upstream as agentrust-io/agent-manifest#255. When it lands: bump the agent-manifest floor, delete ParsedSignature and parse_tpmt_signature from ca2a_verify/tpm.py, and import instead. The tests in test_tpm_attest.py that cover the parser directly can then move upstream too.

What is already correct and needs nothing

Worth recording so this issue is not misread as the TPM path being broken. On real hardware the collector produced a genuine platform-AK quote, parse_tpmt_signature unwrapped a real TPMT_SIGNATURE (RSASSA/SHA-256), the bare signature verified against the shipped key, a tampered attest blob was rejected, and the quote's extraData equalled the derived key-and-nonce binding. Collector and verifier also ran in one process, which retired an earlier caveat. Only the chain step is host-dependent.

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