Add Tinfoil conformance testing#93
Conversation
There was a problem hiding this comment.
10 issues found across 5 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/tinfoil/conformance/cli.py">
<violation number="1" location="src/tinfoil/conformance/cli.py:944">
P1: Custom agent: **Check System Design and Architectural Patterns**
Runtime monkey-patching of library module globals from CLI code creates tight coupling and bypasses explicit interfaces. The conformance CLI directly mutates `collateral_tdx.datetime`, `cert_utils.datetime`, `fetch_collateral`, and `intel_root_ca.INTEL_SGX_ROOT_CA_PEM` rather than using dependency injection or parameterized library calls. This breaks proper layering and makes internal library refactors hazardous.</violation>
<violation number="2" location="src/tinfoil/conformance/cli.py:1008">
P1: CRLs are accepted in `_evaluate_collateral` without signature verification, enabling revocation-check bypass with injected collateral.</violation>
<violation number="3" location="src/tinfoil/conformance/cli.py:1261">
P2: Invalid `min_tee_tcb_svn_hex` is silently ignored, effectively disabling the minimum TCB policy check.</violation>
<violation number="4" location="src/tinfoil/conformance/cli.py:1568">
P2: Mandatory VCEK TCB extension checks are bypassed when extensions are missing or malformed due to `continue` paths.</violation>
<violation number="5" location="src/tinfoil/conformance/cli.py:1689">
P1: Custom agent: **Flag Security Vulnerabilities**
Unbounded gzip decompression of untrusted attestation input enables memory/CPU DoS via a crafted gzip bomb.</violation>
</file>
Tip: instead of fixing issues one by one fix them all with cubic
Re-trigger cubic
| return ("REPORT_FORMAT_UNSUPPORTED", "3.1", | ||
| f"attestation_doc_b64 not valid base64: {e}") | ||
| try: | ||
| report_bytes = _gzip.decompress(gz_bytes) |
There was a problem hiding this comment.
P1: Custom agent: Flag Security Vulnerabilities
Unbounded gzip decompression of untrusted attestation input enables memory/CPU DoS via a crafted gzip bomb.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/tinfoil/conformance/cli.py, line 1689:
<comment>Unbounded gzip decompression of untrusted attestation input enables memory/CPU DoS via a crafted gzip bomb.</comment>
<file context>
@@ -0,0 +1,2029 @@
+ return ("REPORT_FORMAT_UNSUPPORTED", "3.1",
+ f"attestation_doc_b64 not valid base64: {e}")
+ try:
+ report_bytes = _gzip.decompress(gz_bytes)
+ except Exception as e:
+ return ("REPORT_FORMAT_UNSUPPORTED", "3.1",
</file context>
| root_crl_obj = None | ||
| if pck_crl_der: | ||
| crl = x509.load_der_x509_crl(pck_crl_der) | ||
| pck_crl_obj = PckCrl(crl=crl, ca_type="platform", |
There was a problem hiding this comment.
P1: CRLs are accepted in _evaluate_collateral without signature verification, enabling revocation-check bypass with injected collateral.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/tinfoil/conformance/cli.py, line 1008:
<comment>CRLs are accepted in `_evaluate_collateral` without signature verification, enabling revocation-check bypass with injected collateral.</comment>
<file context>
@@ -0,0 +1,2029 @@
+ root_crl_obj = None
+ if pck_crl_der:
+ crl = x509.load_der_x509_crl(pck_crl_der)
+ pck_crl_obj = PckCrl(crl=crl, ca_type="platform",
+ next_update=crl.next_update_utc or datetime.now(timezone.utc))
+ if root_crl_der:
</file context>
| from ..attestation import cert_utils as _cert_mod | ||
| orig_coll_dt = _coll_mod.datetime | ||
| orig_cert_dt = _cert_mod.datetime | ||
| _coll_mod.datetime = _FixedDatetime |
There was a problem hiding this comment.
P1: Custom agent: Check System Design and Architectural Patterns
Runtime monkey-patching of library module globals from CLI code creates tight coupling and bypasses explicit interfaces. The conformance CLI directly mutates collateral_tdx.datetime, cert_utils.datetime, fetch_collateral, and intel_root_ca.INTEL_SGX_ROOT_CA_PEM rather than using dependency injection or parameterized library calls. This breaks proper layering and makes internal library refactors hazardous.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/tinfoil/conformance/cli.py, line 944:
<comment>Runtime monkey-patching of library module globals from CLI code creates tight coupling and bypasses explicit interfaces. The conformance CLI directly mutates `collateral_tdx.datetime`, `cert_utils.datetime`, `fetch_collateral`, and `intel_root_ca.INTEL_SGX_ROOT_CA_PEM` rather than using dependency injection or parameterized library calls. This breaks proper layering and makes internal library refactors hazardous.</comment>
<file context>
@@ -0,0 +1,2029 @@
+ from ..attestation import cert_utils as _cert_mod
+ orig_coll_dt = _coll_mod.datetime
+ orig_cert_dt = _cert_mod.datetime
+ _coll_mod.datetime = _FixedDatetime
+ _cert_mod.datetime = _FixedDatetime
+ try:
</file context>
| ] | ||
| for name, oid, report_val in tcb_pairs: | ||
| raw = ext_map.get(oid) | ||
| if raw is None: |
There was a problem hiding this comment.
P2: Mandatory VCEK TCB extension checks are bypassed when extensions are missing or malformed due to continue paths.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/tinfoil/conformance/cli.py, line 1568:
<comment>Mandatory VCEK TCB extension checks are bypassed when extensions are missing or malformed due to `continue` paths.</comment>
<file context>
@@ -0,0 +1,2029 @@
+ ]
+ for name, oid, report_val in tcb_pairs:
+ raw = ext_map.get(oid)
+ if raw is None:
+ continue
+ cert_val = _decode_int_ext_value(raw)
</file context>
| f"tee_tcb_svn[{i}]={tee_tcb_svn[i]} < min[{i}]={minimum[i]} " | ||
| f"(quote={tee_tcb_svn.hex()}, minimum={minimum.hex()})") | ||
| except ValueError: | ||
| pass |
There was a problem hiding this comment.
P2: Invalid min_tee_tcb_svn_hex is silently ignored, effectively disabling the minimum TCB policy check.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/tinfoil/conformance/cli.py, line 1261:
<comment>Invalid `min_tee_tcb_svn_hex` is silently ignored, effectively disabling the minimum TCB policy check.</comment>
<file context>
@@ -0,0 +1,2029 @@
+ f"tee_tcb_svn[{i}]={tee_tcb_svn[i]} < min[{i}]={minimum[i]} "
+ f"(quote={tee_tcb_svn.hex()}, minimum={minimum.hex()})")
+ except ValueError:
+ pass
+
+ return "", ""
</file context>
0433927 to
f198856
Compare
There was a problem hiding this comment.
2 issues found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/tinfoil/conformance/cli.py">
<violation number="1" location="src/tinfoil/conformance/cli.py:2034">
P2: Missing top-level JSON object validation can crash the CLI on non-object input.</violation>
<violation number="2" location="src/tinfoil/conformance/cli.py:2040">
P2: Hex parsing error handling is incomplete; non-string report_data_hex bypasses the current exception handler.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Fix all with cubic | Re-trigger cubic
|
|
||
| try: | ||
| report_data = bytes.fromhex(inp.get("report_data_hex", "")) | ||
| except ValueError as e: |
There was a problem hiding this comment.
P2: Hex parsing error handling is incomplete; non-string report_data_hex bypasses the current exception handler.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/tinfoil/conformance/cli.py, line 2040:
<comment>Hex parsing error handling is incomplete; non-string report_data_hex bypasses the current exception handler.</comment>
<file context>
@@ -2003,6 +2010,88 @@ def cmd_verify_full() -> int:
+
+ try:
+ report_data = bytes.fromhex(inp.get("report_data_hex", ""))
+ except ValueError as e:
+ sys.stderr.write(f"report_data_hex not valid hex: {e}\n")
+ return EXIT_BAD_INPUT
</file context>
| except ValueError as e: | |
| except (TypeError, ValueError) as e: |
| except json.JSONDecodeError as e: | ||
| sys.stderr.write(f"input is not valid JSON: {e}\n") | ||
| return EXIT_BAD_INPUT | ||
| if inp.get("schema_version") != "1": |
There was a problem hiding this comment.
P2: Missing top-level JSON object validation can crash the CLI on non-object input.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/tinfoil/conformance/cli.py, line 2034:
<comment>Missing top-level JSON object validation can crash the CLI on non-object input.</comment>
<file context>
@@ -2003,6 +2010,88 @@ def cmd_verify_full() -> int:
+ except json.JSONDecodeError as e:
+ sys.stderr.write(f"input is not valid JSON: {e}\n")
+ return EXIT_BAD_INPUT
+ if inp.get("schema_version") != "1":
+ sys.stderr.write('input.schema_version != "1"\n')
+ return EXIT_BAD_INPUT
</file context>
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/tinfoil/conformance/cli.py">
<violation number="1" location="src/tinfoil/conformance/cli.py:1340">
P2: Custom agent: **Nuke belt-and-suspenders**
Broad `except Exception: pass` swallows re-parsing errors for data already validated upstream, constituting unnecessary belt-and-suspenders defensive coding.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Fix all with cubic | Re-trigger cubic
| try: | ||
| _tb = parse_tdx_quote(base64.b64decode(inp["quote_b64"])).td_quote_body | ||
| config_kwargs.setdefault("expected_td_attributes", _tb.td_attributes) | ||
| config_kwargs.setdefault("expected_xfam", _tb.xfam) | ||
| config_kwargs.setdefault("expected_minimum_tee_tcb_svn", _tb.tee_tcb_svn) | ||
| config_kwargs.setdefault("accepted_mr_seams", (_tb.mr_seam,)) | ||
| except Exception: | ||
| pass |
There was a problem hiding this comment.
P2: Custom agent: Nuke belt-and-suspenders
Broad except Exception: pass swallows re-parsing errors for data already validated upstream, constituting unnecessary belt-and-suspenders defensive coding.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/tinfoil/conformance/cli.py, line 1340:
<comment>Broad `except Exception: pass` swallows re-parsing errors for data already validated upstream, constituting unnecessary belt-and-suspenders defensive coding.</comment>
<file context>
@@ -1291,6 +1327,24 @@ def _cmd_verify_attestation_tdx_public(inp: dict[str, Any], policy: dict[str, An
+ # synthetic vectors at the baseline gate before the fixture's targeted
+ # check. Re-baseline to the quote's own values; the fixture's SPEC §4.8
+ # pins are then enforced precisely by _enforce_extended_policy below.
+ try:
+ _tb = parse_tdx_quote(base64.b64decode(inp["quote_b64"])).td_quote_body
+ config_kwargs.setdefault("expected_td_attributes", _tb.td_attributes)
</file context>
| try: | |
| _tb = parse_tdx_quote(base64.b64decode(inp["quote_b64"])).td_quote_body | |
| config_kwargs.setdefault("expected_td_attributes", _tb.td_attributes) | |
| config_kwargs.setdefault("expected_xfam", _tb.xfam) | |
| config_kwargs.setdefault("expected_minimum_tee_tcb_svn", _tb.tee_tcb_svn) | |
| config_kwargs.setdefault("accepted_mr_seams", (_tb.mr_seam,)) | |
| except Exception: | |
| pass | |
| _tb = parse_tdx_quote(base64.b64decode(inp["quote_b64"])).td_quote_body | |
| config_kwargs.setdefault("expected_td_attributes", _tb.td_attributes) | |
| config_kwargs.setdefault("expected_xfam", _tb.xfam) | |
| config_kwargs.setdefault("expected_minimum_tee_tcb_svn", _tb.tee_tcb_svn) | |
| config_kwargs.setdefault("accepted_mr_seams", (_tb.mr_seam,)) |
There was a problem hiding this comment.
1 issue found across 3 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/tinfoil/sigstore.py">
<violation number="1" location="src/tinfoil/sigstore.py:2">
P2: Importing private `_OIDC_ISSUER_V2_OID` from `sigstore.verify.policy` couples the code to an unstable implementation detail of sigstore-python. A future dependency update could rename or remove this constant without a major version bump, breaking the import at load time. The OID value is a fixed Fulco extension identifier (1.3.6.1.4.1.57264.1.8); defining it locally removes the hidden dependency on a private constant.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Fix all with cubic | Re-trigger cubic
| from sigstore.verify import Verifier | ||
| from sigstore.verify.policy import AllOf, OIDCIssuer, GitHubWorkflowRepository, Certificate, _OIDC_GITHUB_WORKFLOW_REF_OID, ExtensionNotFound | ||
| from sigstore.verify import Verifier | ||
| from sigstore.verify.policy import AllOf, OIDCIssuer, OIDCIssuerV2, GitHubWorkflowRepository, Certificate, _OIDC_GITHUB_WORKFLOW_REF_OID, _OIDC_ISSUER_V2_OID, ExtensionNotFound |
There was a problem hiding this comment.
P2: Importing private _OIDC_ISSUER_V2_OID from sigstore.verify.policy couples the code to an unstable implementation detail of sigstore-python. A future dependency update could rename or remove this constant without a major version bump, breaking the import at load time. The OID value is a fixed Fulco extension identifier (1.3.6.1.4.1.57264.1.8); defining it locally removes the hidden dependency on a private constant.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/tinfoil/sigstore.py, line 2:
<comment>Importing private `_OIDC_ISSUER_V2_OID` from `sigstore.verify.policy` couples the code to an unstable implementation detail of sigstore-python. A future dependency update could rename or remove this constant without a major version bump, breaking the import at load time. The OID value is a fixed Fulco extension identifier (1.3.6.1.4.1.57264.1.8); defining it locally removes the hidden dependency on a private constant.</comment>
<file context>
@@ -1,5 +1,5 @@
-from sigstore.verify import Verifier
-from sigstore.verify.policy import AllOf, OIDCIssuer, GitHubWorkflowRepository, Certificate, _OIDC_GITHUB_WORKFLOW_REF_OID, ExtensionNotFound
+from sigstore.verify import Verifier
+from sigstore.verify.policy import AllOf, OIDCIssuer, OIDCIssuerV2, GitHubWorkflowRepository, Certificate, _OIDC_GITHUB_WORKFLOW_REF_OID, _OIDC_ISSUER_V2_OID, ExtensionNotFound
from sigstore.models import Bundle
from sigstore.errors import VerificationError
</file context>
| from sigstore.verify.policy import AllOf, OIDCIssuer, OIDCIssuerV2, GitHubWorkflowRepository, Certificate, _OIDC_GITHUB_WORKFLOW_REF_OID, _OIDC_ISSUER_V2_OID, ExtensionNotFound | |
| from sigstore.verify.policy import AllOf, OIDCIssuer, OIDCIssuerV2, GitHubWorkflowRepository, Certificate, _OIDC_GITHUB_WORKFLOW_REF_OID, ExtensionNotFound | |
| from cryptography.x509.oid import ObjectIdentifier | |
| # Canonical Fulcio V2 issuer extension (1.3.6.1.4.1.57264.1.8) | |
| _OIDC_ISSUER_V2_OID = ObjectIdentifier("1.3.6.1.4.1.57264.1.8") |
…oil-conformance binary Mid-level entry point + CLI binary that bring tinfoil-python into the cross-SDK conformance suite (https://github.com/lsd-cat/tinfoil-conformance). Mirrors the same split landed earlier in tinfoil-rs and tinfoil-js. Library changes (src/tinfoil/sigstore.py): * New SigstorePolicy dataclass — one field per SPEC §5 clause. Mirrors the structs in tinfoil-rs / tinfoil-js so the conformance harness can pass identical policy objects to every SDK. * `default_sigstore_policy(repo)` returns the canonical Tinfoil settings. * New SigstoreVerification dataclass carries the extracted measurement plus cert/rekor/tlog/sct fields surfaced to the conformance harness. * New GitHubWorkflowRefPrefix policy class — strict-prefix startswith() check on the cert's GitHubWorkflowRef extension (.1.6). Replaces the previous regex-based GitHubWorkflowRefPattern, matching SPEC §5.3's prefix semantics. * New `verify_sigstore_bundle_with_policy(bundle_bytes, expected_digest, policy, trust_root_json)`. Hermetic — no network, no embedded trust root. Trust root is loaded via sigstore.models.TrustedRoot.from_file after the inline JSON is written to a scoped temp directory (PyCA `sigstore` 4.x only exposes the file loader). * Post-DSSE verification checks: payload_type exact match, in-toto statement type allow-list, subject digest binding (lowercase normalize per SPEC §7.3), predicate type allow-list, register extraction, and pure bundle observables for the harness (rekor logId hex, integrated time, tlog count, sct count, cert OIDC issuer, cert workflow repository, cert BuildSignerURI). CLI binary (src/tinfoil/conformance/{__init__,cli}.py): * tinfoil-conformance console_script — implements the capabilities / verify-sigstore subcommands and the exit-code contract (0 / 10 / 20 / 30 / 1) the harness expects. * capabilities honestly declares the known limitations of the sigstore PyPI package: - accepts_multi_tlog_entries: False (lib hardcodes exactly-1) - oidc_issuer_v2_preferred: False (lib reads V1 before V2) - scts_count_distinguish_missing_vs_duplicate: False (lib raises the same "Expected one certificate timestamp" error for missing and duplicate SCTs) - legacy_bundle_format_supported: False (sigstore-python Bundle.from_json validates against v0.3 layout) * Error classifier maps both our own policy-driven prefix codes and sigstore-python's natural English error phrasings to the SPEC- anchored rejection-code taxonomy. CI (.github/workflows/tinfoil-conformance.yml): Self-contained workflow matching the patterns in tinfoil-rs and tinfoil-js: uv sync to build the SDK, checkout the conformance suite, install harness via pip, run vectors, upload results, append summary. Verification: - 286/286 existing pytest unit tests pass. - Full conformance suite (45 fixtures) runs against tinfoil-py: 42 pass + 3 capability-gated skips + 0 fails. - All three SDKs now run the same suite simultaneously; 131 of 135 assertions are green, the 4 skips are honestly declared capability gaps in either Rust (legacy bundle format) or Python (multi-tlog, V2 preference, SCT duplicate distinction). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Same three flags landing in tinfoil-rs and tinfoil-js, declared
honestly for sigstore-python's behavior:
* rejects_duplicate_sct_log: True — sigstore-python uses the same
"Expected one certificate timestamp" count-check for both
missing-SCT and duplicate-SCT, so any duplicate gets rejected
(though through the same error path that rejects missing — see
scts_count_distinguish_missing_vs_duplicate=False).
* checks_only_subject_0: True — sigstore-python's verify_dsse path
only inspects subject[0].
* in_toto_statement_tolerates_extra_fields: True — sigstore-python's
statement parser uses pydantic models with extra=allow.
Lets the four-SDK suite cleanly gate fixtures 066/073/074 on tinfoil-go
(which declares all three as False because sigstore-go behaves
differently).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cmd_verify_measurement wraps tinfoil.attestation.types.Measurement —
its existing fingerprint() and assert_equal() already implement SPEC §7.2/
§7.3 with the right semantics. The conformance binary adds:
* type/register-count validation against the SPEC §7.1 layout table
(returns MEASUREMENT_TYPE_UNKNOWN / MEASUREMENT_REGISTER_COUNT_INVALID
before reaching the lib)
* lowercase normalization of registers per SPEC §7.3 normative rule
* exception → rejection code classifier:
Rtmr3NotZeroError → MEASUREMENT_RTMR3_NONZERO
MeasurementMismatchError → MEASUREMENT_MISMATCH
FormatMismatchError → MEASUREMENT_TYPE_COMBINATION_UNSUPPORTED
Capabilities:
- stages_supported += "verify-measurement"
- measurement.compare_multiplatform_to_tdx_supported = true
17 of 17 measurement fixtures pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cmd_verify_hardware_measurements wraps tinfoil.attestation.verify_tdx_hardware,
which already implements SPEC §6.3 step 1–4 with the right semantics. The
conformance binary adds:
* SPEC §7.3 lowercase normalization on both sides before calling the lib
* type/count validation at the binary boundary (so the SPEC-anchored
code surfaces as ENCLAVE_MEASUREMENT_TYPE_INVALID /
ENCLAVE_REGISTER_COUNT_INVALID rather than a ValueError from the lib)
* HardwareMeasurementError → HARDWARE_NO_MATCH mapping
Capabilities:
- stages_supported += "verify-hardware-measurements"
11 of 11 hardware-measurement fixtures pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cross-SDK conformance repo moved from lsd-cat/ to tinfoilsh/ on GitHub. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the new attestation_tdx capability bag from tinfoilsh/tinfoil-conformance Phase 1, declared honestly as false: TDX attestation verification through the conformance contract isn't wired up yet (SDK either lacks TDX entirely or needs a custom collateral-injection Getter wrapper). Phase 1.5 lands the wrapper and flips the flag to true. For now, attestation-tdx fixtures skip cleanly on this SDK with reason "stage not in stages_supported". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…se 1.5)
tinfoil-python's native TDX verifier (tinfoil.attestation.verify_tdx) has
clean injection-friendly APIs all along — verify_tdx_quote(quote,
raw_quote) does Intel §4.1.2 steps 1-4 (PCK chain, quote signature, QE
report signature, AK ↔ QE report data binding) with zero network calls.
The "needs Phase 1.5 wrapper" disclaimer I added on the original
capabilities declaration was wrong; the API was already injection-ready
for the structural path.
* cmd_verify_attestation_tdx parses the raw quote via abi_tdx.parse_quote
and calls verify_tdx_quote_crypto for steps 1-4. Body field extraction
+ TDATTRIBUTES bit-decoding mirrors the Go binary 1:1 so the harness
output diff is comparable across SDKs.
* tcb_evaluation_required=true is currently rejected with a
QV_RESULT_TERMINAL_UNSPECIFIED carrying a clear "Phase 1.5 only
structural" message. Phase 3 will wire the full collateral path
via verify_tcb_info_signature + verify_qe_identity_signature
(which already accept injected response bytes + issuer chain
parameters — no API change needed in the lib).
Capabilities flipped to true:
attestation_tdx.supported = true
attestation_tdx.injected_collateral_supported = true
Fixture 300-tdx-v4-happy passes; full suite tally:
tinfoil-py: pass=74 fail=0 skip=3 (was 73 / 4 before)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds substring patterns for tinfoil-python's TDX verifier error phrasings
to map onto SPEC-anchored rejection codes. Cleaner messages than go-tdx-
guest emits (e.g. "Invalid TEE type: 0x0. Expected 0x81 (TDX).") but the
classifier needed updating to recognize them.
Order matters: chain errors mention "signature" but should map to
PCK_CHAIN_INVALID, not QUOTE_SIGNATURE_INVALID. The chain matchers run
before the generic signature pattern.
Recognized patterns:
* "invalid tee type" / "tee type" → WRONG_TEE_TYPE
* "attestation key type" → ATTESTATION_KEY_TYPE_UNSUPPORTED
* "qe vendor" / "unknown qe" → QE_VENDOR_UNKNOWN
* "quote too short" / "minimum size" → QUOTE_TRUNCATED
* "certification data" / "size mismatch" → QUOTE_FORMAT_UNSUPPORTED
* "pck...chain" / "certificate chain" → PCK_CHAIN_INVALID
* "expired" / "not yet valid" → PCK_EXPIRED
After: 13/15 Phase 2A fixtures pass on Python. Two skip:
* 324-pck-leaf-expired — verify_intel_chain calls datetime.now()
unconditionally; gated on verification_time_override="supported"
(declared "system-clock-only").
* 325-pck-fmspc-mismatch — gated on FMSPC policy capability.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…= false Phase 2B introduces collateral-tampering fixtures (326, 327, 340-345) gated on attestation_tdx.tcb_evaluation_supported=true. tinfoil-python's cmd_verify_attestation_tdx still rejects tcb_evaluation_required=true with a "Phase 1.5 only structural" message — the collateral wrapper (verify_tcb_info_signature, verify_qe_identity_signature, TCB level matching) hasn't been wired yet. Declaring false so Phase 2B fixtures skip honestly until that work lands. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ment Mirrors the Go binary's enforceExtendedPolicy: a pure-Python helper that parses the TD body fields out of the raw quote bytes and compares each against any policy.expected_*_hex pin the fixture sets. Mapping (each enforced only when policy field present): * td_attributes → TD_ATTRIBUTES_MISMATCH (§4.8.2) * xfam → XFAM_MISMATCH (§4.8.1) * mr_signer_seam → MR_SIGNER_SEAM_MISMATCH (§4.8.4) * seam_attributes → SEAM_ATTRIBUTES_MISMATCH (§4.8.3) * mr_seam (allowlist)→ MR_SEAM_NOT_ALLOWED (§4.8.5) * mrtd → MRTD_MISMATCH (§4.10) * mr_config_id → MR_CONFIG_ID_MISMATCH (§4.8.6) * mr_owner → MR_OWNER_MISMATCH (§4.8.6) * mr_owner_config → MR_OWNER_CONFIG_MISMATCH (§4.8.6) * rtmr3 → RTMR3_NONZERO (§4.10/§7.3.6) * report_data → REPORT_DATA_MISMATCH (§8.2) * qe_vendor_id → QE_VENDOR_ID_MISMATCH (§4.8.6) * min_tee_tcb_svn → TEE_TCB_SVN_BELOW_MINIMUM (§4.8.7) Capability flipped to true: attestation_tdx.extended_td_checks_supported = true 13 of 13 Phase 4 fixtures pass on Python. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
You were right — the lib already had the complete collateral evaluation
API (verify_tcb_info_signature, verify_qe_identity_signature,
check_collateral_freshness, validate_certificate_revocation,
validate_tcb_status, validate_tdx_module_identity, validate_qe_identity).
The conformance binary now orchestrates them per SPEC §4.9 step 10 when
policy.tcb_evaluation_required=true, with fixture-injected collateral
bytes (no Intel PCS fetch) and fixture-controlled verification time.
* _evaluate_collateral runs:
1. extract_pck_extensions from PCK leaf
2. parse_tcb_info_response + parse_qe_identity_response
3. parse_pem_chain on injected issuer chains
4. verify_tcb_info_signature + verify_qe_identity_signature
(when issuer chains supplied)
5. Load PCK CRL + Root CRL DER
6. inline freshness using fixture's expiration_check_date
7. validate_certificate_revocation
8. validate_tcb_status
9. validate_tdx_module_identity
10. validate_qe_identity
* _maybe_override_intel_root monkey-patches the embedded Intel SGX
Root CA PEM for the duration of the call, enabling Phase 3
synthetic-chain fixtures.
* _maybe_override_time monkey-patches the module-level datetime in
collateral_tdx + cert_utils so all internal freshness/validity
checks use the fixture's expiration_check_date_unix. The lib has
no public time-injection API; this is the cleanest workaround
until check_collateral_freshness / verify_intel_chain /
validate_certificate_revocation gain a `now` parameter.
* Classifier maps CollateralError messages to SPEC rejection codes
(TCB_REVOKED, TCB_INFO_SIGNATURE_INVALID, TCB_INFO_EXPIRED,
QE_IDENTITY_*, PCK_REVOKED, etc.).
Capability flags flipped:
attestation_tdx.tcb_evaluation_supported = true (was false)
attestation_tdx.accepts_non_terminal_tcb_statuses = true (new)
After: 114/119 attestation-tdx fixtures pass on Python — including the
three Phase 3 non-terminal statuses (360-362) that Go can't accept
because go-tdx-guest's ErrTcbStatus collapses every non-UpToDate
status. Python's validate_tcb_status follows SPEC §4.7.7 properly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors Go's enforce_spec_defaults handling in _enforce_extended_policy:
* TD Attributes DEBUG bit (§4.8.2) — must be 0
* TD Attributes FIXED0 mask (§4.8.2) — only {0, 28, 30, 63} may be set
* XFAM FIXED1 (§4.8.1) — bits 0 + 1 (FP + SSE) must be set
* XFAM FIXED0 (§4.8.1) — only 0x0006DBE7 bits may be set
7 of 8 Phase 4B fixtures pass on Python (401-405, 412-413).
Also flipped verification_time_override from "system-clock-only" to
"supported" since the monkey-patched datetime in cmd_verify_attestation_
tdx propagates through cert_utils.verify_intel_chain to the structural
PCK chain validation too — fixture 324 (pck-leaf-expired) now passes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cmd_verify_attestation_sev builds CertificateChain directly with the fixture-supplied VCEK + lib's embedded ARK_CERT/ASK_CERT, bypassing the lib's CertificateChain.from_report which would fetch VCEK from AMD KDS over the network. Fixture-supplied amd_root_ca_pem / ask_pem swap the embedded constants for Phase 4B-SEV synthetic-chain fixtures. Decodes the 1184-byte SEV-SNP v3 report into the cross-SDK body_fields shape (policy bits per AMD APM Vol 3 Table B-3, platform_info bits, current/committed/launch TCB parts, every measurement register). SPEC §3.4 mandatory cross-checks (VCEK HWID ↔ report.chip_id, every SPL extension ↔ report.reported_tcb part) live in _enforce_sev_vcek_cross_ checks since the conformance binary bypasses the lib's validate_report. Unconditional MIGRATE_MA check rejects guest_policy bit 18 set. The lib's verify_attestation / verify_chain print error diagnostics to stdout when verification fails (the lib's contract). The conformance binary's contract is JSON-only on stdout, so the lib's stdout is captured into stderr; the captured message also feeds the error classifier so e.g. "certificate signature failure" lands on VCEK_CHAIN_INVALID rather than the generic fallback. Capabilities: declare verification_time_override=system-clock-only (pyOpenSSL's X509Store.verify_certificate delegates to OpenSSL's clock with no Python-level override hook). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Chains verify-sigstore + verify-attestation-sev + a Measurement.assert_equal cross-stage comparison, parallel to the Go/Rust conformance binaries. Brings tinfoil-python to verify-full parity so the four-SDK matrix has no n/a columns. Refactors cmd_verify_sigstore and cmd_verify_attestation_sev to expose inner functions (_run_verify_sigstore_inner, _run_verify_attestation_ sev_inner) returning either the successful result or a (code, spec_ref, message) rejection triple. Outer cmd_* functions become thin emit wrappers. The inner helpers default missing schema_version to "1" so the verify-full envelope's nested sub-blocks (which omit it — the envelope carries it) work. cmd_verify_full handles SPEC §11.1 standard/bundle mode (sigstore → MultiPlatform → SEV cross-check) and §11.3 pinned-measurement mode. Rejections carry the originating sub-stage so fixtures can pin both code AND stage. TDX path is not wired (Rust + Go are TDX-capable but Python's SEV-focused fixtures already pass the n/a column). Capabilities: + "verify-full" added to stages_supported + flow_modes_supported expanded to ["standard", "pinned"] Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EHBP transport is implemented in the SDK (default transport) and cmd_verify_full already accepts the bundle flow mode (the SDK ships a full bundle pipeline), but the conformance binary declared neither. Declare ehbp transport and the bundle flow mode. No fixture gates on transport or the bundle flow, so this is a library-capability declaration matching tinfoil-go and tinfoil-js. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the verify-ehbp-key-binding stage + ehbp.key_binding_supported capability. The handler extracts the attested HPKE key from report_data[32:64] using the SDK's own layout constants (TLS_KEY_FP_SIZE/HPKE_KEY_SIZE) and validates the offered key with the same parser the EHBP transport uses (ehbp.ServerIdentity.from_public_key_hex). A mismatch fails closed with EHBP_KEY_BINDING_MISMATCH. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds execution_mode=public_api to the SEV stage: drives the SDK's real public verifier attestation.verify_sev_attestation_v2 with only the VCEK injected (embedded ARK/ASK, no network) instead of the adapter's manual chain assembly + verify_attestation path. Captures the lib's stdout diagnostics (it prints on failure) to keep stdout JSON-only and reuses _classify_sev_error / _decode_sev_body_fields. Declares attestation_sev.public_api_hooks_supported. Time-override / injected-ARK / policy-pin fixtures stay adapter-only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The execution_mode=public_api SEV handler drove the real verifier but skipped the fixture's §3.7 policy pins (measurement / host_data / report_data / key-digest / TCB-minimum), so policy-mismatch fixtures (400/410/420/430/440/450/451) wrongly ACCEPTED in full flow while the Go public handler and the adapter path correctly rejected them. Apply _enforce_sev_policy after the verifier accepts, matching the Go public handler. Conformance-binary only; no SDK lib changes. Verified: the policy-mismatch SEV fixtures now reject in full flow (MEASUREMENT_MISMATCH, etc.); 366 unit tests still pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Makes execution_mode=public_api actually exercise the SDK's real TDX
verifier for the deep fixtures, not just pre-policy ones. Three pieces,
all conformance-binary only (no lib changes):
* Re-baseline the verifier config (expected_td_attributes/xfam/
tee_tcb_svn/mr_seams + min_tcb_evaluation_data_number=0) to the
synthetic quote's own values, so the lib's production-policy baseline
doesn't reject the synthetic vectors before the targeted check; the
fixture's §4.8 pins are still enforced by _enforce_extended_policy.
* Classifier patterns for the lib's policy/collateral messages reached
in the full path: TD_ATTRIBUTES (debug/reserved/mismatch), XFAM
(required-clear/forbidden-set), QE-report mrsigner/field mismatch,
PCK FMSPC mismatch, root-CRL signature.
* §4.7.7 accepted_qv_results gate (the verifier accepts non-terminal
TCB statuses by default; enforce the fixture's policy against the
matched TCB level — fixes 365 wrongly accepting).
Result: 50/65 TDX fixtures verify correctly in full flow (was effectively
0 past the PCK chain). The remaining 15 (300/325 + the 4xx extended-TD
suite) are blocked by synthetic quote SVN ↔ TCB-info level inconsistency
that the real TCB-level match correctly rejects — needs fixturegen, not
config. No regression: full harness 206/0-fail, 366 unit tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Carries the production OIDCIssuerV2Preferred fix (src/tinfoil/sigstore.py)
plus the conformance-side updates that depend on it:
- conformance/sigstore.py: the injected-policy verifier now uses
OIDCIssuerV2Preferred instead of OIDCIssuer (V1-only), so the policy check
agrees with _extract_cert_info, which already reported the V2-preferred
issuer.
- conformance/cli.py: _classify now maps both the V1 ("OIDCIssuer does not
match") and V2 ("OIDCIssuerV2 does not match") mismatch phrasings, and the
missing-extension phrasing, to OIDC_ISSUER_MISMATCH (was BUNDLE_MALFORMED).
- conformance/cli.py: declare sigstore.oidc_issuer_v2_preferred = true, so
fixture 069 (V1/V2 disagree, V2 wins) now runs and passes instead of being
skipped.
Full sigstore suite for tinfoil-python: 46 pass / 2 skip / 0 fail.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bffc416 to
706c7a4
Compare
sigstore-python rejects a cert with ≠1 SCT generically ("Expected one
certificate timestamp, found N"), so it rejected fixture 066 only
incidentally, emitting SCT_INSUFFICIENT instead of the principled
SCT_DUPLICATE_LOG that rs/js/go emit.
Add reject_duplicate_sct_logs (src/tinfoil/sigstore.py): inspect the leaf
cert's embedded SCT list and reject if any CT log ID repeats, on the same
log-id basis as the other SDKs. Called ahead of verify_dsse in both the
production verifier and the conformance handler. The conformance classifier
already maps "duplicate ... sct" to SCT_DUPLICATE_LOG.
py now rejects 066 with SCT_DUPLICATE_LOG (all four SDKs aligned). Offline
verification tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
reject_duplicate_sct_logs now runs ahead of sigstore-python's exactly-one check, so missing SCTs classify as SCT_INSUFFICIENT and a repeated log id as SCT_DUPLICATE_LOG — the two are now distinguished. Flip false -> true. Informational capability (no fixture gates on it); suite unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…5.2) sigstore-python parses the legacy v0.1/v0.2 bundle layout (cert under verificationMaterial.x509CertificateChain), so py accepted fixture 079. Per SPEC §5.2 the legacy layout MUST be rejected (it can carry CA certs; tinfoil only produces v0.3). - sigstore.py: reject_legacy_bundle_format rejects a bundle whose verificationMaterial uses x509CertificateChain; called ahead of Bundle.from_json in the production verifier. - conformance/sigstore.py: conformance handler calls it too. - cli.py: classifier maps "legacy bundle format" -> BUNDLE_MALFORMED; capability comment corrected. py now rejects 079 with BUNDLE_MALFORMED. Offline verification tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…(SPEC §5.4) sigstore-python tolerates unknown top-level fields in the in-toto statement, so py accepted fixture 074. Per SPEC §5.4 the statement MUST contain only the recognized fields; unknown ones MUST be rejected (BUNDLE_MALFORMED), matching tinfoil-go/-rs/-js. - sigstore.py: reject_unknown_intoto_fields (allowed set _type/subject/ predicateType/predicate); called after the statement parse in the production verifier. - conformance/sigstore.py: conformance handler calls it too. - cli.py: classifier maps "unknown top-level field" -> BUNDLE_MALFORMED; capability in_toto_statement_tolerates_extra_fields true -> false. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follows the other SDKs to implement conformance testing. Build the dedicated CLI and add a CI job.
Summary by cubic
Adds a cross‑SDK
tinfoil-conformanceCLI and CI workflow to exercise policy‑layer verification across Sigstore, TDX, and SEV‑SNP, plus EHBP key binding. Now also rejects unknown top‑level in‑toto statement fields (SPEC §5.4), in addition to duplicate CT‑log SCTs and legacyx509CertificateChainbundles, and distinguishes missing vs duplicate SCTs.New Features
tinfoil-conformanceCLI:capabilities,verify-sigstore,verify-measurement,verify-hardware-measurements,verify-attestation-tdx(adapter/public API),verify-attestation-sev(adapter/public API),verify-full(standard/bundle/pinned),verify-ehbp-key-binding.verify_sigstore_bundle_with_policywith hermetic trust root (sigstore.TrustedRoot); OIDC issuer V2 preferred (V1 fallback); emits bundle observables;reject_duplicate_sct_logs()(SCT_DUPLICATE_LOG),reject_legacy_bundle_format()(BUNDLE_MALFORMED),reject_unknown_intoto_fields()(BUNDLE_MALFORMED).attestation.verify_sev_attestation_v2(no network) and applies §3.7 policy pins; time override is system‑clock‑only.verify-full: chains Sigstore → SEV, compares measurements (SPEC §11), supports standard/bundle/pinned, and returns a final fingerprint with originating‑stage rejections.report_data[32:64]and validates via the SDK’s EHBP parser; rejects with EHBP_KEY_BINDING_MISMATCH.verify-measurement,verify-hardware-measurements,verify-full,verify-ehbp-key-binding; flow modes["standard","bundle","pinned"];sigstore.oidc_issuer_v2_preferred=true,sigstore.rejects_duplicate_sct_log=true,sigstore.scts_count_distinguish_missing_vs_duplicate=true,sigstore.checks_only_subject_0=true,sigstore.in_toto_statement_tolerates_extra_fields=false; TDX/SEV support and extended checks;attestation_sev.public_api_hooks_supported=true;verification_time_override="supported"(TDX) /"system-clock-only"(SEV);measurement.compare_multiplatform_to_tdx_supported=true;transport_ehbp_supported=true;ehbp.key_binding_supported=true.Refactors
workflow_ref_prefix(SPEC §5.3).tinfoil.conformance.sigstore; added CI workflow to run the cross‑SDK suite against thetinfoil-conformancebinary.Written for commit 982122c. Summary will update on new commits.