User Guide#

This guide covers the TCG RIM-based device-attestation workflow. For the CoRIM verifier, see the CoRIM verifier guide.

Overview#

This guide provides detailed examples and best practices for using the NVIDIA Attestation SDK Rust bindings.

API Organization#

The Rust SDK provides a safe API that wraps the C library. Key modules include:

  • client: SDK initialization and global configuration

  • context: Attestation context and workflow orchestration

  • nonce: Nonce generation and management

  • evidence: Evidence collection and handling

  • verifier: Local and remote verification

  • result: Attestation results and claims

Core Concepts#

SDK Initialization#

The SDK must be initialized before use with NvatSdk::init_default() or NvatSdk::init(options):

// Simple initialization with defaults
let client = NvatSdk::init_default()?;

// Or with custom options
let mut opts = SdkOptions::new()?;
// Configure options...
let client = NvatSdk::init(opts)?;

The NvatSdk instance manages global SDK state and should be kept alive for the duration of SDK usage.

Attestation Context#

The AttestationContext represents an attestation session and is used to configure and execute attestation:

let mut ctx = AttestationContext::new()?;
ctx.set_device_type(DeviceType::Gpu)?;
ctx.set_verifier_type(VerifierType::Remote)?;

Device Types#

  • DeviceType::Gpu: NVIDIA GPUs (Hopper, Blackwell, etc.)

  • DeviceType::NvSwitch: NVIDIA NVLink Switches

Verifier Types#

  • VerifierType::Local: Verify evidence locally using RIM store and OCSP

  • VerifierType::Remote: Use NVIDIA Remote Attestation Service (NRAS)

Nonce Operations#

Nonces provide freshness guarantees for attestation:

// Generate random nonce
let nonce = Nonce::generate(32)?;
println!("Nonce: {}", nonce.to_hex_string()?);

// Create from hex string
let nonce2 = Nonce::from_hex("0123456789abcdef")?;

// Get raw bytes
let bytes = nonce.as_bytes();

Error Handling#

All operations return Result<T, NvatError>:

match ctx.attest_device(None) {
    Ok(result) => {
        println!("Success: {:?}", result);
    }
    Err(e) => {
        eprintln!("Error {}: {}", e.code, e.message());
    }
}

The NvatError type provides:

  • Error code from the C library

  • Human-readable error message

  • Implementation of std::error::Error for integration with error handling libraries

GPU Ready State#

After successful attestation, GPUs may need to be transitioned to the Ready state before they can be used for workloads. This is only required when Confidential Computing is enabled.

For details on setting the GPU Ready state, see the Overview - GPU Ready State section.

Quick Start#

Examples#

The attestation examples below work with supported NVIDIA GPU architectures in both single and multi-GPU configurations.

If attestation is successful, the examples print the detached EAT and the claims. For more details on the structure of both, see the Claims Schema Reference.

For attestation on Hopper PPCIE, please refer to this documentation.

Example 1: Remote GPU Attestation#

View Source Code

This example demonstrates end-to-end GPU attestation with remote verification using NRAS:

use nv_attestation_sdk::{
    NvatSdk, AttestationContext, Nonce,
    DeviceType, VerifierType
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize SDK
    let _client = NvatSdk::init_default()?;

    // Create and configure attestation context
    let mut ctx = AttestationContext::new()?;
    ctx.set_device_type(DeviceType::Gpu)?;
    ctx.set_verifier_type(VerifierType::Remote)?;

    // Generate nonce for freshness
    let nonce = Nonce::generate(32)?;
    println!("Generated nonce: {}", nonce.to_hex_string()?);

    // Perform attestation
    println!("Attesting GPU...");
    let result = ctx.attest_device(Some(&nonce))?;

    // Get and display results
    let eat = result.eat_json()?;
    let claims = result.claims_json()?;

    println!("\n=== Attestation Successful ===");
    println!("\nEntity Attestation Token (EAT):");
    println!("{}", eat);
    println!("\nClaims:");
    println!("{}", claims);

    Ok(())
}

Key Concepts:

  • SDK initialization with NvatSdk::init_default()

  • Attestation context creation and configuration

  • Nonce generation for replay protection

  • Remote verification via NRAS

  • Results extraction and display

Example 2: Local GPU Attestation#

View Source Code

This example shows local verification using a RIM store and OCSP client:

