From d46cf830a81004e23c93e685d8bdb976c564d6fa Mon Sep 17 00:00:00 2001 From: bredamatt <28816406+bredamatt@users.noreply.github.com> Date: Thu, 23 Jan 2025 18:01:56 +0000 Subject: [PATCH 01/21] feat: amd-sev-snp draft and feature flags for confidential computing --- .env.example | 5 +- Cargo.lock | 50 ++++++++- Cargo.toml | 2 + atoma-bin/atoma_node.rs | 9 +- atoma-confidential/Cargo.toml | 4 + atoma-confidential/src/lib.rs | 2 + atoma-confidential/src/service.rs | 176 +++++++++++++++++++++++------- atoma-confidential/src/sev_snp.rs | 19 ++++ atoma-service/Cargo.toml | 2 +- atoma-service/src/config.rs | 3 + atoma-sui/src/client.rs | 14 +-- config.example.toml | 1 + 12 files changed, 234 insertions(+), 53 deletions(-) create mode 100644 atoma-confidential/src/sev_snp.rs diff --git a/.env.example b/.env.example index 3a7ed1a6..27623f05 100644 --- a/.env.example +++ b/.env.example @@ -89,6 +89,9 @@ IMAGE_GENERATIONS_ARCHITECTURE=flux MISTRALRS_IMAGE=ghcr.io/ericlbuehler/mistral.rs:cuda-80-0.3.1 # ---------------------------------------------------------------------------------- -# TDX configuration +# CC configuration - note that only one of the following can be enabled at a time +# TODO? # Enable TDX by setting ENABLE_TDX=true for confidential compute, otherwise it will be disabled ENABLE_TDX=false +# Enable SEV-SNP by setting ENABLE_SEV_SNP=true for ADM SEV-SNP, otherwise it will be disabled +ENABLE_SEV_SNP=false \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 03dec70b..0fd670da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -748,6 +748,8 @@ dependencies = [ "dcap-rs", "flume", "rand", + "sev 5.0.0", + "strum 0.26.3", "tdx", "thiserror 1.0.69", "tokio", @@ -1628,7 +1630,7 @@ dependencies = [ "rand", "serde", "serde-big-array", - "sev", + "sev 4.0.0", "sysinfo", "tss-esapi", "users", @@ -7253,6 +7255,30 @@ dependencies = [ "uuid", ] +[[package]] +name = "sev" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b06afe5192a43814047ea0072f4935f830a1de3c8cb43b56c90ae6918468b94d" +dependencies = [ + "base64 0.22.1", + "bincode", + "bitfield 0.15.0", + "bitflags 1.3.2", + "byteorder", + "codicon", + "dirs 5.0.1", + "hex", + "iocuddle", + "lazy_static", + "libc", + "serde", + "serde-big-array", + "serde_bytes", + "static_assertions", + "uuid", +] + [[package]] name = "sha-1" version = "0.9.8" @@ -7792,6 +7818,15 @@ dependencies = [ "strum_macros 0.25.3", ] +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros 0.26.4", +] + [[package]] name = "strum_macros" version = "0.24.3" @@ -7818,6 +7853,19 @@ dependencies = [ "syn 2.0.96", ] +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2 1.0.93", + "quote 1.0.38", + "rustversion", + "syn 2.0.96", +] + [[package]] name = "subtle" version = "2.6.1" diff --git a/Cargo.toml b/Cargo.toml index 0b2c93c6..d05e9011 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,11 +49,13 @@ prometheus = "0.13.4" rand = "0.8.5" reqwest = "0.12.1" rs_merkle = "1.4.2" +sev = "5.0.0" serde = "1.0.204" serde_json = "1.0.120" serde_yaml = "0.9.34" serial_test = "3.1.1" sha2 = "0.10.8" +strum = "0.26.3" sqlx = "0.8.2" sui-keys = { git = "https://github.com/mystenlabs/sui", package = "sui-keys", tag = "testnet-v1.39.3" } sui-sdk = { git = "https://github.com/mystenlabs/sui", package = "sui-sdk", tag = "testnet-v1.39.3" } diff --git a/atoma-bin/atoma_node.rs b/atoma-bin/atoma_node.rs index f1b3ee16..c347e05b 100644 --- a/atoma-bin/atoma_node.rs +++ b/atoma-bin/atoma_node.rs @@ -5,7 +5,7 @@ use std::{ }; use anyhow::{Context, Result}; -use atoma_confidential::AtomaConfidentialComputeService; +use atoma_confidential::{AtomaConfidentialComputeService, service::AtomaConfidentialComputeProvider}; use atoma_daemon::{AtomaDaemonConfig, DaemonState}; use atoma_service::{ config::AtomaServiceConfig, @@ -265,6 +265,12 @@ async fn main() -> Result<()> { let (compute_shared_secret_sender, _compute_shared_secret_receiver) = tokio::sync::mpsc::unbounded_channel(); + let confidential_compute_provider = config.service.confidential_compute_provider + .as_ref() + .and_then(|provider| { + AtomaConfidentialComputeProvider::from_str(provider).ok() + }); + let confidential_compute_service_handle = spawn_with_shutdown( AtomaConfidentialComputeService::start_confidential_compute_service( client.clone(), @@ -272,6 +278,7 @@ async fn main() -> Result<()> { _app_state_decryption_receiver, _app_state_encryption_receiver, _compute_shared_secret_receiver, + confidential_compute_provider, shutdown_receiver.clone(), ), shutdown_sender.clone(), diff --git a/atoma-confidential/Cargo.toml b/atoma-confidential/Cargo.toml index 4bee724f..d54c4971 100644 --- a/atoma-confidential/Cargo.toml +++ b/atoma-confidential/Cargo.toml @@ -12,6 +12,8 @@ atoma-utils = { workspace = true } blake2 = { workspace = true } dcap-rs = { workspace = true, optional = true } flume = { workspace = true } +sev = { workspace = true, optional = true } +strum = { workspace = true, features = ["derive"] } rand = { workspace = true } tokio = { workspace = true } tdx = { workspace = true, optional = true } @@ -19,6 +21,8 @@ thiserror = { workspace = true } tracing = { workspace = true } x25519-dalek = { workspace = true, features = ["static_secrets"] } + [features] default = [] tdx = ["dep:dcap-rs", "dep:tdx" ] +sev_snp = ["dep:dcap-rs", "dep:sev" ] diff --git a/atoma-confidential/src/lib.rs b/atoma-confidential/src/lib.rs index c55d632b..fc2f7b75 100644 --- a/atoma-confidential/src/lib.rs +++ b/atoma-confidential/src/lib.rs @@ -2,6 +2,8 @@ pub mod key_management; pub mod service; #[cfg(feature = "tdx")] pub mod tdx; +#[cfg(feature = "sev_snp")] +pub mod sev_snp; pub mod types; pub use service::AtomaConfidentialComputeService; diff --git a/atoma-confidential/src/service.rs b/atoma-confidential/src/service.rs index 51519d07..e6a351b8 100644 --- a/atoma-confidential/src/service.rs +++ b/atoma-confidential/src/service.rs @@ -15,6 +15,7 @@ use atoma_sui::client::AtomaSuiClient; use atoma_sui::{client::AtomaSuiClientError, events::AtomaEvent}; use atoma_utils::constants::NONCE_SIZE; use std::sync::Arc; +use strum::EnumString; use thiserror::Error; use tokio::sync::{mpsc::UnboundedReceiver, oneshot, RwLock}; use tracing::instrument; @@ -66,6 +67,26 @@ pub struct AtomaConfidentialComputeService { service_shared_secret_receiver: UnboundedReceiver, /// Signal receiver for coordinating graceful shutdown of the service shutdown_signal: tokio::sync::watch::Receiver, + /// Provider for the confidential compute service + confidential_compute_provider: Option, +} + +/// Represents the supported confidential compute providers. +/// +/// This enum defines the different types of confidential computing technologies +/// that can be used for secure computation and attestation. +/// +/// # Variants +/// * `IntelTdx` - Intel Trust Domain Extensions (TDX) technology +/// * `AmdSevSnp` - AMD Secure Encrypted Virtualization with Secure Nested Paging (SEV-SNP) +/// +/// The variants are serialized in kebab-case format (e.g., "intel-tdx", "amd-sev-snp") +/// when converting to/from strings using the `EnumString` trait from the `strum` crate. +#[derive(Debug, Clone, Copy, EnumString)] +#[strum(serialize_all = "kebab-case")] +pub enum AtomaConfidentialComputeProvider { + IntelTdx, // intel-tdx + AmdSevSnp, // amd-sev-snp } impl AtomaConfidentialComputeService { @@ -76,6 +97,7 @@ impl AtomaConfidentialComputeService { service_decryption_receiver: UnboundedReceiver, service_encryption_receiver: UnboundedReceiver, service_shared_secret_receiver: UnboundedReceiver, + confidential_compute_provider: Option, shutdown_signal: tokio::sync::watch::Receiver, ) -> Result { let key_manager = X25519KeyPairManager::new()?; @@ -88,6 +110,7 @@ impl AtomaConfidentialComputeService { service_encryption_receiver, service_shared_secret_receiver, shutdown_signal, + confidential_compute_provider }) } @@ -121,6 +144,7 @@ impl AtomaConfidentialComputeService { service_decryption_receiver: UnboundedReceiver, service_encryption_receiver: UnboundedReceiver, service_shared_secret_receiver: UnboundedReceiver, + confidential_compute_provider: Option, shutdown_signal: tokio::sync::watch::Receiver, ) -> Result<()> { let mut service = Self::new( @@ -129,11 +153,12 @@ impl AtomaConfidentialComputeService { service_decryption_receiver, service_encryption_receiver, service_shared_secret_receiver, + confidential_compute_provider, shutdown_signal, )?; - // NOTE: Submit the first node key rotation attestation, because the node is starting up afresh - service.submit_node_key_rotation_tdx_attestation().await?; + service.submit_node_key_rotation_attestation().await?; + service.run().await?; Ok(()) @@ -250,51 +275,118 @@ impl AtomaConfidentialComputeService { /// This function can return: /// - `AtomaConfidentialComputeError::KeyManagerError` if key rotation or public key retrieval fails /// - `AtomaConfidentialComputeError::SuiClientError` if the attestation submission to Sui fails + /// + /// + /// Make sure this is under a feature flag for TDX, and add a different CC implemntation for ADM SEV-SNP #[instrument(level = "debug", skip_all)] - async fn submit_node_key_rotation_tdx_attestation(&mut self) -> Result<()> { + async fn submit_node_key_rotation_attestation(&mut self) -> Result<()> { self.key_manager.rotate_keys(); - #[cfg(feature = "tdx")] + + if let Some(_cc_provider) = self.confidential_compute_provider { + #[cfg(feature = "tdx")] + if matches!( + _cc_provider, + AtomaConfidentialComputeProvider::IntelTdx + ) { + return self.submit_tdx_attestation().await; + } + #[cfg(feature = "sev_snp")] + if matches!( + _cc_provider, + AtomaConfidentialComputeProvider::AmdSevSnp + ) { + return self.submit_sev_snp_attestation().await; + } + } + + // If we get here, either the feature flags don't match or the provider isn't supported + tracing::warn!( + target = "atoma-confidential-compute-service", + provider = ?self.confidential_compute_provider, + "No attestation implementation available for provider" + ); + Ok(()) + } + + + #[cfg(feature = "tdx")] + async fn submit_tdx_attestation(&mut self) -> Result<()> { + let public_key = self.key_manager.get_public_key(); + let public_key_bytes = public_key.to_bytes(); + let tdx_quote = get_compute_data_attestation(&public_key_bytes)?; + let tdx_quote_bytes = tdx_quote.to_bytes(); + + match self + .sui_client + .write() + .await + .submit_key_rotation_remote_attestation( + public_key_bytes, + tdx_quote_bytes, + None, + None, + None, + ) + .await { - let public_key = self.key_manager.get_public_key(); - let public_key_bytes = public_key.to_bytes(); - let tdx_quote = get_compute_data_attestation(&public_key_bytes)?; - let tdx_quote_bytes = tdx_quote.to_bytes(); - match self - .sui_client - .write() - .await - .submit_key_rotation_remote_attestation( - public_key_bytes, - tdx_quote_bytes, - None, - None, - None, - ) - .await - { - Ok((digest, key_rotation_counter)) => { - tracing::info!( - target = "atoma-tdx-service", - digest = digest, - key_rotation_counter = key_rotation_counter, - "Submitted node key rotation attestation successfully" - ); - self.key_rotation_counter = Some(key_rotation_counter); - Ok(()) - } - Err(e) => { - tracing::error!( - target = "atoma-tdx-service", - error = %e, - "Failed to submit node key rotation attestation" - ); - Err(AtomaConfidentialComputeError::SuiClientError(e)) - } + Ok((digest, key_rotation_counter)) => { + tracing::info!( + target = "atoma-confidential-compute-service", + digest = digest, + key_rotation_counter = key_rotation_counter, + "Submitted TDX attestation successfully" + ); + self.key_rotation_counter = Some(key_rotation_counter); + Ok(()) + } + Err(e) => { + tracing::error!( + target = "atoma-confidential-compute-service", + error = %e, + "Failed to submit TDX attestation" + ); + Err(AtomaConfidentialComputeError::SuiClientError(e)) } } - #[cfg(not(feature = "tdx"))] + } + + #[cfg(feature = "sev_snp")] + async fn submit_sev_snp_attestation(&mut self) -> Result<()> { + let public_key = self.key_manager.get_public_key(); + let public_key_bytes = public_key.to_bytes(); + let sev_snp_quote = get_compute_data_attestation(&public_key_bytes)?; + let sev_snp_quote_bytes = sev_snp_quote.to_bytes(); + + match self + .sui_client + .write() + .await + .submit_key_rotation_remote_attestation( + public_key_bytes, + sev_snp_quote_bytes, + None, + None, + None + ).await { - Ok(()) + Ok((digest, key_rotation_counter)) => { + tracing::info!( + target = "atoma-confidential-compute-service", + digest = digest, + key_rotation_counter = key_rotation_counter, + "Submitted SEV-SNP attestation successfully" + ); + self.key_rotation_counter = Some(key_rotation_counter); + Ok(()) + } + Err(e) => { + tracing::error!( + target = "atoma-confidential-compute-service", + error = %e, + "Failed to submit SEV-SNP attestation" + ); + Err(AtomaConfidentialComputeError::SuiClientError(e)) + } } } @@ -520,7 +612,7 @@ impl AtomaConfidentialComputeService { .map(|counter| counter < event.key_rotation_counter) .unwrap_or(true) { - self.submit_node_key_rotation_tdx_attestation().await?; + self.submit_node_key_rotation_attestation().await?; } } _ => { diff --git a/atoma-confidential/src/sev_snp.rs b/atoma-confidential/src/sev_snp.rs new file mode 100644 index 00000000..7d76509d --- /dev/null +++ b/atoma-confidential/src/sev_snp.rs @@ -0,0 +1,19 @@ +use crate::ToBytes; +use dcap_rs::types::quotes::{body::QuoteBody, version_4::QuoteV4}; +use sev::{ + attestation::{ + AttestationReport, + AttestationReportSignature + }, + error::SevError, + firmware::Firmware, +}; + +use thiserror::Error; + + +pub const SEV_SNP_REPORT_DATA_SIZE: usize = 64; + +type Result = std::result::Result; + + diff --git a/atoma-service/Cargo.toml b/atoma-service/Cargo.toml index 2106fad6..dbc39169 100644 --- a/atoma-service/Cargo.toml +++ b/atoma-service/Cargo.toml @@ -59,4 +59,4 @@ sqlx = { workspace = true, features = ["runtime-tokio", "postgres"] } tempfile = { workspace = true } [features] -tdx = ["atoma-confidential/tdx"] +tdx = ["atoma-confidential/tdx"] \ No newline at end of file diff --git a/atoma-service/src/config.rs b/atoma-service/src/config.rs index 7f4bed5c..ddff3e6d 100644 --- a/atoma-service/src/config.rs +++ b/atoma-service/src/config.rs @@ -43,6 +43,9 @@ pub struct AtomaServiceConfig { /// /// This field specifies the address and port on which the Atoma Service will bind. pub service_bind_address: String, + + /// Provider for the confidential compute service + pub confidential_compute_provider: Option, } impl AtomaServiceConfig { diff --git a/atoma-sui/src/client.rs b/atoma-sui/src/client.rs index c760aba7..1ce40fc5 100644 --- a/atoma-sui/src/client.rs +++ b/atoma-sui/src/client.rs @@ -1035,13 +1035,13 @@ impl AtomaSuiClient { /// Submits a transaction to rotate a node's key with remote attestation in the Atoma network. /// - /// This method creates and submits a transaction that rotates a node's key using Intel TDX remote + /// This method creates and submits a transaction that rotates a node's key using remote /// attestation. The node must have a valid node badge to perform this operation. The method requires /// both the TDX quote bytes (remote attestation proof) and the new public key bytes. /// /// # Arguments /// - /// * `tdx_quote_bytes` - A vector of bytes containing the Intel TDX remote attestation quote + /// * `tdx_quote_bytes` - A vector of bytes containing the remote attestation quote /// * `public_key_bytes` - A 32-byte array containing the new public key /// * `gas` - Optional ObjectID to use as gas for the transaction. If None, the system will /// automatically select a gas object @@ -1066,12 +1066,12 @@ impl AtomaSuiClient { /// use sui_sdk::types::base_types::ObjectID; /// /// async fn example(client: &mut AtomaSuiClient) -> Result<()> { - /// let tdx_quote = vec![1, 2, 3, 4]; // Your TDX quote bytes + /// let quote = vec![1, 2, 3, 4]; // Your quote bytes /// let public_key = [0u8; 32]; // Your new public key /// /// // Submit with default gas settings /// let tx_digest = client.submit_key_rotation_remote_attestation( - /// tdx_quote, + /// quote, /// public_key, /// None, // default gas /// None, // default gas budget @@ -1085,12 +1085,12 @@ impl AtomaSuiClient { #[instrument(level = "info", skip_all, fields( address = %self.wallet_ctx.active_address().unwrap(), public_key = %hex::encode(public_key_bytes), - remote_attestation_quote = %hex::encode(&tdx_quote_bytes) + remote_attestation_quote = %hex::encode("e_bytes) ))] pub async fn submit_key_rotation_remote_attestation( &mut self, public_key_bytes: [u8; 32], - tdx_quote_bytes: Vec, + quote_bytes: Vec, gas: Option, gas_budget: Option, gas_price: Option, @@ -1115,7 +1115,7 @@ impl AtomaSuiClient { SuiJsonValue::from_object_id(self.config.atoma_db()), SuiJsonValue::from_object_id(node_badge_id), SuiJsonValue::new(public_key_bytes.to_vec().into())?, - SuiJsonValue::new(tdx_quote_bytes.into())?, + SuiJsonValue::new(quote_bytes.into())?, ], gas, gas_budget.unwrap_or(GAS_BUDGET), diff --git a/config.example.toml b/config.example.toml index f0ea8eb1..c80d250c 100644 --- a/config.example.toml +++ b/config.example.toml @@ -6,6 +6,7 @@ image_generations_service_url = "http://image-generations:80" models = ["meta-llama/Llama-3.2-3B-Instruct"] revisions = ["main"] service_bind_address = "0.0.0.0:3000" +confidential_compute_provider = "intel-tdx" [atoma_sui] http_rpc_node_addr = "https://fullnode.testnet.sui.io:443" # Current RPC node address for testnet From 1aeffc67b3616602f306d780f9dfa03d9a86bbf9 Mon Sep 17 00:00:00 2001 From: bredamatt <28816406+bredamatt@users.noreply.github.com> Date: Fri, 24 Jan 2025 12:47:39 +0000 Subject: [PATCH 02/21] fix: add missing feature flag --- atoma-confidential/Cargo.toml | 2 +- atoma-confidential/src/lib.rs | 2 +- atoma-confidential/src/service.rs | 4 ++-- atoma-service/Cargo.toml | 3 ++- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/atoma-confidential/Cargo.toml b/atoma-confidential/Cargo.toml index d54c4971..12846cc3 100644 --- a/atoma-confidential/Cargo.toml +++ b/atoma-confidential/Cargo.toml @@ -25,4 +25,4 @@ x25519-dalek = { workspace = true, features = ["static_secrets"] } [features] default = [] tdx = ["dep:dcap-rs", "dep:tdx" ] -sev_snp = ["dep:dcap-rs", "dep:sev" ] +sev-snp = ["dep:dcap-rs", "dep:sev" ] diff --git a/atoma-confidential/src/lib.rs b/atoma-confidential/src/lib.rs index fc2f7b75..6357a88d 100644 --- a/atoma-confidential/src/lib.rs +++ b/atoma-confidential/src/lib.rs @@ -2,7 +2,7 @@ pub mod key_management; pub mod service; #[cfg(feature = "tdx")] pub mod tdx; -#[cfg(feature = "sev_snp")] +#[cfg(feature = "sev-snp")] pub mod sev_snp; pub mod types; diff --git a/atoma-confidential/src/service.rs b/atoma-confidential/src/service.rs index e6a351b8..8ff57b01 100644 --- a/atoma-confidential/src/service.rs +++ b/atoma-confidential/src/service.rs @@ -290,7 +290,7 @@ impl AtomaConfidentialComputeService { ) { return self.submit_tdx_attestation().await; } - #[cfg(feature = "sev_snp")] + #[cfg(feature = "sev-snp")] if matches!( _cc_provider, AtomaConfidentialComputeProvider::AmdSevSnp @@ -350,7 +350,7 @@ impl AtomaConfidentialComputeService { } } - #[cfg(feature = "sev_snp")] + #[cfg(feature = "sev-snp")] async fn submit_sev_snp_attestation(&mut self) -> Result<()> { let public_key = self.key_manager.get_public_key(); let public_key_bytes = public_key.to_bytes(); diff --git a/atoma-service/Cargo.toml b/atoma-service/Cargo.toml index dbc39169..eb89b164 100644 --- a/atoma-service/Cargo.toml +++ b/atoma-service/Cargo.toml @@ -59,4 +59,5 @@ sqlx = { workspace = true, features = ["runtime-tokio", "postgres"] } tempfile = { workspace = true } [features] -tdx = ["atoma-confidential/tdx"] \ No newline at end of file +tdx = ["atoma-confidential/tdx"] +sev-snp = ["atoma-confidential/sev-snp"] From 5eabcca3339305d0f5203cc0e64c28685d323743 Mon Sep 17 00:00:00 2001 From: bredamatt <28816406+bredamatt@users.noreply.github.com> Date: Fri, 24 Jan 2025 12:47:54 +0000 Subject: [PATCH 03/21] fix: cargo fmt --- atoma-bin/atoma_node.rs | 14 ++++++++------ atoma-confidential/src/lib.rs | 4 ++-- atoma-confidential/src/service.rs | 24 +++++++++--------------- atoma-confidential/src/sev_snp.rs | 8 +------- 4 files changed, 20 insertions(+), 30 deletions(-) diff --git a/atoma-bin/atoma_node.rs b/atoma-bin/atoma_node.rs index c347e05b..2675a4d6 100644 --- a/atoma-bin/atoma_node.rs +++ b/atoma-bin/atoma_node.rs @@ -5,7 +5,9 @@ use std::{ }; use anyhow::{Context, Result}; -use atoma_confidential::{AtomaConfidentialComputeService, service::AtomaConfidentialComputeProvider}; +use atoma_confidential::{ + service::AtomaConfidentialComputeProvider, AtomaConfidentialComputeService, +}; use atoma_daemon::{AtomaDaemonConfig, DaemonState}; use atoma_service::{ config::AtomaServiceConfig, @@ -265,12 +267,12 @@ async fn main() -> Result<()> { let (compute_shared_secret_sender, _compute_shared_secret_receiver) = tokio::sync::mpsc::unbounded_channel(); - let confidential_compute_provider = config.service.confidential_compute_provider + let confidential_compute_provider = config + .service + .confidential_compute_provider .as_ref() - .and_then(|provider| { - AtomaConfidentialComputeProvider::from_str(provider).ok() - }); - + .and_then(|provider| AtomaConfidentialComputeProvider::from_str(provider).ok()); + let confidential_compute_service_handle = spawn_with_shutdown( AtomaConfidentialComputeService::start_confidential_compute_service( client.clone(), diff --git a/atoma-confidential/src/lib.rs b/atoma-confidential/src/lib.rs index 6357a88d..c0f2a8cc 100644 --- a/atoma-confidential/src/lib.rs +++ b/atoma-confidential/src/lib.rs @@ -1,9 +1,9 @@ pub mod key_management; pub mod service; -#[cfg(feature = "tdx")] -pub mod tdx; #[cfg(feature = "sev-snp")] pub mod sev_snp; +#[cfg(feature = "tdx")] +pub mod tdx; pub mod types; pub use service::AtomaConfidentialComputeService; diff --git a/atoma-confidential/src/service.rs b/atoma-confidential/src/service.rs index 8ff57b01..8e169714 100644 --- a/atoma-confidential/src/service.rs +++ b/atoma-confidential/src/service.rs @@ -85,7 +85,7 @@ pub struct AtomaConfidentialComputeService { #[derive(Debug, Clone, Copy, EnumString)] #[strum(serialize_all = "kebab-case")] pub enum AtomaConfidentialComputeProvider { - IntelTdx, // intel-tdx + IntelTdx, // intel-tdx AmdSevSnp, // amd-sev-snp } @@ -110,7 +110,7 @@ impl AtomaConfidentialComputeService { service_encryption_receiver, service_shared_secret_receiver, shutdown_signal, - confidential_compute_provider + confidential_compute_provider, }) } @@ -275,8 +275,8 @@ impl AtomaConfidentialComputeService { /// This function can return: /// - `AtomaConfidentialComputeError::KeyManagerError` if key rotation or public key retrieval fails /// - `AtomaConfidentialComputeError::SuiClientError` if the attestation submission to Sui fails - /// - /// + /// + /// /// Make sure this is under a feature flag for TDX, and add a different CC implemntation for ADM SEV-SNP #[instrument(level = "debug", skip_all)] async fn submit_node_key_rotation_attestation(&mut self) -> Result<()> { @@ -284,17 +284,11 @@ impl AtomaConfidentialComputeService { if let Some(_cc_provider) = self.confidential_compute_provider { #[cfg(feature = "tdx")] - if matches!( - _cc_provider, - AtomaConfidentialComputeProvider::IntelTdx - ) { + if matches!(_cc_provider, AtomaConfidentialComputeProvider::IntelTdx) { return self.submit_tdx_attestation().await; } #[cfg(feature = "sev-snp")] - if matches!( - _cc_provider, - AtomaConfidentialComputeProvider::AmdSevSnp - ) { + if matches!(_cc_provider, AtomaConfidentialComputeProvider::AmdSevSnp) { return self.submit_sev_snp_attestation().await; } } @@ -308,7 +302,6 @@ impl AtomaConfidentialComputeService { Ok(()) } - #[cfg(feature = "tdx")] async fn submit_tdx_attestation(&mut self) -> Result<()> { let public_key = self.key_manager.get_public_key(); @@ -366,8 +359,9 @@ impl AtomaConfidentialComputeService { sev_snp_quote_bytes, None, None, - None - ).await + None, + ) + .await { Ok((digest, key_rotation_counter)) => { tracing::info!( diff --git a/atoma-confidential/src/sev_snp.rs b/atoma-confidential/src/sev_snp.rs index 7d76509d..c7939512 100644 --- a/atoma-confidential/src/sev_snp.rs +++ b/atoma-confidential/src/sev_snp.rs @@ -1,19 +1,13 @@ use crate::ToBytes; use dcap_rs::types::quotes::{body::QuoteBody, version_4::QuoteV4}; use sev::{ - attestation::{ - AttestationReport, - AttestationReportSignature - }, + attestation::{AttestationReport, AttestationReportSignature}, error::SevError, firmware::Firmware, }; use thiserror::Error; - pub const SEV_SNP_REPORT_DATA_SIZE: usize = 64; type Result = std::result::Result; - - From ca6f7940dd081e4d6c30469a0ecc1d526b678d00 Mon Sep 17 00:00:00 2001 From: bredamatt <28816406+bredamatt@users.noreply.github.com> Date: Fri, 24 Jan 2025 17:45:06 +0000 Subject: [PATCH 04/21] fix: add None to CoCo constructor --- atoma-service/src/tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/atoma-service/src/tests.rs b/atoma-service/src/tests.rs index 9a117b84..1a19678c 100644 --- a/atoma-service/src/tests.rs +++ b/atoma-service/src/tests.rs @@ -269,6 +269,7 @@ mod middleware { decryption_receiver, encryption_receiver, compute_shared_secret_receiver, + None, shutdown_receiver, ) .expect("Failed to create confidential compute service"); From 0f45b09de36038044f2bd949356d665c9b7d746c Mon Sep 17 00:00:00 2001 From: bredamatt <28816406+bredamatt@users.noreply.github.com> Date: Fri, 24 Jan 2025 17:50:57 +0000 Subject: [PATCH 05/21] wip: add sev-snp attestation report and certificate chain verification sketch --- Cargo.lock | 69 ++++++++++++++++++++++++++ atoma-confidential/Cargo.toml | 2 +- atoma-confidential/src/sev_snp.rs | 82 ++++++++++++++++++++++++++++++- 3 files changed, 150 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0fd670da..0879c59a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2167,6 +2167,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", + "der_derive", + "flagset", "pem-rfc7468 0.7.0", "zeroize", ] @@ -2185,6 +2187,17 @@ dependencies = [ "rusticata-macros", ] +[[package]] +name = "der_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" +dependencies = [ + "proc-macro2 1.0.93", + "quote 1.0.38", + "syn 2.0.96", +] + [[package]] name = "deranged" version = "0.3.11" @@ -2548,6 +2561,7 @@ dependencies = [ "ff 0.13.0", "generic-array", "group 0.13.0", + "hkdf", "pem-rfc7468 0.7.0", "pkcs8 0.10.2", "rand_core", @@ -2948,6 +2962,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" +[[package]] +name = "flagset" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3ea1ec5f8307826a5b71094dd91fc04d4ae75d5709b20ad351c7fb4815c86ec" + [[package]] name = "flate2" version = "1.0.35" @@ -5443,6 +5463,18 @@ dependencies = [ "sha2 0.10.8", ] +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "primeorder", + "sha2 0.10.8", +] + [[package]] name = "pairing" version = "0.23.0" @@ -7272,11 +7304,15 @@ dependencies = [ "iocuddle", "lazy_static", "libc", + "p384", + "rsa 0.9.7", "serde", "serde-big-array", "serde_bytes", + "sha2 0.10.8", "static_assertions", "uuid", + "x509-cert", ] [[package]] @@ -8646,6 +8682,27 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tls_codec" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e78c9c330f8c85b2bae7c8368f2739157db9991235123aa1b15ef9502bfb6a" +dependencies = [ + "tls_codec_derive", + "zeroize", +] + +[[package]] +name = "tls_codec_derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d9ef545650e79f30233c0003bcc2504d7efac6dad25fca40744de773fe2049c" +dependencies = [ + "proc-macro2 1.0.93", + "quote 1.0.38", + "syn 2.0.96", +] + [[package]] name = "tokenizers" version = "0.21.0" @@ -9930,6 +9987,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "x509-cert" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94" +dependencies = [ + "const-oid", + "der 0.7.9", + "spki 0.7.3", + "tls_codec", +] + [[package]] name = "x509-parser" version = "0.14.0" diff --git a/atoma-confidential/Cargo.toml b/atoma-confidential/Cargo.toml index 12846cc3..5a538bbe 100644 --- a/atoma-confidential/Cargo.toml +++ b/atoma-confidential/Cargo.toml @@ -12,7 +12,7 @@ atoma-utils = { workspace = true } blake2 = { workspace = true } dcap-rs = { workspace = true, optional = true } flume = { workspace = true } -sev = { workspace = true, optional = true } +sev = { workspace = true, optional = true, features = ["crypto_nossl"]} strum = { workspace = true, features = ["derive"] } rand = { workspace = true } tokio = { workspace = true } diff --git a/atoma-confidential/src/sev_snp.rs b/atoma-confidential/src/sev_snp.rs index c7939512..c1a48b42 100644 --- a/atoma-confidential/src/sev_snp.rs +++ b/atoma-confidential/src/sev_snp.rs @@ -1,7 +1,7 @@ use crate::ToBytes; -use dcap_rs::types::quotes::{body::QuoteBody, version_4::QuoteV4}; + use sev::{ - attestation::{AttestationReport, AttestationReportSignature}, + attestation::{AttestationReport, AttestationReportSignature, CertTableEntry}, error::SevError, firmware::Firmware, }; @@ -11,3 +11,81 @@ use thiserror::Error; pub const SEV_SNP_REPORT_DATA_SIZE: usize = 64; type Result = std::result::Result; + +/// Generates an SEV-SNP attestation report for the given compute data. +/// +/// This function takes a slice of bytes representing the attested data and returns +/// an AttestationReport. The attested data must be exactly 64 bytes long, +/// matching the SEV_SNP_REPORT_DATA_SIZE constant. +/// +/// # Arguments +/// +/// * `attested_data` - A slice of bytes containing the data to be attested (public key) +/// +/// # Returns +/// +/// * `Result` - On success, returns an AttestationReport. +/// On failure, returns a SevError. +/// +/// # Errors +/// +/// Returns `SevError::InvalidAttestedDataSize` if the input data size is not 64 bytes. +/// Returns `SevError::FailedVerification` if the attestation report verification fails. +/// Returns `SevError::FailedToOpenFirmware` if the firmware interface cannot be opened. +/// Returns `SevError::FailedToGetAttestationReport` if the attestation report cannot be generated. +/// +/// # Example +/// +/// ``` +/// let data_to_attest = [1u8; 64]; +/// match get_compute_data_attestation(&data_to_attest) { +/// Ok(report) => println!("Attestation successful"), +/// Err(e) => eprintln!("Attestation failed: {}", e), +/// } +/// ``` +pub fn get_compute_data_attestation(attested_data: &[u8]) -> Result { + if attested_data.len() != SEV_SNP_REPORT_DATA_SIZE { + return Err(SevError::InvalidAttestedDataSize(attested_data.len())); + } + + let mut firmware = Firmware::open().map_err(SevError::FailedToOpenFirmware)?; + + // Ask the PSP to generate an attestation report for the given data + let (attestation_report, cert_chain): (AttestationReport, Vec) = firmware + .get_ext_report(Some(1), Some(attested_data), Some(1)) + .map_err(SevError::FailedToGetAttestationReport)?; + + // Verify the attestation report signature using the VCEK. + // This uses the crypto_nossl feature of the sev crate for detailed error handling and a pure Rust implementation. + (attestation_report, cert_chain) + .verify() + .map_err(|e| SevError::FailedVerification(format!("Attestation report verification failed: {}", e)))?; + + Ok(attestation_report) +} + +#[derive(Error, Debug)] +pub enum SevError { + #[error("Invalid attested data size: expected {}, got {0}", SEV_SNP_REPORT_DATA_SIZE)] + InvalidAttestedDataSize(usize), + #[error("Failed to verify attestation report: {0}")] + FailedVerification(#[source] std::io::Error), + #[error("Failed to open firmware interface: {0}")] + FailedToOpenFirmware(#[source] std::io::Error), + #[error("Failed to get attestation report: {0}")] + FailedToGetAttestationReport(#[source] std::io::Error), +} + +// TODO +impl ToBytes for AttestationReport { + fn to_bytes(&self) -> Vec { + self.to_bytes() + } +} + +// TODO +impl ToBytes for CertTableEntry { + fn to_bytes(&self) -> Vec { + self.to_bytes() + } +} From 08fbd6787f520903fac1b40af3933e5b693f294d Mon Sep 17 00:00:00 2001 From: bredamatt <28816406+bredamatt@users.noreply.github.com> Date: Fri, 24 Jan 2025 17:51:14 +0000 Subject: [PATCH 06/21] fix: include the sev-snp feature-gated dependency --- atoma-confidential/src/service.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/atoma-confidential/src/service.rs b/atoma-confidential/src/service.rs index 8e169714..fc332f24 100644 --- a/atoma-confidential/src/service.rs +++ b/atoma-confidential/src/service.rs @@ -6,6 +6,11 @@ use crate::{ ConfidentialComputeSharedSecretRequest, ConfidentialComputeSharedSecretResponse, }, }; +#[cfg(feature = "sev-snp")] +use crate::{ + sev_snp::{get_compute_data_attestation, SevError}, + ToBytes, +}; #[cfg(feature = "tdx")] use crate::{ tdx::{get_compute_data_attestation, TdxError}, From 46f3ccc66ab3616a26ffa549bcfee92102e49c99 Mon Sep 17 00:00:00 2001 From: bredamatt <28816406+bredamatt@users.noreply.github.com> Date: Thu, 30 Jan 2025 11:04:04 +0000 Subject: [PATCH 07/21] wip: add wrapper struct with verify implementation --- atoma-confidential/src/sev_snp.rs | 99 +++++++++++++++++++++++-------- 1 file changed, 73 insertions(+), 26 deletions(-) diff --git a/atoma-confidential/src/sev_snp.rs b/atoma-confidential/src/sev_snp.rs index c1a48b42..ce0baec5 100644 --- a/atoma-confidential/src/sev_snp.rs +++ b/atoma-confidential/src/sev_snp.rs @@ -1,7 +1,7 @@ use crate::ToBytes; use sev::{ - attestation::{AttestationReport, AttestationReportSignature, CertTableEntry}, + attestation::{AttestationReport, AttestationReportSignature, Chain}, error::SevError, firmware::Firmware, }; @@ -30,7 +30,6 @@ type Result = std::result::Result; /// # Errors /// /// Returns `SevError::InvalidAttestedDataSize` if the input data size is not 64 bytes. -/// Returns `SevError::FailedVerification` if the attestation report verification fails. /// Returns `SevError::FailedToOpenFirmware` if the firmware interface cannot be opened. /// Returns `SevError::FailedToGetAttestationReport` if the attestation report cannot be generated. /// @@ -43,25 +42,87 @@ type Result = std::result::Result; /// Err(e) => eprintln!("Attestation failed: {}", e), /// } /// ``` -pub fn get_compute_data_attestation(attested_data: &[u8]) -> Result { +pub fn get_compute_data_attestation(attested_data: &[u8]) -> Result { if attested_data.len() != SEV_SNP_REPORT_DATA_SIZE { return Err(SevError::InvalidAttestedDataSize(attested_data.len())); } let mut firmware = Firmware::open().map_err(SevError::FailedToOpenFirmware)?; - // Ask the PSP to generate an attestation report for the given data - let (attestation_report, cert_chain): (AttestationReport, Vec) = firmware - .get_ext_report(Some(1), Some(attested_data), Some(1)) + // Message version is set as a protocol version indicator, default is 1. + let message_version = 1; + + // Virtual Machine Privilege Level, 0 = least privileged, 3 = most privileged. + // VMPL allows for page security levels in the SNP Guest. + // If VMPL is disabled on the platform, set to 0. + // See: https://docs.enclaive.cloud/confidential-cloud/technology-in-depth/amd-sev/technology/fundamentals/features/virtual-machine-privilege-levels + let vmpl = 0; + + // Ask the PSP to generate an extended attestation report for the given data as the proxy will need to verify the report. + let (chain, report): (Chain, AttestationReport) = firmware + .get_ext_report(Some(message_version), Some(attested_data), Some(vmpl)) .map_err(SevError::FailedToGetAttestationReport)?; - // Verify the attestation report signature using the VCEK. - // This uses the crypto_nossl feature of the sev crate for detailed error handling and a pure Rust implementation. - (attestation_report, cert_chain) - .verify() - .map_err(|e| SevError::FailedVerification(format!("Attestation report verification failed: {}", e)))?; + let snp_attestation_report = (chain, report).to_snp_attestation_report(); + + Ok(snp_attestation_report) +} + +/// A wrapper struct for the attestation report and certificate chain received from the SNP_GET_EXT_REPORT ioctl. +#[derive(Debug, Clone)] +pub struct SNPAttestationReport { + /// The chain of certificates that form the chain of trust for the attestation report. + pub chain: Chain, + /// The attestation report generated by the PSP. + pub report: AttestationReport, +} + +/// Trait for converting a tuple of (Chain, AttestationReport) to an SNPAttestationReport. +pub trait ToSNPAttestationReport { + fn to_snp_attestation_report(&self) -> SNPAttestationReport; +} + +impl ToSNPAttestationReport for (Chain, AttestationReport) { + fn to_snp_attestation_report(&self) -> SNPAttestationReport { + SNPAttestationReport { + chain: self.0, + report: self.1, + } + } +} + +/// Implements the Verifiable trait (crypto_nossl feature-gated implementation) for SNPAttestationReport. +impl sev::snp::certs::Verifiable for SNPAttestationReport { + type Output = bool; - Ok(attestation_report) + /// Verifies the attestation report against the VCEK certificate. + /// Taken from: https://docs.rs/sev/5.0.0/src/sev/firmware/guest/types/snp.rs.html#351-391 + fn verify(&self) -> Result<(), SevError> { + // According to Chapter 3 of the [Versioned Chip Endorsement Key (VCEK) Certificate and + // KDS Interface Specification][spec], the VCEK certificate certifies an ECDSA public key on curve P-384, + // and the signature hash algorithm is sha384. + // [spec]: https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/specifications/57230.pdf + + let vcek = self.0.verify()?; + let sig = p384::ecdsa::Signature::try_from(&self.1.signature)?; + + let measurable_bytes: &[u8] = &bincode::serialize(self.1).map_err(|e| { + SevError::FailedVerification(format!("Unable to serialize bytes: {}", e)) + })?[..0x2a0]; + + use sha2::Digest; + let base_digest = sha2::Sha384::new_with_prefix(measurable_bytes); + + let verifying_key = p384::ecdsa::VerifyingKey::from_sec1_bytes(vcek.public_key_sec1()) + .map_err(|e| { + SevError::FailedVerification(format!("failed to deserialize public key from sec1 bytes: {e:?}")) + })?; + + use p384::ecdsa::signature::DigestVerifier; + verifying_key.verify_digest(base_digest, &sig).map_err(|e| { + SevError::FailedVerification(format!("VCEK does not sign the attestation report: {e:?}")) + }) + } } #[derive(Error, Debug)] @@ -75,17 +136,3 @@ pub enum SevError { #[error("Failed to get attestation report: {0}")] FailedToGetAttestationReport(#[source] std::io::Error), } - -// TODO -impl ToBytes for AttestationReport { - fn to_bytes(&self) -> Vec { - self.to_bytes() - } -} - -// TODO -impl ToBytes for CertTableEntry { - fn to_bytes(&self) -> Vec { - self.to_bytes() - } -} From bc2488f21f171936ba179b3ec61fbd4b571fcef8 Mon Sep 17 00:00:00 2001 From: bredamatt <28816406+bredamatt@users.noreply.github.com> Date: Thu, 30 Jan 2025 11:08:11 +0000 Subject: [PATCH 08/21] feat: add TEEProvider to NodePublicKeyCommittmentEvent --- atoma-confidential/src/types.rs | 48 +++++++++++++++++++ atoma-state/src/handlers.rs | 4 +- ...0241202121152_node_public_key_rotation.sql | 5 +- atoma-state/src/state_manager.rs | 41 +++++++++++----- atoma-sui/src/events.rs | 5 ++ 5 files changed, 87 insertions(+), 16 deletions(-) diff --git a/atoma-confidential/src/types.rs b/atoma-confidential/src/types.rs index 1efa045d..572d8869 100644 --- a/atoma-confidential/src/types.rs +++ b/atoma-confidential/src/types.rs @@ -81,3 +81,51 @@ pub struct ConfidentialComputeSharedSecretResponse { /// Cryptographic nonce used in the encryption process pub nonce: [u8; NONCE_SIZE], } + +/// Represents the type of Trusted Execution Environment (TEE) provider used by a node. +/// +/// This enum identifies different TEE providers that can be used for secure computation +/// and attestation in the Atoma network. Each variant corresponds to a specific TEE +/// technology from different hardware vendors. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum TEEProvider { + /// Intel Trust Domain Extensions (TDX) technology + Tdx = 0, + /// AMD SEV with Secure Nested Paging (SNP) technology + Snp = 1, + /// ARM TrustZone technology + Arm = 2, +} + +impl crate::ToBytes for TEEProvider { + /// Converts the TEE provider enum to a single byte representation. + /// + /// # Returns + /// A vector containing a single byte representing the TEE provider variant. + fn to_bytes(&self) -> Vec { + vec![*self as u8] + } +} + +impl TEEProvider { + /// Creates a TEEProvider from its byte representation. + /// + /// # Arguments + /// * `bytes` - A byte slice containing the TEE provider identifier. + /// Expected to be a single byte with value 0, 1, or 2. + /// + /// # Returns + /// * `Ok(TEEProvider)` - The corresponding TEE provider variant + /// * `Err(anyhow::Error)` - If the byte value is not recognized as a valid TEE provider + /// + /// # Errors + /// Returns an error if the input byte does not correspond to a known TEE provider variant. + fn _from_bytes(bytes: &[u8]) -> Result { + Ok(match bytes[0] { + 0 => TEEProvider::Tdx, + 1 => TEEProvider::Snp, + 2 => TEEProvider::Arm, + _ => anyhow::bail!("Invalid TEE provider"), + }) + } +} diff --git a/atoma-state/src/handlers.rs b/atoma-state/src/handlers.rs index 9a72f2b2..c63f909c 100644 --- a/atoma-state/src/handlers.rs +++ b/atoma-state/src/handlers.rs @@ -732,7 +732,7 @@ pub(crate) async fn handle_state_manager_event( /// # Behavior /// /// The function performs the following steps: -/// 1. Extracts the `epoch`, `node_id`, `new_public_key`, and `tee_remote_attestation_bytes` from the event. +/// 1. Extracts the `epoch`, `node_id`, `new_public_key`, `tee_remote_attestation_bytes`, and `tee_provider` from the event. /// 2. Calls the `insert_node_public_key_rotation` method on the `AtomaStateManager` to update the node's public key in the database. #[instrument(level = "info", skip_all)] async fn handle_node_key_rotation_event( @@ -750,6 +750,7 @@ async fn handle_node_key_rotation_event( node_id, new_public_key, tee_remote_attestation_bytes, + tee_provider, } = event; state_manager .state @@ -759,6 +760,7 @@ async fn handle_node_key_rotation_event( node_id.inner, new_public_key, tee_remote_attestation_bytes, + tee_provider, ) .await?; Ok(()) diff --git a/atoma-state/src/migrations/20241202121152_node_public_key_rotation.sql b/atoma-state/src/migrations/20241202121152_node_public_key_rotation.sql index 627736a2..ee51ff3a 100644 --- a/atoma-state/src/migrations/20241202121152_node_public_key_rotation.sql +++ b/atoma-state/src/migrations/20241202121152_node_public_key_rotation.sql @@ -2,6 +2,7 @@ CREATE TABLE IF NOT EXISTS node_public_key_rotations ( node_small_id BIGINT PRIMARY KEY, public_key_bytes BYTEA UNIQUE NOT NULL, - tdx_quote_bytes BYTEA NOT NULL, - epoch BIGINT NOT NULL + tee_quote_bytes BYTEA NOT NULL, + tee_provider BYTEA NOT NULL, + epoch BIGINT NOT NULL ); diff --git a/atoma-state/src/state_manager.rs b/atoma-state/src/state_manager.rs index 56a23477..3e51eb71 100644 --- a/atoma-state/src/state_manager.rs +++ b/atoma-state/src/state_manager.rs @@ -770,21 +770,24 @@ impl AtomaState { node_small_id: u64, public_key_bytes: Vec, tee_remote_attestation_bytes: Vec, + tee_provider: Vec, ) -> Result<()> { sqlx::query( - "INSERT INTO node_public_key_rotations (epoch, key_rotation_counter, node_small_id, public_key_bytes, tdx_quote_bytes) VALUES ($1, $2, $3, $4, $5) + "INSERT INTO node_public_key_rotations (epoch, key_rotation_counter, node_small_id, public_key_bytes, tee_quote_bytes, tee_provider) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (node_small_id) DO UPDATE SET epoch = $1, key_rotation_counter = $2, public_key_bytes = $4, - tdx_quote_bytes = $5", + tee_quote_bytes = $5, + tee_provider = $6", ) .bind(epoch as i64) .bind(key_rotation_counter as i64) .bind(node_small_id as i64) .bind(public_key_bytes) .bind(tee_remote_attestation_bytes) + .bind(tee_provider) .execute(&self.db) .await?; Ok(()) @@ -4442,6 +4445,7 @@ mod tests { let node_small_id = 789u64; let public_key_bytes = vec![1, 2, 3, 4]; let tee_remote_attestation_bytes = vec![5, 6, 7, 8]; + let tee_provider = vec![0]; // Insert initial rotation state_manager @@ -4451,6 +4455,7 @@ mod tests { node_small_id, public_key_bytes.clone(), tee_remote_attestation_bytes.clone(), + tee_provider.clone(), ) .await?; @@ -4468,15 +4473,16 @@ mod tests { assert_eq!(row.get::("node_small_id"), node_small_id as i64); assert_eq!(row.get::, _>("public_key_bytes"), public_key_bytes); assert_eq!( - row.get::, _>("tdx_quote_bytes"), + row.get::, _>("tee_quote_bytes"), tee_remote_attestation_bytes ); - + assert_eq!(row.get::, _>("tee_provider"), tee_provider); // Test update with new values let new_epoch = 999u64; let new_key_rotation_counter = 888u64; let new_public_key_bytes = vec![9, 10, 11, 12]; let new_tee_remote_attestation_bytes = vec![13, 14, 15, 16]; + let new_tee_provider = vec![2]; state_manager .insert_node_public_key_rotation( @@ -4485,6 +4491,7 @@ mod tests { node_small_id, new_public_key_bytes.clone(), new_tee_remote_attestation_bytes.clone(), + new_tee_provider.clone(), ) .await?; @@ -4509,9 +4516,13 @@ mod tests { new_public_key_bytes ); assert_eq!( - updated_row.get::, _>("tdx_quote_bytes"), + updated_row.get::, _>("tee_quote_bytes"), new_tee_remote_attestation_bytes ); + assert_eq!( + updated_row.get::, _>("tee_provider"), + new_tee_provider + ); // Verify only one row exists let count = sqlx::query( @@ -4535,13 +4546,13 @@ mod tests { // Test data for multiple nodes let nodes = [ - (1u64, vec![1, 2, 3], vec![4, 5, 6]), - (2u64, vec![7, 8, 9], vec![10, 11, 12]), - (3u64, vec![13, 14, 15], vec![16, 17, 18]), + (1u64, vec![1, 2, 3], vec![4, 5, 6], vec![0]), + (2u64, vec![7, 8, 9], vec![10, 11, 12], vec![1]), + (3u64, vec![13, 14, 15], vec![16, 17, 18], vec![2]), ]; // Insert rotations for multiple nodes - for (node_id, pub_key, tee_bytes) in nodes.iter() { + for (node_id, pub_key, tee_bytes, tee_provider) in nodes.iter() { state_manager .insert_node_public_key_rotation( 100u64, @@ -4549,12 +4560,13 @@ mod tests { *node_id, pub_key.clone(), tee_bytes.clone(), + tee_provider.clone(), ) .await?; } // Verify all insertions - for (node_id, pub_key, tee_bytes) in nodes.iter() { + for (node_id, pub_key, tee_bytes, tee_provider) in nodes.iter() { let row = sqlx::query("SELECT * FROM node_public_key_rotations WHERE node_small_id = $1") .bind(*node_id as i64) @@ -4563,7 +4575,8 @@ mod tests { assert_eq!(row.get::("node_small_id"), *node_id as i64); assert_eq!(row.get::, _>("public_key_bytes"), *pub_key); - assert_eq!(row.get::, _>("tdx_quote_bytes"), *tee_bytes); + assert_eq!(row.get::, _>("tee_quote_bytes"), *tee_bytes); + assert_eq!(row.get::, _>("tee_provider"), *tee_provider); } // Verify total count @@ -4593,6 +4606,7 @@ mod tests { node_small_id, empty_bytes.clone(), empty_bytes.clone(), + empty_bytes.clone(), ) .await?; @@ -4602,8 +4616,9 @@ mod tests { .await?; assert_eq!(row.get::, _>("public_key_bytes"), empty_bytes); - assert_eq!(row.get::, _>("tdx_quote_bytes"), empty_bytes); - + assert_eq!(row.get::, _>("tee_quote_bytes"), empty_bytes); + assert_eq!(row.get::, _>("tee_provider"), empty_bytes); + truncate_tables(&state_manager.db).await; Ok(()) } diff --git a/atoma-sui/src/events.rs b/atoma-sui/src/events.rs index 091e3d63..1fe3c075 100644 --- a/atoma-sui/src/events.rs +++ b/atoma-sui/src/events.rs @@ -839,6 +839,11 @@ pub struct NodePublicKeyCommittmentEvent { /// The TEE remote attestation report attesting for /// the public key's generation integrity, in byte format. pub tee_remote_attestation_bytes: Vec, + + /// The TEE Provider used to generate the public key. + /// This type is used to identify which attestation + /// verification method should be used. + pub tee_provider: Vec } /// Represents an event emitted when Atoma's smart contract requests new node key rotation. From 3f781e829cc1181d102f0b992f611ea18b727ff1 Mon Sep 17 00:00:00 2001 From: bredamatt <28816406+bredamatt@users.noreply.github.com> Date: Thu, 6 Feb 2025 09:44:09 +0000 Subject: [PATCH 09/21] fix: Add Dockerfile modifications for sev-snp support --- Dockerfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index fb11da03..4d5ab81b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,6 +6,7 @@ ARG TARGETPLATFORM ARG BUILDPLATFORM ARG TARGETARCH ARG ENABLE_TDX +ARG ENABLE_SEV_SNP # Install build dependencies RUN apt-get update && apt-get install -y \ @@ -14,7 +15,7 @@ RUN apt-get update && apt-get install -y \ curl \ libssl-dev \ libssl1.1 \ - && if [ "$ENABLE_TDX" = "true" ]; then \ + && if [ "$ENABLE_TDX" = "true" ] || [ "$ENABLE_SEV_SNP" = "true" ]; then \ apt-get install -y libtss2-dev; \ fi \ && rm -rf /var/lib/apt/lists/* @@ -26,6 +27,8 @@ COPY . . # Compile RUN if [ "$ENABLE_TDX" = "true" ]; then \ RUST_LOG=${TRACE_LEVEL} cargo build --release --bin atoma-node --features tdx; \ + elif [ "$ENABLE_SEV_SNP" = "true" ]; then \ + RUST_LOG=${TRACE_LEVEL} cargo build --release --bin atoma-node --features sev-snp; \ else \ RUST_LOG=${TRACE_LEVEL} cargo build --release --bin atoma-node; \ fi From 870fef2b70926782b34058728a282217290ca600 Mon Sep 17 00:00:00 2001 From: bredamatt <28816406+bredamatt@users.noreply.github.com> Date: Thu, 6 Feb 2025 13:07:14 +0000 Subject: [PATCH 10/21] fix: feature-gate behind linux os, add p384 dependency as optional in cc service --- Cargo.lock | 2 ++ Cargo.toml | 1 + atoma-confidential/Cargo.toml | 9 +++++---- atoma-confidential/src/sev_snp.rs | 6 +++++- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f0af989a..f304849b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -745,8 +745,10 @@ dependencies = [ "blake2", "dcap-rs", "flume", + "p384", "rand", "sev 5.0.0", + "sha2 0.10.8", "strum 0.26.3", "tdx", "thiserror 2.0.11", diff --git a/Cargo.toml b/Cargo.toml index fc8787c1..0c31e8e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ lazy_static = "1.5.0" metrics = "0.23" metrics-exporter-prometheus = "0.14.0" once_cell = "1.20.2" +p384 = "0.13.0" prometheus = "0.13.4" rand = "0.8.5" reqwest = "0.12.1" diff --git a/atoma-confidential/Cargo.toml b/atoma-confidential/Cargo.toml index 5a538bbe..b1cd2b45 100644 --- a/atoma-confidential/Cargo.toml +++ b/atoma-confidential/Cargo.toml @@ -12,17 +12,18 @@ atoma-utils = { workspace = true } blake2 = { workspace = true } dcap-rs = { workspace = true, optional = true } flume = { workspace = true } +p384 = { workspace = true, optional = true } +rand = { workspace = true } sev = { workspace = true, optional = true, features = ["crypto_nossl"]} +sha2 = { workspace = true } strum = { workspace = true, features = ["derive"] } -rand = { workspace = true } -tokio = { workspace = true } tdx = { workspace = true, optional = true } thiserror = { workspace = true } +tokio = { workspace = true } tracing = { workspace = true } x25519-dalek = { workspace = true, features = ["static_secrets"] } - [features] default = [] tdx = ["dep:dcap-rs", "dep:tdx" ] -sev-snp = ["dep:dcap-rs", "dep:sev" ] +sev-snp = [ "dep:p384", "dep:sev" ] diff --git a/atoma-confidential/src/sev_snp.rs b/atoma-confidential/src/sev_snp.rs index ce0baec5..41fa7720 100644 --- a/atoma-confidential/src/sev_snp.rs +++ b/atoma-confidential/src/sev_snp.rs @@ -42,6 +42,7 @@ type Result = std::result::Result; /// Err(e) => eprintln!("Attestation failed: {}", e), /// } /// ``` +#[cfg(all(target_os="linux", feature="sev-snp"))] pub fn get_compute_data_attestation(attested_data: &[u8]) -> Result { if attested_data.len() != SEV_SNP_REPORT_DATA_SIZE { return Err(SevError::InvalidAttestedDataSize(attested_data.len())); @@ -92,6 +93,7 @@ impl ToSNPAttestationReport for (Chain, AttestationReport) { } /// Implements the Verifiable trait (crypto_nossl feature-gated implementation) for SNPAttestationReport. +#[cfg(all(target_os="linux", feature="sev-snp"))] impl sev::snp::certs::Verifiable for SNPAttestationReport { type Output = bool; @@ -127,7 +129,7 @@ impl sev::snp::certs::Verifiable for SNPAttestationReport { #[derive(Error, Debug)] pub enum SevError { - #[error("Invalid attested data size: expected {}, got {0}", SEV_SNP_REPORT_DATA_SIZE)] + #[error("Invalid attested data size: expected {expected}, got {actual}", expected = SEV_SNP_REPORT_DATA_SIZE, actual = _0)] InvalidAttestedDataSize(usize), #[error("Failed to verify attestation report: {0}")] FailedVerification(#[source] std::io::Error), @@ -135,4 +137,6 @@ pub enum SevError { FailedToOpenFirmware(#[source] std::io::Error), #[error("Failed to get attestation report: {0}")] FailedToGetAttestationReport(#[source] std::io::Error), + #[error("SEV-SNP attestation is only supported on Linux platforms")] + UnsupportedPlatform, } From 2a0a2ac70365b4a3be607f4c3c98594ee008c26a Mon Sep 17 00:00:00 2001 From: bredamatt <28816406+bredamatt@users.noreply.github.com> Date: Thu, 6 Feb 2025 18:19:48 +0000 Subject: [PATCH 11/21] fix: change from Vec to u16. Must be u16 due to supported types by the db --- atoma-state/src/state_manager.rs | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/atoma-state/src/state_manager.rs b/atoma-state/src/state_manager.rs index 06332c91..69905044 100644 --- a/atoma-state/src/state_manager.rs +++ b/atoma-state/src/state_manager.rs @@ -803,7 +803,7 @@ impl AtomaState { node_small_id: u64, public_key_bytes: Vec, tee_remote_attestation_bytes: Vec, - tee_provider: Vec, + tee_provider: u16, ) -> Result<()> { sqlx::query( "INSERT INTO node_public_key_rotations (epoch, key_rotation_counter, node_small_id, public_key_bytes, tee_quote_bytes, tee_provider) VALUES ($1, $2, $3, $4, $5, $6) @@ -820,7 +820,7 @@ impl AtomaState { .bind(node_small_id as i64) .bind(public_key_bytes) .bind(tee_remote_attestation_bytes) - .bind(tee_provider) + .bind(tee_provider as i16) .execute(&self.db) .await?; Ok(()) @@ -4478,7 +4478,7 @@ mod tests { let node_small_id = 789u64; let public_key_bytes = vec![1, 2, 3, 4]; let tee_remote_attestation_bytes = vec![5, 6, 7, 8]; - let tee_provider = vec![0]; + let tee_provider = 0u16; // Insert initial rotation state_manager @@ -4488,7 +4488,7 @@ mod tests { node_small_id, public_key_bytes.clone(), tee_remote_attestation_bytes.clone(), - tee_provider.clone(), + tee_provider, ) .await?; @@ -4509,13 +4509,13 @@ mod tests { row.get::, _>("tee_quote_bytes"), tee_remote_attestation_bytes ); - assert_eq!(row.get::, _>("tee_provider"), tee_provider); + assert_eq!(row.get::("tee_provider"), tee_provider as i16); // Test update with new values let new_epoch = 999u64; let new_key_rotation_counter = 888u64; let new_public_key_bytes = vec![9, 10, 11, 12]; let new_tee_remote_attestation_bytes = vec![13, 14, 15, 16]; - let new_tee_provider = vec![2]; + let new_tee_provider = 2u16; state_manager .insert_node_public_key_rotation( @@ -4552,10 +4552,7 @@ mod tests { updated_row.get::, _>("tee_quote_bytes"), new_tee_remote_attestation_bytes ); - assert_eq!( - updated_row.get::, _>("tee_provider"), - new_tee_provider - ); + assert_eq!(updated_row.get::("tee_provider"), new_tee_provider as i16); // Verify only one row exists let count = sqlx::query( @@ -4579,9 +4576,9 @@ mod tests { // Test data for multiple nodes let nodes = [ - (1u64, vec![1, 2, 3], vec![4, 5, 6], vec![0]), - (2u64, vec![7, 8, 9], vec![10, 11, 12], vec![1]), - (3u64, vec![13, 14, 15], vec![16, 17, 18], vec![2]), + (1u64, vec![1, 2, 3], vec![4, 5, 6], 0u16), + (2u64, vec![7, 8, 9], vec![10, 11, 12], 1u16), + (3u64, vec![13, 14, 15], vec![16, 17, 18], 2u16), ]; // Insert rotations for multiple nodes @@ -4609,7 +4606,7 @@ mod tests { assert_eq!(row.get::("node_small_id"), *node_id as i64); assert_eq!(row.get::, _>("public_key_bytes"), *pub_key); assert_eq!(row.get::, _>("tee_quote_bytes"), *tee_bytes); - assert_eq!(row.get::, _>("tee_provider"), *tee_provider); + assert_eq!(row.get::("tee_provider"), *tee_provider as i16); } // Verify total count @@ -4639,7 +4636,7 @@ mod tests { node_small_id, empty_bytes.clone(), empty_bytes.clone(), - empty_bytes.clone(), + 0u16, ) .await?; @@ -4650,7 +4647,7 @@ mod tests { assert_eq!(row.get::, _>("public_key_bytes"), empty_bytes); assert_eq!(row.get::, _>("tee_quote_bytes"), empty_bytes); - assert_eq!(row.get::, _>("tee_provider"), empty_bytes); + assert_eq!(row.get::("tee_provider"), 0 as i16); truncate_tables(&state_manager.db).await; Ok(()) From 8e6477feeb184b33efa303a0b9b851580e5f96ce Mon Sep 17 00:00:00 2001 From: bredamatt <28816406+bredamatt@users.noreply.github.com> Date: Thu, 6 Feb 2025 18:20:15 +0000 Subject: [PATCH 12/21] fix: change tee_provider attribute to u16 in stead of Vec --- atoma-sui/src/events.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atoma-sui/src/events.rs b/atoma-sui/src/events.rs index 259d1dd8..544501f7 100644 --- a/atoma-sui/src/events.rs +++ b/atoma-sui/src/events.rs @@ -843,7 +843,7 @@ pub struct NodePublicKeyCommittmentEvent { /// The TEE Provider used to generate the public key. /// This type is used to identify which attestation /// verification method should be used. - pub tee_provider: Vec + pub tee_provider: u16 } /// Represents an event emitted when Atoma's smart contract requests new node key rotation. From cd5ff4abe4bda759dff9f3c1700303daac3fada9 Mon Sep 17 00:00:00 2001 From: bredamatt <28816406+bredamatt@users.noreply.github.com> Date: Thu, 6 Feb 2025 18:40:10 +0000 Subject: [PATCH 13/21] fix: trait implementations, rename error, remove dup Error --- Cargo.lock | 1 + Cargo.toml | 1 + atoma-confidential/Cargo.toml | 1 + atoma-confidential/src/sev_snp.rs | 57 ++++++++++++++++++++++--------- 4 files changed, 44 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f304849b..91a8e5c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -742,6 +742,7 @@ dependencies = [ "anyhow", "atoma-sui", "atoma-utils", + "bincode", "blake2", "dcap-rs", "flume", diff --git a/Cargo.toml b/Cargo.toml index 0c31e8e3..a0d9518e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ atoma-sui = { path = "./atoma-sui" } atoma-utils = { path = "./atoma-utils" } axum = "0.7.5" base64 = "0.22.1" +bincode = "1.3.3" blake2 = "0.10.6" clap = "4.5.4" config = "0.14.0" diff --git a/atoma-confidential/Cargo.toml b/atoma-confidential/Cargo.toml index b1cd2b45..2e6cfb9e 100644 --- a/atoma-confidential/Cargo.toml +++ b/atoma-confidential/Cargo.toml @@ -9,6 +9,7 @@ aes-gcm = { workspace = true } anyhow = { workspace = true } atoma-sui = { workspace = true } atoma-utils = { workspace = true } +bincode = { workspace = true } blake2 = { workspace = true } dcap-rs = { workspace = true, optional = true } flume = { workspace = true } diff --git a/atoma-confidential/src/sev_snp.rs b/atoma-confidential/src/sev_snp.rs index 41fa7720..c8fb67a2 100644 --- a/atoma-confidential/src/sev_snp.rs +++ b/atoma-confidential/src/sev_snp.rs @@ -1,8 +1,9 @@ +use bincode::{serialize, deserialize}; + use crate::ToBytes; use sev::{ attestation::{AttestationReport, AttestationReportSignature, Chain}, - error::SevError, firmware::Firmware, }; @@ -10,7 +11,7 @@ use thiserror::Error; pub const SEV_SNP_REPORT_DATA_SIZE: usize = 64; -type Result = std::result::Result; +type Result = std::result::Result; /// Generates an SEV-SNP attestation report for the given compute data. /// @@ -20,18 +21,18 @@ type Result = std::result::Result; /// /// # Arguments /// -/// * `attested_data` - A slice of bytes containing the data to be attested (public key) +/// * `attested_data` - A slice of bytes containing the data to be attested for (public key) /// /// # Returns /// /// * `Result` - On success, returns an AttestationReport. -/// On failure, returns a SevError. +/// On failure, returns a SnpError. /// /// # Errors /// -/// Returns `SevError::InvalidAttestedDataSize` if the input data size is not 64 bytes. -/// Returns `SevError::FailedToOpenFirmware` if the firmware interface cannot be opened. -/// Returns `SevError::FailedToGetAttestationReport` if the attestation report cannot be generated. +/// Returns `SnpError::InvalidAttestedDataSize` if the input data size is not 64 bytes. +/// Returns `SnpError::FailedToOpenFirmware` if the firmware interface cannot be opened. +/// Returns `SnpError::FailedToGetAttestationReport` if the attestation report cannot be generated. /// /// # Example /// @@ -45,10 +46,11 @@ type Result = std::result::Result; #[cfg(all(target_os="linux", feature="sev-snp"))] pub fn get_compute_data_attestation(attested_data: &[u8]) -> Result { if attested_data.len() != SEV_SNP_REPORT_DATA_SIZE { - return Err(SevError::InvalidAttestedDataSize(attested_data.len())); + return Err(SnpError::InvalidAttestedDataSize(attested_data.len())); } - let mut firmware = Firmware::open().map_err(SevError::FailedToOpenFirmware)?; + // Open the SEV-SNP firmware interface + let mut firmware = Firmware::open().map_err(SnpError::FailedToOpenFirmware)?; // Message version is set as a protocol version indicator, default is 1. let message_version = 1; @@ -59,10 +61,10 @@ pub fn get_compute_data_attestation(attested_data: &[u8]) -> Result Vec { + bincode::serialize(&self).unwrap() + } +} + +/// Trait for converting Vec to an SNPAttestationReport. +pub trait FromBytes { + fn from_bytes(bytes: Vec) -> Result; +} + +impl FromBytes for SNPAttestationReport { + fn from_bytes(bytes: Vec) -> Result { + bincode::deserialize(&bytes).map_err(|e| { + SnpError::FailedToDeserializeBytes(format!("Unable to deserialize bytes for SNPAttestationReport: {}", e)) + }) + } +} + /// Trait for converting a tuple of (Chain, AttestationReport) to an SNPAttestationReport. pub trait ToSNPAttestationReport { fn to_snp_attestation_report(&self) -> SNPAttestationReport; @@ -99,7 +120,7 @@ impl sev::snp::certs::Verifiable for SNPAttestationReport { /// Verifies the attestation report against the VCEK certificate. /// Taken from: https://docs.rs/sev/5.0.0/src/sev/firmware/guest/types/snp.rs.html#351-391 - fn verify(&self) -> Result<(), SevError> { + fn verify(&self) -> Result<(), SnpError> { // According to Chapter 3 of the [Versioned Chip Endorsement Key (VCEK) Certificate and // KDS Interface Specification][spec], the VCEK certificate certifies an ECDSA public key on curve P-384, // and the signature hash algorithm is sha384. @@ -109,7 +130,7 @@ impl sev::snp::certs::Verifiable for SNPAttestationReport { let sig = p384::ecdsa::Signature::try_from(&self.1.signature)?; let measurable_bytes: &[u8] = &bincode::serialize(self.1).map_err(|e| { - SevError::FailedVerification(format!("Unable to serialize bytes: {}", e)) + SnpError::FailedVerification(format!("Unable to serialize bytes for AttestationReport: {}", e)) })?[..0x2a0]; use sha2::Digest; @@ -117,18 +138,18 @@ impl sev::snp::certs::Verifiable for SNPAttestationReport { let verifying_key = p384::ecdsa::VerifyingKey::from_sec1_bytes(vcek.public_key_sec1()) .map_err(|e| { - SevError::FailedVerification(format!("failed to deserialize public key from sec1 bytes: {e:?}")) + SnpError::FailedVerification(format!("Failed to deserialize public key from sec1 bytes: {e:?}")) })?; use p384::ecdsa::signature::DigestVerifier; verifying_key.verify_digest(base_digest, &sig).map_err(|e| { - SevError::FailedVerification(format!("VCEK does not sign the attestation report: {e:?}")) + SnpError::FailedVerification(format!("VCEK does not sign the attestation report: {e:?}")) }) } } #[derive(Error, Debug)] -pub enum SevError { +pub enum SnpError { #[error("Invalid attested data size: expected {expected}, got {actual}", expected = SEV_SNP_REPORT_DATA_SIZE, actual = _0)] InvalidAttestedDataSize(usize), #[error("Failed to verify attestation report: {0}")] @@ -137,6 +158,10 @@ pub enum SevError { FailedToOpenFirmware(#[source] std::io::Error), #[error("Failed to get attestation report: {0}")] FailedToGetAttestationReport(#[source] std::io::Error), + #[error("Failed to serialize bytes for SNPAttestationReport: {0}")] + FailedToSerializeBytes(#[source] std::io::Error), + #[error("Failed to deserialize bytes for SNPAttestationReport: {0}")] + FailedToDeserializeBytes(#[source] std::io::Error), #[error("SEV-SNP attestation is only supported on Linux platforms")] UnsupportedPlatform, } From 214c6b7f3c5954a4c0f4617fa6c00d8a64465219 Mon Sep 17 00:00:00 2001 From: bredamatt <28816406+bredamatt@users.noreply.github.com> Date: Thu, 6 Feb 2025 18:43:11 +0000 Subject: [PATCH 14/21] fix: revert migration change and add new migration in stead --- .../20241202121152_node_public_key_rotation.sql | 3 +-- .../20250206184237_node_public_key_rotation.sql | 8 ++++++++ 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 atoma-state/src/migrations/migrations/20250206184237_node_public_key_rotation.sql diff --git a/atoma-state/src/migrations/20241202121152_node_public_key_rotation.sql b/atoma-state/src/migrations/20241202121152_node_public_key_rotation.sql index ee51ff3a..9442dc70 100644 --- a/atoma-state/src/migrations/20241202121152_node_public_key_rotation.sql +++ b/atoma-state/src/migrations/20241202121152_node_public_key_rotation.sql @@ -2,7 +2,6 @@ CREATE TABLE IF NOT EXISTS node_public_key_rotations ( node_small_id BIGINT PRIMARY KEY, public_key_bytes BYTEA UNIQUE NOT NULL, - tee_quote_bytes BYTEA NOT NULL, - tee_provider BYTEA NOT NULL, + tdx_quote_bytes BYTEA NOT NULL, epoch BIGINT NOT NULL ); diff --git a/atoma-state/src/migrations/migrations/20250206184237_node_public_key_rotation.sql b/atoma-state/src/migrations/migrations/20250206184237_node_public_key_rotation.sql new file mode 100644 index 00000000..702595f5 --- /dev/null +++ b/atoma-state/src/migrations/migrations/20250206184237_node_public_key_rotation.sql @@ -0,0 +1,8 @@ +-- Create tasks table +CREATE TABLE IF NOT EXISTS node_public_key_rotations ( + node_small_id BIGINT PRIMARY KEY, + public_key_bytes BYTEA UNIQUE NOT NULL, + tee_quote_bytes BYTEA NOT NULL, + tee_provider BYTEA NOT NULL, + epoch BIGINT NOT NULL +); \ No newline at end of file From 4902d570fcbf65922b892573cbcfece4d190295a Mon Sep 17 00:00:00 2001 From: bredamatt <28816406+bredamatt@users.noreply.github.com> Date: Thu, 6 Feb 2025 18:43:47 +0000 Subject: [PATCH 15/21] nit: move file --- .../{migrations => }/20250206184237_node_public_key_rotation.sql | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename atoma-state/src/migrations/{migrations => }/20250206184237_node_public_key_rotation.sql (100%) diff --git a/atoma-state/src/migrations/migrations/20250206184237_node_public_key_rotation.sql b/atoma-state/src/migrations/20250206184237_node_public_key_rotation.sql similarity index 100% rename from atoma-state/src/migrations/migrations/20250206184237_node_public_key_rotation.sql rename to atoma-state/src/migrations/20250206184237_node_public_key_rotation.sql From 8fb44c1c8e8a4b417786f01edf9cc29d01083e7b Mon Sep 17 00:00:00 2001 From: bredamatt <28816406+bredamatt@users.noreply.github.com> Date: Thu, 6 Feb 2025 19:24:14 +0000 Subject: [PATCH 16/21] fix: u16 issues in state_manager --- Cargo.lock | 8 ++++---- atoma-state/src/state_manager.rs | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 635f7b0f..e67efa16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -747,7 +747,7 @@ dependencies = [ "dcap-rs", "flume", "p384", - "rand", + "rand 0.8.5", "sev 5.0.0", "sha2 0.10.8", "strum 0.26.3", @@ -2194,7 +2194,7 @@ checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", ] [[package]] @@ -7806,7 +7806,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.96", + "syn 2.0.98", ] [[package]] @@ -8630,7 +8630,7 @@ checksum = "8d9ef545650e79f30233c0003bcc2504d7efac6dad25fca40744de773fe2049c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.98", ] [[package]] diff --git a/atoma-state/src/state_manager.rs b/atoma-state/src/state_manager.rs index 69905044..c60ef99b 100644 --- a/atoma-state/src/state_manager.rs +++ b/atoma-state/src/state_manager.rs @@ -4524,7 +4524,7 @@ mod tests { node_small_id, new_public_key_bytes.clone(), new_tee_remote_attestation_bytes.clone(), - new_tee_provider.clone(), + new_tee_provider, ) .await?; @@ -4590,7 +4590,7 @@ mod tests { *node_id, pub_key.clone(), tee_bytes.clone(), - tee_provider.clone(), + *tee_provider, ) .await?; } @@ -4647,7 +4647,7 @@ mod tests { assert_eq!(row.get::, _>("public_key_bytes"), empty_bytes); assert_eq!(row.get::, _>("tee_quote_bytes"), empty_bytes); - assert_eq!(row.get::("tee_provider"), 0 as i16); + assert_eq!(row.get::("tee_provider"), 0_i16); truncate_tables(&state_manager.db).await; Ok(()) From 8d0f3dada9d7ea851180df83e34c9a9a89c764ae Mon Sep 17 00:00:00 2001 From: bredamatt <28816406+bredamatt@users.noreply.github.com> Date: Thu, 6 Feb 2025 19:29:46 +0000 Subject: [PATCH 17/21] fix: from and to bytes functions for TEEProvider --- atoma-confidential/src/types.rs | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/atoma-confidential/src/types.rs b/atoma-confidential/src/types.rs index 572d8869..fe5a5f04 100644 --- a/atoma-confidential/src/types.rs +++ b/atoma-confidential/src/types.rs @@ -97,22 +97,12 @@ pub enum TEEProvider { Arm = 2, } -impl crate::ToBytes for TEEProvider { - /// Converts the TEE provider enum to a single byte representation. - /// - /// # Returns - /// A vector containing a single byte representing the TEE provider variant. - fn to_bytes(&self) -> Vec { - vec![*self as u8] - } -} impl TEEProvider { /// Creates a TEEProvider from its byte representation. /// /// # Arguments - /// * `bytes` - A byte slice containing the TEE provider identifier. - /// Expected to be a single byte with value 0, 1, or 2. + /// * `byte` - A byte containing the TEE provider identifier (0, 1, or 2) /// /// # Returns /// * `Ok(TEEProvider)` - The corresponding TEE provider variant @@ -120,12 +110,21 @@ impl TEEProvider { /// /// # Errors /// Returns an error if the input byte does not correspond to a known TEE provider variant. - fn _from_bytes(bytes: &[u8]) -> Result { - Ok(match bytes[0] { - 0 => TEEProvider::Tdx, - 1 => TEEProvider::Snp, - 2 => TEEProvider::Arm, + pub fn from_byte(byte: u8) -> Result { + Ok(match byte { + 0 => Self::Tdx, + 1 => Self::Snp, + 2 => Self::Arm, _ => anyhow::bail!("Invalid TEE provider"), }) } + + /// Converts the TEE provider enum to a byte representation. + /// + /// # Returns + /// A byte representing the TEE provider variant. + #[must_use] + pub const fn to_byte(self) -> u8 { + self as u8 + } } From 879c9235e7eaba22d7357adab32105241a62c1f0 Mon Sep 17 00:00:00 2001 From: bredamatt <28816406+bredamatt@users.noreply.github.com> Date: Thu, 6 Feb 2025 20:10:37 +0000 Subject: [PATCH 18/21] fix: cargo fmt, relabelling of function, add SevSnpDeviceError to AtomaConfidentialComputeError --- atoma-confidential/src/service.rs | 11 ++++++---- atoma-confidential/src/sev_snp.rs | 34 ++++++++++++++++++++----------- atoma-confidential/src/types.rs | 1 - atoma-state/src/state_manager.rs | 7 +++++-- atoma-sui/src/events.rs | 2 +- 5 files changed, 35 insertions(+), 20 deletions(-) diff --git a/atoma-confidential/src/service.rs b/atoma-confidential/src/service.rs index fbbd5255..c6098cca 100644 --- a/atoma-confidential/src/service.rs +++ b/atoma-confidential/src/service.rs @@ -8,12 +8,12 @@ use crate::{ }; #[cfg(feature = "sev-snp")] use crate::{ - sev_snp::{get_compute_data_attestation, SevError}, + sev_snp::{get_compute_data_attestation as snp_attestation, SnpError}, ToBytes, }; #[cfg(feature = "tdx")] use crate::{ - tdx::{get_compute_data_attestation, TdxError}, + tdx::{get_compute_data_attestation as tdx_attestation, TdxError}, ToBytes, }; use atoma_sui::client::Client; @@ -328,7 +328,7 @@ impl AtomaConfidentialCompute { async fn submit_tdx_attestation(&mut self) -> Result<()> { let public_key = self.key_manager.get_public_key(); let public_key_bytes = public_key.to_bytes(); - let tdx_quote = get_compute_data_attestation(&public_key_bytes)?; + let tdx_quote = tdx_attestation(&public_key_bytes)?; let tdx_quote_bytes = tdx_quote.to_bytes(); match self @@ -369,7 +369,7 @@ impl AtomaConfidentialCompute { async fn submit_sev_snp_attestation(&mut self) -> Result<()> { let public_key = self.key_manager.get_public_key(); let public_key_bytes = public_key.to_bytes(); - let sev_snp_quote = get_compute_data_attestation(&public_key_bytes)?; + let sev_snp_quote = snp_attestation(&public_key_bytes)?; let sev_snp_quote_bytes = sev_snp_quote.to_bytes(); match self @@ -651,4 +651,7 @@ pub enum AtomaConfidentialComputeError { #[cfg(feature = "tdx")] #[error("TDX device error: {0}")] TdxDeviceError(#[from] TdxError), + #[cfg(feature = "sev-snp")] + #[error("SEV-SNP device error: {0}")] + SevSnpDeviceError(#[from] SnpError), } diff --git a/atoma-confidential/src/sev_snp.rs b/atoma-confidential/src/sev_snp.rs index c8fb67a2..be0fc0e9 100644 --- a/atoma-confidential/src/sev_snp.rs +++ b/atoma-confidential/src/sev_snp.rs @@ -1,10 +1,10 @@ -use bincode::{serialize, deserialize}; +use bincode::{deserialize, serialize}; use crate::ToBytes; use sev::{ attestation::{AttestationReport, AttestationReportSignature, Chain}, - firmware::Firmware, + firmware::guest::Firmware, }; use thiserror::Error; @@ -43,7 +43,7 @@ type Result = std::result::Result; /// Err(e) => eprintln!("Attestation failed: {}", e), /// } /// ``` -#[cfg(all(target_os="linux", feature="sev-snp"))] +#[cfg(all(target_os = "linux", feature = "sev-snp"))] pub fn get_compute_data_attestation(attested_data: &[u8]) -> Result { if attested_data.len() != SEV_SNP_REPORT_DATA_SIZE { return Err(SnpError::InvalidAttestedDataSize(attested_data.len())); @@ -56,7 +56,7 @@ pub fn get_compute_data_attestation(attested_data: &[u8]) -> Result to an SNPAttestationReport. pub trait FromBytes { - fn from_bytes(bytes: Vec) -> Result; + fn from_bytes(bytes: Vec) -> Result; } impl FromBytes for SNPAttestationReport { - fn from_bytes(bytes: Vec) -> Result { + fn from_bytes(bytes: Vec) -> Result { bincode::deserialize(&bytes).map_err(|e| { - SnpError::FailedToDeserializeBytes(format!("Unable to deserialize bytes for SNPAttestationReport: {}", e)) + SnpError::FailedToDeserializeBytes(format!( + "Unable to deserialize bytes for SNPAttestationReport: {}", + e + )) }) } } @@ -114,7 +117,7 @@ impl ToSNPAttestationReport for (Chain, AttestationReport) { } /// Implements the Verifiable trait (crypto_nossl feature-gated implementation) for SNPAttestationReport. -#[cfg(all(target_os="linux", feature="sev-snp"))] +#[cfg(all(target_os = "linux", feature = "sev-snp"))] impl sev::snp::certs::Verifiable for SNPAttestationReport { type Output = bool; @@ -127,10 +130,13 @@ impl sev::snp::certs::Verifiable for SNPAttestationReport { // [spec]: https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/specifications/57230.pdf let vcek = self.0.verify()?; - let sig = p384::ecdsa::Signature::try_from(&self.1.signature)?; + let sig = p384::ecdsa::Signature::try_from(&self.1.signature)?; let measurable_bytes: &[u8] = &bincode::serialize(self.1).map_err(|e| { - SnpError::FailedVerification(format!("Unable to serialize bytes for AttestationReport: {}", e)) + SnpError::FailedVerification(format!( + "Unable to serialize bytes for AttestationReport: {}", + e + )) })?[..0x2a0]; use sha2::Digest; @@ -138,12 +144,16 @@ impl sev::snp::certs::Verifiable for SNPAttestationReport { let verifying_key = p384::ecdsa::VerifyingKey::from_sec1_bytes(vcek.public_key_sec1()) .map_err(|e| { - SnpError::FailedVerification(format!("Failed to deserialize public key from sec1 bytes: {e:?}")) + SnpError::FailedVerification(format!( + "Failed to deserialize public key from sec1 bytes: {e:?}" + )) })?; use p384::ecdsa::signature::DigestVerifier; verifying_key.verify_digest(base_digest, &sig).map_err(|e| { - SnpError::FailedVerification(format!("VCEK does not sign the attestation report: {e:?}")) + SnpError::FailedVerification(format!( + "VCEK does not sign the attestation report: {e:?}" + )) }) } } diff --git a/atoma-confidential/src/types.rs b/atoma-confidential/src/types.rs index fe5a5f04..ba7a3b12 100644 --- a/atoma-confidential/src/types.rs +++ b/atoma-confidential/src/types.rs @@ -97,7 +97,6 @@ pub enum TEEProvider { Arm = 2, } - impl TEEProvider { /// Creates a TEEProvider from its byte representation. /// diff --git a/atoma-state/src/state_manager.rs b/atoma-state/src/state_manager.rs index c60ef99b..0252c0f1 100644 --- a/atoma-state/src/state_manager.rs +++ b/atoma-state/src/state_manager.rs @@ -4552,7 +4552,10 @@ mod tests { updated_row.get::, _>("tee_quote_bytes"), new_tee_remote_attestation_bytes ); - assert_eq!(updated_row.get::("tee_provider"), new_tee_provider as i16); + assert_eq!( + updated_row.get::("tee_provider"), + new_tee_provider as i16 + ); // Verify only one row exists let count = sqlx::query( @@ -4648,7 +4651,7 @@ mod tests { assert_eq!(row.get::, _>("public_key_bytes"), empty_bytes); assert_eq!(row.get::, _>("tee_quote_bytes"), empty_bytes); assert_eq!(row.get::("tee_provider"), 0_i16); - + truncate_tables(&state_manager.db).await; Ok(()) } diff --git a/atoma-sui/src/events.rs b/atoma-sui/src/events.rs index 544501f7..9abad677 100644 --- a/atoma-sui/src/events.rs +++ b/atoma-sui/src/events.rs @@ -843,7 +843,7 @@ pub struct NodePublicKeyCommittmentEvent { /// The TEE Provider used to generate the public key. /// This type is used to identify which attestation /// verification method should be used. - pub tee_provider: u16 + pub tee_provider: u16, } /// Represents an event emitted when Atoma's smart contract requests new node key rotation. From f718d69ffad368679e8add3e36e3678ccdf016fe Mon Sep 17 00:00:00 2001 From: bredamatt <28816406+bredamatt@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:08:32 +0000 Subject: [PATCH 19/21] fix: migrations --- .../20250206184237_node_public_key_rotation.sql | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/atoma-state/src/migrations/20250206184237_node_public_key_rotation.sql b/atoma-state/src/migrations/20250206184237_node_public_key_rotation.sql index 702595f5..e89a9b20 100644 --- a/atoma-state/src/migrations/20250206184237_node_public_key_rotation.sql +++ b/atoma-state/src/migrations/20250206184237_node_public_key_rotation.sql @@ -1,8 +1,5 @@ --- Create tasks table -CREATE TABLE IF NOT EXISTS node_public_key_rotations ( - node_small_id BIGINT PRIMARY KEY, - public_key_bytes BYTEA UNIQUE NOT NULL, - tee_quote_bytes BYTEA NOT NULL, - tee_provider BYTEA NOT NULL, - epoch BIGINT NOT NULL +ALTER TABLE node_public_key_rotations ( + DROP COLUMN tdx_quote_bytes, + ADD COLUMN tee_quote_bytes BYTEA NOT NULL, + ADD COLUMN tee_provider BYTEA NOT NULL, ); \ No newline at end of file From 10c2a6d09c2509b387fc8851d19fa8edaf64f54d Mon Sep 17 00:00:00 2001 From: bredamatt <28816406+bredamatt@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:13:28 +0000 Subject: [PATCH 20/21] fix: conditional logic in Dockerfile --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4d5ab81b..6c150365 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,9 +25,9 @@ WORKDIR /usr/src/atoma-node COPY . . # Compile -RUN if [ "$ENABLE_TDX" = "true" ]; then \ +RUN if [ "$ENABLE_TDX" = "true" ] && [ "$ENABLE_SEV_SNP" = "false" ]; then \ RUST_LOG=${TRACE_LEVEL} cargo build --release --bin atoma-node --features tdx; \ - elif [ "$ENABLE_SEV_SNP" = "true" ]; then \ + elif [ "$ENABLE_SEV_SNP" = "true" ] && [ "$ENABLE_TDX" = "false" ]; then \ RUST_LOG=${TRACE_LEVEL} cargo build --release --bin atoma-node --features sev-snp; \ else \ RUST_LOG=${TRACE_LEVEL} cargo build --release --bin atoma-node; \ From e65c19c15187d65f6ae69907ce136178f32b7377 Mon Sep 17 00:00:00 2001 From: bredamatt <28816406+bredamatt@users.noreply.github.com> Date: Mon, 10 Feb 2025 16:26:28 +0000 Subject: [PATCH 21/21] fix: adjust imports --- atoma-confidential/src/service.rs | 10 ++-------- atoma-confidential/src/sev_snp.rs | 12 ++++-------- atoma-confidential/src/tdx.rs | 3 +-- 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/atoma-confidential/src/service.rs b/atoma-confidential/src/service.rs index c6098cca..bf17df94 100644 --- a/atoma-confidential/src/service.rs +++ b/atoma-confidential/src/service.rs @@ -7,15 +7,9 @@ use crate::{ }, }; #[cfg(feature = "sev-snp")] -use crate::{ - sev_snp::{get_compute_data_attestation as snp_attestation, SnpError}, - ToBytes, -}; +use crate::sev_snp::{get_compute_data_attestation as snp_attestation, SnpError}; #[cfg(feature = "tdx")] -use crate::{ - tdx::{get_compute_data_attestation as tdx_attestation, TdxError}, - ToBytes, -}; +use crate::tdx::{get_compute_data_attestation as tdx_attestation, TdxError}; use atoma_sui::client::Client; use atoma_sui::{client::AtomaSuiClientError, events::AtomaEvent}; use atoma_utils::constants::NONCE_SIZE; diff --git a/atoma-confidential/src/sev_snp.rs b/atoma-confidential/src/sev_snp.rs index be0fc0e9..c92d3f27 100644 --- a/atoma-confidential/src/sev_snp.rs +++ b/atoma-confidential/src/sev_snp.rs @@ -1,14 +1,10 @@ +use thiserror::Error; use bincode::{deserialize, serialize}; - -use crate::ToBytes; - use sev::{ - attestation::{AttestationReport, AttestationReportSignature, Chain}, - firmware::guest::Firmware, + certs::sev::Chain, + firmware::guest::{Firmware, AttestationReport}, }; -use thiserror::Error; - pub const SEV_SNP_REPORT_DATA_SIZE: usize = 64; type Result = std::result::Result; @@ -80,7 +76,7 @@ pub struct SNPAttestationReport { pub report: AttestationReport, } -impl ToBytes for SNPAttestationReport { +impl crate::ToBytes for SNPAttestationReport { fn to_bytes(&self) -> Vec { bincode::serialize(&self).unwrap() } diff --git a/atoma-confidential/src/tdx.rs b/atoma-confidential/src/tdx.rs index 2deaec86..7ca05e72 100644 --- a/atoma-confidential/src/tdx.rs +++ b/atoma-confidential/src/tdx.rs @@ -1,4 +1,3 @@ -use crate::ToBytes; use dcap_rs::types::quotes::{body::QuoteBody, version_4::QuoteV4}; use tdx::{ device::{Device, DeviceOptions}, @@ -51,7 +50,7 @@ pub fn get_compute_data_attestation(attested_data: &[u8]) -> Result { Ok(device.get_attestation_report()?) } -impl ToBytes for QuoteV4 { +impl crate::ToBytes for QuoteV4 { fn to_bytes(&self) -> Vec { let mut bytes = Vec::new(); bytes.extend_from_slice(&self.header.to_bytes());