Adds the canonical TEE attestation verifier package at cc/attest. This
is the home for chain validation + trust anchor management; the
luxcpp/crypto/attestation parser primitives feed bytes here BEFORE
their measurement output is trusted.
Stage 1 — SEV-SNP — PRODUCTION:
- cc/attest/sev.go: SEVSNP{} verifier built on
github.com/google/go-sev-guest v0.14.1. Live VCEK fetch from AMD
KDS, ARK→ASK→VCEK chain validation, signature verify against the
leaf, MEASUREMENT/REPORT_DATA/HOST_DATA/CHIP_ID extraction,
caller-supplied policy gates (WithExpectedReportData,
WithExpectedMeasurement). 197 LOC.
- testdata/: real-vendor Milan attestation report + VCEK leaf
committed verbatim from the upstream go-sev-guest test corpus.
Tests replay the AMD KDS responses via SimpleGetter so CI never
hits the network.
- 9 SEV-SNP tests passing (happy path, dispatch, length reject,
tampered signature, wrong report_data, wrong measurement,
deterministic composite hash, unknown kind, stub-panic guard).
Stage 2 — TDX — STUB (#222 stage 2):
- cc/attest/tdx.go: TDX{}.Verify panics with ErrNotImplemented so
any caller routing TDX evidence during stage 1 fails LOUD. Plan
documented inline: go-tdx-guest + Intel PCS + QE/TDX module
identity pinning.
Stage 3 — NRAS — STUB (#222 stage 3):
- cc/attest/nras.go: NRAS{}.Verify panics with ErrNotImplemented.
Plan: lift the existing pkg/attestation/nvidia NRAS client into
the cc/attest contract with a JWKS cache.
Wire-in:
- pkg/attestation/composite.go gains VerifyEvidence(ctx, opts...)
which dispatches each Evidence blob to cc/attest by Issuer.
Callers (kms release gate, scheduler) MUST invoke this before
accepting parser output.
Doc:
- cc/attest/doc.go states the production/stub matrix and the
layering contract between the C-ABI parser and this package.
VerifyOptions.SkipSignature was a test-only boolean that bypassed
JWS verification entirely. Removed unconditionally. Production has
always required verification; the only caller was a test that
exercised the skip path. JWS verification is now non-optional.
verifyJWS dispatched on the JWS header alg, then handed off to the
shared verifySignature helper which dispatched on the public-key type.
A token forged with header alg=EdDSA against a trust root that holds
an *rsa.PublicKey for the same kid would: (1) take the EdDSA branch
in verifyJWS, (2) call verifySignature(rsaPub, signedInput, sig),
(3) hit the *rsa.PublicKey case and run RSA-PSS over attacker-chosen
signedInput, (4) accept whatever sig forged. Classic CVE-2015-9235
algorithm-substitution shape.
Fix: the JWS path no longer routes through verifySignature. Each alg
arm in verifyJWS asserts the trust-root key type before any
verification primitive runs; for ECDSA the curve is also bound to
the alg (ES256→P-256, ES384→P-384). alg=none (any case) and unknown
algs are refused with explicit sentinels.
The companion verifySignature in rim.go is intentionally retained
for the RIM detached-signature path: RIM has no alg envelope, so
key-type dispatch is the only correct choice there. Splitting the
two paths is the only correct factoring; merging them re-introduces
this attack.
Sentinels:
ErrAlgKeyTypeMismatch — alg announced does not match key type
ErrAlgNoneRefused — alg=none rejected unconditionally
ErrUnknownAlg — alg not recognized
Algs supported: EdDSA, ES256, ES384, PS256, PS384.
Tests (8/8 PASS, -race clean):
TestVerifyJWS_EdDSA_Valid
TestVerifyJWS_ES256_Valid
TestVerifyJWS_PS256_Valid
TestVerifyJWS_EdDSA_AgainstRSAPub_Rejected (the substitution attack)
TestVerifyJWS_ES256_AgainstEd25519Pub_Rejected
TestVerifyJWS_ES256_AgainstP384Pub_Rejected (curve mismatch)
TestVerifyJWS_AlgNone_Refused
TestVerifyJWS_UnknownAlg_Refused
Pre-existing nvidia package tests (RIM ed25519/ECDSA/RSA-PSS, NRAS
ES256 happy path, etc.) continue to pass.
LOC delta: nras_client.go +107/-65, nras_jws_test.go +178/-0.
v0.61 — wire the scheduler workload-policy gate into the MPC SignGate.
SignGate gains optional Scheduler + Worker fields; AuthorizeSign now
runs scheduler.Eligible(worker, ci.PolicyID) BEFORE the policy
verifier and short-circuits on insufficient trust posture. Existing
callers that pass nil Scheduler are unaffected.
pkg/scheduler/policy_gate.go — TrustMode/IOLevel enums,
WorkloadPolicy.Eligible(Worker)
pkg/scheduler/service_drain.go — AuthorizeDrain + AuthorizeSession
pkg/mpc/sign_orchestrator.go — Scheduler/Worker fields,
WithScheduler(...), two-stage gate
v0.62 — NVIDIA Confidential-Compute attestation primitives. Clean-room
parsers + verifiers against the publicly documented nvTrust schema
(Apache-2 derived); no NVIDIA proprietary code vendored.
pkg/attestation/nvidia/gpu_report.go — GPU evidence-report parser
pkg/attestation/nvidia/nras_client.go — NRAS HTTP client + JWS verify
pkg/attestation/nvidia/rim.go — RIM signature + value match
pkg/attestation/nvidia/composite.go — VerifiedBundle (CPU+GPU bind)
Tests (57 passing, -race clean):
pkg/scheduler — 11 (truth-table, drain,
session, orchestrator
integration)
pkg/attestation/nvidia — 46 (parser, RIM Ed25519/ECDSA/
RSA-PSS, NRAS happy/error/
signature paths, ECDSA JOSE)
pkg/attestation/nvidia (hwattest) — 3, env-skipped without real GPU
Sibling v0.60 reconciliation: pkg/registry + pkg/attestation already
land WorkerCapability + CompositeAttestation envelope using lattice
trust types. nvidia.VerifiedBundle is renamed (was Composite-) to
avoid collision with pkg/attestation.CompositeAttestation; the
canonical envelope wraps the verified blobs this package emits.
Residual: lattice/v7/types is not yet published, so pkg/registry +
pkg/attestation cannot compile against luxfi/[email protected]; the
sibling v0.60 work tracks that. pkg/policy still has a pre-existing
unresolved import of luxfi/fhe/threshold (should be /pkg/threshold);
sign_orchestrator_scheduler_test.go is ready to run as soon as that
upstream issue is fixed by its owner.
Build the GPU-runtime trust registry that gates every dispatch decision
against typed capability records and a deterministic attestation root.
Reference: LP-137-TRUST-REGISTRY.md.
pkg/registry:
- types.go: WorkerCapability, NodeCapability, GPUArch, GPUBackend,
PeerInterconnect with Validate() that enforces TEE-arch + attestation
invariants at the API edge.
- policy.go: LanePolicy (operator-controlled), WorkloadPolicy
(per-request), and a single canonical Eligible(lane, workload, worker)
check that runs twelve gates in fixed order. Privacy-class consistency
is re-checked on the hot path so a stale worker record cannot bypass
the API edge.
pkg/attestation:
- composite.go: CompositeAttestation envelope over CPU TEE + GPU TEE +
platform evidence. Root() is RFC 8949 §4.2 deterministic CBOR via
fxamacker/cbor (already a direct dep) followed by SHA-256. The same
encoder is used by Marshal so Root and the wire form agree byte-for-
byte. Validate() rejects malformed envelopes including
composite-trust-without-GPU-evidence.
Tests (103 pass): truth-table eligibility coverage, gate-ordering
regression, sensitivity-to-every-field digest, deterministic-bytes
round-trip, garbage-decode rejection.
go.mod: promote luxfi/lattice/v7 from indirect to direct require, add
local replace pointing at ../lattice (mirrors the existing fhe pattern).
Drop the replace once luxfi/lattice publishes a tag containing
types/trust.go.