Files
cc/attest/nvtrust_test.go
zeekay c84126eaa1 feat(attest/nvtrust): real NVIDIA GPU CC verification — SPDM report sig + device-chain + RIM, ModeLocal/ModeNRAS
The prior KindNVTrust local path was cryptographically hollow: it matched
PLAINTEXT JSON measurements against the (public) RIM and never parsed or
verified the GPU-signed SPDM report or the device certificate chain — a
host could echo golden values and pass. This makes it production-real.

ModeLocal (cloud-free, full hardware verify), in order, refusing on any
failure:
  1. chain the GPU device leaf cert to an operator-pinned NVIDIA device-
     identity root (no insecure mode; empty root => refuse);
  2. verify the SPDM MEASUREMENTS (0x60) response signature over the
     DSP0274 transcript (request || response[:-sig]) with the leaf key,
     SPDM 1.1 and 1.2, ECDSA P-384/P-256 + RSA-PSS, with a strict
     algo<->key-type binding (substitution defense);
  3. extract measurements from the SIGNED record and match them BY INDEX
     to the NVIDIA-signed RIM (completeness enforced — closes the drop-a-
     bad-measurement attack);
  4. bind the SIGNED requester nonce to the caller's freshness challenge.

ModeNRAS (remote): verify an NVIDIA NRAS EAT/JWT — JWS signature against
pinned signer keys (or x5c chain to a pinned NRAS root), exp/nbf freshness,
eat_nonce binding, positive overall-result. No network in Verify (the POST
stays in NRASClient).

GPU measurement -> VerifiedReport.Measurement (RIM root / EAT root); trust-
score/tier stays caller policy on top. ChipID is the trusted leaf serial,
not the untrusted envelope UUID. Trust anchors are operator-pinned via
NewNVTrust options, never from evidence; the zero value (Dispatch) is fail-
closed. No new dependencies (stdlib crypto only).

New: attest/nvidia/{spdm,devicechain,eat}.go. Extended: gpu_report.go
(spdm_request/response/asym_algo), rim.go (index-keyed MatchSPDM). 30 new
tests: self-consistent SPDM+chain+RIM vectors (1.1 and 1.2), NRAS EAT
vectors, and tamper rejection (bad SPDM sig, tampered signed record, wrong
device root, RIM mismatch, RIM sig tamper, nonce mismatch, missing SPDM,
bad/expired/negative EAT). go build + go test ./... green (race-clean).
2026-06-27 13:51:24 -07:00

683 lines
23 KiB
Go

