Found while consolidating TPM verification across the stack (cmcp#447). cA2A's TPM provider is not merely thin, it is inconsistent with its own contract, and its report model cannot carry attestation evidence at all.
1. detect() and attest() disagree
BaseProvider's docstring states:
Real hardware providers (TPM, SEV-SNP, TDX, OPAQUE) are Tier 3 and are not implemented in this release; detect returns False for them so they are never selected automatically.
But TpmProvider.detect() returns True whenever a TPM device node exists:
@classmethod
def detect(cls) -> bool:
import os
return any(os.path.exists(dev) for dev in TPM_DEVICES)
while attest() raises unconditionally, even on a host that has a TPM:
def attest(self, public_key: str, nonce: str) -> AttestationReport:
raise AttestationUnsupported("TPM quote generation requires a real TPM", ...)
So on any Linux host with a TPM or vTPM, which includes every Azure Trusted Launch VM and most modern client hardware, auto-detection selects the TPM provider and then attestation fails. The error text also misreports the cause: it says no TPM is present when one is.
Either detect() should return False until the provider works, matching the documented contract, or attest() should work. The current pair is the worst combination: selected, then broken.
2. The report model cannot carry evidence
@dataclass(frozen=True)
class AttestationReport:
platform: str
measurement: str
public_key: str
nonce: str
There is no field for a quote, a signature, or a certificate chain. A relying party cannot verify anything from this: a measurement string with no signed evidence behind it is an assertion, not attestation. So even with attest() implemented, verification would be impossible by construction until the model grows.
For comparison, cmcp's AttestationReport now carries raw_evidence, quote_signature, attestation_key_pem, and attestation_key_chain_pem.
3. Why this matters beyond tidiness
cA2A's stated purpose includes verifying a peer's runtime attestation before delegating work to it, and sealing a payload to a verified measurement. Both depend on the peer's evidence being verifiable. Today the TPM tier cannot supply verifiable evidence, so on TPM-only hosts those properties are unavailable, which is a stronger statement than what LIMITATIONS.md currently records about the sealed channel.
Proposed work
- Make the contract honest now. Either return False from
detect() or gate it behind an explicit opt-in, and correct the error message so it does not claim the TPM is absent when it is present. Small, and it stops a misleading failure mode today.
- Extend
AttestationReport with the evidence fields, mirroring cmcp so the two are portable.
- Port the collector from cmcp, which is now hardware-validated: platform attestation key at persistent handle
0x81000003 with its certificate from NV 0x01C101D0, chunked NV read, AIA chain assembly, transient-key fallback.
- Delegate verification to
agent_manifest.verify_tpm_quote rather than writing a third implementation. cA2A already depends on agent-manifest>=0.5, and verify_tpm_quote is exported from the released 0.7.0. See cmcp#448 for the pattern and cmcp#447 for why three divergent copies is the problem being retired.
Step 1 is worth doing independently of the rest, since it is a live misleading failure on any TPM host.
Found while consolidating TPM verification across the stack (cmcp#447). cA2A's TPM provider is not merely thin, it is inconsistent with its own contract, and its report model cannot carry attestation evidence at all.
1.
detect()andattest()disagreeBaseProvider's docstring states:But
TpmProvider.detect()returns True whenever a TPM device node exists:while
attest()raises unconditionally, even on a host that has a TPM:So on any Linux host with a TPM or vTPM, which includes every Azure Trusted Launch VM and most modern client hardware, auto-detection selects the TPM provider and then attestation fails. The error text also misreports the cause: it says no TPM is present when one is.
Either
detect()should return False until the provider works, matching the documented contract, orattest()should work. The current pair is the worst combination: selected, then broken.2. The report model cannot carry evidence
There is no field for a quote, a signature, or a certificate chain. A relying party cannot verify anything from this: a measurement string with no signed evidence behind it is an assertion, not attestation. So even with
attest()implemented, verification would be impossible by construction until the model grows.For comparison, cmcp's
AttestationReportnow carriesraw_evidence,quote_signature,attestation_key_pem, andattestation_key_chain_pem.3. Why this matters beyond tidiness
cA2A's stated purpose includes verifying a peer's runtime attestation before delegating work to it, and sealing a payload to a verified measurement. Both depend on the peer's evidence being verifiable. Today the TPM tier cannot supply verifiable evidence, so on TPM-only hosts those properties are unavailable, which is a stronger statement than what
LIMITATIONS.mdcurrently records about the sealed channel.Proposed work
detect()or gate it behind an explicit opt-in, and correct the error message so it does not claim the TPM is absent when it is present. Small, and it stops a misleading failure mode today.AttestationReportwith the evidence fields, mirroring cmcp so the two are portable.0x81000003with its certificate from NV0x01C101D0, chunked NV read, AIA chain assembly, transient-key fallback.agent_manifest.verify_tpm_quoterather than writing a third implementation. cA2A already depends onagent-manifest>=0.5, andverify_tpm_quoteis exported from the released 0.7.0. See cmcp#448 for the pattern and cmcp#447 for why three divergent copies is the problem being retired.Step 1 is worth doing independently of the rest, since it is a live misleading failure on any TPM host.