From d9e1939199795d4861442b55f44980fa0f8a43cd Mon Sep 17 00:00:00 2001 From: Raghuvar Karthik Durga <177662415+drk-ram@users.noreply.github.com> Date: Sat, 31 Oct 2026 10:07:21 -0500 Subject: [PATCH] fix(verify): require TDX quote version 4 and validate expected digest lengths in verify_tdx_quote and verify_sev_snp_report (#283) Signed-off-by: Raghuvar Karthik Durga <177662415+drk-ram@users.noreply.github.com> --- src/ca2a_verify/sev_snp.py | 14 +++++++++++ src/ca2a_verify/tdx.py | 13 ++++++++++ tests/unit/test_tdx.py | 51 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+) diff --git a/src/ca2a_verify/sev_snp.py b/src/ca2a_verify/sev_snp.py index 61e7e48f..8cda04ab 100644 --- a/src/ca2a_verify/sev_snp.py +++ b/src/ca2a_verify/sev_snp.py @@ -83,6 +83,18 @@ def verify_sev_snp_report( ``agent_manifest.PLATFORM_INFO_BITS``. Both default to ``None``, which appraises nothing and leaves prior behaviour unchanged. """ + if expected_measurement is not None and ( + not isinstance(expected_measurement, (bytes, bytearray, memoryview)) + or len(expected_measurement) != 48 + ): + raise ValueError("expected_measurement must be a 48-byte SNP measurement") + if expected_report_data is not None and ( + not isinstance(expected_report_data, (bytes, bytearray, memoryview)) + or len(expected_report_data) != 64 + ): + raise ValueError("expected_report_data must be a 64-byte SNP report_data value") + if not isinstance(reject_unrecognized_platform_bits, bool): + raise ValueError("reject_unrecognized_platform_bits must be a boolean") report = SevSnpReport.parse(report_bytes) if report.signature_algo != SIG_ALGO_ECDSA_P384_SHA384: @@ -155,6 +167,8 @@ def sev_snp_verifier( raise ValueError("expected_measurement must be a 48-byte SNP measurement") if type(min_guest_svn) is not int or not 0 <= min_guest_svn <= 0xFFFFFFFF: raise ValueError("min_guest_svn must be an unsigned 32-bit integer") + if not isinstance(reject_unrecognized_platform_bits, bool): + raise ValueError("reject_unrecognized_platform_bits must be a boolean") roots, chain = list(trusted_roots), list(vcek_chain) required, forbidden = set(require_platform), set(forbid_platform) diff --git a/src/ca2a_verify/tdx.py b/src/ca2a_verify/tdx.py index 9f959d53..9e1447be 100644 --- a/src/ca2a_verify/tdx.py +++ b/src/ca2a_verify/tdx.py @@ -54,8 +54,21 @@ def verify_tdx_quote( expected_report_data: bytes | None = None, ) -> TdxQuote: """Appraise a TDX quote offline. Raises AttestationFailed on any failure.""" + if expected_mrtd is not None and ( + not isinstance(expected_mrtd, (bytes, bytearray, memoryview)) or len(expected_mrtd) != 48 + ): + raise ValueError("expected_mrtd must be a 48-byte TDX MRTD") + if expected_report_data is not None and ( + not isinstance(expected_report_data, (bytes, bytearray, memoryview)) + or len(expected_report_data) != 64 + ): + raise ValueError("expected_report_data must be a 64-byte TDX report_data value") quote = TdxQuote.parse(quote_bytes) + if quote.version != 4: + raise AttestationFailed( + "unsupported TDX quote version", detail=f"version={quote.version}, expected 4" + ) if quote.tee_type != TEE_TYPE_TDX: raise AttestationFailed("quote is not a TDX quote", detail=f"tee_type={quote.tee_type:#x}") diff --git a/tests/unit/test_tdx.py b/tests/unit/test_tdx.py index 51eefb1e..9da7e2d4 100644 --- a/tests/unit/test_tdx.py +++ b/tests/unit/test_tdx.py @@ -260,3 +260,54 @@ def test_trailing_padding_after_the_signature_section_is_ignored(quote_and_root) expected_mrtd=quote_and_root["mrtd"], expected_report_data=quote_and_root["rd"], ) + + +@pytest.mark.parametrize("bad_version", [3, 5, 0]) +def test_verify_tdx_quote_rejects_non_v4_version(quote_and_root, bad_version: int) -> None: + q = bytearray(quote_and_root["quote"]) + struct.pack_into(" None: + from ca2a_verify.sev_snp import sev_snp_verifier, verify_sev_snp_report + + with pytest.raises(ValueError): + verify_tdx_quote( + quote_and_root["quote"], + trusted_roots=[quote_and_root["root"]], + expected_mrtd=bad_mrtd, + expected_report_data=bad_rd, + ) + with pytest.raises(ValueError): + verify_sev_snp_report( + b"\x00" * 1184, + [], + trusted_roots=[], + expected_measurement=bad_mrtd, + expected_report_data=bad_rd, + ) + with pytest.raises(ValueError, match="reject_unrecognized_platform_bits"): + sev_snp_verifier( + trusted_roots=[], + vcek_chain=[], + expected_measurement=b"\x11" * 48, + min_guest_svn=1, + require_platform=set(), + forbid_platform=set(), + reject_unrecognized_platform_bits="yes", # type: ignore[arg-type] + )