use nv_attestation_sdk::{
    NvatSdk, AttestationContext, RimStore, OcspClient,
    HttpOptions, DeviceType, VerifierType
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let _client = NvatSdk::init_default()?;

    // Configure HTTP options
    let mut http_opts = HttpOptions::default_options()?;
    http_opts.set_max_retry_count(5);
    http_opts.set_connection_timeout_ms(10000);
    http_opts.set_request_timeout_ms(30000);

    // Create RIM store
    let _rim_store = RimStore::create_filesystem("/var/lib/nvat/rims")?;

    // Create OCSP client
    let _ocsp_client = OcspClient::create_default(
        Some("https://ocsp.attestation.nvidia.com"),
        Some("your-api-key"),
        Some(&http_opts)
    )?;

    // Configure for local verification
    let mut ctx = AttestationContext::new()?;
    ctx.set_device_type(DeviceType::Gpu)?;
    ctx.set_verifier_type(VerifierType::Local)?;

    // Perform attestation
    println!("Performing local attestation...");
    let result = ctx.attest_device(None)?;

    println!("Local attestation successful!");
    println!("Claims: {}", result.claims_json()?);

    Ok(())
}

Key Concepts:

  • HTTP options configuration for network requests

  • Filesystem-based RIM store

  • OCSP client for certificate revocation checking; see Selecting the OCSP CertID hash for CertID hash selection

  • Local verification workflow

Example 3: Low-Level Evidence Collection#

View Source Code

This example demonstrates manual evidence collection and verification using low-level APIs:

use nv_attestation_sdk::{
    EvidencePolicy, GpuEvidenceSource, GpuLocalVerifier, HttpOptions,
    Nonce, NvatSdk, OcspClient, RimStore, SdkOptions,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize SDK with options
    let opts = SdkOptions::new()?;
    let _client = NvatSdk::init(opts)?;

    // Create evidence source (NVML)
    let evidence_source = GpuEvidenceSource::from_nvml()?;

    // Generate nonce
    let nonce = Nonce::generate(32)?;

    // Collect evidence
    println!("Collecting GPU evidence...");
    let evidence = evidence_source.collect(&nonce)?;

    // Serialize evidence to JSON
    let evidence_json = evidence.to_json()?;
    println!("Evidence collected: {} bytes", evidence_json.len());

    // Set up verification components
    let http_opts = HttpOptions::builder()
        .max_retry_count(5)
        .connection_timeout_ms(10000)
        .request_timeout_ms(30000)
        .build()?;

    let rim_store = RimStore::create_remote(None, None, Some(&http_opts))?;
    let ocsp_client = OcspClient::create_default(
        None,  // Use default OCSP URL
        None,  // Use default API key
        Some(&http_opts)
    )?;

    // Create local verifier
    let verifier = GpuLocalVerifier::new(&rim_store, &ocsp_client)?;

    // Create evidence policy for verification
    let policy = EvidencePolicy::builder()
        .verify_rim_signature(true)
        .verify_rim_cert_chain(true)
        .build()?;

    // Verify evidence
    println!("Verifying evidence...");
    let verification_result = verifier.verify(&evidence, &policy)?;

    println!("Verification successful!");
    println!("Claims: {}", verification_result.claims_json()?);

    Ok(())
}

Key Concepts:

  • Manual evidence source creation

  • Direct evidence collection

  • Evidence serialization

  • Manual verifier setup

  • Low-level verification workflow

Example 4: Custom Logging#

This example shows how to integrate SDK logging with Rust’s logging ecosystem:

use nv_attestation_sdk::{NvatSdk, SdkOptions, Logger};
use log::{info, debug};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize env_logger for Rust logging
    env_logger::Builder::from_default_env()
        .filter_level(log::LevelFilter::Debug)
        .init();

    info!("Initializing NVIDIA Attestation SDK");

    // Create SDK options
    let mut opts = SdkOptions::new()?;

    // Create logger that forwards C SDK logs to Rust's log facade
    let logger = Logger::new()?;
    opts.set_logger(logger);

    // Initialize SDK with logging
    let _client = NvatSdk::init(opts)?;

    debug!("SDK initialized successfully");

    // SDK operations will now log through env_logger
    // ...

    Ok(())
}

Key Concepts:

  • Integration with env_logger

  • Custom logger creation

  • C SDK log forwarding to Rust log facade

  • Unified logging for Rust and C components

Example 5: Verifying an attestation token (relying party)#

A relying party that receives a detached EAT from an attesting machine can verify it using verify_attestation_result. A service key is optional — the public JWKS endpoint at <nras_base_url>/.well-known/jwks.json does not require one.

