Merge remote-tracking branch 'origin/feat/kind-tdx-real'

This commit is contained in:
2026-06-27 14:07:54 -07:00
12 changed files with 705 additions and 82 deletions
+297 -27
View File
@@ -5,42 +5,312 @@ package attest
import (
"context"
"crypto/sha512"
"crypto/subtle"
"encoding/hex"
"fmt"
"strings"
"github.com/google/go-tdx-guest/abi"
tdxpb "github.com/google/go-tdx-guest/proto/tdx"
"github.com/google/go-tdx-guest/verify"
"github.com/google/go-tdx-guest/verify/trust"
)
// TDX is the Intel TDX verifier.
// TDX is the production Intel TDX verifier. It accepts a raw Intel
// DCAP v4 TDX quote (the bytes the guest reads from the TDX quote-
// generation service) and performs the full DCAP attestation flow via
// github.com/google/go-tdx-guest — the canonical Google verifier, sibling
// of go-sev-guest used by the SEV-SNP path:
//
// Status: STUB — tracked at issue #222 stage 2.
// 1. Parse the quote via go-tdx-guest/abi.QuoteToProto (QuoteV4 only).
// 2. Verify the PCK certificate chain that is EMBEDDED in the quote's
// signed certification data (PCK Leaf -> Intel SGX Platform CA ->
// Intel SGX Root CA) up to Intel's Root CA, which ships pinned inside
// go-tdx-guest (verify/trusted_root.pem). Unlike SEV — where the VCEK
// leaf is fetched from the AMD KDS — the TDX leaf travels inside the
// quote, so the chain itself needs no network.
// 3. Verify the Quoting Enclave (QE) report signature with the verified
// PCK leaf key, and verify the attestation-key binding: the QE report
// data must equal SHA-256(ECDSA attestation key || QE auth data), so
// the key that signed the TD report is the one Intel's QE vouched for.
// 4. Verify the TD report (TDQuoteBody) ECDSA-P256 signature made by that
// attestation key over the quote header + body.
// 5. Fetch Intel PCS collateral (TCB Info + QE Identity, plus PCK and Root
// CA CRLs) and enforce TCB status: the platform's TEE_TCB_SVN must map
// to an "UpToDate" TCB level, the QE identity must match, and no cert
// in the chain may be revoked. This is the freshness/patch-level gate;
// a downlevel or revoked platform is REFUSED.
// 6. Extract MRTD + the four RTMRs and fold them into a single 48-byte
// measurement root, extract REPORTDATA, and enforce caller policy
// (expected measurement / expected report data == nonce binding).
//
// Production plan (intentionally not yet implemented):
// Trust anchor: the Intel SGX Root CA pinned inside go-tdx-guest. It is
// never taken from the quote. Production requires network reachability to
// the Intel PCS (api.trustedservices.intel.com) for collateral, exactly as
// the SEV-SNP path requires the AMD KDS; operators that cannot reach Intel
// directly front it with a PCCS mirror and point the getter at it. In tests
// the getter is overridden (see WithKDSGetter) to replay committed Intel
// collateral from testdata/ so verification runs offline.
//
// - Parse the TDREPORT / quote via github.com/google/go-tdx-guest/abi.
// - Fetch the PCK (Provisioning Certification Key) chain from the
// Intel PCS at api.trustedservices.intel.com using the report's
// FMSPC + PCEID.
// - Validate the chain to Intel's pinned root certificate and check
// the report signature against the PCK.
// - Cross-check the QE Identity and TDX Module Identity against the
// pinned values published by Intel.
// - Extract MRTD, RTMRs, REPORTDATA, and the platform identity.
// - Compose CompositeHash via the same domain-separated SHA-256 as
// the SEV path (see verifier.go::computeCompositeHash).
//
// Verify returns ErrNotImplemented so the call site refuses the release
// without crashing the process. Callers MUST treat this exactly like
// any other refusal: do not release, do not fall back. A silent no-op
// or success would let the release gate accidentally accept evidence
// the verifier has not actually checked; the explicit error-return path
// preserves the same hard-refusal guarantee while letting Dispatch be
// invoked from request-handling goroutines without taking the whole
// process down.
// As with every Kind: (nil, err) means refuse. There is no partial report
// and no best-effort fallback.
type TDX struct{}
// Verify implements Verifier. NOT YET IMPLEMENTED — do not enable TDX
// dispatch in production until #222 stage 2 lands.
const (
tdxVendor = "intel.tdx"
// Field sizes from the Intel TDX DCAP quote spec (see go-tdx-guest/abi):
// MRTD and each RTMR are SHA-384 (48 bytes); REPORTDATA is 64 bytes.
tdxMrTdSize = 48
tdxRtmrSize = 48
tdxRtmrCount = 4
tdxReportDataLen = 64
)
// tdxMeasureFoldDomain domain-separates the TD measurement-root fold so it
// can never collide with a raw register value or another kind's hash input.
var tdxMeasureFoldDomain = []byte("LUX-TDX-MEAS-FOLD-v1")
// Verify implements Verifier for Intel TDX DCAP v4 quotes.
func (TDX) Verify(ctx context.Context, evidence []byte, opts ...Option) (*VerifiedReport, error) {
return nil, fmt.Errorf("%w: TDX attestation envelope verification not configured for this build (tracked at #222 stage 2: go-tdx-guest + Intel PCS)",
ErrNotImplemented)
if err := ctx.Err(); err != nil {
return nil, err
}
if len(evidence) < abi.QuoteMinSize {
return nil, fmt.Errorf("%w: tdx quote expects at least %d bytes, got %d",
ErrInvalidEvidence, abi.QuoteMinSize, len(evidence))
}
cfg := applyOptions(opts...)
// Parse first so we fail fast on malformed framing, and so the extracted
// fields below come from a structurally validated quote (CheckQuoteV4 runs
// inside QuoteToProto).
anyQuote, err := abi.QuoteToProto(evidence)
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrInvalidEvidence, err)
}
quote, ok := anyQuote.(*tdxpb.QuoteV4)
if !ok {
return nil, fmt.Errorf("%w: unsupported tdx quote type %T (only DCAP QuoteV4)",
ErrInvalidEvidence, anyQuote)
}
// Build the full-rigor verification options. GetCollateral and
// CheckRevocations are ON: without collateral the TEE_TCB_SVN is never
// compared to Intel's signed TCB levels, so a downlevel/vulnerable but
// otherwise well-formed platform would pass. DefaultOptions() leaves both
// off, so we set them explicitly.
vopts := &verify.Options{
GetCollateral: true,
CheckRevocations: true,
Now: cfg.nowOrWall(),
}
// Getter resolution mirrors the SEV path. The cc/attest option carries an
// HTTPS getter as `any` (WithKDSGetter); for KindTDX it supplies the Intel
// PCS / PCCS getter and tests inject a replay map so the suite runs
// offline. Production leaves it nil and the live Intel PCS getter is used.
if cfg.kdsGetter != nil {
g, ok := cfg.kdsGetter.(trust.HTTPSGetter)
if !ok {
return nil, fmt.Errorf("%w: injected getter does not implement go-tdx-guest trust.HTTPSGetter",
ErrInvalidEvidence)
}
vopts.Getter = g
} else {
vopts.Getter = trust.DefaultHTTPSGetter()
}
// Full DCAP verification: PCK chain -> Intel SGX Root CA, QE report sig,
// attestation-key binding, TD report ECDSA-P256 sig, TCB status, and
// revocation. Reuses the parse above (no re-decode).
if err := verify.TdxQuote(quote, vopts); err != nil {
return nil, classifyTDXVerifyError(err)
}
// Chain + signatures + TCB are valid; extract measurements and apply
// caller policy.
return buildTDXReport(quote, cfg)
}
// classifyTDXVerifyError maps go-tdx-guest's flat error strings onto the
// package refusal taxonomy so callers can switch on errors.Is. go-tdx-guest
// returns a single error value; we classify by content. Every branch is a
// refusal — when in doubt we treat the failure as a vendor-chain failure
// (ErrChainInvalid), never as success. Ordering is significant: the most
// specific causes are matched first.
func classifyTDXVerifyError(err error) error {
msg := err.Error()
low := strings.ToLower(msg)
switch {
// Structural / framing problems the parser surfaces late.
case strings.Contains(low, "could not convert raw bytes"),
strings.Contains(low, "quote format not supported"),
strings.Contains(low, "unsupported quote type"),
strings.Contains(low, "is nil"):
return fmt.Errorf("%w: %v", ErrInvalidEvidence, err)
// TCB status: the chain is cryptographically valid but the platform's
// patch level does not meet Intel's "UpToDate" bar (or no level matches).
// This is a policy refusal on otherwise-valid evidence.
case strings.Contains(low, "tcb status"),
strings.Contains(low, "matching tcb level"),
strings.Contains(low, "tcb level"):
return fmt.Errorf("%w: %v", ErrPolicy, err)
// Revocation: a certificate in the chain was pulled by Intel.
case strings.Contains(low, "revoked"),
strings.Contains(low, "revocation"):
return fmt.Errorf("%w: %v", ErrChainInvalid, err)
// Signature / attestation-key binding failures. ErrHashVerificationFail
// ("unable to verify message digest using quote's signature and ecdsa
// attestation key") is the TD report signature; ErrSHA56VerificationFail
// ("QE Report Data does not match ... ECDSA Attestation Key ...") is the
// attestation-key binding. Both mean the signing key is not authentic.
case strings.Contains(low, "signature"),
strings.Contains(low, "ecdsa attestation key"),
strings.Contains(low, "message digest"),
strings.Contains(low, "report data does not match"):
return fmt.Errorf("%w: %v", ErrSignatureInvalid, err)
// Everything else — cert/chain/CRL/issuer/public-key/collateral/expiry —
// is a vendor-chain validation failure. Conservative default: refuse.
default:
return fmt.Errorf("%w: %v", ErrChainInvalid, err)
}
}
// buildTDXReport extracts the verified measurements from a TD quote whose
// chain, signatures, and TCB status have already passed, applies caller
// policy, and returns the canonical VerifiedReport.
func buildTDXReport(quote *tdxpb.QuoteV4, cfg config) (*VerifiedReport, error) {
body := quote.GetTdQuoteBody()
if body == nil {
return nil, fmt.Errorf("%w: tdx quote has no TD report body", ErrInvalidEvidence)
}
mrtd := body.GetMrTd()
rtmrs := body.GetRtmrs()
reportData := body.GetReportData()
// Defense in depth: the parser already enforced these, but we are about to
// make trust decisions on the output of C-ABI / proto code, so we recheck
// the lengths that feed the measurement fold and the policy comparisons.
if len(mrtd) != tdxMrTdSize {
return nil, fmt.Errorf("%w: MRTD is %d bytes, expected %d", ErrInvalidEvidence, len(mrtd), tdxMrTdSize)
}
if len(rtmrs) != tdxRtmrCount {
return nil, fmt.Errorf("%w: got %d RTMRs, expected %d", ErrInvalidEvidence, len(rtmrs), tdxRtmrCount)
}
for i, r := range rtmrs {
if len(r) != tdxRtmrSize {
return nil, fmt.Errorf("%w: RTMR%d is %d bytes, expected %d", ErrInvalidEvidence, i, len(r), tdxRtmrSize)
}
}
if len(reportData) != tdxReportDataLen {
return nil, fmt.Errorf("%w: REPORTDATA is %d bytes, expected %d", ErrInvalidEvidence, len(reportData), tdxReportDataLen)
}
// Fold MRTD + the four RTMRs into a single 48-byte TD measurement root.
// MRTD is the build-time launch measurement; the RTMRs are the runtime-
// extended registers (firmware config, kernel, OS, runtime). Pinning the
// fold pins the entire launch+runtime state in one comparison value.
measurement := foldTDXMeasurement(mrtd, rtmrs)
// Caller policy on the verified fields. Constant-time compares.
if cfg.expectedReportData != nil {
if subtle.ConstantTimeCompare(cfg.expectedReportData, reportData) != 1 {
return nil, fmt.Errorf("%w: report_data mismatch (got %s)",
ErrPolicy, hex.EncodeToString(reportData))
}
}
if cfg.expectedMeasurement != nil {
if subtle.ConstantTimeCompare(cfg.expectedMeasurement, measurement) != 1 {
return nil, fmt.Errorf("%w: measurement mismatch (got %s)",
ErrPolicy, hex.EncodeToString(measurement))
}
}
out := &VerifiedReport{
Kind: KindTDX,
Vendor: tdxVendor,
Measurement: measurement,
ReportData: cloneBytes(reportData),
// HostData/ChipID: TDX exposes no host-data or per-chip identifier in
// the TD report (PCK identity is per-platform FMSPC, surfaced in Extra
// where available). Left nil rather than populated with a stand-in.
IssuedAt: cfg.nowOrWall().UTC(),
Extra: buildTDXExtra(body),
}
out.CompositeHash = computeCompositeHash(KindTDX, canonicalTDXBytes(body))
return out, nil
}
// foldTDXMeasurement returns SHA-384(domain || 0x00 || MRTD || RTMR0..3).
// SHA-384 keeps the root at the 48-byte width of the TDX measurement
// registers themselves, symmetric with the SEV-SNP 48-byte launch digest.
func foldTDXMeasurement(mrtd []byte, rtmrs [][]byte) []byte {
h := sha512.New384()
h.Write(tdxMeasureFoldDomain)
h.Write([]byte{0x00}) // domain separator
h.Write(mrtd)
for _, r := range rtmrs {
h.Write(r)
}
return h.Sum(nil)
}
// buildTDXExtra collects TDX-specific fields that don't fit the common shape.
// Keys are stable wire identifiers prefixed "tdx."; values are hex. The raw
// MRTD and each RTMR are exposed individually so callers can pin finer-grained
// policy than the folded measurement root.
func buildTDXExtra(body *tdxpb.TDQuoteBody) map[string]string {
extra := make(map[string]string, 12)
extra["tdx.mrtd"] = hex.EncodeToString(body.GetMrTd())
for i, r := range body.GetRtmrs() {
extra[fmt.Sprintf("tdx.rtmr%d", i)] = hex.EncodeToString(r)
}
extra["tdx.mrseam"] = hex.EncodeToString(body.GetMrSeam())
extra["tdx.mrsignerseam"] = hex.EncodeToString(body.GetMrSignerSeam())
extra["tdx.seam_attributes"] = hex.EncodeToString(body.GetSeamAttributes())
extra["tdx.td_attributes"] = hex.EncodeToString(body.GetTdAttributes())
extra["tdx.xfam"] = hex.EncodeToString(body.GetXfam())
extra["tdx.mrconfigid"] = hex.EncodeToString(body.GetMrConfigId())
extra["tdx.mrowner"] = hex.EncodeToString(body.GetMrOwner())
extra["tdx.mrownerconfig"] = hex.EncodeToString(body.GetMrOwnerConfig())
extra["tdx.tee_tcb_svn"] = hex.EncodeToString(body.GetTeeTcbSvn())
return extra
}
// canonicalTDXBytes returns the fixed-shape bytes that participate in
// CompositeHash. We hash the verifier-extracted TD report fields (never the
// raw wire) in a fixed order and width so a consumer re-deriving the hash
// reproduces it iff the same quote was verified. Field widths follow the
// Intel TDX quote spec; fixedSize zero-pads/truncates to keep the blob shape
// constant.
func canonicalTDXBytes(body *tdxpb.TDQuoteBody) []byte {
// 16(teeTcbSvn) + 48(mrSeam) + 48(mrSignerSeam) + 8(seamAttrs)
// + 8(tdAttrs) + 8(xfam) + 48(mrTd) + 48(mrConfigId) + 48(mrOwner)
// + 48(mrOwnerConfig) + 4*48(rtmrs) + 64(reportData) = 632 bytes.
buf := make([]byte, 0, 632)
buf = append(buf, fixedSize(body.GetTeeTcbSvn(), 16)...)
buf = append(buf, fixedSize(body.GetMrSeam(), 48)...)
buf = append(buf, fixedSize(body.GetMrSignerSeam(), 48)...)
buf = append(buf, fixedSize(body.GetSeamAttributes(), 8)...)
buf = append(buf, fixedSize(body.GetTdAttributes(), 8)...)
buf = append(buf, fixedSize(body.GetXfam(), 8)...)
buf = append(buf, fixedSize(body.GetMrTd(), 48)...)
buf = append(buf, fixedSize(body.GetMrConfigId(), 48)...)
buf = append(buf, fixedSize(body.GetMrOwner(), 48)...)
buf = append(buf, fixedSize(body.GetMrOwnerConfig(), 48)...)
for _, r := range body.GetRtmrs() {
buf = append(buf, fixedSize(r, 48)...)
}
buf = append(buf, fixedSize(body.GetReportData(), 64)...)
return buf
}
// Compile-time guard: TDX satisfies Verifier.
var _ Verifier = TDX{}
+390
View File
@@ -0,0 +1,390 @@
// Copyright (c) 2026, Lux Industries Inc.
// SPDX-License-Identifier: BSD-3-Clause
package attest
import (
"bytes"
"context"
_ "embed"
"errors"
"strings"
"testing"
"time"
"github.com/google/go-tdx-guest/abi"
tdxpb "github.com/google/go-tdx-guest/proto/tdx"
"github.com/google/go-tdx-guest/verify"
)
// tdxProdQuote is a real Intel TDX DCAP v4 quote captured from a Sapphire
// Rapids (SPR, E4 stepping) TD. Sourced verbatim from the upstream
// go-tdx-guest test corpus (testing/testdata/tdx_prod_quote_SPR_E4.dat) and
// committed so this package's tests need no live TDX hardware or network.
//
//go:embed testdata/tdx_prod_quote_spr_e4.bin
var tdxProdQuote []byte
// The Intel PCS collateral that pairs with the quote above, committed
// verbatim from the same upstream corpus. NOTE: upstream deliberately bumped
// the TCB SVNs in tdx_tcb_info.json above the quote's TEE_TCB_SVN, so the
// full collateral path is EXPECTED to refuse on TCB status — that is what
// TestTDX_Verify_FullProductionPath_RefusesStaleTCB asserts. The quote itself
// is cryptographically genuine (TestTDX_QuoteIsCryptographicallyAuthentic).
//
//go:embed testdata/tdx_tcb_info.json
var tdxTcbInfoBody []byte
//go:embed testdata/tdx_qe_identity.json
var tdxQeIdentityBody []byte
//go:embed testdata/tdx_pck_crl.body
var tdxPckCrlBody []byte
//go:embed testdata/tdx_root_ca_crl.der
var tdxRootCaCrlBody []byte
// Issuer chains returned as Intel PCS response headers. The TCB Info and QE
// Identity share the Intel SGX TCB-Signing issuer chain; the PCK CRL uses the
// PCK Platform CA chain.
//
//go:embed testdata/tdx_collateral_issuer_chain.txt
var tdxCollateralIssuerChain string
//go:embed testdata/tdx_pck_crl_issuer_chain.txt
var tdxPckCrlIssuerChain string
// tdxFixedNow pins the verification clock inside the validity window of the
// committed quote's PCK chain and collateral (the quote is from ~2023). Tests
// are reproducible regardless of when CI runs, exactly like the SEV path's
// fixedNow.
func tdxFixedNow() time.Time {
return time.Date(2023, time.July, 1, 1, 0, 0, 0, time.UTC)
}
// Absolute byte offsets into the raw quote, per the Intel DCAP v4 layout
// (header 0x30, then the TD quote body; see go-tdx-guest/abi):
// - MRTD begins at header(0x30) + body-relative MRTD offset(0x88).
// - The ECDSA-P256 signature begins at header(0x30) + body(0x248) +
// signed-data-size(4).
const (
tdxAbsMRTDOffset = 0x30 + 0x88
tdxAbsSigOffset = 0x30 + 0x248 + 4
)
// tdxReplayGetter replays the committed Intel PCS collateral so the full
// production verification path runs offline. The URL keys are exactly the
// ones go-tdx-guest requests (mirrored from the upstream TestGetter); a
// substring fallback keeps the test robust to incidental URL formatting.
type tdxReplayGetter struct{}
func (tdxReplayGetter) Get(url string) (map[string][]string, []byte, error) {
const (
hdrTCB = "Tcb-Info-Issuer-Chain"
hdrQE = "Sgx-Enclave-Identity-Issuer-Chain"
hdrPCK = "Sgx-Pck-Crl-Issuer-Chain"
)
switch {
case strings.Contains(url, "qe/identity"):
return map[string][]string{hdrQE: {tdxCollateralIssuerChain}}, tdxQeIdentityBody, nil
case strings.Contains(url, "/tcb"):
return map[string][]string{hdrTCB: {tdxCollateralIssuerChain}}, tdxTcbInfoBody, nil
case strings.Contains(url, "pckcrl"):
return map[string][]string{hdrPCK: {tdxPckCrlIssuerChain}}, tdxPckCrlBody, nil
case strings.Contains(url, "RootCA"):
return nil, tdxRootCaCrlBody, nil
default:
return nil, nil, errors.New("tdx replay getter: unexpected url " + url)
}
}
// tdxErrGetter simulates an unreachable Intel PCS.
type tdxErrGetter struct{}
func (tdxErrGetter) Get(string) (map[string][]string, []byte, error) {
return nil, nil, errors.New("intel pcs unreachable")
}
func mustParseQuote(t *testing.T, raw []byte) *tdxpb.QuoteV4 {
t.Helper()
any, err := abi.QuoteToProto(raw)
if err != nil {
t.Fatalf("QuoteToProto: %v", err)
}
q, ok := any.(*tdxpb.QuoteV4)
if !ok {
t.Fatalf("quote is %T, want *tdxpb.QuoteV4", any)
}
return q
}
// -----------------------------------------------------------------------------
// Cryptographic authenticity of the real quote
// -----------------------------------------------------------------------------
// TestTDX_QuoteIsCryptographicallyAuthentic proves the committed SPR quote
// passes the full DCAP cryptographic chain — PCK chain to the Intel SGX Root
// CA pinned in go-tdx-guest, QE report signature, attestation-key binding, and
// the TD report ECDSA-P256 signature — independent of the (deliberately stale)
// TCB collateral. This documents that the only reason the full production path
// refuses this fixture is the TCB SVN bump, not a broken quote.
func TestTDX_QuoteIsCryptographicallyAuthentic(t *testing.T) {
quote := mustParseQuote(t, tdxProdQuote)
// GetCollateral:false isolates the chain+signature checks from the TCB
// collateral comparison.
err := verify.TdxQuote(quote, &verify.Options{
GetCollateral: false,
CheckRevocations: false,
Now: tdxFixedNow(),
})
if err != nil {
t.Fatalf("real Intel SPR quote failed cryptographic verification: %v", err)
}
}
// -----------------------------------------------------------------------------
// Full production path: TCB-status enforcement against real Intel collateral
// -----------------------------------------------------------------------------
// TestTDX_Verify_FullProductionPath_RefusesStaleTCB drives the public
// TDX.Verify with the live-equivalent replay getter, exercising the entire
// production pipeline: parse -> PCK chain -> Intel SGX Root CA -> QE sig ->
// attestation-key binding -> TD report sig -> Intel PCS collateral fetch ->
// TCB-status comparison -> revocation. The committed collateral's TCB SVN is
// above the quote's, so the TCB gate fires and the platform is REFUSED.
func TestTDX_Verify_FullProductionPath_RefusesStaleTCB(t *testing.T) {
rep, err := TDX{}.Verify(context.Background(), tdxProdQuote,
WithKDSGetter(tdxReplayGetter{}), WithNow(tdxFixedNow()))
if err == nil {
t.Fatalf("expected refusal on stale TCB, got report %+v", rep)
}
if rep != nil {
t.Errorf("refusal must return a nil report, got %+v", rep)
}
if !errors.Is(err, ErrPolicy) {
t.Errorf("stale TCB: want ErrPolicy, got %v", err)
}
if !strings.Contains(err.Error(), "TCB") {
t.Errorf("stale TCB: want a TCB-status message, got %v", err)
}
}
// TestTDX_Reject_NoFallbackOnCollateralFailure proves there is no best-effort
// fallback: if Intel PCS is unreachable, verification refuses.
func TestTDX_Reject_NoFallbackOnCollateralFailure(t *testing.T) {
rep, err := TDX{}.Verify(context.Background(), tdxProdQuote,
WithKDSGetter(tdxErrGetter{}), WithNow(tdxFixedNow()))
if err == nil || rep != nil {
t.Fatalf("expected refusal when collateral is unreachable, got (%+v, %v)", rep, err)
}
if !errors.Is(err, ErrChainInvalid) {
t.Errorf("collateral unreachable: want ErrChainInvalid, got %v", err)
}
}
// TestTDX_Reject_StaleClock proves the verifier honors the verification clock
// and refuses material that is expired at that time.
func TestTDX_Reject_StaleClock(t *testing.T) {
farFuture := time.Date(2053, time.July, 1, 1, 0, 0, 0, time.UTC)
_, err := TDX{}.Verify(context.Background(), tdxProdQuote,
WithKDSGetter(tdxReplayGetter{}), WithNow(farFuture))
if err == nil {
t.Fatal("expected refusal with an expired chain/collateral at far-future clock")
}
if !errors.Is(err, ErrChainInvalid) {
t.Errorf("stale clock: want ErrChainInvalid, got %v", err)
}
}
// -----------------------------------------------------------------------------
// Measurement extraction, fold, composite hash, and policy
// -----------------------------------------------------------------------------
func TestTDX_ExtractMeasurements_RealQuote(t *testing.T) {
quote := mustParseQuote(t, tdxProdQuote)
body := quote.GetTdQuoteBody()
rep, err := buildTDXReport(quote, applyOptions())
if err != nil {
t.Fatalf("buildTDXReport: %v", err)
}
if rep.Kind != KindTDX {
t.Errorf("Kind = %q, want %q", rep.Kind, KindTDX)
}
if rep.Vendor != tdxVendor {
t.Errorf("Vendor = %q, want %q", rep.Vendor, tdxVendor)
}
// Measurement is the 48-byte fold over MRTD + the four RTMRs.
if len(rep.Measurement) != tdxMrTdSize {
t.Fatalf("Measurement len = %d, want %d", len(rep.Measurement), tdxMrTdSize)
}
wantFold := foldTDXMeasurement(body.GetMrTd(), body.GetRtmrs())
if !bytes.Equal(rep.Measurement, wantFold) {
t.Errorf("Measurement is not the MRTD+RTMR fold")
}
// The fold must not equal MRTD alone (proves the RTMRs are bound in).
if bytes.Equal(rep.Measurement, body.GetMrTd()) {
t.Errorf("Measurement equals raw MRTD; RTMRs are not folded in")
}
if bytes.Equal(rep.Measurement, make([]byte, tdxMrTdSize)) {
t.Errorf("Measurement is all-zero")
}
// ReportData round-trips the verified 64-byte field.
if len(rep.ReportData) != tdxReportDataLen {
t.Errorf("ReportData len = %d, want %d", len(rep.ReportData), tdxReportDataLen)
}
if !bytes.Equal(rep.ReportData, body.GetReportData()) {
t.Errorf("ReportData does not match the quote body")
}
// CompositeHash is non-zero and deterministic.
var zero [32]byte
if rep.CompositeHash == zero {
t.Errorf("CompositeHash is zero")
}
rep2, _ := buildTDXReport(quote, applyOptions())
if rep.CompositeHash != rep2.CompositeHash {
t.Errorf("CompositeHash is not deterministic")
}
// Extra carries the raw registers for finer-grained policy/audit.
for _, k := range []string{"tdx.mrtd", "tdx.rtmr0", "tdx.rtmr1", "tdx.rtmr2", "tdx.rtmr3", "tdx.tee_tcb_svn"} {
if rep.Extra[k] == "" {
t.Errorf("Extra missing key %q", k)
}
}
t.Logf("TD measurement root (fold) = %x", rep.Measurement)
t.Logf("CompositeHash = %x", rep.CompositeHash)
}
func TestTDX_Policy_MeasurementAndReportDataBinding(t *testing.T) {
quote := mustParseQuote(t, tdxProdQuote)
body := quote.GetTdQuoteBody()
goodFold := foldTDXMeasurement(body.GetMrTd(), body.GetRtmrs())
goodRD := body.GetReportData()
// Correct expected measurement -> accepted.
if _, err := buildTDXReport(quote, applyOptions(WithExpectedMeasurement(goodFold))); err != nil {
t.Errorf("correct expected measurement rejected: %v", err)
}
// Wrong expected measurement -> ErrPolicy.
wrong := append([]byte(nil), goodFold...)
wrong[0] ^= 0xFF
if _, err := buildTDXReport(quote, applyOptions(WithExpectedMeasurement(wrong))); !errors.Is(err, ErrPolicy) {
t.Errorf("wrong measurement: want ErrPolicy, got %v", err)
}
// Correct expected report data (nonce binding) -> accepted.
if _, err := buildTDXReport(quote, applyOptions(WithExpectedReportData(goodRD))); err != nil {
t.Errorf("correct expected report data rejected: %v", err)
}
// Wrong expected report data -> ErrPolicy.
wrongRD := append([]byte(nil), goodRD...)
wrongRD[0] ^= 0xFF
if _, err := buildTDXReport(quote, applyOptions(WithExpectedReportData(wrongRD))); !errors.Is(err, ErrPolicy) {
t.Errorf("wrong report data: want ErrPolicy, got %v", err)
}
}
// -----------------------------------------------------------------------------
// Tamper rejection
// -----------------------------------------------------------------------------
// TestTDX_Tamper_MRTD_BreaksSignature flips a byte of MRTD and proves the TD
// report signature no longer verifies — i.e. the measurement is genuinely
// bound by the hardware signature, so a forged measurement is rejected.
func TestTDX_Tamper_MRTD_BreaksSignature(t *testing.T) {
tampered := append([]byte(nil), tdxProdQuote...)
tampered[tdxAbsMRTDOffset] ^= 0xFF
any, err := abi.QuoteToProto(tampered)
if err != nil {
return // parser rejecting it is also a valid refusal
}
quote := any.(*tdxpb.QuoteV4)
verr := verify.TdxQuote(quote, &verify.Options{GetCollateral: false, Now: tdxFixedNow()})
if verr == nil {
t.Fatal("tampered MRTD still verified — measurement not bound by signature")
}
if got := classifyTDXVerifyError(verr); !errors.Is(got, ErrSignatureInvalid) {
t.Errorf("tampered MRTD: want ErrSignatureInvalid, got %v (raw: %v)", got, verr)
}
}
// TestTDX_Tamper_Signature_Rejected flips a byte of the ECDSA signature and
// proves verification refuses.
func TestTDX_Tamper_Signature_Rejected(t *testing.T) {
tampered := append([]byte(nil), tdxProdQuote...)
tampered[tdxAbsSigOffset] ^= 0xFF
any, err := abi.QuoteToProto(tampered)
if err != nil {
return // parser rejecting it is also a valid refusal
}
quote := any.(*tdxpb.QuoteV4)
verr := verify.TdxQuote(quote, &verify.Options{GetCollateral: false, Now: tdxFixedNow()})
if verr == nil {
t.Fatal("tampered signature still verified")
}
if got := classifyTDXVerifyError(verr); !errors.Is(got, ErrSignatureInvalid) {
t.Errorf("tampered signature: want ErrSignatureInvalid, got %v (raw: %v)", got, verr)
}
}
// TestTDX_Reject_BadVersion proves an unsupported quote version is refused at
// the parse boundary as ErrInvalidEvidence (no getter needed).
func TestTDX_Reject_BadVersion(t *testing.T) {
bad := append([]byte(nil), tdxProdQuote...)
bad[0] = 0x03 // version is a little-endian uint16 at offset 0; 4 -> 3
_, err := TDX{}.Verify(context.Background(), bad, WithNow(tdxFixedNow()))
if !errors.Is(err, ErrInvalidEvidence) {
t.Errorf("bad version: want ErrInvalidEvidence, got %v", err)
}
}
// TestTDX_Reject_Truncated proves a too-short blob is refused before any trust
// decision.
func TestTDX_Reject_Truncated(t *testing.T) {
_, err := TDX{}.Verify(context.Background(), tdxProdQuote[:abi.QuoteMinSize-1], WithNow(tdxFixedNow()))
if !errors.Is(err, ErrInvalidEvidence) {
t.Errorf("truncated: want ErrInvalidEvidence, got %v", err)
}
}
// TestTDX_Reject_Garbage proves random bytes of sufficient length are refused.
func TestTDX_Reject_Garbage(t *testing.T) {
garbage := make([]byte, abi.QuoteMinSize+16)
for i := range garbage {
garbage[i] = byte(i * 7)
}
_, err := TDX{}.Verify(context.Background(), garbage, WithNow(tdxFixedNow()))
if !errors.Is(err, ErrInvalidEvidence) {
t.Errorf("garbage: want ErrInvalidEvidence, got %v", err)
}
}
// -----------------------------------------------------------------------------
// Dispatch wiring + interface conformance
// -----------------------------------------------------------------------------
// TestTDX_DispatchRoutesToTDX proves Dispatch(KindTDX, ...) reaches this
// verifier (and, with a stale-TCB fixture, refuses just like a direct call).
func TestTDX_DispatchRoutesToTDX(t *testing.T) {
_, err := Dispatch(context.Background(), KindTDX, tdxProdQuote,
WithKDSGetter(tdxReplayGetter{}), WithNow(tdxFixedNow()))
if !errors.Is(err, ErrPolicy) {
t.Errorf("Dispatch(KindTDX): want ErrPolicy (stale TCB), got %v", err)
}
}
func TestTDX_ContextCanceled(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
_, err := TDX{}.Verify(ctx, tdxProdQuote, WithKDSGetter(tdxReplayGetter{}), WithNow(tdxFixedNow()))
if !errors.Is(err, context.Canceled) {
t.Errorf("canceled context: want context.Canceled, got %v", err)
}
}
+1
View File
@@ -0,0 +1 @@
-----BEGIN%20CERTIFICATE-----%0AMIICizCCAjKgAwIBAgIUfjiC1ftVKUpASY5FhAPpFJG99FUwCgYIKoZIzj0EAwIw%0AaDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv%0AcnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ%0ABgNVBAYTAlVTMB4XDTE4MDUyMTEwNTAxMFoXDTI1MDUyMTEwNTAxMFowbDEeMBwG%0AA1UEAwwVSW50ZWwgU0dYIFRDQiBTaWduaW5nMRowGAYDVQQKDBFJbnRlbCBDb3Jw%0Ab3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNBMQswCQYD%0AVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABENFG8xzydWRfK92bmGv%0AP%2BmAh91PEyV7Jh6FGJd5ndE9aBH7R3E4A7ubrlh%2FzN3C4xvpoouGlirMba%2BW2lju%0AypajgbUwgbIwHwYDVR0jBBgwFoAUImUM1lqdNInzg7SVUr9QGzknBqwwUgYDVR0f%0ABEswSTBHoEWgQ4ZBaHR0cHM6Ly9jZXJ0aWZpY2F0ZXMudHJ1c3RlZHNlcnZpY2Vz%0ALmludGVsLmNvbS9JbnRlbFNHWFJvb3RDQS5kZXIwHQYDVR0OBBYEFH44gtX7VSlK%0AQEmORYQD6RSRvfRVMA4GA1UdDwEB%2FwQEAwIGwDAMBgNVHRMBAf8EAjAAMAoGCCqG%0ASM49BAMCA0cAMEQCIB9C8wOAN%2FImxDtGACV246KcqjagZOR0kyctyBrsGGJVAiAj%0AftbrNGsGU8YH211dRiYNoPPu19Zp%2Fze8JmhujB0oBw%3D%3D%0A-----END%20CERTIFICATE-----%0A-----BEGIN%20CERTIFICATE-----%0AMIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw%0AaDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv%0AcnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ%0ABgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG%0AA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0%0AaW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT%0AAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj%2FiPWsCzaEKi7%0A1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB%0AuzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ%0AMEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50%0AZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV%0AUr9QGzknBqwwDgYDVR0PAQH%2FBAQDAgEGMBIGA1UdEwEB%2FwQIMAYBAf8CAQEwCgYI%0AKoZIzj0EAwIDSQAwRgIhAOW%2F5QkR%2BS9CiSDcNoowLuPRLsWGf%2FYi7GSX94BgwTwg%0AAiEA4J0lrHoMs%2BXo5o%2FsX6O9QWxHRAvZUGOdRQ7cvqRXaqI%3D%0A-----END%20CERTIFICATE-----%0A
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
-----BEGIN%20CERTIFICATE-----%0AMIICljCCAj2gAwIBAgIVAJVvXc29G%2BHpQEnJ1PQzzgFXC95UMAoGCCqGSM49BAMC%0AMGgxGjAYBgNVBAMMEUludGVsIFNHWCBSb290IENBMRowGAYDVQQKDBFJbnRlbCBD%0Ab3Jwb3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNBMQsw%0ACQYDVQQGEwJVUzAeFw0xODA1MjExMDUwMTBaFw0zMzA1MjExMDUwMTBaMHAxIjAg%0ABgNVBAMMGUludGVsIFNHWCBQQ0sgUGxhdGZvcm0gQ0ExGjAYBgNVBAoMEUludGVs%0AIENvcnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0Ex%0ACzAJBgNVBAYTAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENSB%2F7t21lXSO%0A2Cuzpxw74eJB72EyDGgW5rXCtx2tVTLq6hKk6z%2BUiRZCnqR7psOvgqFeSxlmTlJl%0AeTmi2WYz3qOBuzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBS%0ABgNVHR8ESzBJMEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2Vy%0AdmljZXMuaW50ZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUlW9d%0Azb0b4elAScnU9DPOAVcL3lQwDgYDVR0PAQH%2FBAQDAgEGMBIGA1UdEwEB%2FwQIMAYB%0AAf8CAQAwCgYIKoZIzj0EAwIDRwAwRAIgXsVki0w%2Bi6VYGW3UF%2F22uaXe0YJDj1Ue%0AnA%2BTjD1ai5cCICYb1SAmD5xkfTVpvo4UoyiSYxrDWLmUR4CI9NKyfPN%2B%0A-----END%20CERTIFICATE-----%0A-----BEGIN%20CERTIFICATE-----%0AMIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw%0AaDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv%0AcnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ%0ABgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG%0AA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0%0AaW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT%0AAlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj%2FiPWsCzaEKi7%0A1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB%0AuzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ%0AMEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50%0AZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV%0AUr9QGzknBqwwDgYDVR0PAQH%2FBAQDAgEGMBIGA1UdEwEB%2FwQIMAYBAf8CAQEwCgYI%0AKoZIzj0EAwIDSQAwRgIhAOW%2F5QkR%2BS9CiSDcNoowLuPRLsWGf%2FYi7GSX94BgwTwg%0AAiEA4J0lrHoMs%2BXo5o%2FsX6O9QWxHRAvZUGOdRQ7cvqRXaqI%3D%0A-----END%20CERTIFICATE-----%0A
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
{"enclaveIdentity":{"id":"TD_QE","version":2,"issueDate":"2023-06-08T07:24:59Z","nextUpdate":"2023-07-08T07:24:59Z","tcbEvaluationDataNumber":15,"miscselect":"00000000","miscselectMask":"FFFFFFFF","attributes":"11000000000000000000000000000000","attributesMask":"FBFFFFFFFFFFFFFF0000000000000000","mrsigner":"DC9E2A7C6F948F17474E34A7FC43ED030F7C1563F1BABDDF6340C82E0E54A8C5","isvprodid":2,"tcbLevels":[{"tcb":{"isvsvn":4},"tcbDate":"2023-02-15T00:00:00Z","tcbStatus":"UpToDate"}]},"signature":"b6a601f05de27f2ca5105eec24bdd4bf7dd1b8bbfffc76dffe4f4d16b8a395843e4b92d430fd6744b0648bf44302c528412fcb9cbf3cc9ce6922a3057932b6a6"}
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
{"tcbInfo":{"id":"TDX","version":3,"issueDate":"2023-06-18T08:42:58Z","nextUpdate":"2023-07-18T08:42:58Z","fmspc":"50806f000000","pceId":"0000","tcbType":0,"tcbEvaluationDataNumber":15,"tdxModule":{"mrsigner":"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","attributes":"0000000000000000","attributesMask":"FFFFFFFFFFFFFFFF"},"tcbLevels":[{"tcb":{"sgxtcbcomponents":[{"svn":5,"category":"BIOS","type":"Early Microcode Update"},{"svn":5,"category":"OS/VMM","type":"SGX Late Microcode Update"},{"svn":2,"category":"OS/VMM","type":"TXT SINIT"},{"svn":2,"category":"BIOS"},{"svn":3,"category":"BIOS"},{"svn":1,"category":"BIOS"},{"svn":0},{"svn":3,"category":"OS/VMM","type":"SEAMLDR ACM"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}],"pcesvn":11,"tdxtcbcomponents":[{"svn":3,"category":"OS/VMM","type":"TDX Module"},{"svn":0,"category":"OS/VMM","type":"TDX Module"},{"svn":5,"category":"OS/VMM","type":"TDX Late Microcode Update"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}]},"tcbDate":"2023-02-15T00:00:00Z","tcbStatus":"UpToDate"},{"tcb":{"sgxtcbcomponents":[{"svn":5,"category":"BIOS","type":"Early Microcode Update"},{"svn":5,"category":"OS/VMM","type":"SGX Late Microcode Update"},{"svn":2,"category":"OS/VMM","type":"TXT SINIT"},{"svn":2,"category":"BIOS"},{"svn":3,"category":"BIOS"},{"svn":1,"category":"BIOS"},{"svn":0},{"svn":3,"category":"OS/VMM","type":"SEAMLDR ACM"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}],"pcesvn":5,"tdxtcbcomponents":[{"svn":3,"category":"OS/VMM","type":"TDX Module"},{"svn":0,"category":"OS/VMM","type":"TDX Module"},{"svn":5,"category":"OS/VMM","type":"TDX Late Microcode Update"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}]},"tcbDate":"2018-01-04T00:00:00Z","tcbStatus":"OutOfDate","advisoryIDs":["INTEL-SA-00106","INTEL-SA-00115","INTEL-SA-00135","INTEL-SA-00203","INTEL-SA-00220","INTEL-SA-00233","INTEL-SA-00270","INTEL-SA-00293","INTEL-SA-00320","INTEL-SA-00329","INTEL-SA-00381","INTEL-SA-00389","INTEL-SA-00477"]}]},"signature":"f6502d6fad1e3b7281df2b7eddc773d5b5281187346c12c5647b4f243cea49212be96a7a1a6b5d83e36323fe3fa9dacd61ebfbc38e631ff0fe29ef14ae0db0b4"}
+8 -54
View File
@@ -233,57 +233,11 @@ func TestDispatch_RejectsUnknownKind(t *testing.T) {
}
}
// TestStubsReturnNotImplemented confirms the TDX stub FAILS LOUD —
// returning ErrNotImplemented (a hard refusal callers must not paper
// over) rather than silently returning a nil error. This guards against
// an accidental future change that silently accepts evidence the
// verifier hasn't actually checked, while keeping the failure
// recoverable so request-handling goroutines do not crash the whole
// process. The NVIDIA path is no longer a stub — see nvtrust_test.go.
// When stage 2 of #222 lands, replace this test with a real TDX case.
func TestStubsReturnNotImplemented(t *testing.T) {
cases := []struct {
name string
v Verifier
}{
{"tdx", TDX{}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Fatalf("%s.Verify panicked (must error-return instead): %v", c.name, r)
}
}()
rep, err := c.v.Verify(context.Background(), []byte{0x00})
if rep != nil {
t.Fatalf("%s.Verify returned non-nil report: %+v", c.name, rep)
}
if !errors.Is(err, ErrNotImplemented) {
t.Fatalf("%s.Verify returned %v, want ErrNotImplemented", c.name, err)
}
})
}
}
// TestDispatch_StubKindsReturnNotImplemented covers the Dispatch path
// for the stub kinds: the entry point must surface ErrNotImplemented
// instead of crashing.
func TestDispatch_StubKindsReturnNotImplemented(t *testing.T) {
for _, kind := range []Kind{KindTDX} {
t.Run(string(kind), func(t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Fatalf("Dispatch(%s) panicked: %v", kind, r)
}
}()
rep, err := Dispatch(context.Background(), kind, []byte{0x00})
if rep != nil {
t.Fatalf("Dispatch(%s) returned non-nil report: %+v", kind, rep)
}
if !errors.Is(err, ErrNotImplemented) {
t.Fatalf("Dispatch(%s) returned %v, want ErrNotImplemented", kind, err)
}
})
}
}
// All evidence Kinds are now real verifiers, each fail-closed on bad input
// and covered in its own test file: SEV-SNP (this file), TDX (tdx_test.go),
// and NVTrust (nvtrust_test.go). No stub Kinds remain, so the former
// TestStubsReturnNotImplemented / TestDispatch_StubKindsReturnNotImplemented
// (which asserted TDX returned ErrNotImplemented) have been retired now that
// TDX is production-real. ErrNotImplemented stays a valid refusal sentinel:
// any future stub Kind should ship its own fail-loud test alongside the
// verifier, mirroring the SEV/TDX/NVTrust per-kind test layout.
+4 -1
View File
@@ -2,7 +2,10 @@ module github.com/luxfi/cc
go 1.26.4
require github.com/google/go-sev-guest v0.14.1
require (
github.com/google/go-sev-guest v0.14.1
github.com/google/go-tdx-guest v0.3.1
)
require (
github.com/google/logger v1.1.1 // indirect
+2
View File
@@ -6,6 +6,8 @@ github.com/google/go-configfs-tsm v0.2.2 h1:YnJ9rXIOj5BYD7/0DNnzs8AOp7UcvjfTvt21
github.com/google/go-configfs-tsm v0.2.2/go.mod h1:EL1GTDFMb5PZQWDviGfZV9n87WeGTR/JUg13RfwkgRo=
github.com/google/go-sev-guest v0.14.1 h1:j/DXy9jk1qSW/dEV9vDiQnhAVFD1zqnWNVu6p1J0Jgo=
github.com/google/go-sev-guest v0.14.1/go.mod h1:SK9vW+uyfuzYdVN0m8BShL3OQCtXZe/JPF7ZkpD3760=
github.com/google/go-tdx-guest v0.3.1 h1:gl0KvjdsD4RrJzyLefDOvFOUH3NAJri/3qvaL5m83Iw=
github.com/google/go-tdx-guest v0.3.1/go.mod h1:/rc3d7rnPykOPuY8U9saMyEps0PZDThLk/RygXm04nE=
github.com/google/logger v1.1.1 h1:+6Z2geNxc9G+4D4oDO9njjjn2d0wN5d7uOo0vOIW1NQ=
github.com/google/logger v1.1.1/go.mod h1:BkeJZ+1FhQ+/d087r4dzojEg1u2ZX+ZqG1jTUrLM+zQ=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=