use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256, Sha384};
use std::collections::HashMap;
use std::sync::Arc;
use parking_lot::RwLock;
use tenzro_types::tee::*;
use crate::certs;
use crate::error::{Result, TeeError};
use crate::traits::TeeProvider;
pub struct NvidiaGpuProvider {
config: NvidiaGpuConfig,
gpu_info: RwLock<Option<GpuDeviceInfo>>,
available: RwLock<Option<bool>>,
simulate: bool,
keys: Arc<RwLock<HashMap<uuid::Uuid, EnclaveKeyHandle>>>,
secret_keys: Arc<RwLock<HashMap<uuid::Uuid, Vec<u8>>>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NvidiaGpuConfig {
pub device_index: u32,
pub nras_endpoint: String,
pub use_remote_attestation: bool,
pub min_driver_version: String,
pub min_cc_firmware_version: String,
pub max_report_age_ms: i64,
pub expected_architecture: GpuArchitecture,
}
impl Default for NvidiaGpuConfig {
fn default() -> Self {
Self {
device_index: 0,
nras_endpoint: certs::NVIDIA_NRAS_ENDPOINT.to_string(),
use_remote_attestation: true,
min_driver_version: "550.0".to_string(),
min_cc_firmware_version: "1.0".to_string(),
max_report_age_ms: 24 * 60 * 60 * 1000, expected_architecture: GpuArchitecture::Hopper,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum GpuArchitecture {
Hopper,
Blackwell,
AdaLovelace,
Ampere,
Turing,
Volta,
}
impl GpuArchitecture {
pub fn supports_cc(&self) -> bool {
matches!(self, GpuArchitecture::Hopper | GpuArchitecture::Blackwell | GpuArchitecture::AdaLovelace)
}
}
impl std::fmt::Display for GpuArchitecture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GpuArchitecture::Hopper => write!(f, "Hopper"),
GpuArchitecture::Blackwell => write!(f, "Blackwell"),
GpuArchitecture::AdaLovelace => write!(f, "Ada Lovelace"),
GpuArchitecture::Ampere => write!(f, "Ampere"),
GpuArchitecture::Turing => write!(f, "Turing"),
GpuArchitecture::Volta => write!(f, "Volta"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GpuDeviceInfo {
pub name: String,
pub architecture: GpuArchitecture,
pub pci_device_id: String,
pub driver_version: String,
pub compute_capability: String,
pub memory_total: u64,
pub cc_enabled: bool,
pub cc_firmware_version: Option<String>,
pub serial_hash: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GpuAttestationReport {
pub device_info: GpuDeviceInfo,
pub measurements: GpuMeasurements,
pub cc_status: CcAttestationStatus,
pub nonce: Vec<u8>,
pub timestamp: i64,
pub signature: Vec<u8>,
pub cert_chain: Vec<Vec<u8>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GpuMeasurements {
pub vbios_hash: Vec<u8>,
pub driver_hash: Vec<u8>,
pub cc_firmware_hash: Vec<u8>,
pub ecc_enabled: bool,
pub mig_enabled: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum CcAttestationStatus {
Enabled,
Available,
NotSupported,
Failed,
}
#[derive(Debug, Serialize)]
struct NrasAttestationRequest {
evidence: String,
nonce: String,
arch: String,
}
#[derive(Debug, Deserialize)]
struct NrasAttestationResponse {
#[serde(default)]
token: String,
#[serde(default)]
attestation_result: bool,
#[serde(default)]
error: Option<String>,
}
#[derive(Debug, Deserialize)]
struct NrasTokenClaims {
#[serde(default)]
gpu_attestation_result: bool,
#[serde(default)]
gpu_arch: String,
#[serde(default)]
gpu_model: String,
#[serde(default)]
vbios_measurement: String,
#[serde(default)]
driver_measurement: String,
#[serde(default)]
cc_fw_measurement: String,
#[serde(default)]
iat: i64,
#[serde(default)]
exp: i64,
#[serde(default)]
nonce: String,
}
impl NvidiaGpuProvider {
pub fn new(config: NvidiaGpuConfig) -> Self {
let simulate = std::env::var("TENZRO_SIMULATE_GPU")
.unwrap_or_else(|_| "0".to_string()) == "1";
tracing::info!(
"Initializing NVIDIA GPU TEE provider (device: {}, arch: {}, simulate: {})",
config.device_index,
config.expected_architecture,
simulate
);
Self {
config,
gpu_info: RwLock::new(None),
available: RwLock::new(None),
simulate,
keys: Arc::new(RwLock::new(HashMap::new())),
secret_keys: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn with_simulate(mut self) -> Self {
self.simulate = true;
self
}
async fn detect_gpu(&self) -> Result<GpuDeviceInfo> {
tracing::debug!("Detecting NVIDIA GPU at device index {}", self.config.device_index);
if self.simulate {
tracing::debug!("NVIDIA GPU running in simulation mode");
let info = GpuDeviceInfo {
name: "NVIDIA H100 80GB HBM3 (SIMULATED)".to_string(),
architecture: self.config.expected_architecture,
pci_device_id: known_gpus::H100_SXM5.to_string(),
driver_version: "550.90.07".to_string(),
compute_capability: "9.0".to_string(),
memory_total: 80 * 1024 * 1024 * 1024, cc_enabled: true,
cc_firmware_version: Some("1.0.1".to_string()),
serial_hash: "sim_".to_string() + &hex::encode(Sha256::digest(b"simulated_gpu_serial")),
};
*self.gpu_info.write() = Some(info.clone());
return Ok(info);
}
self.detect_gpu_real().await
}
async fn detect_gpu_real(&self) -> Result<GpuDeviceInfo> {
let output = tokio::process::Command::new("nvidia-smi")
.args([
&format!("--id={}", self.config.device_index),
"--query-gpu=name,pci.device_id,driver_version,compute_cap,memory.total",
"--format=csv,noheader,nounits",
])
.output()
.await
.map_err(|e| TeeError::not_available(format!("nvidia-smi not found: {}", e)))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(TeeError::not_available(format!(
"nvidia-smi failed: {}", stderr
)));
}
let stdout = String::from_utf8_lossy(&output.stdout);
let fields: Vec<&str> = stdout.trim().split(", ").collect();
if fields.len() < 5 {
return Err(TeeError::not_available(format!(
"nvidia-smi returned unexpected format: {}", stdout
)));
}
let name = fields[0].to_string();
let pci_device_id = fields[1].trim_start_matches("0x").to_uppercase();
let driver_version = fields[2].to_string();
let compute_capability = fields[3].to_string();
let memory_total_mib: u64 = fields[4].trim().parse().unwrap_or(0);
let architecture = known_gpus::architecture_for_pci_id(&pci_device_id)
.unwrap_or(self.config.expected_architecture);
let cc_enabled = if known_gpus::cc_capable(&pci_device_id) {
self.check_cc_status().await
} else if known_gpus::architecture_for_pci_id(&pci_device_id).is_some() {
tracing::info!(
"GPU {} ({}) is recognized but not CC-capable — serving inference without TEE attestation",
name, pci_device_id
);
false
} else {
self.check_cc_status().await
};
let cc_firmware_version = if cc_enabled {
self.query_cc_firmware_version().await
} else {
None
};
let serial_hash = self.query_serial_hash().await;
let info = GpuDeviceInfo {
name,
architecture,
pci_device_id,
driver_version,
compute_capability,
memory_total: memory_total_mib * 1024 * 1024,
cc_enabled,
cc_firmware_version,
serial_hash,
};
*self.gpu_info.write() = Some(info.clone());
Ok(info)
}
async fn check_cc_status(&self) -> bool {
let output = tokio::process::Command::new("nvidia-smi")
.args(["conf-compute", "-gsc"])
.output()
.await;
match output {
Ok(out) if out.status.success() => {
let stdout = String::from_utf8_lossy(&out.stdout);
stdout.contains("ON") || stdout.contains("Enabled") || stdout.contains("enabled")
}
_ => {
tracing::debug!("nvidia-smi conf-compute not available — CC may not be supported");
false
}
}
}
async fn query_cc_firmware_version(&self) -> Option<String> {
let output = tokio::process::Command::new("nvidia-smi")
.args([
&format!("--id={}", self.config.device_index),
"--query-gpu=gsp_firmware_version",
"--format=csv,noheader",
])
.output()
.await;
match output {
Ok(out) if out.status.success() => {
let version = String::from_utf8_lossy(&out.stdout).trim().to_string();
if !version.is_empty() && version != "[N/A]" {
Some(version)
} else {
None
}
}
_ => None,
}
}
async fn query_serial_hash(&self) -> String {
let output = tokio::process::Command::new("nvidia-smi")
.args([
&format!("--id={}", self.config.device_index),
"--query-gpu=serial",
"--format=csv,noheader",
])
.output()
.await;
match output {
Ok(out) if out.status.success() => {
let serial = String::from_utf8_lossy(&out.stdout).trim().to_string();
hex::encode(Sha256::digest(serial.as_bytes()))
}
_ => "unknown".to_string(),
}
}
async fn collect_gpu_evidence(&self, device_info: &GpuDeviceInfo, nonce: &[u8]) -> Result<Vec<u8>> {
if self.simulate {
let evidence = SimulatedEvidence {
gpu_name: device_info.name.clone(),
architecture: format!("{}", device_info.architecture),
pci_device_id: device_info.pci_device_id.clone(),
driver_version: device_info.driver_version.clone(),
cc_enabled: device_info.cc_enabled,
cc_firmware_version: device_info.cc_firmware_version.clone(),
nonce: hex::encode(nonce),
timestamp: chrono::Utc::now().timestamp(),
measurements: SimulatedMeasurements {
vbios: hex::encode(Sha384::digest(format!("vbios_{}", device_info.name).as_bytes())),
driver: hex::encode(Sha384::digest(device_info.driver_version.as_bytes())),
cc_firmware: hex::encode(Sha384::digest(
device_info.cc_firmware_version.as_deref().unwrap_or("none").as_bytes()
)),
},
};
return serde_json::to_vec(&evidence)
.map_err(|e| TeeError::AttestationGenerationFailed(format!(
"Failed to serialize simulated evidence: {}", e
)));
}
#[cfg(target_os = "linux")]
{
let evidence = self.collect_real_evidence(device_info, nonce).await?;
Ok(evidence)
}
#[cfg(not(target_os = "linux"))]
{
Err(TeeError::not_available(
"GPU evidence collection requires Linux with NVIDIA drivers"
))
}
}
#[cfg(target_os = "linux")]
async fn collect_real_evidence(&self, device_info: &GpuDeviceInfo, nonce: &[u8]) -> Result<Vec<u8>> {
let nscq_path = std::path::Path::new("/usr/lib/x86_64-linux-gnu/libnvidia-nscq.so");
let alt_nscq_path = std::path::Path::new("/usr/lib64/libnvidia-nscq.so");
if nscq_path.exists() || alt_nscq_path.exists() {
tracing::info!("NVIDIA NSCQ library found — full SPDM evidence available");
}
let evidence = MinimalGpuEvidence {
device_name: device_info.name.clone(),
pci_device_id: device_info.pci_device_id.clone(),
driver_version: device_info.driver_version.clone(),
compute_capability: device_info.compute_capability.clone(),
cc_enabled: device_info.cc_enabled,
cc_firmware_version: device_info.cc_firmware_version.clone(),
serial_hash: device_info.serial_hash.clone(),
nonce: hex::encode(nonce),
timestamp: chrono::Utc::now().timestamp(),
};
serde_json::to_vec(&evidence)
.map_err(|e| TeeError::AttestationGenerationFailed(format!(
"Failed to serialize GPU evidence: {}", e
)))
}
async fn generate_gpu_attestation(&self, nonce: &[u8]) -> Result<GpuAttestationReport> {
let device_info = self.detect_gpu().await?;
if !device_info.cc_enabled {
return Err(TeeError::not_available(
"NVIDIA Confidential Computing is not enabled on this GPU"
));
}
let evidence = self.collect_gpu_evidence(&device_info, nonce).await?;
let measurements = if self.simulate {
GpuMeasurements {
vbios_hash: Sha384::digest(format!("vbios_{}", device_info.name).as_bytes()).to_vec(),
driver_hash: Sha384::digest(device_info.driver_version.as_bytes()).to_vec(),
cc_firmware_hash: Sha384::digest(
device_info.cc_firmware_version.as_deref().unwrap_or("none").as_bytes()
).to_vec(),
ecc_enabled: true,
mig_enabled: false,
}
} else {
self.extract_measurements_from_evidence(&evidence)?
};
let signature = if self.simulate {
let mut hasher = Sha384::new();
hasher.update(&evidence);
hasher.update(nonce);
hasher.finalize().to_vec()
} else {
return Err(TeeError::AttestationGenerationFailed(
"NVIDIA GPU AK signature requires libnvidia-nscq FFI (SPDM attestation response). \
Install the NVIDIA Confidential Computing SDK and rebuild with `--features nvidia-nscq`, \
or run with TENZRO_SIMULATE_NVIDIA_GPU=1 for simulation."
.to_string(),
));
};
let report = GpuAttestationReport {
device_info,
measurements,
cc_status: CcAttestationStatus::Enabled,
nonce: nonce.to_vec(),
timestamp: chrono::Utc::now().timestamp_millis(),
signature,
cert_chain: if self.simulate {
vec![vec![0x30; 64]] } else {
vec![] },
};
Ok(report)
}
fn extract_measurements_from_evidence(&self, evidence: &[u8]) -> Result<GpuMeasurements> {
if let Ok(minimal) = serde_json::from_slice::<MinimalGpuEvidence>(evidence) {
return Ok(GpuMeasurements {
vbios_hash: Sha384::digest(format!("vbios_{}", minimal.device_name).as_bytes()).to_vec(),
driver_hash: Sha384::digest(minimal.driver_version.as_bytes()).to_vec(),
cc_firmware_hash: Sha384::digest(
minimal.cc_firmware_version.as_deref().unwrap_or("none").as_bytes()
).to_vec(),
ecc_enabled: true,
mig_enabled: false,
});
}
Err(TeeError::InvalidAttestationReport(
"Failed to extract measurements from GPU evidence".to_string()
))
}
#[cfg(feature = "nvidia-gpu")]
async fn verify_via_nras(
&self,
gpu_report: &GpuAttestationReport,
evidence: &[u8],
) -> Result<NrasVerificationResult> {
let nras_endpoint = &self.config.nras_endpoint;
let arch_str = match gpu_report.device_info.architecture {
GpuArchitecture::Hopper => "HOPPER",
GpuArchitecture::Blackwell => "BLACKWELL",
GpuArchitecture::AdaLovelace => "ADA_LOVELACE",
GpuArchitecture::Ampere | GpuArchitecture::Turing | GpuArchitecture::Volta => {
return Err(TeeError::AttestationVerificationFailed(format!(
"GPU architecture {} is not Confidential-Computing capable; NRAS verification is not applicable",
gpu_report.device_info.architecture
)));
}
};
let request = NrasAttestationRequest {
evidence: base64::Engine::encode(
&base64::engine::general_purpose::STANDARD,
evidence,
),
nonce: hex::encode(&gpu_report.nonce),
arch: arch_str.to_string(),
};
tracing::info!(
"Sending GPU attestation to NRAS at {}: arch={}, nonce={}",
nras_endpoint, arch_str, &request.nonce[..16]
);
#[cfg(feature = "nvidia-gpu")]
{
if self.simulate {
return Ok(NrasVerificationResult {
verified: true,
token: "simulated_jwt_token".to_string(),
claims: NrasTokenClaims {
gpu_attestation_result: true,
gpu_arch: arch_str.to_string(),
gpu_model: gpu_report.device_info.name.clone(),
vbios_measurement: hex::encode(&gpu_report.measurements.vbios_hash),
driver_measurement: hex::encode(&gpu_report.measurements.driver_hash),
cc_fw_measurement: hex::encode(&gpu_report.measurements.cc_firmware_hash),
iat: chrono::Utc::now().timestamp(),
exp: chrono::Utc::now().timestamp() + 86400,
nonce: hex::encode(&gpu_report.nonce),
},
});
}
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(30))
.build()
.map_err(|e| TeeError::AttestationVerificationFailed(format!(
"Failed to create HTTP client for NRAS: {}", e
)))?;
let response = client
.post(nras_endpoint)
.json(&request)
.send()
.await
.map_err(|e| TeeError::AttestationVerificationFailed(format!(
"NRAS request failed: {}", e
)))?;
if !response.status().is_success() {
let status = response.status();
let body = response.text().await.unwrap_or_default();
return Err(TeeError::AttestationVerificationFailed(format!(
"NRAS returned HTTP {}: {}", status, body
)));
}
let nras_response: NrasAttestationResponse = response
.json()
.await
.map_err(|e| TeeError::AttestationVerificationFailed(format!(
"Failed to parse NRAS response: {}", e
)))?;
if let Some(error) = &nras_response.error {
return Err(TeeError::AttestationVerificationFailed(format!(
"NRAS verification failed: {}", error
)));
}
if !nras_response.attestation_result {
return Err(TeeError::AttestationVerificationFailed(
"NRAS returned attestation_result=false".to_string()
));
}
let claims = parse_jwt_claims(&nras_response.token)?;
let expected_nonce = hex::encode(&gpu_report.nonce);
if !claims.nonce.is_empty() && claims.nonce != expected_nonce {
return Err(TeeError::AttestationVerificationFailed(format!(
"NRAS JWT nonce mismatch: expected {}, got {}",
&expected_nonce[..16.min(expected_nonce.len())],
&claims.nonce[..16.min(claims.nonce.len())]
)));
}
let now_secs = chrono::Utc::now().timestamp();
if claims.exp != 0 && now_secs >= claims.exp {
return Err(TeeError::AttestationVerificationFailed(format!(
"NRAS JWT expired: exp={}, now={}", claims.exp, now_secs
)));
}
if claims.iat != 0 && claims.iat > now_secs + 300 {
return Err(TeeError::AttestationVerificationFailed(format!(
"NRAS JWT issued in the future: iat={}, now={}",
claims.iat, now_secs
)));
}
let local_vbios = hex::encode(&gpu_report.measurements.vbios_hash);
if !claims.vbios_measurement.is_empty()
&& !claims.vbios_measurement.eq_ignore_ascii_case(&local_vbios)
{
tracing::warn!(
"NRAS VBIOS measurement differs from local: nras={}..., local={}...",
&claims.vbios_measurement[..16.min(claims.vbios_measurement.len())],
&local_vbios[..16.min(local_vbios.len())]
);
}
let local_driver = hex::encode(&gpu_report.measurements.driver_hash);
if !claims.driver_measurement.is_empty()
&& !claims.driver_measurement.eq_ignore_ascii_case(&local_driver)
{
tracing::warn!(
"NRAS driver measurement differs from local: nras={}..., local={}...",
&claims.driver_measurement[..16.min(claims.driver_measurement.len())],
&local_driver[..16.min(local_driver.len())]
);
}
let local_cc = hex::encode(&gpu_report.measurements.cc_firmware_hash);
if !claims.cc_fw_measurement.is_empty()
&& !claims.cc_fw_measurement.eq_ignore_ascii_case(&local_cc)
{
tracing::warn!(
"NRAS CC firmware measurement differs from local: nras={}..., local={}...",
&claims.cc_fw_measurement[..16.min(claims.cc_fw_measurement.len())],
&local_cc[..16.min(local_cc.len())]
);
}
Ok(NrasVerificationResult {
verified: claims.gpu_attestation_result,
token: nras_response.token,
claims,
})
}
}
async fn verify_gpu_attestation_local(&self, report: &GpuAttestationReport) -> Result<bool> {
let now = chrono::Utc::now().timestamp_millis();
let age = now - report.timestamp;
if age > self.config.max_report_age_ms {
return Err(TeeError::attestation_failed(format!(
"GPU attestation report too old: {}ms > {}ms",
age, self.config.max_report_age_ms
)));
}
if age < 0 {
return Err(TeeError::attestation_failed(
"GPU attestation report timestamp is in the future"
));
}
if report.cc_status != CcAttestationStatus::Enabled {
return Err(TeeError::attestation_failed(
"GPU Confidential Computing is not enabled"
));
}
if report.device_info.architecture != self.config.expected_architecture {
return Err(TeeError::attestation_failed(format!(
"GPU architecture mismatch: expected {}, got {}",
self.config.expected_architecture, report.device_info.architecture
)));
}
if !version_gte(&report.device_info.driver_version, &self.config.min_driver_version) {
return Err(TeeError::attestation_failed(format!(
"GPU driver version {} below minimum {}",
report.device_info.driver_version, self.config.min_driver_version
)));
}
if let Some(cc_fw) = &report.device_info.cc_firmware_version
&& !version_gte(cc_fw, &self.config.min_cc_firmware_version)
{
return Err(TeeError::attestation_failed(format!(
"CC firmware version {} below minimum {}",
cc_fw, self.config.min_cc_firmware_version
)));
}
if report.measurements.vbios_hash.is_empty() {
return Err(TeeError::attestation_failed(
"VBIOS measurement is empty"
));
}
if report.measurements.driver_hash.is_empty() {
return Err(TeeError::attestation_failed(
"Driver measurement is empty"
));
}
tracing::info!(
"NVIDIA GPU attestation verified locally: {} (CC: {:?}, driver: {})",
report.device_info.name,
report.cc_status,
report.device_info.driver_version
);
Ok(true)
}
fn to_attestation_report(&self, gpu_report: &GpuAttestationReport, user_data: &[u8]) -> AttestationReport {
let mut metadata = HashMap::new();
metadata.insert("gpu_name".to_string(), gpu_report.device_info.name.clone());
metadata.insert("architecture".to_string(), format!("{}", gpu_report.device_info.architecture));
metadata.insert("cc_status".to_string(), format!("{:?}", gpu_report.cc_status));
metadata.insert("driver_version".to_string(), gpu_report.device_info.driver_version.clone());
metadata.insert("pci_device_id".to_string(), gpu_report.device_info.pci_device_id.clone());
if let Some(ref cc_fw) = gpu_report.device_info.cc_firmware_version {
metadata.insert("cc_firmware_version".to_string(), cc_fw.clone());
}
if self.simulate {
metadata.insert("simulated".to_string(), "true".to_string());
}
AttestationReport {
id: uuid::Uuid::new_v4(),
vendor: TeeVendor::NvidiaGpu,
user_data: user_data.to_vec(),
attestation_data: serde_json::to_vec(gpu_report).unwrap_or_default(),
certificates: gpu_report.cert_chain.clone(),
timestamp: tenzro_types::primitives::Timestamp::now(),
metadata,
quote: gpu_report.signature.clone(),
measurement: gpu_report.measurements.vbios_hash.clone(),
signature: gpu_report.signature.clone(),
vendor_data: serde_json::to_vec(&gpu_report.measurements).unwrap_or_default(),
}
}
}
#[async_trait]
impl TeeProvider for NvidiaGpuProvider {
fn vendor(&self) -> TeeVendor {
TeeVendor::NvidiaGpu
}
async fn is_available(&self) -> Result<bool> {
if let Some(cached) = *self.available.read() {
return Ok(cached);
}
let result = match self.detect_gpu().await {
Ok(info) => info.cc_enabled,
Err(_) => false,
};
*self.available.write() = Some(result);
Ok(result)
}
async fn generate_attestation(&self, user_data: &[u8]) -> Result<AttestationReport> {
let nonce = if user_data.is_empty() {
let mut nonce_data = vec![0u8; 32];
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
nonce_data[..16].copy_from_slice(×tamp.to_le_bytes());
let hash = Sha256::digest(timestamp.to_le_bytes());
nonce_data[16..32].copy_from_slice(&hash[..16]);
nonce_data
} else {
Sha256::digest(user_data).to_vec()
};
let gpu_report = self.generate_gpu_attestation(&nonce).await?;
Ok(self.to_attestation_report(&gpu_report, user_data))
}
async fn verify_attestation(&self, report: &AttestationReport) -> Result<AttestationResult> {
if report.vendor != TeeVendor::NvidiaGpu {
return Err(TeeError::attestation_failed(format!(
"Expected NvidiaGpu vendor, got {:?}", report.vendor
)));
}
let gpu_report: GpuAttestationReport = serde_json::from_slice(&report.attestation_data)
.map_err(|e| TeeError::InvalidAttestationReport(format!(
"Failed to parse GPU attestation report: {}", e
)))?;
let mut valid = self.verify_gpu_attestation_local(&gpu_report).await?;
if self.simulate {
tracing::warn!(
"NVIDIA GPU verifier: simulated report — AttestationResult.valid \
will be false. Simulated reports carry no cryptographic authority."
);
valid = false;
}
#[allow(unused_mut, unused_assignments)]
let mut nras_token: Option<String> = None;
#[allow(unused_mut, unused_assignments)]
let mut nras_attested: bool = false;
#[allow(unused_mut, unused_assignments)]
let mut nras_claims: Option<NrasTokenClaims> = None;
#[cfg(feature = "nvidia-gpu")]
if valid && self.config.use_remote_attestation {
let evidence_bytes = serde_json::to_vec(&gpu_report)
.map_err(|e| TeeError::AttestationVerificationFailed(format!(
"Failed to serialize GPU report for NRAS: {}", e
)))?;
match self.verify_via_nras(&gpu_report, &evidence_bytes).await {
Ok(nras_result) => {
nras_attested = nras_result.verified;
nras_token = Some(nras_result.token);
if !nras_result.verified {
tracing::warn!(
"NRAS rejected GPU attestation despite local pass: arch={}",
nras_result.claims.gpu_arch
);
valid = false;
} else {
tracing::info!(
"NRAS attested GPU: arch={}, model={}",
nras_result.claims.gpu_arch,
nras_result.claims.gpu_model
);
}
nras_claims = Some(nras_result.claims);
}
Err(e) => {
tracing::error!("NRAS verification failed: {}", e);
valid = false;
}
}
}
let tcb_ver = gpu_report.device_info.cc_firmware_version
.clone()
.unwrap_or_else(|| "unknown".to_string());
if valid {
let mut result = AttestationResult::success(
TeeVendor::NvidiaGpu,
gpu_report.measurements.vbios_hash.clone(),
);
result.tcb_version = tcb_ver;
result.measurements = vec![
Measurement {
index: 0,
algorithm: "SHA-384".to_string(),
value: gpu_report.measurements.vbios_hash.clone(),
register: "vbios".to_string(),
description: Some("VBIOS firmware measurement".to_string()),
},
Measurement {
index: 1,
algorithm: "SHA-384".to_string(),
value: gpu_report.measurements.driver_hash.clone(),
register: "driver".to_string(),
description: Some("GPU driver measurement".to_string()),
},
Measurement {
index: 2,
algorithm: "SHA-384".to_string(),
value: gpu_report.measurements.cc_firmware_hash.clone(),
register: "cc_firmware".to_string(),
description: Some("CC firmware measurement".to_string()),
},
];
result.cert_chain_valid = !self.simulate;
if self.simulate {
result.details.insert("simulated".to_string(), "true".to_string());
}
result.details.insert("verification_method".to_string(),
if self.config.use_remote_attestation { "nras" } else { "local" }.to_string()
);
result.details.insert("gpu_architecture".to_string(),
format!("{}", gpu_report.device_info.architecture)
);
if let Some(token) = nras_token {
result.details.insert("nras_token".to_string(), token);
result.details.insert("nras_attested".to_string(), nras_attested.to_string());
}
if let Some(claims) = nras_claims {
if !claims.gpu_arch.is_empty() {
result.details.insert("nras_gpu_arch".to_string(), claims.gpu_arch);
}
if !claims.gpu_model.is_empty() {
result.details.insert("nras_gpu_model".to_string(), claims.gpu_model);
}
if !claims.vbios_measurement.is_empty() {
result.details.insert("nras_vbios_measurement".to_string(), claims.vbios_measurement);
}
if !claims.driver_measurement.is_empty() {
result.details.insert("nras_driver_measurement".to_string(), claims.driver_measurement);
}
if !claims.cc_fw_measurement.is_empty() {
result.details.insert("nras_cc_fw_measurement".to_string(), claims.cc_fw_measurement);
}
if claims.iat != 0 {
result.details.insert("nras_token_iat".to_string(), claims.iat.to_string());
}
if claims.exp != 0 {
result.details.insert("nras_token_exp".to_string(), claims.exp.to_string());
}
if !claims.nonce.is_empty() {
result.details.insert("nras_token_nonce".to_string(), claims.nonce);
}
}
Ok(result)
} else {
Ok(AttestationResult::failure(
TeeVendor::NvidiaGpu,
"GPU attestation verification failed".to_string(),
))
}
}
async fn execute_in_enclave(&self, request: EnclaveRequest) -> Result<EnclaveResponse> {
tracing::debug!("Executing request '{}' in GPU CC enclave", request.operation);
let mut hasher = Sha256::new();
hasher.update(request.operation.as_bytes());
hasher.update(&request.params);
let execution_digest = hasher.finalize().to_vec();
Ok(EnclaveResponse {
request_id: request.id,
success: true,
data: request.params,
error: None,
attestation: Some(AttestationReport {
vendor: TeeVendor::NvidiaGpu,
quote: execution_digest,
timestamp: tenzro_types::primitives::Timestamp::now(),
..Default::default()
}),
})
}
async fn enclave_keygen(&self, params: KeyGenParams) -> Result<EnclaveKeyHandle> {
tracing::debug!("Generating key in GPU CC enclave: {:?}", params.algorithm);
let key_id = uuid::Uuid::new_v4();
let (public_key_bytes, secret_key_bytes) = match params.algorithm {
KeyAlgorithm::Ed25519 => {
let keypair = tenzro_crypto::keys::KeyPair::generate(
tenzro_crypto::keys::KeyType::Ed25519,
).map_err(|e| TeeError::KeyGenerationFailed(format!(
"Ed25519 key generation failed: {}", e
)))?;
let pub_bytes = keypair.public_key().as_bytes().to_vec();
let sec_bytes = keypair.secret_key().as_bytes().to_vec();
(pub_bytes, sec_bytes)
}
KeyAlgorithm::Secp256k1 => {
let keypair = tenzro_crypto::keys::KeyPair::generate(
tenzro_crypto::keys::KeyType::Secp256k1,
).map_err(|e| TeeError::KeyGenerationFailed(format!(
"Secp256k1 key generation failed: {}", e
)))?;
let pub_bytes = keypair.public_key().as_bytes().to_vec();
let sec_bytes = keypair.secret_key().as_bytes().to_vec();
(pub_bytes, sec_bytes)
}
KeyAlgorithm::Aes256Gcm => {
let mut key_bytes = vec![0u8; 32];
tenzro_crypto::rng::fill_random_bytes(&mut key_bytes);
let pub_bytes = Vec::new(); (pub_bytes, key_bytes)
}
};
let handle = EnclaveKeyHandle {
id: key_id,
algorithm: params.algorithm,
public_key: if public_key_bytes.is_empty() { None } else { Some(public_key_bytes) },
created_at: tenzro_types::primitives::Timestamp::now(),
attestation: None,
};
self.keys.write().insert(key_id, handle.clone());
self.secret_keys.write().insert(key_id, secret_key_bytes);
tracing::info!("Generated {:?} key in GPU CC enclave: {}", params.algorithm, key_id);
Ok(handle)
}
async fn enclave_sign(&self, key: &EnclaveKeyHandle, data: &[u8]) -> Result<Vec<u8>> {
tracing::debug!("Signing data in GPU CC enclave, key_id={}", key.id);
let secret_keys = self.secret_keys.read();
let secret_key_bytes = secret_keys.get(&key.id).ok_or_else(|| {
TeeError::InvalidKeyHandle(format!("Key {} not found in GPU CC enclave", key.id))
})?;
match key.algorithm {
KeyAlgorithm::Ed25519 => {
let keypair = tenzro_crypto::keys::KeyPair::from_bytes(
tenzro_crypto::keys::KeyType::Ed25519,
secret_key_bytes,
).map_err(|e| TeeError::CryptoOperationFailed(format!(
"Failed to reconstruct Ed25519 key: {}", e
)))?;
let signer = tenzro_crypto::signatures::Ed25519SignerImpl::new(keypair)
.map_err(|e| TeeError::CryptoOperationFailed(format!(
"Failed to create Ed25519 signer: {}", e
)))?;
use tenzro_crypto::signatures::Signer;
let sig = signer.sign(data)
.map_err(|e| TeeError::CryptoOperationFailed(format!(
"Ed25519 signing failed: {}", e
)))?;
Ok(sig.as_bytes().to_vec())
}
KeyAlgorithm::Secp256k1 => {
let keypair = tenzro_crypto::keys::KeyPair::from_bytes(
tenzro_crypto::keys::KeyType::Secp256k1,
secret_key_bytes,
).map_err(|e| TeeError::CryptoOperationFailed(format!(
"Failed to reconstruct Secp256k1 key: {}", e
)))?;
let signer = tenzro_crypto::signatures::Secp256k1SignerImpl::new(keypair)
.map_err(|e| TeeError::CryptoOperationFailed(format!(
"Failed to create Secp256k1 signer: {}", e
)))?;
use tenzro_crypto::signatures::Signer;
let sig = signer.sign(data)
.map_err(|e| TeeError::CryptoOperationFailed(format!(
"Secp256k1 signing failed: {}", e
)))?;
Ok(sig.as_bytes().to_vec())
}
KeyAlgorithm::Aes256Gcm => {
Err(TeeError::CryptoOperationFailed(
"Cannot sign with AES-256-GCM symmetric key".to_string(),
))
}
}
}
async fn enclave_encrypt(&self, key: &EnclaveKeyHandle, plaintext: &[u8]) -> Result<Vec<u8>> {
tracing::debug!("Encrypting data in GPU CC enclave, key_id={}", key.id);
if !self.keys.read().contains_key(&key.id) {
return Err(TeeError::InvalidKeyHandle(format!(
"Key {} not found in GPU CC enclave", key.id
)));
}
crate::enclave_crypto::enclave_encrypt_aes256gcm(&key.id, b"nvidia-gpu", plaintext)
}
async fn enclave_decrypt(&self, key: &EnclaveKeyHandle, ciphertext: &[u8]) -> Result<Vec<u8>> {
tracing::debug!("Decrypting data in GPU CC enclave, key_id={}", key.id);
if !self.keys.read().contains_key(&key.id) {
return Err(TeeError::InvalidKeyHandle(format!(
"Key {} not found in GPU CC enclave", key.id
)));
}
crate::enclave_crypto::enclave_decrypt_aes256gcm(&key.id, b"nvidia-gpu", ciphertext)
}
}
pub mod known_gpus {
use super::GpuArchitecture;
pub const H100_SXM5: &str = "2330";
pub const H100_PCIE: &str = "2331";
pub const H100_NVL: &str = "2321";
pub const H200_SXM: &str = "2335";
pub const H200_NVL: &str = "2336";
pub const H800_SXM: &str = "2322";
pub const H20: &str = "232C";
pub const B100: &str = "2900";
pub const B200: &str = "2901";
pub const GB200: &str = "2902";
pub const L40S: &str = "26B9";
pub const L40: &str = "26B5";
pub const L4: &str = "27B8";
pub const RTX_4090: &str = "2684";
pub const RTX_4080_SUPER: &str = "2702";
pub const RTX_4080: &str = "2704";
pub const RTX_4070_TI_SUPER: &str = "2705";
pub const RTX_4070_TI: &str = "2782";
pub const RTX_4070_SUPER: &str = "2783";
pub const RTX_4070: &str = "2786";
pub const RTX_4060_TI: &str = "2803";
pub const RTX_4060: &str = "2882";
pub const A100_SXM4_80: &str = "20B2";
pub const A100_PCIE_80: &str = "20B5";
pub const A100_SXM4_40: &str = "20B0";
pub const A100_PCIE_40: &str = "20F1";
pub const A40: &str = "2235";
pub const A30: &str = "20B7";
pub const A10: &str = "2236";
pub const A16: &str = "20F3";
pub const A2: &str = "25B6";
pub const RTX_3090_TI: &str = "2203";
pub const RTX_3090: &str = "2204";
pub const RTX_3080_TI: &str = "2208";
pub const RTX_3080: &str = "2206";
pub const RTX_3070_TI: &str = "2482";
pub const RTX_3070: &str = "2484";
pub const RTX_3060_TI: &str = "2486";
pub const RTX_3060: &str = "2503";
pub const RTX_3050: &str = "2507";
pub const T4: &str = "1EB8";
pub const RTX_2080_TI: &str = "1E07";
pub const RTX_2080_SUPER: &str = "1E81";
pub const RTX_2080: &str = "1E87";
pub const RTX_2070_SUPER: &str = "1E84";
pub const RTX_2070: &str = "1F02";
pub const RTX_2060_SUPER: &str = "1F06";
pub const RTX_2060: &str = "1F08";
pub const V100_SXM2_32: &str = "1DB5";
pub const V100_PCIE_32: &str = "1DB6";
pub const V100_SXM2_16: &str = "1DB1";
pub const V100_PCIE_16: &str = "1DB4";
pub fn architecture_for_pci_id(pci_device_id: &str) -> Option<GpuArchitecture> {
match pci_device_id {
H100_SXM5 | H100_PCIE | H100_NVL | H200_SXM | H200_NVL | H800_SXM | H20 => {
Some(GpuArchitecture::Hopper)
}
B100 | B200 | GB200 => Some(GpuArchitecture::Blackwell),
L40S | L40 | L4
| RTX_4090 | RTX_4080_SUPER | RTX_4080
| RTX_4070_TI_SUPER | RTX_4070_TI | RTX_4070_SUPER | RTX_4070
| RTX_4060_TI | RTX_4060 => Some(GpuArchitecture::AdaLovelace),
A100_SXM4_80 | A100_PCIE_80 | A100_SXM4_40 | A100_PCIE_40
| A40 | A30 | A10 | A16 | A2
| RTX_3090_TI | RTX_3090 | RTX_3080_TI | RTX_3080
| RTX_3070_TI | RTX_3070 | RTX_3060_TI | RTX_3060 | RTX_3050 => {
Some(GpuArchitecture::Ampere)
}
T4 | RTX_2080_TI | RTX_2080_SUPER | RTX_2080
| RTX_2070_SUPER | RTX_2070 | RTX_2060_SUPER | RTX_2060 => {
Some(GpuArchitecture::Turing)
}
V100_SXM2_32 | V100_PCIE_32 | V100_SXM2_16 | V100_PCIE_16 => {
Some(GpuArchitecture::Volta)
}
_ => None,
}
}
pub fn cc_capable(pci_device_id: &str) -> bool {
matches!(
pci_device_id,
H100_SXM5 | H100_PCIE | H100_NVL
| H200_SXM | H200_NVL
| H800_SXM | H20
| B100 | B200 | GB200
| L40S
)
}
}
struct NrasVerificationResult {
verified: bool,
token: String,
claims: NrasTokenClaims,
}
#[derive(Debug, Serialize, Deserialize)]
struct SimulatedEvidence {
gpu_name: String,
architecture: String,
pci_device_id: String,
driver_version: String,
cc_enabled: bool,
cc_firmware_version: Option<String>,
nonce: String,
timestamp: i64,
measurements: SimulatedMeasurements,
}
#[derive(Debug, Serialize, Deserialize)]
struct SimulatedMeasurements {
vbios: String,
driver: String,
cc_firmware: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct MinimalGpuEvidence {
device_name: String,
pci_device_id: String,
driver_version: String,
compute_capability: String,
cc_enabled: bool,
cc_firmware_version: Option<String>,
serial_hash: String,
nonce: String,
timestamp: i64,
}
fn parse_jwt_claims(token: &str) -> Result<NrasTokenClaims> {
let parts: Vec<&str> = token.split('.').collect();
if parts.len() != 3 {
return Err(TeeError::AttestationVerificationFailed(
"Invalid JWT token format from NRAS".to_string()
));
}
let payload = base64::Engine::decode(
&base64::engine::general_purpose::URL_SAFE_NO_PAD,
parts[1],
).map_err(|e| TeeError::AttestationVerificationFailed(format!(
"Failed to decode JWT payload: {}", e
)))?;
serde_json::from_slice(&payload)
.map_err(|e| TeeError::AttestationVerificationFailed(format!(
"Failed to parse JWT claims: {}", e
)))
}
fn version_gte(actual: &str, required: &str) -> bool {
let actual_parts: Vec<u64> = actual
.split('.')
.filter_map(|s| s.parse().ok())
.collect();
let required_parts: Vec<u64> = required
.split('.')
.filter_map(|s| s.parse().ok())
.collect();
let max_len = actual_parts.len().max(required_parts.len());
for i in 0..max_len {
let a = actual_parts.get(i).copied().unwrap_or(0);
let r = required_parts.get(i).copied().unwrap_or(0);
if a > r {
return true;
}
if a < r {
return false;
}
}
true }
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_nvidia_provider_creation() {
let config = NvidiaGpuConfig::default();
let provider = NvidiaGpuProvider::new(config).with_simulate();
assert_eq!(provider.vendor(), TeeVendor::NvidiaGpu);
assert!(provider.simulate);
}
#[tokio::test]
async fn test_gpu_detection_simulated() {
let config = NvidiaGpuConfig::default();
let provider = NvidiaGpuProvider::new(config).with_simulate();
let available = provider.is_available().await.unwrap();
assert!(available); }
#[tokio::test]
async fn test_generate_attestation_simulated() {
let config = NvidiaGpuConfig::default();
let provider = NvidiaGpuProvider::new(config).with_simulate();
let user_data = b"test attestation data for gpu";
let report = provider.generate_attestation(user_data).await.unwrap();
assert_eq!(report.vendor, TeeVendor::NvidiaGpu);
assert_eq!(report.user_data, user_data);
assert!(!report.attestation_data.is_empty());
assert_eq!(report.metadata.get("simulated"), Some(&"true".to_string()));
assert!(report.metadata.contains_key("gpu_name"));
assert!(report.metadata.contains_key("architecture"));
assert!(report.metadata.contains_key("driver_version"));
}
#[tokio::test]
async fn test_verify_attestation_simulated_is_invalid() {
let config = NvidiaGpuConfig::default();
let provider = NvidiaGpuProvider::new(config).with_simulate();
let report = provider.generate_attestation(b"test").await.unwrap();
let result = provider.verify_attestation(&report).await.unwrap();
assert!(
!result.valid,
"simulated NVIDIA GPU reports must never report valid=true"
);
assert_eq!(result.vendor, TeeVendor::NvidiaGpu);
}
#[tokio::test]
async fn test_verify_wrong_vendor_rejected() {
let config = NvidiaGpuConfig::default();
let provider = NvidiaGpuProvider::new(config).with_simulate();
let mut report = provider.generate_attestation(b"test").await.unwrap();
report.vendor = TeeVendor::IntelTdx;
let result = provider.verify_attestation(&report).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_enclave_keygen_and_sign() {
let config = NvidiaGpuConfig::default();
let provider = NvidiaGpuProvider::new(config);
let params = KeyGenParams {
algorithm: KeyAlgorithm::Ed25519,
purpose: KeyPurpose::Signing,
exportable: false,
params: HashMap::new(),
};
let key = provider.enclave_keygen(params).await.unwrap();
assert!(key.public_key.is_some());
assert_eq!(key.algorithm, KeyAlgorithm::Ed25519);
let signature = provider.enclave_sign(&key, b"test data").await.unwrap();
assert_eq!(signature.len(), 64);
let signature2 = provider.enclave_sign(&key, b"test data").await.unwrap();
assert_eq!(signature, signature2);
let signature3 = provider.enclave_sign(&key, b"different data").await.unwrap();
assert_ne!(signature, signature3);
let pubkey = tenzro_crypto::keys::PublicKey::new(
tenzro_crypto::keys::KeyType::Ed25519,
key.public_key.unwrap(),
);
let sig = tenzro_crypto::signatures::Signature::new(
tenzro_crypto::keys::KeyType::Ed25519,
signature,
);
assert!(tenzro_crypto::signatures::verify(&pubkey, b"test data", &sig).is_ok());
}
#[tokio::test]
async fn test_enclave_encrypt_decrypt() {
let config = NvidiaGpuConfig::default();
let provider = NvidiaGpuProvider::new(config);
let params = KeyGenParams {
algorithm: KeyAlgorithm::Aes256Gcm,
purpose: KeyPurpose::Encryption,
exportable: false,
params: HashMap::new(),
};
let key = provider.enclave_keygen(params).await.unwrap();
let plaintext = b"confidential GPU computation result";
let ciphertext = provider.enclave_encrypt(&key, plaintext).await.unwrap();
assert_ne!(ciphertext, plaintext);
let decrypted = provider.enclave_decrypt(&key, &ciphertext).await.unwrap();
assert_eq!(decrypted, plaintext); }
#[tokio::test]
async fn test_invalid_key_handle() {
let config = NvidiaGpuConfig::default();
let provider = NvidiaGpuProvider::new(config);
let fake_key = EnclaveKeyHandle {
id: uuid::Uuid::new_v4(),
algorithm: KeyAlgorithm::Ed25519,
public_key: None,
created_at: tenzro_types::primitives::Timestamp::now(),
attestation: None,
};
let result = provider.enclave_sign(&fake_key, b"test").await;
assert!(result.is_err());
}
#[test]
fn test_gpu_architecture_display() {
assert_eq!(GpuArchitecture::Hopper.to_string(), "Hopper");
assert_eq!(GpuArchitecture::Blackwell.to_string(), "Blackwell");
assert_eq!(GpuArchitecture::AdaLovelace.to_string(), "Ada Lovelace");
}
#[test]
fn test_version_comparison() {
assert!(version_gte("550.90.07", "550.0"));
assert!(version_gte("550.0", "550.0"));
assert!(version_gte("551.0", "550.0"));
assert!(!version_gte("549.0", "550.0"));
assert!(version_gte("1.0.1", "1.0"));
assert!(!version_gte("1.0", "1.0.1"));
assert!(version_gte("2.0", "1.9.9"));
}
#[test]
fn test_known_gpu_architectures() {
assert_eq!(
known_gpus::architecture_for_pci_id("2330"),
Some(GpuArchitecture::Hopper)
);
assert_eq!(
known_gpus::architecture_for_pci_id("2335"),
Some(GpuArchitecture::Hopper)
);
assert_eq!(
known_gpus::architecture_for_pci_id("2900"),
Some(GpuArchitecture::Blackwell)
);
assert_eq!(
known_gpus::architecture_for_pci_id("26B9"),
Some(GpuArchitecture::AdaLovelace)
);
assert_eq!(
known_gpus::architecture_for_pci_id("2684"),
Some(GpuArchitecture::AdaLovelace)
);
assert_eq!(
known_gpus::architecture_for_pci_id("20B2"),
Some(GpuArchitecture::Ampere)
);
assert_eq!(
known_gpus::architecture_for_pci_id("2204"),
Some(GpuArchitecture::Ampere)
);
assert_eq!(
known_gpus::architecture_for_pci_id("1EB8"),
Some(GpuArchitecture::Turing)
);
assert_eq!(
known_gpus::architecture_for_pci_id("1E07"),
Some(GpuArchitecture::Turing)
);
assert_eq!(
known_gpus::architecture_for_pci_id("1DB5"),
Some(GpuArchitecture::Volta)
);
assert_eq!(
known_gpus::architecture_for_pci_id("XXXX"),
None
);
}
#[test]
fn test_cc_capable_predicate() {
assert!(known_gpus::cc_capable("2330")); assert!(known_gpus::cc_capable("2335")); assert!(known_gpus::cc_capable("2900")); assert!(known_gpus::cc_capable("26B9")); assert!(!known_gpus::cc_capable("2684")); assert!(!known_gpus::cc_capable("20B2")); assert!(!known_gpus::cc_capable("2204")); assert!(!known_gpus::cc_capable("1EB8")); assert!(!known_gpus::cc_capable("1DB5")); assert!(!known_gpus::cc_capable("XXXX"));
}
#[test]
fn test_architecture_supports_cc() {
assert!(GpuArchitecture::Hopper.supports_cc());
assert!(GpuArchitecture::Blackwell.supports_cc());
assert!(GpuArchitecture::AdaLovelace.supports_cc());
assert!(!GpuArchitecture::Ampere.supports_cc());
assert!(!GpuArchitecture::Turing.supports_cc());
assert!(!GpuArchitecture::Volta.supports_cc());
}
#[test]
fn test_architecture_display_full() {
assert_eq!(GpuArchitecture::Ampere.to_string(), "Ampere");
assert_eq!(GpuArchitecture::Turing.to_string(), "Turing");
assert_eq!(GpuArchitecture::Volta.to_string(), "Volta");
}
#[test]
fn test_nras_endpoint_from_certs() {
let config = NvidiaGpuConfig::default();
assert_eq!(config.nras_endpoint, certs::NVIDIA_NRAS_ENDPOINT);
assert!(config.nras_endpoint.starts_with("https://"));
assert!(config.nras_endpoint.contains("attestation.nvidia.com"));
}
#[test]
fn test_jwt_parse_claims() {
let header = base64::Engine::encode(
&base64::engine::general_purpose::URL_SAFE_NO_PAD,
r#"{"alg":"ES384","typ":"JWT"}"#,
);
let payload = base64::Engine::encode(
&base64::engine::general_purpose::URL_SAFE_NO_PAD,
r#"{"gpu_attestation_result":true,"gpu_arch":"HOPPER","gpu_model":"H100","iat":1700000000,"exp":1700086400,"nonce":"deadbeef"}"#,
);
let token = format!("{}.{}.fake_signature", header, payload);
let claims = parse_jwt_claims(&token).unwrap();
assert!(claims.gpu_attestation_result);
assert_eq!(claims.gpu_arch, "HOPPER");
assert_eq!(claims.gpu_model, "H100");
assert_eq!(claims.nonce, "deadbeef");
}
#[test]
fn test_jwt_parse_invalid_format() {
assert!(parse_jwt_claims("not.a.valid.jwt.token").is_err());
assert!(parse_jwt_claims("single_segment").is_err());
}
}