use nv_attestation_sdk::{NvatSdk, RelyingPartyPolicy, verify_attestation_result};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let _client = NvatSdk::init_default()?;

    // eat_json is the detached EAT string produced by nvat_attest_device /
    // AttestationContext::attest_device on the attesting machine.
    let eat_json: String = std::fs::read_to_string("eat.json")?;

    // Step 1: Verify the token and decode the claims.
    // Returns Ok(VerifiedAttestationResult) for both NVAT_RC_OK and NVAT_RC_OVERALL_RESULT_FALSE
    // (signature verified; claims present in both). Returns Err for genuine failures
    // such as NVAT_RC_NRAS_TOKEN_INVALID, NVAT_RC_BAD_ARGUMENT, or HTTP/JWKS errors.
    let result = verify_attestation_result(&eat_json, "https://nras.attestation.nvidia.com")?;

    if !result.overall_result_passed {
        println!("Token verified but overall attestation result is FALSE");
    }

    let claims = &result.claims;

    // Step 2: Optionally apply a relying-party policy to the decoded claims.
    let rego_src = r#"
        package policy
        import future.keywords.every
        default nv_match := false
        nv_match {
          every result in input {
            result["x-nvidia-device-type"] == "gpu"
            result.secboot
            result.dbgstat == "disabled"
          }
        }
    "#;

    let policy = RelyingPartyPolicy::from_rego(rego_src)?;
    policy.apply(claims)?;  // Err with NVAT_RC_RP_POLICY_MISMATCH if rejected

    println!("Token verified and policy matched.");
    println!("Claims: {}", claims.to_json()?);

    Ok(())
}

Key Concepts:

  • verify_attestation_result(eat, nras_base_url) — verifies signature, issuer, and structure; returns Ok(VerifiedAttestationResult) for both NVAT_RC_OK and NVAT_RC_OVERALL_RESULT_FALSE (claims present in both); Err only on genuine failure. No service key needed.

  • verify_attestation_result_with_options(eat, nras_base_url, service_key, expected_nonce, http_options, jwt_validation_options) — same, with optional verification settings:

    • service_key is sent with the JWKS fetch. The public NRAS endpoint does not require one, so None is the common case; use Some(key) only when that fetch requires authentication.

    • http_options configures the JWKS HTTPS fetch, such as a custom TLS CA bundle for a private NRAS or non-default timeouts. None uses defaults.

    • jwt_validation_options configures leeway for JWT exp, nbf, and iat validation. None uses the 60-second default; zero enables strict validation.

    • When expected_nonce is Some(&nonce), the token’s eat_nonce must match or the call returns Err(NvatError { code: NVAT_RC_NONCE_MISMATCH, … }). None skips the nonce comparison.

  • VerifiedAttestationResult.overall_result_passed — true when the token’s overall attestation result passed; false when the token verified but the overall result is false.

  • RelyingPartyPolicy::from_rego(src) — builds a policy from an OPA Rego source string.

  • RelyingPartyPolicy::apply(&claims) — returns Err(NvatError { code: NVAT_RC_RP_POLICY_MISMATCH, … }) when the policy does not match.

Freshness / nonce flow#

The relying party is the challenger in the RATS model. To bind a token to a specific request:

  1. Relying party generates a nonce and sends it to the attesting party:

    let nonce = Nonce::generate(32)?;
    // ... transport nonce to the attesting party ...
    
  2. Attesting party runs attestation with the nonce and returns the EAT:

    let result = ctx.attest_device(Some(&nonce))?;
    // ... send result.eat_json()? to the relying party ...
    
  3. Relying party verifies the returned EAT, passing the nonce it issued so the SDK enforces freshness:

    let result = verify_attestation_result_with_options(&eat_json, nras_url, None, Some(&nonce), None, None)?;
    let claims = &result.claims;
    

Note: When expected_nonce is Some(&nonce), verify_attestation_result_with_options verifies that the token’s overall eat_nonce equals it and returns NVAT_RC_NONCE_MISMATCH otherwise. None (and the convenience verify_attestation_result) skips this comparison (the token must still contain a nonce). A relying party that prefers to compare freshness itself can still inspect eat_nonce in the decoded claims.

Example 6: NVSwitch Attestation#

This example demonstrates NVSwitch attestation:

use nv_attestation_sdk::{
    NvatSdk, AttestationContext,
    DeviceType, VerifierType
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let _client = NvatSdk::init_default()?;

    let mut ctx = AttestationContext::new()?;

    // Configure for NVSwitch
    ctx.set_device_type(DeviceType::NvSwitch)?;
    ctx.set_verifier_type(VerifierType::Remote)?;

    // Perform attestation
    println!("Attesting NVSwitch...");
    let result = ctx.attest_device(None)?;

    println!("NVSwitch attestation successful!");
    println!("Claims: {}", result.claims_json()?);

    Ok(())
}

