Skip to content

fix(tee): resolve the AK issuer from a vendored bundle when there is no AIA - #520

Open
zohebk8s wants to merge 1 commit into
agentrust-io:mainfrom
zohebk8s:fix/ca03-chain-assembly
Open

fix(tee): resolve the AK issuer from a vendored bundle when there is no AIA#520
zohebk8s wants to merge 1 commit into
agentrust-io:mainfrom
zohebk8s:fix/ca03-chain-assembly

Conversation

@zohebk8s

Copy link
Copy Markdown
Collaborator

Refs #453.

The problem

Azure Trusted Launch presents more than one vTPM attestation-key hierarchy at NV index 0x01C101D0. The one issued by Global Virtual TPM CA - 03 carries no AIA extension, so _chain_from_leaf has nothing to walk, stops at the leaf, and the chain never reaches a pinned root. verify_tpm_quote then fails with "AK chain root is not among the supplied trusted TPM roots", which names the root although the actual cause is that chain[-1] is still the leaf.

The same dependency blocks the NV certify path: platform_attestation_key() calls _chain_from_leaf, so the gateway measurement proof from #432 has the same failure on the same hosts. The 2026-08-01 certify run had to be anchored on the leaf for exactly this reason.

Why this is not a new trust anchor

Both hierarchies terminate at Azure Virtual TPM Root Certificate Authority 2023 — the root already pinned in cmcp_verify/tpm_roots.py. Global Virtual TPM CA - 03 is issued directly by it. What is missing on a CA-03 host is therefore the intermediate, not an anchor.

Microsoft publishes that intermediate only inline in the Trusted Launch FAQ, with no fetchable URL (the equivalent pkiops path 404s), so it has to be vendored.

Verification of the vendored bytes, against Microsoft's PKI server rather than the docs page alone:

  • The root downloaded from microsoft.com/pkiops/certs/Azure%20Virtual%20TPM%20Root%20Certificate%20Authority%202023.crt is byte-identical (DER) to both the docs-page copy and to AZURE_VTPM_ROOT_2023_PEM as it stands in tpm_roots.py. All three: sha256 e6c596b17f8ffefba5c300f714cfb1260c6028704ecf7becc4ab5018efb00e95.
  • Global Virtual TPM CA - 03 verifies under that downloaded root, and is not on its CRL.
  • Negative controls: it is correctly rejected under an unrelated root, and a single flipped base64 character in its signature is correctly rejected under the real root.
subject  CN = Global Virtual TPM CA - 03
issuer   CN = Azure Virtual TPM Root Certificate Authority 2023
serial   33000000092740E5AC727B0EA6000000000009
validity 2025-04-24 → 2027-04-24
sha256   FD:7C:92:DA:BC:E4:DC:EC:9F:EA:A3:0F:8B:08:7A:DA:
         05:98:41:31:89:18:21:52:B8:81:F5:56:40:64:A2:9C
SKI      67:09:86:F8:F9:81:5E:93:AD:F9:C9:64:E7:14:0A:DA:99:CB:0A:2D

What changed

  • New src/cmcp_runtime/tee/vtpm_ca_bundle.py: the two certificates, with provenance and fingerprints in comments, plus vendored_issuer_for().
  • _chain_from_leaf consults the bundle when AIA is absent or yields nothing, so the chain becomes [leaf, ICA-03, root] and terminates at the pinned root.
  • The function docstring no longer claims a missing AIA ends the walk.

Vendoring a public CA certificate inline is the existing pattern here — INTEL_SGX_ROOT_CA_PEM in agent_manifest/_tdx_verify.py, the AMD chain handling in _snp_verify.py, and tpm_roots.py itself. The difference is that those pin roots, and this pins an intermediate on a much shorter rotation cycle, which is what the expiry test is for.

Security impact

Per CONTRIBUTING.md, this touches TEE provider integration.

The trust set does not change. Nothing in the new module is a trust anchor. The certificates are chain-building material supplied in place of an AIA fetch, and the pinned root set in cmcp_verify/tpm_roots.py is untouched.

