Problem statement
PeerNode grew the mutual-attestation knobs in #89 and #90: require_caller_attestation (the none / any / hardware ladder), caller_verifier, and challenge_ttl_seconds. None of them are reachable from ca2a start. Ca2aConfig has no fields for them, bootstrap.build_peer_node never passes them, and docs/configuration.md does not mention them.
So the protocol is only usable from Python. Anyone running the reference transport through the CLI gets a callee that never appraises its callers and cannot be told to. LIMITATIONS.md describes mutual attestation as "off by default"; through ca2a start it is off with no switch. That also means the hardware verifier seam, the thing #47 section 3 needs a callee to exercise on a confidential VM, cannot be driven from a config file.
I added ca2a start in #52, so this is a gap in my own work.
Proposed solution
Extend the attestation block of the config and thread it through bootstrap:
attestation:
provider: tpm
require_caller_attestation: hardware # none (default) | any | hardware
caller_verifier:
platform: tpm
trusted_roots_path: vtpm-roots.pem # resolved relative to the config file
challenge_ttl_seconds: 60
Behaviour, all fail-closed and mirroring what PeerNode already enforces:
require_caller_attestation is validated against peer.REQUIREMENT_VALUES; anything else is a CONFIG_ERROR.
hardware without caller_verifier is a startup error, not a per-call refusal. PeerNode already does this; the config layer should surface it with the field name.
caller_verifier.platform: tpm builds ca2a_verify.tpm.tpm_verifier(roots_pem) from the PEM file. Missing or empty file is a CONFIG_ERROR.
caller_verifier.platform: sev-snp or tdx is rejected with a message saying why: there is no AttestationReport-shaped verifier for either yet (verify_sev_snp_report and verify_tdx_quote exist, but they take raw bytes and a certificate chain, and the SNP collector deliberately drops the auxblob rather than guess at its format). Refusing is better than a config that looks like it appraises and does not.
challenge_ttl_seconds must be a positive integer.
ca2a start prints the requirement rung on startup alongside the provider, so an operator can see what the callee will demand.
require_holder_proof is deliberately left out. It defaults to on and turning it off is a downgrade with no attestation rationale; if a case for exposing it appears it can come separately.
Alternatives considered
- Leave it library-only and document that. Rejected:
ca2a start exists so that a config file is enough to run a peer, and the transport spec already lists it as implemented. A CLI that cannot reach half the protocol undercuts that.
- Expose a generic
caller_verifier: <dotted.path> import hook. Rejected: it turns a config file into code execution and hides which platforms actually have a working verifier.
- Ship SNP and TDX report-level verifiers in the same change. Rejected for scope: that is real design work (KDS fetch or auxblob parsing for the VCEK chain, root pinning, measurement policy) and should be its own issue. This change should refuse those platforms clearly rather than block on them.
Security/TEE impact
Positive. It lets a config-driven callee demand caller attestation, which today it cannot. All new paths fail closed: unknown rung, missing verifier under hardware, unsupported verifier platform, unreadable roots file. No change to the wire format, the record hash, or what PeerNode accepts.
Spec alignment
No trace-spec change required. docs/spec/mutual-attestation.md already documents the ladder and the requirement that "hardware" without a verifier is refused at construction; this exposes it through the existing config surface. docs/configuration.md gets the new fields.
Problem statement
PeerNodegrew the mutual-attestation knobs in #89 and #90:require_caller_attestation(thenone/any/hardwareladder),caller_verifier, andchallenge_ttl_seconds. None of them are reachable fromca2a start.Ca2aConfighas no fields for them,bootstrap.build_peer_nodenever passes them, anddocs/configuration.mddoes not mention them.So the protocol is only usable from Python. Anyone running the reference transport through the CLI gets a callee that never appraises its callers and cannot be told to. LIMITATIONS.md describes mutual attestation as "off by default"; through
ca2a startit is off with no switch. That also means the hardwareverifierseam, the thing #47 section 3 needs a callee to exercise on a confidential VM, cannot be driven from a config file.I added
ca2a startin #52, so this is a gap in my own work.Proposed solution
Extend the
attestationblock of the config and thread it throughbootstrap:Behaviour, all fail-closed and mirroring what
PeerNodealready enforces:require_caller_attestationis validated againstpeer.REQUIREMENT_VALUES; anything else is aCONFIG_ERROR.hardwarewithoutcaller_verifieris a startup error, not a per-call refusal.PeerNodealready does this; the config layer should surface it with the field name.caller_verifier.platform: tpmbuildsca2a_verify.tpm.tpm_verifier(roots_pem)from the PEM file. Missing or empty file is aCONFIG_ERROR.caller_verifier.platform: sev-snportdxis rejected with a message saying why: there is noAttestationReport-shaped verifier for either yet (verify_sev_snp_reportandverify_tdx_quoteexist, but they take raw bytes and a certificate chain, and the SNP collector deliberately drops the auxblob rather than guess at its format). Refusing is better than a config that looks like it appraises and does not.challenge_ttl_secondsmust be a positive integer.ca2a startprints the requirement rung on startup alongside the provider, so an operator can see what the callee will demand.require_holder_proofis deliberately left out. It defaults to on and turning it off is a downgrade with no attestation rationale; if a case for exposing it appears it can come separately.Alternatives considered
ca2a startexists so that a config file is enough to run a peer, and the transport spec already lists it as implemented. A CLI that cannot reach half the protocol undercuts that.caller_verifier: <dotted.path>import hook. Rejected: it turns a config file into code execution and hides which platforms actually have a working verifier.Security/TEE impact
Positive. It lets a config-driven callee demand caller attestation, which today it cannot. All new paths fail closed: unknown rung, missing verifier under
hardware, unsupported verifier platform, unreadable roots file. No change to the wire format, the record hash, or whatPeerNodeaccepts.Spec alignment
No trace-spec change required.
docs/spec/mutual-attestation.mdalready documents the ladder and the requirement that "hardware" without a verifier is refused at construction; this exposes it through the existing config surface.docs/configuration.mdgets the new fields.