Key Concepts:

  • NVSwitch device type configuration

  • NVSwitch-specific attestation workflow

Example 7: Signing the detached EAT with a service key (issuing party)#

A service that performs local verification can have the SDK assemble and sign the detached EAT with the service’s own key, issuer, and key id instead of the default identity. Attach DetachedEatOptions to the context before attesting; the detached EAT produced by attestation is then signed with those options. If the options are not set, the SDK uses its default detached-EAT options.

use nv_attestation_sdk::{
    NvatSdk, AttestationContext, DetachedEatOptions,
    DeviceType, VerifierType,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let _client = NvatSdk::init_default()?;

    // The service's signing key (PEM). The matching public key is
    // published at <nras_base_url>/.well-known/jwks.json under `kid`.
    let private_key_pem = std::fs::read_to_string("service_signing_key.pem")?;

    let options = DetachedEatOptions::new(
        &private_key_pem,
        "https://nras.attestation.nvidia.com",  // issuer (EAT `iss` claim)
        "nras-signing-key-1",                    // key id (JWS `kid` header)
    )?;

    // Configure a local-verification context and attach the signing options.
    let mut ctx = AttestationContext::new()?;
    ctx.set_device_type(DeviceType::Gpu)?;
    ctx.set_verifier_type(VerifierType::Local)?;
    ctx.set_detached_eat_options(options)?;

    // The detached EAT returned by attestation is signed with the service key.
    let result = ctx.attest_device(None)?;
    let signed_eat = result.eat_json()?;

    println!("Signed detached EAT:\n{}", signed_eat);

    Ok(())
}

Key Concepts:

  • Detached EAT signing options with DetachedEatOptions::new(private_key_pem, issuer, kid)

  • Service-supplied signing key, issuer, and key id

  • Attaching signing options to the context via set_detached_eat_options

  • Signed detached EAT issued from the local verification flow

  • Public key published at the JWKS endpoint under kid for relying-party verification (Example 5)

A service that drives the local verifiers directly (instead of the attestation context) passes the same options at construction:

let gpu_verifier = GpuLocalVerifier::new_with_signing(&rim_store, &ocsp_client, options)?;

GpuLocalVerifier::new_with_signing and SwitchLocalVerifier::new_with_signing sign every detached EAT the verifier produces with the given options. To re-sign an already verified ClaimsCollection (for example after a key rotation), ClaimsCollection::detached_eat_es384(&options) assembles and signs the detached EAT without re-running verification.

Claims verified in separate calls (for example one call per device) merge into a single collection with ClaimsCollection::extend, so one detached EAT can cover all of them:

let mut all_claims = first_result.claims.take().expect("verified claims");
all_claims.extend(second_result.claims.as_ref().expect("verified claims"))?;
let eat = all_claims.detached_eat_es384(&options)?;

ClaimsCollection is Send (but not Sync), so collections verified on worker threads can move to the thread that merges and signs them; concurrent access to one collection from multiple threads is not supported.

Thread Safety#

The SDK splits into handles a service builds once and shares, and handles that belong to a single request.

Shared, Send and Sync: GpuLocalVerifier, SwitchLocalVerifier, EvidencePolicy, RimStore and OcspClient. Build one of each on the main thread and hold them behind an Arc. Verification does not mutate the verifier, and cached stores and clients serialize their own reads and writes, so every worker shares one RIM cache and one OCSP cache instead of warming its own.

Per request: AttestationContext and the evidence collections. ClaimsCollection is the output of one verification and is Send but not Sync, so it moves to whichever thread merges and signs.

Not shared at all: NvatSdk. Initialize the SDK once on the main thread, before any worker starts, and keep the handle alive for the life of the process.

let rim_store = RimStore::create_cached(
    RimStore::create_remote(None, None, None)?,
    8 * 1024 * 1024,
    3600,
)?;
let ocsp_client = OcspClient::create_cached(
    OcspClient::create_default(None, None, None)?,
    8 * 1024 * 1024,
    3600,
)?;

let verifier = Arc::new(GpuLocalVerifier::new(&rim_store, &ocsp_client)?);
let policy = Arc::new(EvidencePolicy::builder().build()?);

// Each worker collects its own evidence and verifies through the shared handles.
let result = verifier.verify(&evidence, &policy)?;

Next Steps#