Selection requires a signature, not a name. vendored_issuer_for returns a candidate only after verify_directly_issued_by confirms it actually signed the certificate being resolved. A subject name is not an identity, so if Microsoft has re-keyed a CA under the same name, the impostor cannot enter the chain.

The failure mode is unchanged. When no vendored certificate verifiably signed the leaf, the walk stops exactly as it does today and a short chain travels, which then fails closed at the root pin. This cannot make a CA-03 host worse than it currently is, which is why it is safe to land ahead of the hardware validation below.

A self-signed certificate cannot resolve to itself, guarded inside vendored_issuer_for rather than relying on the caller's ordering.

Tests

7 new tests in tests/unit/test_vtpm_ca_bundle.py, none requiring a TPM or network:

  • chain assembly with no AIA reaches the root (synthetic PKI, so the leaf is genuinely signed)
  • an unresolvable issuer still ships a partial chain rather than raising
  • a same-name/different-key CA is refused
  • a self-signed certificate does not resolve to itself
  • the real CA-03 resolves to the real root
  • the bundled root stays byte-identical to the pinned root (drift guard)
  • the bundled intermediate is not within 90 days of expiry

Remaining validation, not claimed by this PR

Real-hardware CA-03 verification has not been performed. The chain-assembly tests use a synthetic PKI because signing a leaf under Global Virtual TPM CA - 03 requires Microsoft's private key. What is proven against the real published bytes is ICA-03 → root and the root pin. The leaf → ICA-03 link is not.

Concretely, this is unconfirmed: that the Global Virtual TPM CA - 03 vendored here is the same certificate a CA-03 host presents. The subject name is not an identity. The check is whether the AK certificate's AKI on such a host equals the SKI above, and the original D2s_v7 AK certificate artifact is no longer available.

Closing it needs a host presenting the CA-03 hierarchy, which cannot be selected deliberately. The published notes establish two observations only — D2s_v5/eastus/2026-07-31 on CA-11 and D2s_v7/eastus2/2026-08-01 on CA-03 — and they do not separate SKU, region, generation or time. This is observed fleet variance, not selectable behaviour.

If that check fails, the signature gate means the result is today's behaviour, not a wrong chain.

Out of scope

Point 4 of #453 (naming the unrecognised issuer to the operator) is only half-addressable here: this adds a debug log at the truncation point, but the operator-facing message is raised in agent_manifest/_cert_chain.py.

…no AIA

Azure Trusted Launch presents more than one vTPM attestation-key hierarchy at
NV index 0x01C101D0. The one issued by Global Virtual TPM CA - 03 carries no
AIA extension, so _chain_from_leaf has nothing to walk, stops at the leaf, and
the chain never reaches a pinned root. Verification then fails closed with
"AK chain root is not among the supplied trusted TPM roots", which names the
root although the actual cause is that chain[-1] is still the leaf.

Both hierarchies terminate at the root already pinned in cmcp_verify.tpm_roots,
so what is missing is the intermediate, not a trust anchor. Microsoft publishes
it only inline in the Trusted Launch FAQ, with no fetchable URL, so it is
vendored here and consulted when AIA is absent or yields nothing.

This does not widen what is trusted. vendored_issuer_for returns a certificate
only after checking it actually signed the one being resolved, so a same-named
CA with a different key cannot enter the chain; the worst case stays the
present behaviour of a short chain that fails to reach a pinned root.

The leaf -> CA-03 link is unverified against hardware: no CA-03 host was
available. ICA-03 -> root and the root pin are verified against the bytes
Microsoft publishes, and a test fails 90 days ahead of the intermediate's
2027-04-24 expiry since there is no URL to poll for its replacement.

Signed-off-by: Zoheb <zoheb.shaik7@gmail.com>
@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

🟡 Contributor Check: MEDIUM

Check Result
Profile MEDIUM
Credential LOW
Overall MEDIUM

Automated check by AgenTrust Contributor Check.

@github-actions github-actions Bot added the needs-review:MEDIUM Contributor check flagged MEDIUM risk label Aug 17, 2026
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-review:MEDIUM Contributor check flagged MEDIUM risk

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants