Added GPU evidence collection

This commit is contained in:
Jovan Djukic
2026-04-22 17:57:31 +02:00
parent d5badba547
commit 10fa58916e
11 changed files with 652 additions and 26 deletions
+2 -1
View File
@@ -28,4 +28,5 @@ Cargo.lock
*.enc
*.key
*.pub
*.pub
.codex
+76
View File
@@ -0,0 +1,76 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package main
import (
"context"
"crypto/sha256"
"fmt"
"strings"
attestationpb "github.com/ultravioletrs/cocos/internal/proto/attestation/v1"
"github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation/eat"
attestationgpu "github.com/ultravioletrs/cocos/pkg/attestation/gpu"
)
func newGPUCollector(cfg config) (attestationgpu.Collector, error) {
if strings.TrimSpace(cfg.GPUHelperPath) == "" {
return nil, nil
}
return attestationgpu.NewCommandCollector(cfg.GPUHelperPath, cfg.GPUHelperTimeout)
}
func (s *service) claimOptions(ctx context.Context, req *attestationpb.AttestationRequest, platformType attestation.PlatformType) ([]eat.ClaimsOption, error) {
var opts []eat.ClaimsOption
if s.gpuCollector != nil && shouldCollectGPU(platformType) {
sessionNonce := requestNonce(req)
gpuNonce := deriveComponentNonce(sessionNonce, "gpu")
evidence, err := s.gpuCollector.Collect(ctx, gpuNonce)
if err != nil {
// GPU evidence is opportunistic: if no supported CC-capable GPU is
// attached, or the helper cannot collect evidence, we continue with
// the root CPU/TEE attestation instead of failing the whole request.
s.logger.Warn(fmt.Sprintf("[ATTESTATION-SERVICE] Skipping optional GPU evidence collection: %s", err))
return opts, nil
}
s.logger.Info(fmt.Sprintf("[ATTESTATION-SERVICE] Collected GPU evidence: format=%s bytes=%d",
evidence.EvidenceFormat, len(evidence.RawEvidence)))
opts = append(opts, eat.WithGPU(&eat.GPUExtensions{
Vendor: evidence.Vendor,
EvidenceFormat: evidence.EvidenceFormat,
Nonce: evidence.Nonce,
EvidenceJSON: evidence.RawEvidence,
}))
}
return opts, nil
}
func shouldCollectGPU(platformType attestation.PlatformType) bool {
switch platformType {
case attestation.SNP, attestation.SNPvTPM, attestation.TDX, attestation.Azure:
return true
default:
return false
}
}
func requestNonce(req *attestationpb.AttestationRequest) []byte {
if len(req.Nonce) > 0 {
return append([]byte(nil), req.Nonce...)
}
return append([]byte(nil), req.ReportData...)
}
func deriveComponentNonce(sessionNonce []byte, component string) []byte {
digest := sha256.Sum256(append(append([]byte(nil), sessionNonce...), []byte(":"+component)...))
return digest[:]
}
+83
View File
@@ -0,0 +1,83 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package main
import (
"context"
"io"
"log/slog"
"testing"
"github.com/stretchr/testify/assert"
attestationpb "github.com/ultravioletrs/cocos/internal/proto/attestation/v1"
"github.com/ultravioletrs/cocos/pkg/attestation"
attestationgpu "github.com/ultravioletrs/cocos/pkg/attestation/gpu"
)
func TestRequestNonce(t *testing.T) {
req := &attestationpb.AttestationRequest{
ReportData: []byte("report"),
Nonce: []byte("nonce"),
}
assert.Equal(t, []byte("nonce"), requestNonce(req))
req.Nonce = nil
assert.Equal(t, []byte("report"), requestNonce(req))
}
func TestDeriveComponentNonce(t *testing.T) {
sessionNonce := []byte("session-nonce")
gpuNonce := deriveComponentNonce(sessionNonce, "gpu")
gpuNonceAgain := deriveComponentNonce(sessionNonce, "gpu")
teeNonce := deriveComponentNonce(sessionNonce, "tee")
assert.Len(t, gpuNonce, 32)
assert.Equal(t, gpuNonce, gpuNonceAgain)
assert.NotEqual(t, gpuNonce, teeNonce)
}
func TestShouldCollectGPU(t *testing.T) {
assert.True(t, shouldCollectGPU(attestation.SNP))
assert.True(t, shouldCollectGPU(attestation.SNPvTPM))
assert.True(t, shouldCollectGPU(attestation.TDX))
assert.False(t, shouldCollectGPU(attestation.VTPM))
assert.False(t, shouldCollectGPU(attestation.NoCC))
}
func TestNewGPUCollector(t *testing.T) {
collector, err := newGPUCollector(config{})
assert.NoError(t, err)
assert.Nil(t, collector)
collector, err = newGPUCollector(config{
GPUHelperPath: "/tmp/helper",
GPUHelperTimeout: 0,
})
assert.NoError(t, err)
assert.NotNil(t, collector)
}
func TestClaimOptions_SkipsOptionalGPUFailure(t *testing.T) {
svc := &service{
logger: slog.New(slog.NewTextHandler(io.Discard, nil)),
gpuCollector: failingCollector{},
}
req := &attestationpb.AttestationRequest{
ReportData: []byte("report-data"),
Nonce: []byte("nonce-data"),
}
opts, err := svc.claimOptions(context.Background(), req, attestation.TDX)
assert.NoError(t, err)
assert.Empty(t, opts)
}
type failingCollector struct{}
func (failingCollector) Collect(context.Context, []byte) (*attestationgpu.Evidence, error) {
return nil, assert.AnError
}
+42 -24
View File
@@ -11,6 +11,7 @@ import (
"os"
"os/signal"
"syscall"
"time"
mglog "github.com/absmach/supermq/logger"
"github.com/caarlos0/env/v11"
@@ -22,6 +23,7 @@ import (
"github.com/ultravioletrs/cocos/pkg/attestation/azure"
"github.com/ultravioletrs/cocos/pkg/attestation/ccaa"
"github.com/ultravioletrs/cocos/pkg/attestation/eat"
attestationgpu "github.com/ultravioletrs/cocos/pkg/attestation/gpu"
"github.com/ultravioletrs/cocos/pkg/attestation/tdx"
"github.com/ultravioletrs/cocos/pkg/attestation/vtpm"
logclient "github.com/ultravioletrs/cocos/pkg/clients/grpc/log"
@@ -35,16 +37,18 @@ const (
)
type config struct {
LogLevel string `env:"ATTESTATION_LOG_LEVEL" envDefault:"debug"`
Vmpl int `env:"ATTESTATION_VMPL" envDefault:"2"`
AgentMaaURL string `env:"AGENT_MAA_URL" envDefault:"https://sharedeus2.eus2.attest.azure.net"`
AgentOSBuild string `env:"AGENT_OS_BUILD" envDefault:"UVC"`
AgentOSDistro string `env:"AGENT_OS_DISTRO" envDefault:"UVC"`
AgentOSType string `env:"AGENT_OS_TYPE" envDefault:"UVC"`
EATFormat string `env:"ATTESTATION_EAT_FORMAT" envDefault:"CBOR"` // JWT or CBOR
EATIssuer string `env:"ATTESTATION_EAT_ISSUER" envDefault:"cocos-attestation-service"`
UseCCAttestationAgent bool `env:"USE_CC_ATTESTATION_AGENT" envDefault:"false"`
CCAgentAddress string `env:"CC_AGENT_ADDRESS" envDefault:"127.0.0.1:50002"`
LogLevel string `env:"ATTESTATION_LOG_LEVEL" envDefault:"debug"`
Vmpl int `env:"ATTESTATION_VMPL" envDefault:"2"`
AgentMaaURL string `env:"AGENT_MAA_URL" envDefault:"https://sharedeus2.eus2.attest.azure.net"`
AgentOSBuild string `env:"AGENT_OS_BUILD" envDefault:"UVC"`
AgentOSDistro string `env:"AGENT_OS_DISTRO" envDefault:"UVC"`
AgentOSType string `env:"AGENT_OS_TYPE" envDefault:"UVC"`
EATFormat string `env:"ATTESTATION_EAT_FORMAT" envDefault:"CBOR"` // JWT or CBOR
EATIssuer string `env:"ATTESTATION_EAT_ISSUER" envDefault:"cocos-attestation-service"`
UseCCAttestationAgent bool `env:"USE_CC_ATTESTATION_AGENT" envDefault:"false"`
CCAgentAddress string `env:"CC_AGENT_ADDRESS" envDefault:"127.0.0.1:50002"`
GPUHelperPath string `env:"ATTESTATION_GPU_HELPER_PATH" envDefault:""`
GPUHelperTimeout time.Duration `env:"ATTESTATION_GPU_HELPER_TIMEOUT" envDefault:"30s"`
}
func main() {
@@ -218,13 +222,24 @@ func main() {
return
}
gpuCollector, err := newGPUCollector(cfg)
if err != nil {
logger.Error(fmt.Sprintf("failed to configure GPU attestation collector: %s", err))
exitCode = 1
return
}
if gpuCollector != nil {
logger.Info(fmt.Sprintf("[ATTESTATION-SERVICE] GPU evidence collection enabled via helper %s", cfg.GPUHelperPath))
}
grpcServer := grpc.NewServer()
svc := &service{
provider: provider,
logger: logger,
signingKey: signingKey,
eatFormat: cfg.EATFormat,
eatIssuer: cfg.EATIssuer,
provider: provider,
logger: logger,
signingKey: signingKey,
eatFormat: cfg.EATFormat,
eatIssuer: cfg.EATIssuer,
gpuCollector: gpuCollector,
}
attestationpb.RegisterAttestationServiceServer(grpcServer, svc)
@@ -256,11 +271,12 @@ func main() {
type service struct {
attestationpb.UnimplementedAttestationServiceServer
provider attestation.Provider
logger *slog.Logger
signingKey *ecdsa.PrivateKey
eatFormat string
eatIssuer string
provider attestation.Provider
logger *slog.Logger
signingKey *ecdsa.PrivateKey
eatFormat string
eatIssuer string
gpuCollector attestationgpu.Collector
}
func (s *service) FetchAttestation(ctx context.Context, req *attestationpb.AttestationRequest) (*attestationpb.AttestationResponse, error) {
@@ -319,12 +335,14 @@ func (s *service) FetchAttestation(ctx context.Context, req *attestationpb.Attes
}
// Create EAT claims from binary report
nonce := req.ReportData
if len(req.Nonce) > 0 {
nonce = req.Nonce
nonce := requestNonce(req)
claimOpts, err := s.claimOptions(ctx, req, platformType)
if err != nil {
return nil, err
}
claims, err := eat.NewEATClaims(binaryReport, nonce, platformType)
claims, err := eat.NewEATClaims(binaryReport, nonce, platformType, claimOpts...)
if err != nil {
s.logger.Error(fmt.Sprintf("failed to create EAT claims: %s", err))
return nil, fmt.Errorf("failed to create EAT claims: %w", err)
+38 -1
View File
@@ -39,6 +39,7 @@ type EATClaims struct {
SNPExtensions *SNPExtensions `json:"x-cocos-sevsnp,omitempty"`
TDXExtensions *TDXExtensions `json:"x-cocos-tdx,omitempty"`
VTPMExtensions *VTPMExtensions `json:"x-cocos-vtpm,omitempty"`
GPUExtensions *GPUExtensions `json:"x-cocos-gpu,omitempty"`
// Original binary report (for verification)
RawReport []byte `json:"raw_report,omitempty"`
@@ -94,6 +95,36 @@ type VTPMExtensions struct {
Quote []byte `json:"quote,omitempty"` // TPM quote
}
// GPUExtensions contains optional GPU attestation evidence that is bound to the
// same attestation session as the root TEE evidence.
type GPUExtensions struct {
Vendor string `json:"vendor,omitempty" cbor:"vendor,omitempty"`
EvidenceFormat string `json:"evidence_format,omitempty" cbor:"evidence_format,omitempty"`
Nonce []byte `json:"nonce,omitempty" cbor:"nonce,omitempty"`
EvidenceJSON []byte `json:"evidence_json,omitempty" cbor:"evidence_json,omitempty"`
}
// ClaimsOption customizes EAT claims after the root platform claims are
// extracted.
type ClaimsOption func(*EATClaims) error
// WithGPU attaches GPU evidence both as a typed extension and as an EAT submod.
func WithGPU(gpu *GPUExtensions) ClaimsOption {
return func(claims *EATClaims) error {
if gpu == nil {
return nil
}
claims.GPUExtensions = gpu
if claims.Submods == nil {
claims.Submods = map[string]interface{}{}
}
claims.Submods["gpu"] = gpu
return nil
}
}
// DebugStatus constants (RFC 9711 Section 4.2.6).
const (
DebugEnabled = 0 // Debug is enabled
@@ -112,7 +143,7 @@ const (
const MinNonceLength = 8
// NewEATClaims creates EAT claims from binary attestation report.
func NewEATClaims(report []byte, nonce []byte, platformType attestation.PlatformType) (*EATClaims, error) {
func NewEATClaims(report []byte, nonce []byte, platformType attestation.PlatformType, opts ...ClaimsOption) (*EATClaims, error) {
if len(nonce) < MinNonceLength {
return nil, errors.New("eat_nonce must be at least 8 bytes long")
}
@@ -129,6 +160,12 @@ func NewEATClaims(report []byte, nonce []byte, platformType attestation.Platform
return nil, err
}
for _, opt := range opts {
if err := opt(claims); err != nil {
return nil, err
}
}
return claims, nil
}
+21
View File
@@ -202,3 +202,24 @@ func TestNewEATClaims_Platforms(t *testing.T) {
})
}
}
func TestNewEATClaims_WithGPU(t *testing.T) {
gpuEvidence := &GPUExtensions{
Vendor: "nvidia",
EvidenceFormat: "nvat-json",
Nonce: []byte("gpu-nonce"),
EvidenceJSON: []byte(`{"evidence":"gpu"}`),
}
claims, err := NewEATClaims(
[]byte("dummy report"),
[]byte("12345678"),
attestation.NoCC,
WithGPU(gpuEvidence),
)
assert.NoError(t, err)
assert.NotNil(t, claims.GPUExtensions)
assert.Equal(t, gpuEvidence, claims.GPUExtensions)
assert.Contains(t, claims.Submods, "gpu")
assert.Equal(t, gpuEvidence, claims.Submods["gpu"])
}
+133
View File
@@ -0,0 +1,133 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package gpu
import (
"bytes"
"context"
"encoding/hex"
"encoding/json"
"fmt"
"os/exec"
"strings"
"time"
)
const (
DefaultVendor = "nvidia"
DefaultEvidenceFormat = "nvat-json"
)
// Collector retrieves GPU evidence for the current attestation session.
type Collector interface {
Collect(ctx context.Context, nonce []byte) (*Evidence, error)
}
// Evidence contains low-level GPU evidence collected out-of-process.
type Evidence struct {
Vendor string
EvidenceFormat string
Nonce []byte
RawEvidence []byte
}
type commandCollector struct {
binaryPath string
timeout time.Duration
execCommandContext func(ctx context.Context, name string, arg ...string) *exec.Cmd
}
type helperRequest struct {
NonceHex string `json:"nonce_hex"`
}
type helperResponse struct {
Vendor string `json:"vendor,omitempty"`
EvidenceFormat string `json:"evidence_format,omitempty"`
EvidenceJSON json.RawMessage `json:"evidence_json"`
}
// NewCommandCollector creates a collector that shells out to a helper binary.
// The helper is expected to read a JSON request on stdin and emit a JSON
// response on stdout. See tools/nvidia-attestation-helper for the contract.
func NewCommandCollector(binaryPath string, timeout time.Duration) (Collector, error) {
if strings.TrimSpace(binaryPath) == "" {
return nil, fmt.Errorf("gpu helper path cannot be empty")
}
if timeout <= 0 {
timeout = 30 * time.Second
}
return &commandCollector{
binaryPath: binaryPath,
timeout: timeout,
execCommandContext: exec.CommandContext,
}, nil
}
func (c *commandCollector) Collect(ctx context.Context, nonce []byte) (*Evidence, error) {
if len(nonce) == 0 {
return nil, fmt.Errorf("gpu nonce cannot be empty")
}
reqBody, err := json.Marshal(helperRequest{
NonceHex: hex.EncodeToString(nonce),
})
if err != nil {
return nil, fmt.Errorf("failed to marshal GPU helper request: %w", err)
}
runCtx := ctx
cancel := func() {}
if c.timeout > 0 {
runCtx, cancel = context.WithTimeout(ctx, c.timeout)
}
defer cancel()
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
cmd := c.execCommandContext(runCtx, c.binaryPath)
cmd.Stdin = bytes.NewReader(reqBody)
cmd.Stdout = stdout
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
errMsg := strings.TrimSpace(stderr.String())
if errMsg == "" {
errMsg = err.Error()
}
return nil, fmt.Errorf("gpu helper failed: %s", errMsg)
}
var resp helperResponse
if err := json.Unmarshal(stdout.Bytes(), &resp); err != nil {
return nil, fmt.Errorf("failed to decode GPU helper response: %w", err)
}
if len(resp.EvidenceJSON) == 0 {
return nil, fmt.Errorf("gpu helper response did not contain evidence_json")
}
vendor := resp.Vendor
if vendor == "" {
vendor = DefaultVendor
}
evidenceFormat := resp.EvidenceFormat
if evidenceFormat == "" {
evidenceFormat = DefaultEvidenceFormat
}
return &Evidence{
Vendor: vendor,
EvidenceFormat: evidenceFormat,
Nonce: append([]byte(nil), nonce...),
RawEvidence: append([]byte(nil), resp.EvidenceJSON...),
}, nil
}
// SetExecCommandContext allows tests to inject a mock exec.CommandContext.
func (c *commandCollector) SetExecCommandContext(cmdFunc func(ctx context.Context, name string, arg ...string) *exec.Cmd) {
c.execCommandContext = cmdFunc
}
+137
View File
@@ -0,0 +1,137 @@
// Copyright (c) Ultraviolet
// SPDX-License-Identifier: Apache-2.0
package gpu
import (
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func fakeExecCommandContext(_ context.Context, name string, arg ...string) *exec.Cmd {
args := append([]string{"-test.run=TestGPUHelperProcess", "--", name}, arg...)
cmd := exec.Command(os.Args[0], args...)
cmd.Env = append(os.Environ(), "GO_WANT_GPU_HELPER_PROCESS=1")
return cmd
}
func TestGPUHelperProcess(t *testing.T) {
if os.Getenv("GO_WANT_GPU_HELPER_PROCESS") != "1" {
return
}
args := os.Args
for i := range args {
if args[i] == "--" {
args = args[i+1:]
break
}
}
if len(args) == 0 {
fmt.Fprintln(os.Stderr, "missing helper name")
os.Exit(2)
}
switch args[0] {
case "helper-error":
fmt.Fprintln(os.Stderr, "simulated helper failure")
os.Exit(1)
case "helper-invalid-json":
fmt.Fprintln(os.Stdout, "{not-json")
os.Exit(0)
case "helper-empty-evidence":
fmt.Fprintln(os.Stdout, `{"vendor":"nvidia","evidence_format":"nvat-json"}`)
os.Exit(0)
default:
var req helperRequest
if err := json.NewDecoder(os.Stdin).Decode(&req); err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
}
resp := helperResponse{
Vendor: "nvidia",
EvidenceFormat: "nvat-json",
EvidenceJSON: json.RawMessage(fmt.Sprintf(`{"nonce_hex":"%s","evidence":"ok"}`, req.NonceHex)),
}
if err := json.NewEncoder(os.Stdout).Encode(resp); err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
}
os.Exit(0)
}
}
func TestNewCommandCollector(t *testing.T) {
collector, err := NewCommandCollector("helper", time.Second)
assert.NoError(t, err)
assert.NotNil(t, collector)
collector, err = NewCommandCollector("", time.Second)
assert.Error(t, err)
assert.Nil(t, collector)
}
func TestCommandCollectorCollect(t *testing.T) {
collector, err := NewCommandCollector("helper-success", time.Second)
require.NoError(t, err)
cmdCollector, ok := collector.(*commandCollector)
require.True(t, ok)
cmdCollector.SetExecCommandContext(fakeExecCommandContext)
evidence, err := collector.Collect(context.Background(), []byte{0xaa, 0xbb, 0xcc})
require.NoError(t, err)
assert.Equal(t, DefaultVendor, evidence.Vendor)
assert.Equal(t, DefaultEvidenceFormat, evidence.EvidenceFormat)
assert.Equal(t, []byte{0xaa, 0xbb, 0xcc}, evidence.Nonce)
assert.JSONEq(t, `{"nonce_hex":"aabbcc","evidence":"ok"}`, string(evidence.RawEvidence))
}
func TestCommandCollectorCollectErrors(t *testing.T) {
tests := []struct {
name string
helperName string
wantErr string
}{
{
name: "helper process failure",
helperName: "helper-error",
wantErr: "gpu helper failed: simulated helper failure",
},
{
name: "invalid json response",
helperName: "helper-invalid-json",
wantErr: "failed to decode GPU helper response",
},
{
name: "missing evidence payload",
helperName: "helper-empty-evidence",
wantErr: "gpu helper response did not contain evidence_json",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
collector, err := NewCommandCollector(tt.helperName, time.Second)
require.NoError(t, err)
cmdCollector, ok := collector.(*commandCollector)
require.True(t, ok)
cmdCollector.SetExecCommandContext(fakeExecCommandContext)
_, err = collector.Collect(context.Background(), []byte{0xaa})
require.Error(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
})
}
}
@@ -0,0 +1,11 @@
[package]
name = "nvidia-attestation-helper"
version = "0.1.0"
edition = "2021"
license = "Apache-2.0"
[dependencies]
anyhow = "1"
nv-attestation-sdk = { git = "https://github.com/NVIDIA/attestation-sdk", branch = "main", subdirectory = "nv-attestation-sdk-rust/nv-attestation-sdk" }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
+62
View File
@@ -0,0 +1,62 @@
# NVIDIA Attestation Helper
This helper wraps NVIDIA's Rust attestation SDK low-level GPU evidence
collection flow and exposes a tiny JSON stdin/stdout protocol that the Go
attestation service can call.
## Request
The helper reads a single JSON object from stdin:
```json
{
"nonce_hex": "aabbccdd"
}
```
## Response
On success it writes:
```json
{
"vendor": "nvidia",
"evidence_format": "nvat-json",
"evidence_json": { "...": "..." }
}
```
`evidence_json` is the JSON emitted by `GpuEvidence::to_json()`.
## Build
Prerequisites:
- Rust 1.80+
- `libnvat.so.1`
- Clang/LLVM
- NVIDIA GPU driver with NVML support
If you are using a system-installed NVAT library:
```bash
export NVAT_USE_SYSTEM_LIB=1
cargo build --release
```
If you built NVAT locally, make sure the C library is installed or on
`LD_LIBRARY_PATH` before building or running the helper.
## Use With COCOS
Point the attestation service at the compiled binary:
```bash
export ATTESTATION_GPU_HELPER_PATH=/path/to/nvidia-attestation-helper
export ATTESTATION_GPU_HELPER_TIMEOUT=30s
```
When a helper path is configured, COCOS will attempt to collect GPU evidence
opportunistically. If the host does not expose a supported CC-capable NVIDIA
GPU, the attestation service skips GPU evidence and still returns the root
CPU/TEE attestation.
@@ -0,0 +1,47 @@
use anyhow::Context;
use nv_attestation_sdk::{GpuEvidence, GpuEvidenceSource, Nonce, NvatSdk};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::io::{stdin, stdout, Write};
#[derive(Debug, Deserialize)]
struct Request {
nonce_hex: String,
}
#[derive(Debug, Serialize)]
struct Response {
vendor: &'static str,
evidence_format: &'static str,
evidence_json: Value,
}
fn main() -> anyhow::Result<()> {
let _sdk = NvatSdk::init_default().context("failed to initialize NVIDIA attestation SDK")?;
let req: Request =
serde_json::from_reader(stdin().lock()).context("failed to decode helper request")?;
let nonce = Nonce::from_hex(&req.nonce_hex).context("failed to parse nonce")?;
let evidence_source =
GpuEvidenceSource::create_nvml().context("failed to create NVML evidence source")?;
let evidence = GpuEvidence::collect(&evidence_source, Some(&nonce))
.context("failed to collect evidence")?;
let evidence_json = evidence
.to_json()
.context("failed to serialize GPU evidence to JSON")?;
let evidence_json: Value =
serde_json::from_str(&evidence_json).context("failed to parse serialized evidence JSON")?;
let resp = Response {
vendor: "nvidia",
evidence_format: "nvat-json",
evidence_json,
};
let mut out = stdout().lock();
serde_json::to_writer(&mut out, &resp).context("failed to write helper response")?;
let _ = out.write_all(b"\n");
Ok(())
}