// Copyright (c) 2026, Lux Industries Inc.
// SPDX-License-Identifier: BSD-3-Clause
package attest
import (
"bytes"
"context"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"crypto/sha512"
"crypto/x509"
"crypto/x509/pkix"
"encoding/base64"
"encoding/binary"
"encoding/hex"
"encoding/json"
"encoding/pem"
"errors"
"math/big"
"testing"
"time"
"github.com/luxfi/cc/attest/nvidia"
)
// =============================================================================
// Self-consistent ModeLocal vector: a real SPDM-signed measurement report,
// a device cert chain (root -> intermediate -> leaf, ECDSA P-384) that the
// leaf key signs the report under, and an NVIDIA-signed RIM whose index-
// keyed golden values equal the signed measurements.
//
// The verification logic exercised is production-real (real ECDSA P-384
// over the real DSP0274 transcript, real X.509 path validation, real RIM
// signature + index match). Only the trust anchor is a test root, exactly
// as go-sev-guest's tests use a test signer and production pins AMD's root.
// =============================================================================
const (
testFixedNow = "2026-06-27T12:00:00Z"
testArch = "Hopper"
testDriver = "535.104.05"
testVBIOS = "96.00.74.00.01"
testLeafSerial = 0x4e56494449413031 // "NVIDIA01" as an int serial
)
// localVector is everything a ModeLocal Verify needs, plus the knobs to
// tamper for negative cases.
type localVector struct {
evidence []byte // the GPU evidence envelope JSON
rim []byte // signed RIM
rimRoots []nvidia.TrustRoot
devRoots *x509.CertPool
nonce [32]byte
leafCert *x509.Certificate
leafPriv *ecdsa.PrivateKey
intermPEM string
now time.Time
}
func mustTime(t *testing.T, s string) time.Time {
t.Helper()
tm, err := time.Parse(time.RFC3339, s)
if err != nil {
t.Fatalf("parse time: %v", err)
}
return tm
}
func u8ptr(v uint8) *uint8 { return &v }
func mkCert(t *testing.T, tmpl, parent *x509.Certificate, pub crypto.PublicKey, signer crypto.Signer) *x509.Certificate {
t.Helper()
der, err := x509.CreateCertificate(rand.Reader, tmpl, parent, pub, signer)
if err != nil {
t.Fatalf("create cert: %v", err)
}
c, err := x509.ParseCertificate(der)
if err != nil {
t.Fatalf("parse cert: %v", err)
}
return c
}
func certPEM(c *x509.Certificate) string {
return string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: c.Raw}))
}
func sha256Sum(b []byte) []byte {
h := sha256.Sum256(b)
return h[:]
}
func le16(v int) []byte {
b := make([]byte, 2)
binary.LittleEndian.PutUint16(b, uint16(v))
return b
}
func le24(v int) []byte {
return []byte{byte(v), byte(v >> 8), byte(v >> 16)}
}
// joseConcat encodes (r,s) as fixed-width big-endian r||s (the SPDM/JOSE form).
func joseConcatLocal(r, s *big.Int, size int) []byte {
out := make([]byte, size*2)
rB := r.Bytes()
sB := s.Bytes()
copy(out[size-len(rB):size], rB)
copy(out[size*2-len(sB):], sB)
return out
}
// buildSPDMRequest builds a GET_MEASUREMENTS (0xE0) request asking for all
// blocks with a signature, carrying the requester nonce.
func buildSPDMRequest(version byte, nonce [32]byte) []byte {
req := []byte{version, 0xE0, 0x01 /*sig requested*/, 0xFF /*all blocks*/}
req = append(req, nonce[:]...)
req = append(req, 0x00) // SlotIDParam
return req
}
type measBlock struct {
index uint8
value []byte // 48-byte digest
}
// buildSignedSPDMResponse builds a MEASUREMENTS (0x60) response over the
// given blocks and signs the DSP0274 transcript (request || responseBody)
// with leafPriv. Returns the full response message (body || signature).
func buildSignedSPDMResponse(t *testing.T, version byte, blocks []measBlock, respNonce [32]byte, leafPriv *ecdsa.PrivateKey, requestMsg []byte) []byte {
t.Helper()
var record []byte
for _, b := range blocks {
dmtf := []byte{0x00} // value type: digest (bit7=0), type 0
dmtf = append(dmtf, le16(len(b.value))...)
dmtf = append(dmtf, b.value...)
blk := []byte{b.index, 0x01 /*DMTF spec*/}
blk = append(blk, le16(len(dmtf))...)
blk = append(blk, dmtf...)
record = append(record, blk...)
}
body := []byte{version, 0x60, 0x00, 0x00, byte(len(blocks))}
body = append(body, le24(len(record))...)
body = append(body, record...)
body = append(body, respNonce[:]...)
body = append(body, le16(0)...) // opaque length 0
digest := spdmDigestForTest(version, requestMsg, body)
r, s, err := ecdsa.Sign(rand.Reader, leafPriv, digest)
if err != nil {
t.Fatalf("spdm sign: %v", err)
}
sig := joseConcatLocal(r, s, 48) // P-384 -> 96-byte r||s
return append(body, sig...)
}
// spdmDigestForTest independently reproduces the DSP0274 transcript digest
// the verifier checks (SHA-384). 1.1: SHA384(req||body). 1.2: SHA384(ctx ||
// SHA384(req||body)).
func spdmDigestForTest(version byte, requestMsg, body []byte) []byte {
h := sha512.New384()
h.Write(requestMsg)
h.Write(body)
th := h.Sum(nil)
if version < 0x12 {
return th
}
const prefixUnit = "dmtf-spdm-v1.2.*"
const context = "responder-measurements signing"
ctx := make([]byte, 100)
for i := 0; i < 4; i++ {
copy(ctx[i*16:(i+1)*16], prefixUnit)
}
copy(ctx[100-len(context):], context)
h2 := sha512.New384()
h2.Write(ctx)
h2.Write(th)
return h2.Sum(nil)
}
// buildLocalVector assembles a complete, valid ModeLocal vector for the
// given SPDM version.
func buildLocalVector(t *testing.T, version byte) localVector {
t.Helper()
now := mustTime(t, testFixedNow)
// --- device chain: root -> intermediate -> leaf (all P-384) ---
rootPriv, _ := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
intermPriv, _ := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
leafPriv, _ := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
rootTmpl := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{CommonName: "NVIDIA Test Device Identity Root"},
NotBefore: now.Add(-time.Hour),
NotAfter: now.Add(24 * time.Hour),
IsCA: true,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
BasicConstraintsValid: true,
}
rootCert := mkCert(t, rootTmpl, rootTmpl, rootPriv.Public(), rootPriv)
intermTmpl := &x509.Certificate{
SerialNumber: big.NewInt(2),
Subject: pkix.Name{CommonName: "NVIDIA Test GPU Attestation CA"},
NotBefore: now.Add(-time.Hour),
NotAfter: now.Add(24 * time.Hour),
IsCA: true,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
BasicConstraintsValid: true,
}
intermCert := mkCert(t, intermTmpl, rootCert, intermPriv.Public(), rootPriv)
leafTmpl := &x509.Certificate{
SerialNumber: big.NewInt(testLeafSerial),
Subject: pkix.Name{CommonName: "GPU-TEST-0001"},
NotBefore: now.Add(-time.Hour),
NotAfter: now.Add(24 * time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature,
}
leafCert := mkCert(t, leafTmpl, intermCert, leafPriv.Public(), intermPriv)
rootPool := x509.NewCertPool()
rootPool.AddCert(rootCert)
// --- SPDM request + signed response ---
var nonce [32]byte
for i := range nonce {
nonce[i] = byte(i + 1)
}
var respNonce [32]byte
for i := range respNonce {
respNonce[i] = byte(0xA0 + i)
}
val0 := bytes.Repeat([]byte{0x11}, 48)
val1 := bytes.Repeat([]byte{0x22}, 48)
req := buildSPDMRequest(version, nonce)
resp := buildSignedSPDMResponse(t, version, []measBlock{
{index: 0, value: val0},
{index: 1, value: val1},
}, respNonce, leafPriv, req)
// --- RIM: index-keyed golden values == the signed measurements ---
rimPub, rimPriv, _ := ed25519.GenerateKey(rand.Reader)
rimRoots := []nvidia.TrustRoot{{KeyID: "nvidia-rim-1", Public: rimPub}}
entries := []nvidia.RIMEntry{
{Name: "FW_RT", ValueHex: hex.EncodeToString(val0), Index: u8ptr(0)},
{Name: "VBIOS_RT", ValueHex: hex.EncodeToString(val1), Index: u8ptr(1)},
}
rim := signRIMEd25519(t, rimPriv, "nvidia-rim-1", testArch, testDriver, testVBIOS, entries)
// --- evidence envelope ---
env := map[string]any{
"evidence_version": "2.1",
"gpu_uuid": "GPU-TEST-0001",
"architecture": testArch,
"driver_version": testDriver,
"vbios_version": testVBIOS,
"nonce": hex.EncodeToString(nonce[:]),
"cert_chain": []string{certPEM(leafCert), certPEM(intermCert)},
"spdm_request": base64.StdEncoding.EncodeToString(req),
"spdm_response": base64.StdEncoding.EncodeToString(resp),
"spdm_asym_algo": "ecdsa-p384-sha384",
"measurements": []map[string]any{
{"pcr_index": 0, "name": "FW_RT", "value": hex.EncodeToString(val0)},
{"pcr_index": 1, "name": "VBIOS_RT", "value": hex.EncodeToString(val1)},
},
}
evidence, err := json.Marshal(env)
if err != nil {
t.Fatalf("marshal envelope: %v", err)
}
return localVector{
evidence: evidence,
rim: rim,
rimRoots: rimRoots,
devRoots: rootPool,
nonce: nonce,
leafCert: leafCert,
leafPriv: leafPriv,
intermPEM: certPEM(intermCert),
now: now,
}
}
func (v localVector) verifier() NVTrust {
return NewNVTrust(
WithNVTrustDeviceRoots(v.devRoots),
WithNVTrustClock(func() time.Time { return v.now }),
)
}
// =============================================================================
// signRIMEd25519 — reproduces the nvidia RIM wire format (canonical body =
// json.Marshal of architecture/driver/vbios/entries; signed blob appends
// signer_key_id + signature). Entries may carry an index (omitempty).
// =============================================================================
func signRIMEd25519(t *testing.T, priv ed25519.PrivateKey, kid, arch, driver, vbios string, entries []nvidia.RIMEntry) []byte {
t.Helper()
body := struct {
Architecture string `json:"architecture"`
DriverVersion string `json:"driver_version"`
VBIOSVersion string `json:"vbios_version"`
Entries []nvidia.RIMEntry `json:"entries"`
}{arch, driver, vbios, entries}
canon, err := json.Marshal(body)
if err != nil {
t.Fatalf("canon: %v", err)
}
sig := ed25519.Sign(priv, canon)
signed := struct {
Architecture string `json:"architecture"`
DriverVersion string `json:"driver_version"`
VBIOSVersion string `json:"vbios_version"`
Entries []nvidia.RIMEntry `json:"entries"`
SignerKeyID string `json:"signer_key_id"`
SignatureB64 string `json:"signature"`
}{arch, driver, vbios, entries, kid, base64.StdEncoding.EncodeToString(sig)}
out, err := json.Marshal(signed)
if err != nil {
t.Fatalf("marshal: %v", err)
}
return out
}
// =============================================================================
// ModeLocal happy paths
// =============================================================================
func TestNVTrustLocal_HappyPath_SPDM11(t *testing.T) {
v := buildLocalVector(t, 0x11)
rep, err := v.verifier().Verify(context.Background(), v.evidence,
WithNVTrustRIM(v.rim), WithNVTrustTrustRoots(v.rimRoots),
WithExpectedReportData(v.nonce[:]))
if err != nil {
t.Fatalf("verify: %v", err)
}
if rep.Kind != KindNVTrust || rep.Vendor != "nvidia.nvtrust.local" {
t.Fatalf("kind/vendor = %q/%q", rep.Kind, rep.Vendor)
}
// ReportData is the SIGNED requester nonce.
if !bytes.Equal(rep.ReportData, v.nonce[:]) {
t.Fatalf("report_data = %x want %x", rep.ReportData, v.nonce[:])
}
// ChipID is the trusted leaf serial, NOT the envelope UUID.
if !bytes.Equal(rep.ChipID, big.NewInt(testLeafSerial).Bytes()) {
t.Fatalf("chip_id = %x want %x", rep.ChipID, big.NewInt(testLeafSerial).Bytes())
}
// Measurement equals the signed-RIM measurement root.
parsed, err := nvidia.ParseAndVerifyRIM(v.rim, v.rimRoots)
if err != nil {
t.Fatalf("reparse rim: %v", err)
}
wantRoot := parsed.MeasurementRoot()
if !bytes.Equal(rep.Measurement, wantRoot[:]) {
t.Fatalf("measurement = %x want %x", rep.Measurement, wantRoot[:])
}
if rep.CompositeHash == [32]byte{} {
t.Fatal("composite hash must not be zero")
}
if rep.Extra["nvtrust.mode"] != "local" {
t.Fatalf("extra mode = %q", rep.Extra["nvtrust.mode"])
}
if rep.Extra["nvtrust.spdm_asym_algo"] != "ecdsa-p384-sha384" {
t.Fatalf("extra algo = %q", rep.Extra["nvtrust.spdm_asym_algo"])
}
}
func TestNVTrustLocal_HappyPath_SPDM12(t *testing.T) {
v := buildLocalVector(t, 0x12)
_, err := v.verifier().Verify(context.Background(), v.evidence,
WithNVTrustRIM(v.rim), WithNVTrustTrustRoots(v.rimRoots))
if err != nil {
t.Fatalf("verify 1.2: %v", err)
}
}
// =============================================================================
// ModeLocal fail-closed / tamper rejection
// =============================================================================
// Dispatch uses the zero value (no pinned device root) -> must refuse.
func TestNVTrustLocal_Dispatch_FailClosed(t *testing.T) {
v := buildLocalVector(t, 0x11)
_, err := Dispatch(context.Background(), KindNVTrust, v.evidence,
WithNVTrustRIM(v.rim), WithNVTrustTrustRoots(v.rimRoots))
if !errors.Is(err, ErrPolicy) {
t.Fatalf("err = %v, want ErrPolicy (no device root)", err)
}
}
func TestNVTrustLocal_RejectsTamperedSPDMSignature(t *testing.T) {
v := buildLocalVector(t, 0x11)
// Flip a byte inside the base64 spdm_response so the signature breaks.
var env map[string]any
if err := json.Unmarshal(v.evidence, &env); err != nil {
t.Fatal(err)
}
resp, _ := base64.StdEncoding.DecodeString(env["spdm_response"].(string))
resp[len(resp)-1] ^= 0xFF // last byte of the signature
env["spdm_response"] = base64.StdEncoding.EncodeToString(resp)
tampered, _ := json.Marshal(env)
_, err := v.verifier().Verify(context.Background(), tampered,
WithNVTrustRIM(v.rim), WithNVTrustTrustRoots(v.rimRoots))
if !errors.Is(err, ErrSignatureInvalid) {
t.Fatalf("err = %v, want ErrSignatureInvalid", err)
}
}
func TestNVTrustLocal_RejectsTamperedMeasurementInSignedRecord(t *testing.T) {
v := buildLocalVector(t, 0x11)
// Flip a measurement byte in the signed record (not the signature). The
// SPDM signature no longer covers the record -> signature failure.
var env map[string]any
json.Unmarshal(v.evidence, &env)
resp, _ := base64.StdEncoding.DecodeString(env["spdm_response"].(string))
// The first measurement digest sits well inside the record; flipping any
// record byte invalidates the signature.
resp[12] ^= 0x01
env["spdm_response"] = base64.StdEncoding.EncodeToString(resp)
tampered, _ := json.Marshal(env)
_, err := v.verifier().Verify(context.Background(), tampered,
WithNVTrustRIM(v.rim), WithNVTrustTrustRoots(v.rimRoots))
if !errors.Is(err, ErrSignatureInvalid) {
t.Fatalf("err = %v, want ErrSignatureInvalid", err)
}
}
func TestNVTrustLocal_RejectsWrongDeviceRoot(t *testing.T) {
v := buildLocalVector(t, 0x11)
// A device-root pool that does NOT contain the chain's root.
otherPriv, _ := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
otherTmpl := &x509.Certificate{
SerialNumber: big.NewInt(99),
Subject: pkix.Name{CommonName: "Unrelated Root"},
NotBefore: v.now.Add(-time.Hour),
NotAfter: v.now.Add(time.Hour),
IsCA: true,
KeyUsage: x509.KeyUsageCertSign,
BasicConstraintsValid: true,
}
otherRoot := mkCert(t, otherTmpl, otherTmpl, otherPriv.Public(), otherPriv)
pool := x509.NewCertPool()
pool.AddCert(otherRoot)
verifier := NewNVTrust(
WithNVTrustDeviceRoots(pool),
WithNVTrustClock(func() time.Time { return v.now }),
)
_, err := verifier.Verify(context.Background(), v.evidence,
WithNVTrustRIM(v.rim), WithNVTrustTrustRoots(v.rimRoots))
if !errors.Is(err, ErrChainInvalid) {
t.Fatalf("err = %v, want ErrChainInvalid", err)
}
}
func TestNVTrustLocal_RejectsRIMMismatch(t *testing.T) {
v := buildLocalVector(t, 0x11)
// A cryptographically valid RIM whose index-0 golden differs from the
// signed measurement -> POLICY refusal (signed, but does not match).
rimPub2, rimPriv2, _ := ed25519.GenerateKey(rand.Reader)
roots2 := []nvidia.TrustRoot{{KeyID: "nvidia-rim-2", Public: rimPub2}}
bad := []nvidia.RIMEntry{
{Name: "FW_RT", ValueHex: hex.EncodeToString(bytes.Repeat([]byte{0xEE}, 48)), Index: u8ptr(0)},
{Name: "VBIOS_RT", ValueHex: hex.EncodeToString(bytes.Repeat([]byte{0x22}, 48)), Index: u8ptr(1)},
}
badRIM := signRIMEd25519(t, rimPriv2, "nvidia-rim-2", testArch, testDriver, testVBIOS, bad)
_, err := v.verifier().Verify(context.Background(), v.evidence,
WithNVTrustRIM(badRIM), WithNVTrustTrustRoots(roots2))
if !errors.Is(err, ErrPolicy) {
t.Fatalf("err = %v, want ErrPolicy", err)
}
}
func TestNVTrustLocal_RejectsTamperedRIMSignature(t *testing.T) {
v := buildLocalVector(t, 0x11)
tampered := bytes.Replace(v.rim,
[]byte(hex.EncodeToString(bytes.Repeat([]byte{0x11}, 48))),
[]byte(hex.EncodeToString(bytes.Repeat([]byte{0x33}, 48))), 1)
if bytes.Equal(tampered, v.rim) {
t.Fatal("rim did not mutate")
}
_, err := v.verifier().Verify(context.Background(), v.evidence,
WithNVTrustRIM(tampered), WithNVTrustTrustRoots(v.rimRoots))
if !errors.Is(err, ErrChainInvalid) {
t.Fatalf("err = %v, want ErrChainInvalid", err)
}
}
func TestNVTrustLocal_RejectsWrongExpectedNonce(t *testing.T) {
v := buildLocalVector(t, 0x11)
wrong := make([]byte, 32)
wrong[0] = 0xFF
_, err := v.verifier().Verify(context.Background(), v.evidence,
WithNVTrustRIM(v.rim), WithNVTrustTrustRoots(v.rimRoots),
WithExpectedReportData(wrong))
if !errors.Is(err, ErrPolicy) {
t.Fatalf("err = %v, want ErrPolicy (nonce binding)", err)
}
}
func TestNVTrustLocal_RejectsEnvelopeNonceMismatch(t *testing.T) {
v := buildLocalVector(t, 0x11)
// Change the envelope nonce so it no longer equals the SIGNED request
// nonce -> ErrPolicy (consistency).
var env map[string]any
json.Unmarshal(v.evidence, &env)
other := make([]byte, 32)
other[31] = 0x09
env["nonce"] = hex.EncodeToString(other)
tampered, _ := json.Marshal(env)
_, err := v.verifier().Verify(context.Background(), tampered,
WithNVTrustRIM(v.rim), WithNVTrustTrustRoots(v.rimRoots))
if !errors.Is(err, ErrPolicy) {
t.Fatalf("err = %v, want ErrPolicy", err)
}
}
func TestNVTrustLocal_RejectsMissingSPDM(t *testing.T) {
v := buildLocalVector(t, 0x11)
var env map[string]any
json.Unmarshal(v.evidence, &env)
delete(env, "spdm_response")
delete(env, "attestation_quote")
noSPDM, _ := json.Marshal(env)
_, err := v.verifier().Verify(context.Background(), noSPDM,
WithNVTrustRIM(v.rim), WithNVTrustTrustRoots(v.rimRoots))
if !errors.Is(err, ErrInvalidEvidence) {
t.Fatalf("err = %v, want ErrInvalidEvidence", err)
}
}
func TestNVTrustLocal_RefusesWithoutRIM(t *testing.T) {
v := buildLocalVector(t, 0x11)
_, err := v.verifier().Verify(context.Background(), v.evidence,
WithNVTrustTrustRoots(v.rimRoots))
if !errors.Is(err, ErrPolicy) {
t.Fatalf("err = %v, want ErrPolicy", err)
}
}
// =============================================================================
// ModeNRAS: EAT/JWT verification
// =============================================================================
func b64url(b []byte) string { return base64.RawURLEncoding.EncodeToString(b) }
// buildEATES256 builds an ES256-signed EAT compact JWS over the claims.
func buildEATES256(t *testing.T, priv *ecdsa.PrivateKey, kid string, claims map[string]any) string {
t.Helper()
hdr := map[string]string{"alg": "ES256", "kid": kid}
hb, _ := json.Marshal(hdr)
cb, _ := json.Marshal(claims)
signedInput := b64url(hb) + "." + b64url(cb)
sum := sha256Sum([]byte(signedInput))
r, s, err := ecdsa.Sign(rand.Reader, priv, sum)
if err != nil {
t.Fatalf("eat sign: %v", err)
}
sig := joseConcatLocal(r, s, 32)
return signedInput + "." + b64url(sig)
}
func nrasVerifier(t *testing.T, pub *ecdsa.PublicKey, kid string, now time.Time) NVTrust {
return NewNVTrust(
WithNVTrustMode(ModeNRAS),
WithNVTrustNRASRoots([]nvidia.TrustRoot{{KeyID: kid, Public: pub}}),
WithNVTrustClock(func() time.Time { return now }),
)
}
func TestNVTrustNRAS_HappyPath(t *testing.T) {
now := mustTime(t, testFixedNow)
priv, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
var nonce [32]byte
for i := range nonce {
nonce[i] = byte(i + 1)
}
measRoot := bytes.Repeat([]byte{0x5A}, 32)
token := buildEATES256(t, priv, "nras-1", map[string]any{
"eat_nonce": hex.EncodeToString(nonce[:]),
"exp": now.Add(time.Hour).Unix(),
"iat": now.Add(-time.Minute).Unix(),
"x-nvidia-overall-att-result": true,
"x-nvidia-gpu-measurements-root": hex.EncodeToString(measRoot),
})
rep, err := nrasVerifier(t, &priv.PublicKey, "nras-1", now).Verify(
context.Background(), []byte(token), WithExpectedReportData(nonce[:]))
if err != nil {
t.Fatalf("nras verify: %v", err)
}
if rep.Vendor != "nvidia.nvtrust.nras" {
t.Fatalf("vendor = %q", rep.Vendor)
}
if !bytes.Equal(rep.ReportData, nonce[:]) {
t.Fatalf("report_data = %x want %x", rep.ReportData, nonce[:])
}
if !bytes.Equal(rep.Measurement, measRoot) {
t.Fatalf("measurement = %x want %x", rep.Measurement, measRoot)
}
if rep.Extra["nvtrust.nras_result"] != "true" {
t.Fatalf("nras_result = %q", rep.Extra["nvtrust.nras_result"])
}
}
func TestNVTrustNRAS_RejectsBadSignature(t *testing.T) {
now := mustTime(t, testFixedNow)
priv, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
token := buildEATES256(t, priv, "nras-1", map[string]any{
"exp": now.Add(time.Hour).Unix(),
})
// Corrupt the final signature segment.
bad := token[:len(token)-4] + "AAAA"
_, err := nrasVerifier(t, &priv.PublicKey, "nras-1", now).Verify(
context.Background(), []byte(bad))
if !errors.Is(err, ErrSignatureInvalid) {
t.Fatalf("err = %v, want ErrSignatureInvalid", err)
}
}
func TestNVTrustNRAS_RejectsExpired(t *testing.T) {
now := mustTime(t, testFixedNow)
priv, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
token := buildEATES256(t, priv, "nras-1", map[string]any{
"exp": now.Add(-time.Hour).Unix(),
})
_, err := nrasVerifier(t, &priv.PublicKey, "nras-1", now).Verify(
context.Background(), []byte(token))
if !errors.Is(err, ErrPolicy) {
t.Fatalf("err = %v, want ErrPolicy (expired)", err)
}
}
func TestNVTrustNRAS_RejectsNonceMismatch(t *testing.T) {
now := mustTime(t, testFixedNow)
priv, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
var nonce [32]byte
nonce[0] = 0x01
token := buildEATES256(t, priv, "nras-1", map[string]any{
"eat_nonce": hex.EncodeToString(nonce[:]),
"exp": now.Add(time.Hour).Unix(),
})
wrong := make([]byte, 32)
wrong[0] = 0x02
_, err := nrasVerifier(t, &priv.PublicKey, "nras-1", now).Verify(
context.Background(), []byte(token), WithExpectedReportData(wrong))
if !errors.Is(err, ErrPolicy) {
t.Fatalf("err = %v, want ErrPolicy (nonce)", err)
}
}
func TestNVTrustNRAS_RejectsNegativeResult(t *testing.T) {
now := mustTime(t, testFixedNow)
priv, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
token := buildEATES256(t, priv, "nras-1", map[string]any{
"exp": now.Add(time.Hour).Unix(),
"x-nvidia-overall-att-result": false,
})
_, err := nrasVerifier(t, &priv.PublicKey, "nras-1", now).Verify(
context.Background(), []byte(token))
if !errors.Is(err, ErrPolicy) {
t.Fatalf("err = %v, want ErrPolicy (negative result)", err)
}
}
func TestNVTrustNRAS_RefusesWithoutTrust(t *testing.T) {
now := mustTime(t, testFixedNow)
v := NewNVTrust(WithNVTrustMode(ModeNRAS), WithNVTrustClock(func() time.Time { return now }))
_, err := v.Verify(context.Background(), []byte("x.y.z"))
if !errors.Is(err, ErrPolicy) {
t.Fatalf("err = %v, want ErrPolicy (no NRAS trust)", err)
}
}