Skip to content

TPM provider: detect() selects a provider whose attest() always raises, and the report model cannot carry evidence #73

Description

@imran-siddique

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

  1. 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.
  2. Extend AttestationReport with the evidence fields, mirroring cmcp so the two are portable.
  3. 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.
  4. 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.

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