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 configurationcontext: Attestation context and workflow orchestrationnonce: Nonce generation and managementevidence: Evidence collection and handlingverifier: Local and remote verificationresult: 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 OCSPVerifierType::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::Errorfor 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#
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#
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#
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_loggerCustom 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; returnsOk(VerifiedAttestationResult)for bothNVAT_RC_OKandNVAT_RC_OVERALL_RESULT_FALSE(claims present in both);Erronly 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_keyis sent with the JWKS fetch. The public NRAS endpoint does not require one, soNoneis the common case; useSome(key)only when that fetch requires authentication.http_optionsconfigures the JWKS HTTPS fetch, such as a custom TLS CA bundle for a private NRAS or non-default timeouts.Noneuses defaults.jwt_validation_optionsconfigures leeway for JWTexp,nbf, andiatvalidation.Noneuses the 60-second default; zero enables strict validation.When
expected_nonceisSome(&nonce), the token’seat_noncemust match or the call returnsErr(NvatError { code: NVAT_RC_NONCE_MISMATCH, … }).Noneskips the nonce comparison.
VerifiedAttestationResult.overall_result_passed—truewhen the token’s overall attestation result passed;falsewhen 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)— returnsErr(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:
Relying party generates a nonce and sends it to the attesting party:
let nonce = Nonce::generate(32)?; // ... transport nonce to the attesting party ...
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 ...
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_optionsSigned detached EAT issued from the local verification flow
Public key published at the JWKS endpoint under
kidfor 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#
Configuration: Learn about advanced configuration in the Configuration Guide
Claims Schema: Understand attestation results in the Claims Schema Reference
Development: Contribute to the SDK using the Development Guide
API Documentation: Generate API docs with
cargo doc --open