diff --git a/Cargo.lock b/Cargo.lock index e46dde7..c447712 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -975,6 +975,18 @@ dependencies = [ "cc", ] +[[package]] +name = "cms" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b77c319abfd5219629c45c34c89ba945ed3c5e49fcde9d16b6c3885f118a730" +dependencies = [ + "const-oid", + "der 0.7.9", + "spki 0.7.3", + "x509-cert", +] + [[package]] name = "combine" version = "4.6.7" @@ -1039,6 +1051,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc24" version = "0.1.6" @@ -1264,7 +1291,7 @@ dependencies = [ "const-oid", "der_derive", "flagset", - "pem-rfc7468", + "pem-rfc7468 0.7.0", "zeroize", ] @@ -1527,7 +1554,7 @@ dependencies = [ "generic-array", "group 0.13.0", "hkdf", - "pem-rfc7468", + "pem-rfc7468 0.7.0", "pkcs8 0.10.2", "rand_core 0.6.4", "sec1 0.7.3", @@ -1972,6 +1999,18 @@ dependencies = [ "web-sys", ] +[[package]] +name = "gpt" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffa5448a0d9d541f1840c0e1b5fe513360861ca83c4b920619f54efe277f9254" +dependencies = [ + "bitflags 2.6.0", + "crc", + "simple-bytes", + "uuid", +] + [[package]] name = "group" version = "0.12.1" @@ -3576,6 +3615,25 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pe-sign" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c04c052a5cf901a229d69fb8804b04c8017c143712529c6e8277aac243fc2989" +dependencies = [ + "chrono", + "cms", + "der 0.7.9", + "digest", + "num-traits", + "pem-rfc7468 1.0.0-rc.2", + "reqwest 0.12.9", + "rsa", + "sha1", + "sha2", + "x509-cert", +] + [[package]] name = "peeking_take_while" version = "0.1.2" @@ -3591,6 +3649,15 @@ dependencies = [ "base64ct", ] +[[package]] +name = "pem-rfc7468" +version = "1.0.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dfbfa5c6f0906884269722c5478e72fd4d6c0e24fe600332c6d62359567ce1" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -4251,6 +4318,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rtmr-calc" +version = "0.3.0" +dependencies = [ + "anyhow", + "clap 4.5.23", + "gpt", + "hex", + "pe-sign", + "sha2", + "teepot", + "tracing", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -4810,6 +4891,16 @@ dependencies = [ "keccak", ] +[[package]] +name = "sha384-extend" +version = "0.3.0" +dependencies = [ + "anyhow", + "clap 4.5.23", + "hex", + "sha2", +] + [[package]] name = "sha3_ce" version = "0.10.6" @@ -4864,6 +4955,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simple-bytes" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c11532d9d241904f095185f35dcdaf930b1427a94d5b01d7002d74ba19b44cc4" + [[package]] name = "slab" version = "0.4.9" @@ -5100,6 +5197,17 @@ dependencies = [ "bindgen 0.59.2", ] +[[package]] +name = "tdx-extend" +version = "0.3.0" +dependencies = [ + "anyhow", + "clap 4.5.23", + "hex", + "teepot", + "tracing", +] + [[package]] name = "tee-key-preexec" version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml index 113ab1e..31bbed1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ ctrlc = "3.4" enumset = { version = "1.1", features = ["serde"] } futures-core = { version = "0.3.30", features = ["alloc"], default-features = false } getrandom = "0.2.14" +gpt = "4.0.0" hex = { version = "0.4.3", features = ["std"], default-features = false } intel-tee-quote-verification-rs = { package = "teepot-tee-quote-verification-rs", path = "crates/teepot-tee-quote-verification-rs", version = "0.3.0" } intel-tee-quote-verification-sys = { version = "0.2.1" } @@ -36,6 +37,7 @@ jsonrpsee-types = { version = "0.23", default-features = false } num-integer = "0.1.46" num-traits = "0.2.18" p256 = "0.13.2" +pe-sign = "0.1.10" pgp = "0.14.2" pkcs8 = { version = "0.10" } rand = "0.8" diff --git a/bin/rtmr-calc/Cargo.toml b/bin/rtmr-calc/Cargo.toml new file mode 100644 index 0000000..aeb7e96 --- /dev/null +++ b/bin/rtmr-calc/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "rtmr-calc" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true + +[dependencies] +anyhow.workspace = true +clap.workspace = true +gpt.workspace = true +hex.workspace = true +pe-sign.workspace = true +sha2.workspace = true +teepot.workspace = true +tracing.workspace = true diff --git a/bin/rtmr-calc/src/main.rs b/bin/rtmr-calc/src/main.rs new file mode 100644 index 0000000..97b4cf4 --- /dev/null +++ b/bin/rtmr-calc/src/main.rs @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2024 Matter Labs + +use anyhow::{anyhow, Result}; +use clap::Parser; +use pesign::PE; +use sha2::{Digest, Sha384}; +use std::{ + fmt::{Display, Formatter}, + io::{Error, ErrorKind, Read, Seek, SeekFrom}, + path::PathBuf, +}; +use teepot::log::{setup_logging, LogLevelParser}; +use tracing::{debug, info, level_filters::LevelFilter}; + +/// Precalculate rtmr1 and rtmr2 values. +/// +/// Currently tested with the Google confidential compute engines. +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Arguments { + /// disk image to measure the GPT table from + #[arg(long)] + image: PathBuf, + /// path to the used UKI EFI binary + #[arg(long)] + bootefi: PathBuf, + /// path to the used linux kernel EFI binary (contained in the UKI) + #[arg(long)] + kernel: PathBuf, + /// Log level for the log output. + /// Valid values are: `off`, `error`, `warn`, `info`, `debug`, `trace` + #[clap(long, default_value_t = LevelFilter::WARN, value_parser = LogLevelParser)] + pub log_level: LevelFilter, +} + +struct Rtmr { + state: Vec, +} + +impl Rtmr { + pub fn extend(&mut self, hash: &[u8]) -> &[u8] { + self.state.extend(hash); + let bytes = Sha384::digest(&self.state); + self.state.resize(48, 0); + self.state.copy_from_slice(&bytes); + &self.state + } +} + +impl Default for Rtmr { + fn default() -> Self { + Self { + state: [0u8; 48].to_vec(), + } + } +} + +impl Display for Rtmr { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", hex::encode(&self.state)) + } +} + +fn main() -> Result<()> { + let args = Arguments::parse(); + tracing::subscriber::set_global_default(setup_logging( + env!("CARGO_CRATE_NAME"), + &args.log_level, + )?)?; + + let mut rtmr1 = Rtmr::default(); + let mut rtmr2 = Rtmr::default(); + + /* + - pcr_index: 1 + event: efiaction + digests: + - method: sha384 + digest: 77a0dab2312b4e1e57a84d865a21e5b2ee8d677a21012ada819d0a98988078d3d740f6346bfe0abaa938ca20439a8d71 + digest_verification_status: verified + data: Q2FsbGluZyBFRkkgQXBwbGljYXRpb24gZnJvbSBCb290IE9wdGlvbg== + parsed_data: + Ok: + text: Calling EFI Application from Boot Option + */ + rtmr1.extend(&hex::decode("77a0dab2312b4e1e57a84d865a21e5b2ee8d677a21012ada819d0a98988078d3d740f6346bfe0abaa938ca20439a8d71")?); + + /* + - pcr_index: 1 + event: separator + digests: + - method: sha384 + digest: 394341b7182cd227c5c6b07ef8000cdfd86136c4292b8e576573ad7ed9ae41019f5818b4b971c9effc60e1ad9f1289f0 + digest_verification_status: verified + data: AAAAAA== + parsed_data: + Ok: + validseparator: UEFI + */ + rtmr1.extend(&hex::decode("394341b7182cd227c5c6b07ef8000cdfd86136c4292b8e576573ad7ed9ae41019f5818b4b971c9effc60e1ad9f1289f0")?); + + // Open disk image. + let cfg = gpt::GptConfig::new().writable(false); + let disk = cfg.open(args.image)?; + + // Print GPT layout. + info!("Disk (primary) header: {:#?}", disk.primary_header()); + info!("Partition layout: {:#?}", disk.partitions()); + + let header = disk.primary_header()?; + let mut msr = Vec::::new(); + let lb_size = disk.logical_block_size(); + let mut device = disk.device_ref(); + device.seek(SeekFrom::Start(lb_size.as_u64()))?; + let mut buf = [0u8; 92]; + device.read_exact(&mut buf)?; + msr.extend_from_slice(&buf); + + let pstart = header + .part_start + .checked_mul(lb_size.as_u64()) + .ok_or_else(|| Error::new(ErrorKind::Other, "partition overflow - start offset"))?; + let _ = device.seek(SeekFrom::Start(pstart))?; + + assert_eq!(header.part_size, 128); + assert!(header.num_parts < u8::MAX as _); + + let empty_bytes = [0u8; 128]; + + msr.extend_from_slice(&disk.partitions().len().to_le_bytes()); + + for _ in 0..header.num_parts { + let mut bytes = empty_bytes; + + device.read_exact(&mut bytes)?; + if bytes.eq(&empty_bytes) { + continue; + } + msr.extend_from_slice(&bytes); + } + + let mut hasher = Sha384::new(); + hasher.update(&msr); + let result = hasher.finalize(); + info!("GPT hash: {:x}", result); + + rtmr1.extend(&result); + + let mut pe = PE::from_path(&args.bootefi)?; + + let hash = pe.calc_authenticode(pesign::cert::Algorithm::Sha384)?; + info!("hash of {:?}: {hash}", args.bootefi); + rtmr1.extend(&hex::decode(&hash)?); + + let section_table = pe.get_section_table()?; + + for section in section_table.iter() { + debug!(section_name = ?section.name()?); + } + + for sect in [".linux", ".osrel", ".cmdline", ".initrd", ".uname", ".sbat"] { + let mut hasher = Sha384::new(); + hasher.update(sect.as_bytes()); + hasher.update([0u8]); + let out = hasher.finalize(); + debug!(sect, "name: {out:x}"); + rtmr2.extend(&out); + + let s = section_table + .iter() + .find(|s| s.name().unwrap().eq(sect)) + .ok_or(anyhow!("Failed to find section `{sect}`"))?; + + let mut start = s.pointer_to_raw_data as u64; + let end = start + s.virtual_size as u64; + + debug!(sect, start, end, len = (s.virtual_size)); + + let mut hasher = Sha384::new(); + + const CHUNK_SIZE: u64 = 1024 * 128; + loop { + if start >= end { + break; + } + + let mut buf = vec![0; CHUNK_SIZE.min(end - start) as _]; + pe.read_exact_at(start, buf.as_mut_slice())?; + hasher.update(buf.as_slice()); + + start += CHUNK_SIZE; + } + let digest = hasher.finalize(); + debug!(sect, "binary: {digest:x}"); + rtmr2.extend(&digest); + } + + let hash = PE::from_path(&args.kernel)?.calc_authenticode(pesign::cert::Algorithm::Sha384)?; + info!("hash of {:?}: {hash}", args.kernel); + rtmr1.extend(&hex::decode(&hash)?); + + /* + - pcr_index: 1 + event: efiaction + digests: + - method: sha384 + digest: 214b0bef1379756011344877743fdc2a5382bac6e70362d624ccf3f654407c1b4badf7d8f9295dd3dabdef65b27677e0 + digest_verification_status: verified + data: RXhpdCBCb290IFNlcnZpY2VzIEludm9jYXRpb24= + parsed_data: + Ok: + text: Exit Boot Services Invocation + */ + rtmr1.extend(&hex::decode("214b0bef1379756011344877743fdc2a5382bac6e70362d624ccf3f654407c1b4badf7d8f9295dd3dabdef65b27677e0")?); + + /* + - pcr_index: 1 + event: efiaction + digests: + - method: sha384 + digest: 0a2e01c85deae718a530ad8c6d20a84009babe6c8989269e950d8cf440c6e997695e64d455c4174a652cd080f6230b74 + digest_verification_status: verified + data: RXhpdCBCb290IFNlcnZpY2VzIFJldHVybmVkIHdpdGggU3VjY2Vzcw== + parsed_data: + Ok: + text: Exit Boot Services Returned with Success + */ + rtmr1.extend(&hex::decode("0a2e01c85deae718a530ad8c6d20a84009babe6c8989269e950d8cf440c6e997695e64d455c4174a652cd080f6230b74")?); + + println!("{{"); + println!("\t\"rtmr1\": \"{rtmr1}\","); + println!("\t\"rtmr2\": \"{rtmr2}\""); + println!("}}"); + + Ok(()) +} diff --git a/bin/sha384-extend/Cargo.toml b/bin/sha384-extend/Cargo.toml new file mode 100644 index 0000000..f9c58e6 --- /dev/null +++ b/bin/sha384-extend/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "sha384-extend" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true + +[dependencies] +anyhow.workspace = true +clap.workspace = true +hex.workspace = true +sha2.workspace = true diff --git a/bin/sha384-extend/src/main.rs b/bin/sha384-extend/src/main.rs new file mode 100644 index 0000000..2835a3e --- /dev/null +++ b/bin/sha384-extend/src/main.rs @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2024 Matter Labs + +//! Extend the TDX measurement + +#![deny(missing_docs)] +#![deny(clippy::all)] + +use anyhow::{Context, Result}; +use clap::Parser; +use sha2::Digest; + +/// Calculate a TDX rtmr or TPM pcr sha384 value by extending it +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Arguments { + /// digest in hex to extend with + #[arg(long)] + extend: String, + /// initial digest in hex + #[arg(long)] + digest: String, +} + +fn main() -> Result<()> { + let args = Arguments::parse(); + + // Parse the digest string as a hex array + let extend_bytes = hex::decode(&args.extend).context("Invalid digest format")?; + let mut digest_bytes = hex::decode(&args.digest).context("Invalid digest format")?; + + digest_bytes.extend(extend_bytes); + + let bytes = sha2::Sha384::digest(&digest_bytes); + let hex = hex::encode(bytes); + + println!("{hex}"); + Ok(()) +} diff --git a/bin/tdx-extend/Cargo.toml b/bin/tdx-extend/Cargo.toml new file mode 100644 index 0000000..a34e597 --- /dev/null +++ b/bin/tdx-extend/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "tdx-extend" +publish = false +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true + +[dependencies] +anyhow.workspace = true +clap.workspace = true +hex.workspace = true +teepot.workspace = true +tracing.workspace = true diff --git a/bin/tdx-extend/src/main.rs b/bin/tdx-extend/src/main.rs new file mode 100644 index 0000000..1a4056c --- /dev/null +++ b/bin/tdx-extend/src/main.rs @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2024 Matter Labs + +//! Extend the TDX measurement + +#![deny(missing_docs)] +#![deny(clippy::all)] + +use anyhow::{Context, Result}; +use clap::Parser; +use teepot::{ + log::{setup_logging, LogLevelParser}, + pad, + tdx::rtmr::TdxRtmrEvent, +}; +use tracing::{error, level_filters::LevelFilter}; + +/// Extend a TDX rtmr with a hash digest for measured boot. +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Arguments { + /// digest in hex to extend the rtmr with + #[arg(long)] + digest: String, + /// the number or the rtmr + #[arg(long, default_value = "2")] + rtmr: u64, + /// Log level for the log output. + /// Valid values are: `off`, `error`, `warn`, `info`, `debug`, `trace` + #[clap(long, default_value_t = LevelFilter::WARN, value_parser = LogLevelParser)] + pub log_level: LevelFilter, +} + +fn main_with_error() -> Result<()> { + let args = Arguments::parse(); + tracing::subscriber::set_global_default(setup_logging( + env!("CARGO_CRATE_NAME"), + &args.log_level, + )?)?; + + // Parse the digest string as a hex array + let digest_bytes = hex::decode(&args.digest).context("Invalid digest format")?; + let extend_data: [u8; 48] = pad(&digest_bytes); + + // Extend the TDX measurement with the extend data + TdxRtmrEvent::default() + .with_extend_data(extend_data) + .with_rtmr_index(args.rtmr) + .extend()?; + + Ok(()) +} + +fn main() -> Result<()> { + let ret = main_with_error(); + if let Err(e) = &ret { + error!(error = %e, "Execution failed"); + } + ret +} diff --git a/crates/teepot/src/lib.rs b/crates/teepot/src/lib.rs index 293ef83..2d9f7cc 100644 --- a/crates/teepot/src/lib.rs +++ b/crates/teepot/src/lib.rs @@ -13,3 +13,15 @@ pub mod quote; pub mod server; pub mod sgx; pub mod tdx; + +/// pad a byte slice to a fixed sized array +pub fn pad(input: &[u8]) -> [u8; T] { + let mut output = [0; T]; + let len = input.len(); + if len > T { + output.copy_from_slice(&input[..T]); + } else { + output[..len].copy_from_slice(input); + } + output +} diff --git a/crates/teepot/src/tdx/mod.rs b/crates/teepot/src/tdx/mod.rs index 37c03de..09d6e4c 100644 --- a/crates/teepot/src/tdx/mod.rs +++ b/crates/teepot/src/tdx/mod.rs @@ -3,6 +3,8 @@ //! Intel TDX helper functions. +pub mod rtmr; + pub use crate::sgx::tcblevel::{parse_tcb_levels, EnumSet, TcbLevel}; use crate::sgx::QuoteError; pub use intel_tee_quote_verification_rs::Collateral; diff --git a/crates/teepot/src/tdx/rtmr.rs b/crates/teepot/src/tdx/rtmr.rs new file mode 100644 index 0000000..bbe478e --- /dev/null +++ b/crates/teepot/src/tdx/rtmr.rs @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2024 Matter Labs + +//! rtmr event data + +use crate::sgx::QuoteError; + +/// The actual rtmr event data handled in DCAP +#[repr(C, packed)] +pub struct TdxRtmrEvent { + /// Always 1 + version: u32, + + /// The RTMR that will be extended. As defined in + /// https://github.com/confidential-containers/td-shim/blob/main/doc/tdshim_spec.md#td-measurement + /// we will use RTMR 3 for guest application code and configuration. + rtmr_index: u64, + + /// Data that will be used to extend RTMR + extend_data: [u8; 48usize], + + /// Not used in DCAP + event_type: u32, + + /// Always 0 + event_data_size: u32, + + /// Not used in DCAP + event_data: Vec, +} + +impl Default for TdxRtmrEvent { + fn default() -> Self { + Self { + extend_data: [0; 48], + version: 1, + rtmr_index: 3, + event_type: 0, + event_data_size: 0, + event_data: Vec::new(), + } + } +} + +impl TdxRtmrEvent { + /// use the extend data + pub fn with_extend_data(mut self, extend_data: [u8; 48]) -> Self { + self.extend_data = extend_data; + self + } + + /// extend the rtmr index + pub fn with_rtmr_index(mut self, rtmr_index: u64) -> Self { + self.rtmr_index = rtmr_index; + self + } + + /// extending the index, consuming self + pub fn extend(self) -> Result<(), QuoteError> { + let event: Vec = self.into(); + + match tdx_attest_rs::tdx_att_extend(&event) { + tdx_attest_rs::tdx_attest_error_t::TDX_ATTEST_SUCCESS => Ok(()), + error_code => Err(error_code.into()), + } + } +} + +impl From for Vec { + fn from(val: TdxRtmrEvent) -> Self { + let event_ptr = &val as *const TdxRtmrEvent as *const u8; + let event_data_size = std::mem::size_of::() * val.event_data_size as usize; + let res_size = std::mem::size_of::() * 3 + + std::mem::size_of::() + + std::mem::size_of::<[u8; 48]>() + + event_data_size; + let mut res = vec![0; res_size]; + unsafe { + for (i, chunk) in res.iter_mut().enumerate().take(res_size - event_data_size) { + *chunk = *event_ptr.add(i); + } + } + let event_data = val.event_data; + for i in 0..event_data_size { + res[i + res_size - event_data_size] = event_data[i]; + } + + res + } +} diff --git a/packages/teepot/default.nix b/packages/teepot/default.nix index a8f64a0..0879e7e 100644 --- a/packages/teepot/default.nix +++ b/packages/teepot/default.nix @@ -17,6 +17,9 @@ outputs = [ "out" + "rtmr_calc" + "sha384_extend" + "tdx_extend" "tee_key_preexec" "tee_ratls_preexec" "tee_self_attestation_test"