Repository navigation
docs(tee): record the NV extend hardware run, and correct the Azure PKI claim - #452
Merged
Merged
Conversation
…KI claim Ran the TPM path on a real Azure Trusted Launch vTPM (Standard_D2s_v7, eastus2, 2026-08-01). Two outcomes. The NV extend index from #451 works. Its TPM calls had been written against the documented tpm2-pytss API without ever touching a TPM, and every one of them is now exercised: nv_define_space with TPMA_NV.parse(...) | (TPM2_NT.EXTEND << 4) created a genuine extend index (TPM_NT = 4 read back from the public area), nv_extend accumulated as H(old || data) across successive calls, an existing index was reused rather than redefined, and a plain nv_write to the index was refused by the TPM. That last check is the one the tamper-evidence argument actually rests on, and it is confirmed independently by the TPM_NT field rather than only by an exception. The 2026-07-31 note in hardware-validation.md is wrong in its conclusion. It read the appearance of Azure Cloud Virtual TPM CA - 11 with a walkable AIA chain as "Azure has changed its vTPM PKI", superseding an earlier no-AIA observation. The 2026-08-01 host presented the older form again: 994 bytes, issued by Global Virtual TPM CA - 03, no AIA extension at all, and tpm2_getcap handles-nv-index confirmed the intermediates are not in NV as a fallback. So both hierarchies are live concurrently. This is variance, not a migration, and the difference matters: a migration reading says AIA can be relied on going forward, while the truth is that a host may present either and chained verification is impossible on the no-AIA one. Pinning the 2023 root does not make Azure verify everywhere. Worth re-reading against #431, which was closed on the basis that the runtime ships chains with its evidence. It does ship what it can assemble; on this fleet there is nothing to assemble. Docs and comments only. Refs #432, #451, #431 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ran the TPM path on a real Azure Trusted Launch vTPM (
Standard_D2s_v7, eastus2, 2026-08-01), specifically to validate the calls #451 shipped without ever executing against a TPM. Docs and comments only.1. The NV extend index from #451 works on hardware
Every TPM call in
cmcp_runtime.tee.measurementhad been written against the documented tpm2-pytss API and exercised only against a fake. All of them are now confirmed:nv_define_space(None, nv_public, auth_handle=ESYS_TR.OWNER)TPMA_NV.parse(...) | (TPM2_NT.EXTEND << 4)TPM_NT = 4read back from the public areatpm2_getcap handles-nv-index0x01500432, size 32nv_extend(handle, digest, auth_handle=ESYS_TR.OWNER)provisioned=TruethenFalsebefore2 == after1, value movedafter == H(before || digest)nv_writeto the indexThat last row is the load-bearing one: it is what makes an adversary unable to write a chosen clean value, and it is corroborated independently by the
TPM_NTfield rather than resting on an exception type. Thenv_extendkeyword-argument form this module uses is also confirmed correct.Still open for #432: the index value travels as an ordinary NV read that no signature covers, so it remains a local integrity control rather than remote-verifiable evidence.
TPM2_NV_Certifyis the remaining half.2. The 2026-07-31 Azure PKI conclusion is wrong
docs/testing/hardware-validation.mdcurrently says Azure "has changed its vTPM PKI", based on finding the AK certificate issued byAzure Cloud Virtual TPM CA - 11with a walkable AIA chain, superseding an earlier no-AIA observation.The 2026-08-01 host presented the older form again:
D2s_v5eastusD2s_v7eastus2Azure Cloud Virtual TPM CA - 11Global Virtual TPM CA - 03tpm2_getcap handles-nv-indexreturned only0x01C101D0plus the test index, so the intermediates are not stored in NV as a fallback either.This is fleet variance, not a migration, and the distinction changes the design assumption. A migration reading implies AIA can be relied on going forward. What is actually true is that an Azure Trusted Launch host may present either hierarchy, so AIA cannot be relied on, and on the no-AIA host chained verification is impossible: it fails closed with "AK chain root is not among the supplied trusted TPM roots", which is correct, because key provenance genuinely cannot be established there.
Pinning the 2023 root does not make Azure verify everywhere. A deployment must pin the root for the hierarchy its own hosts present, and a fleet spanning both needs both. Noted in the doc and in
cmcp_verify/tpm_roots.py.Worth a second look: #431
#431 was closed on the basis that the runtime ships AK certificate chains with its evidence. It does ship what it can assemble, and the code is correct. But on this fleet there is nothing to assemble, so "ships its chain" does not imply "provenance is verifiable". I have not reopened it, since the code change that closed it stands; flagging it as a judgement call for you.
Note for reviewers
python -m mypy srcreports 5 pre-existing errors insrc/cmcp_verify/tpm.py(hash_cls()instantiation, dating from #444), in a file this PR does not touch. CI passes, so CI's mypy invocation differs from a baremypy src; worth a separate look.🤖 Generated with Claude Code