Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 18 additions & 10 deletions python/src/agent_manifest/_tdx_verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,13 +195,12 @@ def parse_tdx_quote(quote: bytes, *, strict: bool = True) -> TdxQuote:

Args:
quote: the raw DCAP quote bytes.
strict: when ``True`` (default), enforce the production layout —
``version == 4`` and ``tee_type == 0x81`` — raising on anything
else. Pass ``strict=False`` to parse the header/body of an
otherwise well-formed quote whose version/tee_type differ (e.g.
synthetic test vectors), extracting the fields without asserting the
production TDX identity. Signature verification
(:func:`verify_tdx_quote`) is unaffected and always strict.
strict: when ``True`` (default), enforce the production header —
``version == 4``, ``att_key_type == 2`` (ECDSA-P256), and
``tee_type == 0x81`` — raising on anything else. Pass
``strict=False`` only for diagnostic field extraction from an
otherwise well-formed quote; that mode does not authorize any
cryptographic interpretation of the declared profile.
"""
if len(quote) < _QUOTE_HEADER_LEN + _TD_REPORT_LEN:
raise TdxVerificationError(
Expand All @@ -212,6 +211,10 @@ def parse_tdx_quote(quote: bytes, *, strict: bool = True) -> TdxQuote:
if strict:
if version != _TDX_QUOTE_VERSION:
raise TdxVerificationError(f"unsupported TDX quote version {version} (expected 4)")
if att_key_type != _ATT_KEY_TYPE_ECDSA_P256:
raise TdxVerificationError(
f"unsupported TDX attestation key type {att_key_type} (expected 2)"
)
if tee_type != _TEE_TYPE_TDX:
raise TdxVerificationError(f"not a TDX quote: tee_type {tee_type:#x}")
body = quote[_QUOTE_HEADER_LEN:_QUOTE_HEADER_LEN + _TD_REPORT_LEN]
Expand Down Expand Up @@ -248,11 +251,12 @@ def verify_tdx_quote(
) -> bool:
"""Fully verify an Intel TDX v4 DCAP quote (all four steps, fail-closed).

Returns True only when the attestation-key signature, the QE binding, the
Returns True only when the signed quote header declares the production
TDX-v4/ECDSA-P256 profile, the attestation-key signature, the QE binding, the
PCK signature over the QE report, and the PCK chain up to the pinned Intel
SGX Root CA all check out. Raises :class:`TdxVerificationError` on a
malformed quote / broken chain or if ``cryptography`` is unavailable; returns
False on a well-formed-but-invalid signature.
malformed/unsupported quote or broken chain, or if ``cryptography`` is
unavailable; returns False on a well-formed-but-invalid signature.

Every certificate in the PCK chain must be within its validity period (see
:func:`._cert_chain.check_validity_period`); an expired PCK leaf,
Expand All @@ -261,6 +265,10 @@ def verify_tdx_quote(

``trusted_root_pem`` overrides the embedded Intel root (for testing).
"""
# The signed header authorizes the only verification profile implemented
# here. Establish it before accepting any signature/certification semantics.
parse_tdx_quote(quote, strict=True)

try:
from cryptography import x509
from cryptography.exceptions import InvalidSignature
Expand Down
59 changes: 51 additions & 8 deletions python/tests/test_tdx_verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,17 @@ def _cert(subject, pub, issuer_name, issuer_key, ca=False):
return b.sign(issuer_key, hashes.SHA256())


def _build_quote(report_data_digest: bytes, mrtd: bytes = b"\x11" * 48):
"""Return (quote_bytes, test_root_pem) — a self-consistent TDX v4 quote."""
# Header (48): version=4, att_key_type=2, tee_type=0x81, + 40 bytes padding.
header = struct.pack("<HHI", 4, 2, 0x81) + bytes(40)
def _build_quote(
report_data_digest: bytes,
mrtd: bytes = b"\x11" * 48,
*,
version: int = 4,
att_key_type: int = 2,
tee_type: int = 0x81,
):
"""Return a self-consistent quote whose signed header is caller-controlled."""
# Header (48): caller-selected signed profile + 40 bytes padding.
header = struct.pack("<HHI", version, att_key_type, tee_type) + bytes(40)
body = bytearray(_BODY)
body[136:136 + 48] = mrtd
body[520:520 + 32] = report_data_digest # REPORTDATA[:32]
Expand Down Expand Up @@ -117,18 +124,54 @@ def test_parse_rejects_short():


def test_parse_rejects_wrong_tee_type():
quote, _ = _build_quote(hashlib.sha256(b"x").digest())
bad = bytearray(quote)
struct.pack_into("<I", bad, 4, 0x00) # tee_type = SGX, not TDX
quote, _ = _build_quote(hashlib.sha256(b"x").digest(), tee_type=0x00)
with pytest.raises(TdxVerificationError, match="not a TDX quote"):
parse_tdx_quote(bytes(bad))
parse_tdx_quote(quote)


def test_parse_rejects_wrong_attestation_key_type():
quote, _ = _build_quote(hashlib.sha256(b"keytype").digest(), att_key_type=3)
with pytest.raises(TdxVerificationError, match="attestation key type 3"):
parse_tdx_quote(quote)


def test_parse_non_strict_is_diagnostic_only():
quote, _ = _build_quote(
hashlib.sha256(b"diagnostic").digest(),
version=5,
att_key_type=3,
tee_type=0x00,
)
parsed = parse_tdx_quote(quote, strict=False)
assert parsed.version == 5
assert parsed.tee_type == 0x00


def test_verify_full_chain_ok():
quote, root_pem = _build_quote(hashlib.sha256(b"pre").digest())
assert verify_tdx_quote(quote, trusted_root_pem=root_pem) is True


@pytest.mark.parametrize(
("header_overrides", "message"),
[
({"version": 5}, "unsupported TDX quote version 5"),
({"att_key_type": 3}, "attestation key type 3"),
({"tee_type": 0x00}, "not a TDX quote"),
],
)
def test_verify_rejects_self_consistent_signed_unsupported_header(
header_overrides: dict[str, int], message: str
):
"""The header mutation is included in signed_body and re-signed by the same builder."""
quote, root_pem = _build_quote(
hashlib.sha256(b"profile-binding").digest(),
**header_overrides,
)
with pytest.raises(TdxVerificationError, match=message):
verify_tdx_quote(quote, trusted_root_pem=root_pem)


# ---------------------------------------------------------------------------
# CERT-011: every certificate in the PCK chain must be within its validity
# period. verify_tdx_quote used to check signatures only; a PCK leaf,
Expand Down
Loading