diff --git a/Cargo.lock b/Cargo.lock index f8b6bd2..e356965 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1081,6 +1081,7 @@ dependencies = [ name = "dice-verifier" version = "0.3.0-pre0" dependencies = [ + "async-trait", "attest-data", "const-oid", "ed25519-dalek", @@ -5774,6 +5775,7 @@ dependencies = [ "slog-stdlog", "slog-term", "tempfile", + "tokio", "x509-cert", ] diff --git a/Cargo.toml b/Cargo.toml index 2632cb6..6fb0374 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ resolver = "2" [workspace.dependencies] anyhow = { version = "1.0.100", default-features = false } +async-trait = "0.1.89" attest.path = "attest" chrono = { version = "0.4.42", default-features=false } clap = { version = "4.5.51", features = ["derive", "env"] } diff --git a/verifier-cli/Cargo.toml b/verifier-cli/Cargo.toml index 258a151..8d52dfc 100644 --- a/verifier-cli/Cargo.toml +++ b/verifier-cli/Cargo.toml @@ -26,6 +26,7 @@ tempfile.workspace = true dice-verifier.path = "../verifier" x509-cert = { workspace = true, default-features = true } serde_json.workspace = true +tokio = { workspace = true, features = ["full"] } [features] ipcc = ["dice-verifier/ipcc"] diff --git a/verifier-cli/src/main.rs b/verifier-cli/src/main.rs index 44abdf0..0fb2154 100644 --- a/verifier-cli/src/main.rs +++ b/verifier-cli/src/main.rs @@ -33,7 +33,7 @@ fn get_attest(interface: Interface, log: &Logger) -> Result> { slog::info!(log, "attesting via {interface:?}"); match interface { #[cfg(feature = "ipcc")] - Interface::Ipcc => Ok(Box::new(AttestIpcc::new()?)), + Interface::Ipcc => Ok(Box::new(AttestIpcc::new())), Interface::Rot => Ok(Box::new(AttestHiffy::new(AttestTask::Rot))), #[cfg(feature = "sled-agent")] Interface::SledAgent(addr) => { @@ -205,7 +205,8 @@ impl fmt::Display for Encoding { } } -fn main() -> Result<()> { +#[tokio::main(flavor = "current_thread")] +async fn main() -> Result<()> { let args = Args::parse(); let stderr_decorator = slog_term::TermDecorator::new().build(); @@ -246,6 +247,7 @@ fn main() -> Result<()> { Nonce::try_from(nonce).context("Nonce from file contents")?; let attestation = attest .attest(&nonce) + .await .context("Getting attestation with provided Nonce")?; // serialize attestation to json & write to file @@ -261,6 +263,7 @@ fn main() -> Result<()> { AttestCommand::CertChain => { let cert_chain = attest .get_certificates() + .await .context("Getting attestation certificate chain")?; for cert in cert_chain { @@ -277,6 +280,7 @@ fn main() -> Result<()> { AttestCommand::Log => { let log = attest .get_measurement_log() + .await .context("Getting attestation measurement log")?; let mut log = serde_json::to_string(&log) .context("Encode measurement log as JSON")?; @@ -311,13 +315,16 @@ fn main() -> Result<()> { // Use the directory provided by the caller to hold intermediate // files, or fall back to a temp dir. let platform_id = match work_dir { - Some(w) => verify( - attest.as_ref(), - ca_cert.as_deref(), - corpus.as_deref(), - self_signed, - &w, - )?, + Some(w) => { + verify( + attest.as_ref(), + ca_cert.as_deref(), + corpus.as_deref(), + self_signed, + &w, + ) + .await? + } None => { if corpus.is_none() && !skip_appraisal { return Err(anyhow!("no corpus provided but not instructed to skip measurement log appraisal")); @@ -329,7 +336,8 @@ fn main() -> Result<()> { corpus.as_deref(), self_signed, work_dir.as_ref(), - )? + ) + .await? } }; @@ -358,7 +366,7 @@ fn main() -> Result<()> { verify_measurements(&cert_chain, &log, &corpus)?; } AttestCommand::MeasurementSet => { - let set = measurement_set(attest.as_ref())?; + let set = measurement_set(attest.as_ref()).await?; for item in set.into_iter() { println!("* {item}"); } @@ -368,15 +376,17 @@ fn main() -> Result<()> { Ok(()) } -fn measurement_set(attest: &dyn Attest) -> Result { +async fn measurement_set(attest: &dyn Attest) -> Result { info!("getting measurement log"); let log = attest .get_measurement_log() + .await .context("Get measurement log from attestor")?; let mut cert_chain = Vec::new(); let certs = attest .get_certificates() + .await .context("Get certificate chain from attestor")?; for (index, cert) in certs.iter().enumerate() { @@ -431,7 +441,7 @@ fn verify_measurements( .context("Verify measurements") } -fn verify( +async fn verify( attest: &dyn Attest, ca_cert: Option<&Path>, corpus: Option<&Path>, @@ -453,6 +463,7 @@ fn verify( info!("getting attestation"); let attestation = attest .attest(&nonce) + .await .context("Get attestation with nonce")?; // serialize attestation to json & write to file @@ -471,6 +482,7 @@ fn verify( info!("getting measurement log"); let log = attest .get_measurement_log() + .await .context("Get measurement log from attestor")?; let mut log = serde_json::to_string(&log) .context("Serialize measurement log to JSON")?; @@ -494,6 +506,7 @@ fn verify( let certs = attest .get_certificates() + .await .context("Get certificate chain from attestor")?; // the first cert in the chain / the leaf cert is the one diff --git a/verifier/Cargo.toml b/verifier/Cargo.toml index d05cfad..47ce680 100644 --- a/verifier/Cargo.toml +++ b/verifier/Cargo.toml @@ -7,6 +7,7 @@ license = "MPL-2.0" [dependencies] attest-data = { path = "../attest-data", features = ["std"] } +async-trait.workspace = true const-oid.workspace = true ed25519-dalek = { workspace = true, features = ["std"] } env_logger.workspace = true @@ -20,7 +21,7 @@ sha3.workspace = true sled-agent-client = { workspace = true, optional = true } sled-agent-types-versions = { workspace = true, optional = true } slog.workspace = true -tokio = { workspace = true, features = [ "net", "rt", "time" ], optional = true } +tokio = { workspace = true, features = [ "net", "rt", "time", "process" ] } tempfile.workspace = true thiserror.workspace = true x509-cert = { workspace = true, default-features = true } @@ -33,4 +34,4 @@ attest-data = { path = "../attest-data", features = ["std", "testing"] } testing = [] ipcc = ["libipcc"] mock = ["ed25519-dalek/pem"] -sled-agent = ["sled-agent-client", "sled-agent-types-versions", "tokio"] +sled-agent = ["sled-agent-client", "sled-agent-types-versions"] diff --git a/verifier/src/hiffy.rs b/verifier/src/hiffy.rs index b08899e..592b51e 100644 --- a/verifier/src/hiffy.rs +++ b/verifier/src/hiffy.rs @@ -9,29 +9,35 @@ use std::{ fmt, io::{Read, Write}, path::Path, - process::{Command, Output}, + process::Output, }; use tempfile::NamedTempFile; use thiserror::Error; +use tokio::process::Command; use x509_cert::{der::Decode, Certificate, PkiPath}; use crate::{Attest, AttestError}; /// This trait implements the hubris attestation API exposed by the `attest` /// task in the RoT and proxied through the `sprot` task in the SP. +#[async_trait::async_trait] pub trait AttestSprot { - fn attest_len(&self) -> Result; - fn attest( + async fn attest_len(&self) -> Result; + async fn attest( &self, nonce: &Nonce32, out: &mut [u8], ) -> Result<(), AttestHiffyError>; - fn cert_chain_len(&self) -> Result; - fn cert_len(&self, index: u32) -> Result; - fn cert(&self, index: u32, out: &mut [u8]) -> Result<(), AttestHiffyError>; - fn log(&self, out: &mut [u8]) -> Result<(), AttestHiffyError>; - fn log_len(&self) -> Result; - fn record(&self, data: &[u8]) -> Result<(), AttestHiffyError>; + async fn cert_chain_len(&self) -> Result; + async fn cert_len(&self, index: u32) -> Result; + async fn cert( + &self, + index: u32, + out: &mut [u8], + ) -> Result<(), AttestHiffyError>; + async fn log(&self, out: &mut [u8]) -> Result<(), AttestHiffyError>; + async fn log_len(&self) -> Result; + async fn record(&self, data: &[u8]) -> Result<(), AttestHiffyError>; } /// The `AttestHiffy` type can speak to the `Attest` tasks via either the RoT @@ -115,7 +121,7 @@ impl AttestHiffy { /// This convenience function encapsulates a pattern common to /// the hiffy command line for the `Attest` operations that get the /// lengths of the data returned in leases. - fn get_len_cmd( + async fn get_len_cmd( &self, op: &str, args: Option, @@ -131,13 +137,13 @@ impl AttestHiffy { cmd.arg(format!("--arguments={a}")); } - let output = cmd.output().map_err(AttestHiffyError::Humility)?; + let output = cmd.output().await.map_err(AttestHiffyError::Humility)?; Self::u32_from_cmd_output(output) } /// This convenience function encapsulates a pattern common to the hiffy /// command line for the `Attest` operations that return blobs in chunks. - fn get_chunk( + async fn get_chunk( &self, op: &str, length: usize, @@ -159,7 +165,7 @@ impl AttestHiffy { cmd.arg(format!("--input={i}")); } - let output = cmd.output()?; + let output = cmd.output().await?; if output.status.success() { Ok(()) } else { @@ -168,8 +174,9 @@ impl AttestHiffy { } } +#[async_trait::async_trait] impl AttestSprot for AttestHiffy { - fn attest( + async fn attest( &self, nonce: &Nonce32, out: &mut [u8], @@ -188,28 +195,34 @@ impl AttestSprot for AttestHiffy { attestation_tmp.path(), None, Some(&nonce_tmp.path().to_string_lossy()), - )?; + ) + .await?; Ok(attestation_tmp.read_exact(&mut out[..])?) } /// Get length of the measurement log in bytes. - fn attest_len(&self) -> Result { - self.get_len_cmd("attest_len", None) + async fn attest_len(&self) -> Result { + self.get_len_cmd("attest_len", None).await } /// Get length of the certificate chain from the Attest task. This cert /// chain may be self signed or will terminate at the intermediate before /// the root. - fn cert_chain_len(&self) -> Result { - self.get_len_cmd("cert_chain_len", None) + async fn cert_chain_len(&self) -> Result { + self.get_len_cmd("cert_chain_len", None).await } /// Get length of the certificate at the provided index in bytes. - fn cert_len(&self, index: u32) -> Result { + async fn cert_len(&self, index: u32) -> Result { self.get_len_cmd("cert_len", Some(format!("index={index}"))) + .await } - fn cert(&self, index: u32, out: &mut [u8]) -> Result<(), AttestHiffyError> { + async fn cert( + &self, + index: u32, + out: &mut [u8], + ) -> Result<(), AttestHiffyError> { for offset in (0..out.len() - Self::CHUNK_SIZE).step_by(Self::CHUNK_SIZE) { @@ -220,7 +233,8 @@ impl AttestSprot for AttestHiffy { tmp.path(), Some(&format!("index={index},offset={offset}")), None, - )?; + ) + .await?; tmp.read_exact(&mut out[offset..offset + Self::CHUNK_SIZE])?; } @@ -234,7 +248,8 @@ impl AttestSprot for AttestHiffy { tmp.path(), Some(&format!("index={index},offset={offset}")), None, - )?; + ) + .await?; tmp.read_exact(&mut out[offset..])?; } @@ -243,7 +258,7 @@ impl AttestSprot for AttestHiffy { /// Get measurement log. This function assumes that the slice provided /// is sufficiently large to hold the log. - fn log(&self, out: &mut [u8]) -> Result<(), AttestHiffyError> { + async fn log(&self, out: &mut [u8]) -> Result<(), AttestHiffyError> { for offset in (0..out.len() - Self::CHUNK_SIZE).step_by(Self::CHUNK_SIZE) { @@ -254,7 +269,8 @@ impl AttestSprot for AttestHiffy { tmp.path(), Some(&format!("offset={offset}")), None, - )?; + ) + .await?; tmp.read_exact(&mut out[offset..offset + Self::CHUNK_SIZE])?; } @@ -268,7 +284,8 @@ impl AttestSprot for AttestHiffy { tmp.path(), Some(&format!("offset={offset}")), None, - )?; + ) + .await?; tmp.read_exact(&mut out[offset..])?; } @@ -276,12 +293,12 @@ impl AttestSprot for AttestHiffy { } /// Get length of the measurement log in bytes. - fn log_len(&self) -> Result { - self.get_len_cmd("log_len", None) + async fn log_len(&self) -> Result { + self.get_len_cmd("log_len", None).await } /// Record the sha3 hash of a file. - fn record(&self, data: &[u8]) -> Result<(), AttestHiffyError> { + async fn record(&self, data: &[u8]) -> Result<(), AttestHiffyError> { let digest = Sha3_256::digest(data); let mut tmp = NamedTempFile::new()?; tmp.write_all(digest.as_slice())?; @@ -293,7 +310,7 @@ impl AttestSprot for AttestHiffy { cmd.arg(format!("--input={}", tmp.path().to_string_lossy())); cmd.arg("--arguments=algorithm=Sha3_256"); - let output = cmd.output()?; + let output = cmd.output().await?; if output.status.success() { Ok(()) } else { @@ -302,23 +319,24 @@ impl AttestSprot for AttestHiffy { } } +#[async_trait::async_trait] impl Attest for AttestHiffy { - fn get_measurement_log(&self) -> Result { - let log_len = self.log_len()?; + async fn get_measurement_log(&self) -> Result { + let log_len = self.log_len().await?; let mut log = vec![0u8; log_len as usize]; - self.log(&mut log)?; + self.log(&mut log).await?; let (log, _): (Log, _) = hubpack::deserialize(&log).map_err(AttestError::Deserialize)?; Ok(log) } - fn get_certificates(&self) -> Result { + async fn get_certificates(&self) -> Result { let mut cert_chain = PkiPath::new(); - for index in 0..self.cert_chain_len()? { - let cert_len = self.cert_len(index)?; + for index in 0..self.cert_chain_len().await? { + let cert_len = self.cert_len(index).await?; let mut cert = vec![0u8; cert_len as usize]; - self.cert(index, &mut cert)?; + self.cert(index, &mut cert).await?; let cert = Certificate::from_der(&cert)?; @@ -328,11 +346,11 @@ impl Attest for AttestHiffy { Ok(cert_chain) } - fn attest(&self, nonce: &Nonce) -> Result { + async fn attest(&self, nonce: &Nonce) -> Result { let nonce: &Nonce32 = nonce.try_into()?; - let attest_len = self.attest_len()?; + let attest_len = self.attest_len().await?; let mut out = vec![0u8; attest_len as usize]; - AttestSprot::attest(self, nonce, &mut out)?; + AttestSprot::attest(self, nonce, &mut out).await?; let (attestation, _): (Attestation, _) = hubpack::deserialize(&out).map_err(AttestError::Deserialize)?; diff --git a/verifier/src/ipcc.rs b/verifier/src/ipcc.rs index 7a965cd..da3acc4 100644 --- a/verifier/src/ipcc.rs +++ b/verifier/src/ipcc.rs @@ -16,34 +16,52 @@ use x509_cert::{ use crate::{Attest, AttestError}; /// The `AttestIpcc` type communicates with the RoT `Attest` task through the -/// IPCC interface / -pub struct AttestIpcc { - handle: IpccHandle, -} +/// IPCC interface / . +/// +/// The actual handle to the IPCC interface is created and released on-demand. +pub struct AttestIpcc {} impl AttestIpcc { /// Creates a new `Ipcc` instance. - pub fn new() -> Result { - let handle = IpccHandle::new()?; - Ok(Self { handle }) + pub fn new() -> Self { + Self {} + } + + // Doing an actual RoT request is mildly interesting, so this is a function to + // describe the interestingness once. + async fn do_rot_request( + &self, + message: Vec, + ) -> Result, IpccError> { + // `spawn_blocking` for the request because it is possible the RoT is + // otherwise occupied and opening or doing the request will + // synchronously block for some amount of time. + let req = tokio::task::spawn_blocking(move || { + let handle = IpccHandle::new()?; + let mut rot_resp = vec![0; IPCC_MAX_DATA_SIZE]; + let len = handle.rot_request(message.as_slice(), &mut rot_resp)?; + rot_resp.truncate(len); + Ok(rot_resp) + }); + req.await + .expect("handle is not aborted, and we propagate panics") } } +#[async_trait::async_trait] impl Attest for AttestIpcc { - fn get_measurement_log(&self) -> Result { + async fn get_measurement_log(&self) -> Result { let mut rot_message = vec![0; attest_data::messages::MAX_REQUEST_SIZE]; - let mut rot_resp = vec![0; IPCC_MAX_DATA_SIZE]; let len = attest_data::messages::serialize( &mut rot_message, &HostToRotCommand::GetMeasurementLog, |_| 0, ) .map_err(AttestError::Serialize)?; - let len = self - .handle - .rot_request(&rot_message[..len], &mut rot_resp)?; + rot_message.truncate(len); + let rot_resp = self.do_rot_request(rot_message).await?; let data = attest_data::messages::parse_response( - &rot_resp[..len], + &rot_resp, RotToHost::RotMeasurementLog, ) .map_err(AttestError::HostToRot)?; @@ -54,20 +72,18 @@ impl Attest for AttestIpcc { Ok(log) } - fn get_certificates(&self) -> Result { + async fn get_certificates(&self) -> Result { let mut rot_message = vec![0; attest_data::messages::MAX_REQUEST_SIZE]; - let mut rot_resp = vec![0; IPCC_MAX_DATA_SIZE]; let len = attest_data::messages::serialize( &mut rot_message, &HostToRotCommand::GetCertificates, |_| 0, ) .map_err(AttestError::Serialize)?; - let len = self - .handle - .rot_request(&rot_message[..len], &mut rot_resp)?; + rot_message.truncate(len); + let rot_resp = self.do_rot_request(rot_message).await?; let cert_chain_bytes = attest_data::messages::parse_response( - &rot_resp[..len], + &rot_resp, RotToHost::RotCertificates, ) .map_err(AttestError::HostToRot)?; @@ -95,10 +111,9 @@ impl Attest for AttestIpcc { Ok(certs) } - fn attest(&self, nonce: &Nonce) -> Result { + async fn attest(&self, nonce: &Nonce) -> Result { let nonce: &Nonce32 = nonce.try_into()?; let mut rot_message = vec![0; attest_data::messages::MAX_REQUEST_SIZE]; - let mut rot_resp = vec![0; IPCC_MAX_DATA_SIZE]; let len = attest_data::messages::serialize( &mut rot_message, &HostToRotCommand::Attest, @@ -108,11 +123,10 @@ impl Attest for AttestIpcc { }, ) .map_err(AttestError::Serialize)?; - let len = self - .handle - .rot_request(&rot_message[..len], &mut rot_resp)?; + rot_message.truncate(len); + let rot_resp = self.do_rot_request(rot_message).await?; let data = attest_data::messages::parse_response( - &rot_resp[..len], + &rot_resp, RotToHost::RotAttestation, ) .map_err(AttestError::HostToRot)?; diff --git a/verifier/src/lib.rs b/verifier/src/lib.rs index ed1882e..d14afac 100644 --- a/verifier/src/lib.rs +++ b/verifier/src/lib.rs @@ -62,23 +62,24 @@ pub enum AttestError { /// The `Attest` trait is implemented by types that provide access to the RoT /// attestation API. These types are generally proxies that shuttle data over /// some transport between the caller and the RoT. +#[async_trait::async_trait] pub trait Attest { /// Get the measurement log from the attest task. The Log is transmitted /// with no integrity protection so its trustworthiness must be established /// by an external process (see `verify_attestation`). - fn get_measurement_log(&self) -> Result; + async fn get_measurement_log(&self) -> Result; /// Get the certificate chain from the attest task. This cert chain is a /// PKI path (per RFC 6066) starting with the leaf cert for the attestation /// signer and terminating at the intermediate before the root. The /// trustworthiness of this certificate chain must be established through /// an external process (see `verify_cert_chain`). - fn get_certificates(&self) -> Result; + async fn get_certificates(&self) -> Result; /// Get an attestation from the attest task. An attestation is a signature /// over the (hubpack serialized) measurement Log and the provided Nonce. /// To prevent replay attacks each Nonce used must be unique and /// unpredictable. Generally the Nonce should be generated from the /// platform's random number generator (see `Nonce::from_platform_rng`). - fn attest(&self, nonce: &Nonce) -> Result; + async fn attest(&self, nonce: &Nonce) -> Result; } /// Errors related to the creation of signature verifiers for certs in a diff --git a/verifier/src/mock.rs b/verifier/src/mock.rs index 2c83c6c..5197644 100644 --- a/verifier/src/mock.rs +++ b/verifier/src/mock.rs @@ -54,16 +54,17 @@ impl AttestMock { } } +#[async_trait::async_trait] impl Attest for AttestMock { - fn get_measurement_log(&self) -> Result { + async fn get_measurement_log(&self) -> Result { Ok(self.log.clone()) } - fn get_certificates(&self) -> Result { + async fn get_certificates(&self) -> Result { Ok(self.certs.clone()) } - fn attest(&self, nonce: &Nonce) -> Result { + async fn attest(&self, nonce: &Nonce) -> Result { let nonce: &Nonce32 = nonce.try_into()?; let mut buf = vec![0u8; Log::MAX_SIZE]; let len = hubpack::serialize(&mut buf, &self.log) diff --git a/verifier/src/sled_agent.rs b/verifier/src/sled_agent.rs index dd7fcf1..aba0ad4 100644 --- a/verifier/src/sled_agent.rs +++ b/verifier/src/sled_agent.rs @@ -9,37 +9,30 @@ use std::net::SocketAddrV6; use attest_data::{Attestation, Log, Measurement, Nonce}; use sled_agent_client::Client as SledAgentClient; use sled_agent_types_versions::latest::rot as SledAgentTypes; -use tokio::runtime::{Builder, Runtime}; use x509_cert::{der::DecodePem, Certificate, PkiPath}; pub struct AttestSledAgent { client: SledAgentClient, - rt: Runtime, } impl AttestSledAgent { pub fn new(addr: SocketAddrV6, log: &slog::Logger) -> Self { - let rt = Builder::new_current_thread() - .enable_time() - .enable_io() - .build() - .unwrap(); let client = SledAgentClient::new( &format!("http://{addr}"), log.new(slog::o!("SledAgentClient" => addr.to_string())), ); - Self { client, rt } + Self { client } } } +#[async_trait::async_trait] impl Attest for AttestSledAgent { - fn get_measurement_log(&self) -> Result { + async fn get_measurement_log(&self) -> Result { let mut log = Log::default(); let measurments = self - .rt - .block_on( - self.client.rot_measurement_log(&SledAgentTypes::Rot::Oxide), - )? + .client + .rot_measurement_log(&SledAgentTypes::Rot::Oxide) + .await? .into_inner(); for m in measurments.0 { assert!(log.push(match m { @@ -51,13 +44,11 @@ impl Attest for AttestSledAgent { Ok(log) } - fn get_certificates(&self) -> Result { + async fn get_certificates(&self) -> Result { let certs = self - .rt - .block_on( - self.client - .rot_certificate_chain(&SledAgentTypes::Rot::Oxide), - )? + .client + .rot_certificate_chain(&SledAgentTypes::Rot::Oxide) + .await? .into_inner(); Ok(certs .0 @@ -66,14 +57,15 @@ impl Attest for AttestSledAgent { .collect::, _>>()?) } - fn attest(&self, nonce: &Nonce) -> Result { + async fn attest(&self, nonce: &Nonce) -> Result { let &Nonce::N32(nonce) = nonce; let attestation = self - .rt - .block_on(self.client.rot_attest( + .client + .rot_attest( &SledAgentTypes::Rot::Oxide, &SledAgentTypes::Nonce::N32(nonce.0), - ))? + ) + .await? .into_inner(); let attestation = match attestation { SledAgentTypes::Attestation::Ed25519(d) => {