From e0cb22d72466a1df2685f0b1e8da5a5963a7e460 Mon Sep 17 00:00:00 2001 From: Laura Abbott Date: Tue, 25 Mar 2025 14:57:11 -0400 Subject: [PATCH 1/2] WIP --- Cargo.lock | 98 +++++++++----- Cargo.toml | 1 + attest-data/src/lib.rs | 10 ++ verifier-ipcc/Cargo.toml | 24 ++++ verifier-ipcc/README.md | 3 + verifier-ipcc/build.rs | 17 +++ verifier-ipcc/src/main.rs | 268 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 388 insertions(+), 33 deletions(-) create mode 100644 verifier-ipcc/Cargo.toml create mode 100644 verifier-ipcc/README.md create mode 100644 verifier-ipcc/build.rs create mode 100644 verifier-ipcc/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index b7faaca..25e1ab6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "CoreFoundation-sys" @@ -269,7 +269,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.100", ] [[package]] @@ -375,7 +375,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.100", ] [[package]] @@ -399,7 +399,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.10.0", - "syn 2.0.52", + "syn 2.0.100", ] [[package]] @@ -410,7 +410,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" dependencies = [ "darling_core", "quote", - "syn 2.0.52", + "syn 2.0.100", ] [[package]] @@ -443,14 +443,14 @@ checksum = "5fe87ce4529967e0ba1dcf8450bab64d97dfd5010a6256187ffe2e43e6f0e049" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.100", ] [[package]] name = "deranged" -version = "0.3.11" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "28cfac68e08048ae1883171632c2aef3ebc555621ae56fbccce1cbf22dd7f058" dependencies = [ "powerfmt", "serde", @@ -816,9 +816,19 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.153" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" + +[[package]] +name = "libipcc" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/ipcc-rs?rev=524eb8f125003dff50b9703900c6b323f00f9e1b#524eb8f125003dff50b9703900c6b323f00f9e1b" +dependencies = [ + "cfg-if", + "libc", + "thiserror", +] [[package]] name = "libudev" @@ -1002,9 +1012,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] @@ -1193,7 +1203,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.100", ] [[package]] @@ -1209,9 +1219,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.7.0" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a" +checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" dependencies = [ "serde", "serde_derive", @@ -1220,14 +1230,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.7.0" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655" +checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.100", ] [[package]] @@ -1286,7 +1296,7 @@ checksum = "ab0381d1913eeaf4c7bc4094016c9a8de6c1120663afe32a90ff268ad7f80486" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.100", ] [[package]] @@ -1342,9 +1352,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.52" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -1380,14 +1390,14 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.100", ] [[package]] name = "time" -version = "0.3.34" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "num-conv", @@ -1399,15 +1409,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.17" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", @@ -1431,7 +1441,7 @@ checksum = "8d9ef545650e79f30233c0003bcc2504d7efac6dad25fca40744de773fe2049c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.100", ] [[package]] @@ -1488,6 +1498,28 @@ dependencies = [ "x509-cert", ] +[[package]] +name = "verifier-ipcc" +version = "0.1.0" +dependencies = [ + "anyhow", + "attest-data", + "clap", + "const-oid", + "dice-verifier", + "ed25519-dalek", + "env_logger", + "hubpack", + "libipcc", + "log", + "p384", + "pem-rfc7468", + "serde_json", + "sha3", + "tempfile", + "x509-cert", +] + [[package]] name = "version_check" version = "0.9.4" @@ -1521,7 +1553,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.100", "wasm-bindgen-shared", ] @@ -1543,7 +1575,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.100", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1793,7 +1825,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.100", ] [[package]] @@ -1813,5 +1845,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.100", ] diff --git a/Cargo.toml b/Cargo.toml index a95db74..7103c39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "dice-mfg-msgs", "yhsm-audit", "verifier-cli", + "verifier-ipcc", ] resolver = "2" diff --git a/attest-data/src/lib.rs b/attest-data/src/lib.rs index 3d8c04f..9f50798 100644 --- a/attest-data/src/lib.rs +++ b/attest-data/src/lib.rs @@ -165,6 +165,16 @@ impl MeasurementLog { false } } + + pub fn iter(&self) -> impl Iterator { + self.measurements.iter().enumerate().filter_map(|(i, e)| { + if i < (self.index as usize) { + Some(e) + } else { + None + } + }) + } } impl Default for MeasurementLog { diff --git a/verifier-ipcc/Cargo.toml b/verifier-ipcc/Cargo.toml new file mode 100644 index 0000000..77f9517 --- /dev/null +++ b/verifier-ipcc/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "verifier-ipcc" +version = "0.1.0" +edition = "2021" +description = "A CLI tool for working over IPCC" +license = "MPL-2.0" + +[dependencies] +anyhow = { workspace = true, features = ["std"] } +attest-data = { path = "../attest-data", features = ["std"] } +clap.workspace = true +const-oid = { workspace = true, features = ["db"] } +ed25519-dalek = { workspace = true, features = ["std"] } +env_logger.workspace = true +hubpack.workspace = true +libipcc = { git = "https://github.com/oxidecomputer/ipcc-rs", rev = "524eb8f125003dff50b9703900c6b323f00f9e1b" } +log.workspace = true +p384 = { workspace = true, default-features = true } +pem-rfc7468 = { workspace = true, features = ["alloc", "std"] } +sha3.workspace = true +tempfile.workspace = true +dice-verifier.path = "../verifier" +x509-cert = { workspace = true, default-features = true } +serde_json.workspace = true diff --git a/verifier-ipcc/README.md b/verifier-ipcc/README.md new file mode 100644 index 0000000..42dab16 --- /dev/null +++ b/verifier-ipcc/README.md @@ -0,0 +1,3 @@ +Tools for working with measurements over IPCC on actual hardware + +Runs only on illumos diff --git a/verifier-ipcc/build.rs b/verifier-ipcc/build.rs new file mode 100644 index 0000000..24bc741 --- /dev/null +++ b/verifier-ipcc/build.rs @@ -0,0 +1,17 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +/// This path is where Oxide specific libraries live on helios systems. +/// This is needed for linking with libipcc +#[cfg(target_os = "illumos")] +static OXIDE_PLATFORM: &str = "/usr/platform/oxide/lib/amd64/"; + +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + #[cfg(target_os = "illumos")] + { + println!("cargo:rustc-link-arg=-Wl,-R{}", OXIDE_PLATFORM); + println!("cargo:rustc-link-search={}", OXIDE_PLATFORM); + } +} diff --git a/verifier-ipcc/src/main.rs b/verifier-ipcc/src/main.rs new file mode 100644 index 0000000..342db3a --- /dev/null +++ b/verifier-ipcc/src/main.rs @@ -0,0 +1,268 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use anyhow::{anyhow, Result}; +use attest_data::messages::{HostToRotCommand, RotToHost}; +use attest_data::{Attestation, Log, Nonce}; +use clap::Parser; +use dice_verifier::PkiPathSignatureVerifier; +use libipcc::IpccHandle; +use log::{debug, info}; +use std::path::PathBuf; +use x509_cert::{ + der::{self, Decode, DecodePem, Encode, Reader}, + Certificate, +}; + +// A slight hack. These are only defined right now in the `ffi` part +// of libipcc which isn't available on non-illumos targets. Probably +// indicates those constants belong elsewhere... +const IPCC_MAX_DATA_SIZE: usize = 4123 - 19; + +/// Commands for atttestaion supported over IPCC +#[derive(Debug, Parser)] +enum Command { + /// Retreive the measurement log + Log { + /// Path to save log + #[clap(long)] + out: PathBuf, + }, + /// Retreive the certificate chain + PrintCertChain { + /// Optionally verify the cert chain + #[clap(long)] + verify: bool, + /// Use a root certicate for verification + #[clap(long)] + ca_cert: Option, + }, + /// Generate an attestation using the provided nonce + Attest { + #[clap(long)] + nonce: PathBuf, + #[clap(long)] + out: PathBuf, + }, + /// Verify signature over the attesattion + VerifyAttestation { + /// Path to the file holding the attestation + #[clap(long)] + attestation: PathBuf, + /// Path to the file containing the log + #[clap(long)] + log: PathBuf, + /// Path to file containing the nonce + #[clap(long)] + nonce: PathBuf, + }, + /// Generates a nonce, attestation and verifies it + VerifyRoundTrip { + /// Path to file holding trust anchor for the associated PKI. + #[clap( + long, + env = "VERIFIER_CLI_CA_CERT", + conflicts_with = "self_signed" + )] + ca_cert: Option, + + /// Verify the final cert in the provided PkiPath against itself. + #[clap(long, env, conflicts_with = "ca_cert")] + self_signed: bool, + }, + /// Verify the log against the given set of measurements + VerifyLog { + #[clap(long)] + reference: PathBuf, + }, +} + +/// Execute attest command over IPCC, +#[derive(Debug, Parser)] +#[clap(author, version, about, long_about = None)] +struct Args { + /// Command to execute + #[clap(subcommand)] + command: Command, +} + +pub struct Ipcc { + handle: IpccHandle, +} + +impl Ipcc { + /// Creates a new `Ipcc` instance. + pub fn new() -> Result { + let handle = + IpccHandle::new().map_err(|e| anyhow!("Ipcc error {}", e))?; + Ok(Self { handle }) + } + + pub 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(|e| anyhow!("serialize {}", e))?; + let len = self + .handle + .rot_request(&rot_message[..len], &mut rot_resp)?; + let data = attest_data::messages::parse_response( + &rot_resp[..len], + RotToHost::RotMeasurementLog, + ) + .map_err(|e| anyhow!("bad response {:?}", e))?; + Ok(data.to_vec()) + } + + pub 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(|e| anyhow!("serialize {}", e))?; + let len = self + .handle + .rot_request(&rot_message[..len], &mut rot_resp)?; + let cert_chain_bytes = attest_data::messages::parse_response( + &rot_resp[..len], + RotToHost::RotCertificates, + ) + .map_err(|e| anyhow!("bad response {:?}", e))?; + + let mut idx = 0; + + let mut certs = vec![]; + // Turn the DER chain into something we can actually use + while idx < cert_chain_bytes.len() { + let reader = der::SliceReader::new(&cert_chain_bytes[idx..])?; + let header = reader.peek_header()?; + // DER certificates are supposed to be a `Sequence`. + // We could check that here but we're going to get better + // error messages by letting the cert parsing code say + // exactly what went wrong + let seq_len: usize = header.length.try_into()?; + let tag_len: usize = header.encoded_len()?.try_into()?; + // Total len = length from the sequence plus the tag itself + let end = idx + seq_len + tag_len; + + certs.push(Certificate::from_der(&cert_chain_bytes[idx..end])?); + idx += seq_len + tag_len; + } + + Ok(certs) + } + + pub fn attest(&self, nonce: &[u8]) -> 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::Attest, + |buf| { + buf[..nonce.len()].copy_from_slice(&nonce); + 32 + }, + ) + .map_err(|e| anyhow!("serialize {}", e))?; + let len = self + .handle + .rot_request(&rot_message[..len], &mut rot_resp)?; + let data = attest_data::messages::parse_response( + &rot_resp[..len], + RotToHost::RotAttestation, + ) + .map_err(|e| anyhow!("bad response {:?}", e))?; + Ok(data.to_vec()) + } +} + +fn main() -> Result<()> { + let handle = Ipcc::new()?; + + let args = Args::parse(); + + match args.command { + Command::Log { out } => { + let log = handle.get_measurement_log()?; + + std::fs::write(&out, &log)?; + println!("Binary log written to {:?}", out); + + // do a check that what we got back is a Real Log + let (log, _) = hubpack::deserialize::(&log) + .map_err(|e| anyhow!("Failed to deserialize Log: {}", e))?; + + info!("Log entries:"); + for (i, entry) in log.iter().enumerate() { + info!("{}: {:x?}", i, entry); + } + } + Command::PrintCertChain { verify, ca_cert } => { + let chain = handle.get_certificates()?; + + for cert in &chain { + info!("Certificate => {}", cert.tbs_certificate.subject); + debug!("Full certificate => {cert:?}"); + } + + if verify { + let root = match ca_cert { + Some(r) => { + let root = std::fs::read(r)?; + Some(Certificate::from_pem(root)?) + } + None => None, + }; + + let verifier = PkiPathSignatureVerifier::new(root)?; + verifier.verify(&chain)?; + } else { + println!("Skipping chain validation"); + } + } + Command::Attest { nonce, out } => { + let nonce = std::fs::read(nonce)?; + let nonce = Nonce::try_from(&nonce[..])?; + + let attest = handle.attest(&nonce.as_ref())?; + + std::fs::write(&out, &attest)?; + info!("Wrote attestation to {:?}", out); + } + Command::VerifyAttestation { + attestation, + log, + nonce, + } => { + let nonce = std::fs::read(nonce)?; + let nonce = Nonce::try_from(&nonce[..])?; + + let log = std::fs::read(log)?; + + let attestation = std::fs::read(attestation)?; + let (attestation, _) = hubpack::deserialize::( + &attestation, + ) + .map_err(|e| anyhow!("Failed to deserialize Attestation: {}", e))?; + + let chain = handle.get_certificates()?; + + dice_verifier::verify_attestation( + &chain[0], + &attestation, + &log, + &nonce, + )?; + } + _ => todo!(), + } + Ok(()) +} From efb7277a15ff8382098b2a0eebf96b11e4262ca6 Mon Sep 17 00:00:00 2001 From: Laura Abbott Date: Tue, 25 Mar 2025 19:28:57 +0000 Subject: [PATCH 2/2] wip --- attest-data/src/messages.rs | 1 - verifier-ipcc/src/main.rs | 47 ++++++++++++++++++++++++++++--------- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/attest-data/src/messages.rs b/attest-data/src/messages.rs index 96e833c..b9b24b4 100644 --- a/attest-data/src/messages.rs +++ b/attest-data/src/messages.rs @@ -13,7 +13,6 @@ pub const ATTEST_MAGIC: u32 = 0xA77E5700; /// Right now `Attest` and `TqSign` are the only commands that take data /// argumenets. They happen to be the same length right now but this also /// catches anything silly - const fn const_max(a: usize, b: usize) -> usize { [a, b][(a < b) as usize] } diff --git a/verifier-ipcc/src/main.rs b/verifier-ipcc/src/main.rs index 342db3a..b088eb2 100644 --- a/verifier-ipcc/src/main.rs +++ b/verifier-ipcc/src/main.rs @@ -60,16 +60,8 @@ enum Command { /// Generates a nonce, attestation and verifies it VerifyRoundTrip { /// Path to file holding trust anchor for the associated PKI. - #[clap( - long, - env = "VERIFIER_CLI_CA_CERT", - conflicts_with = "self_signed" - )] + #[clap(long, env = "VERIFIER_CLI_CA_CERT")] ca_cert: Option, - - /// Verify the final cert in the provided PkiPath against itself. - #[clap(long, env, conflicts_with = "ca_cert")] - self_signed: bool, }, /// Verify the log against the given set of measurements VerifyLog { @@ -167,7 +159,7 @@ impl Ipcc { &mut rot_message, &HostToRotCommand::Attest, |buf| { - buf[..nonce.len()].copy_from_slice(&nonce); + buf[..nonce.len()].copy_from_slice(nonce); 32 }, ) @@ -185,6 +177,7 @@ impl Ipcc { } fn main() -> Result<()> { + env_logger::init(); let handle = Ipcc::new()?; let args = Args::parse(); @@ -232,7 +225,7 @@ fn main() -> Result<()> { let nonce = std::fs::read(nonce)?; let nonce = Nonce::try_from(&nonce[..])?; - let attest = handle.attest(&nonce.as_ref())?; + let attest = handle.attest(nonce.as_ref())?; std::fs::write(&out, &attest)?; info!("Wrote attestation to {:?}", out); @@ -261,6 +254,38 @@ fn main() -> Result<()> { &log, &nonce, )?; + info!("Attestation succeeded."); + } + Command::VerifyRoundTrip { ca_cert } => { + let nonce = Nonce::from_platform_rng()?; + + let attestation = handle.attest(nonce.as_ref())?; + let (attestation, _) = hubpack::deserialize::( + &attestation, + ) + .map_err(|e| anyhow!("Failed to deserialize Attestation: {}", e))?; + + let log = handle.get_measurement_log()?; + + let chain = handle.get_certificates()?; + let root = match ca_cert { + Some(r) => { + let root = std::fs::read(r)?; + Some(Certificate::from_pem(root)?) + } + None => None, + }; + + let verifier = PkiPathSignatureVerifier::new(root)?; + verifier.verify(&chain)?; + + dice_verifier::verify_attestation( + &chain[0], + &attestation, + &log, + &nonce, + )?; + info!("Success."); } _ => todo!(), }