Summary
cmcp's SEV-SNP verifier (src/cmcp_verify/sev_snp.py) lags the equivalent in ca2a (src/ca2a_verify/sev_snp.py). ca2a already verifies the full VCEK -> ASK -> ARK chain to the AMD root, verifies the report signature with the VCEK key, and treats a report_data mismatch as fatal. cmcp does none of these. This is the same "verify-the-trust-anchor" class we care about: without chain verification the "measurement matches" check is self-referential, and a non-fatal report_data check means the confirmation-key binding / freshness is not enforced.
Current behavior (cmcp)
verify_sev_snp_measurement in src/cmcp_verify/sev_snp.py:
report_data mismatch is non-fatal. Docstring: "report_data (nonce) if provided (mismatch is not fatal)." On mismatch the code simply omits report_data from verified_fields instead of failing. report_data carries the confirmation-key binding / freshness nonce, so when a value is expected a mismatch should fail closed.
- VCEK/VLEK cert-chain verification is not implemented (explicitly out of scope; always placed in
unverified_fields). The report signature is never checked against the AMD root, so the "measurement matches" comparison hashes the report's own measurement field and compares it to the claim's own measurement string, both attacker-supplied.
Requested changes
- When
report_data_hex is supplied and does not equal the report's report_data, set verified = False with PUBLIC_KEY_NOT_BOUND (binding) or ATTESTATION_STALE (freshness), as appropriate.
- Port ca2a's chain verification: fetch VCEK by
chip_id + reported_tcb from AMD KDS, verify VCEK -> ASK -> ARK against a trusted AMD root, then verify the report signature over the report body with the VCEK key. ca2a/src/ca2a_verify/sev_snp.py is the reference implementation.
- Apply the same treatment to the TDX (Intel PCS/DCAP) and TPM paths.
- Guardrail: ensure the dispatcher can never return
VERIFIED while vcek_cert_chain (or the TDX/TPM equivalent) is in unverified_fields -- only PARTIALLY_VERIFIED.
Notes
- Keep the "software-only scope" language for dev-mode records; this is about not over-claiming on the hardware path.
- Internal security-review finding; suggested triage: security / high for item 1 (quick fix), feature for item 2.
Summary
cmcp's SEV-SNP verifier (src/cmcp_verify/sev_snp.py) lags the equivalent inca2a(src/ca2a_verify/sev_snp.py). ca2a already verifies the full VCEK -> ASK -> ARK chain to the AMD root, verifies the report signature with the VCEK key, and treats areport_datamismatch as fatal. cmcp does none of these. This is the same "verify-the-trust-anchor" class we care about: without chain verification the "measurement matches" check is self-referential, and a non-fatalreport_datacheck means the confirmation-key binding / freshness is not enforced.Current behavior (cmcp)
verify_sev_snp_measurementinsrc/cmcp_verify/sev_snp.py:report_datamismatch is non-fatal. Docstring: "report_data (nonce) if provided (mismatch is not fatal)." On mismatch the code simply omitsreport_datafromverified_fieldsinstead of failing.report_datacarries the confirmation-key binding / freshness nonce, so when a value is expected a mismatch should fail closed.unverified_fields). The report signature is never checked against the AMD root, so the "measurement matches" comparison hashes the report's ownmeasurementfield and compares it to the claim's ownmeasurementstring, both attacker-supplied.Requested changes
report_data_hexis supplied and does not equal the report'sreport_data, setverified = FalsewithPUBLIC_KEY_NOT_BOUND(binding) orATTESTATION_STALE(freshness), as appropriate.chip_id+reported_tcbfrom AMD KDS, verify VCEK -> ASK -> ARK against a trusted AMD root, then verify the report signature over the report body with the VCEK key.ca2a/src/ca2a_verify/sev_snp.pyis the reference implementation.VERIFIEDwhilevcek_cert_chain(or the TDX/TPM equivalent) is inunverified_fields-- onlyPARTIALLY_VERIFIED.Notes