feat: add tdx-extend, sha384-extend and rtmr-calc

This enables pre-calculating the TDX rtmr[1,2,3] values for an attested boot process.

Signed-off-by: Harald Hoyer <harald@matterlabs.dev>
This commit is contained in:
Harald Hoyer 2024-12-17 13:18:10 +01:00
parent fbc4897dad
commit 5d32396966
Signed by: harald
GPG key ID: F519A1143B3FBE32
12 changed files with 603 additions and 2 deletions

112
Cargo.lock generated
View file

@ -975,6 +975,18 @@ dependencies = [
"cc", "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]] [[package]]
name = "combine" name = "combine"
version = "4.6.7" version = "4.6.7"
@ -1039,6 +1051,21 @@ dependencies = [
"libc", "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]] [[package]]
name = "crc24" name = "crc24"
version = "0.1.6" version = "0.1.6"
@ -1264,7 +1291,7 @@ dependencies = [
"const-oid", "const-oid",
"der_derive", "der_derive",
"flagset", "flagset",
"pem-rfc7468", "pem-rfc7468 0.7.0",
"zeroize", "zeroize",
] ]
@ -1527,7 +1554,7 @@ dependencies = [
"generic-array", "generic-array",
"group 0.13.0", "group 0.13.0",
"hkdf", "hkdf",
"pem-rfc7468", "pem-rfc7468 0.7.0",
"pkcs8 0.10.2", "pkcs8 0.10.2",
"rand_core 0.6.4", "rand_core 0.6.4",
"sec1 0.7.3", "sec1 0.7.3",
@ -1972,6 +1999,18 @@ dependencies = [
"web-sys", "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]] [[package]]
name = "group" name = "group"
version = "0.12.1" version = "0.12.1"
@ -3576,6 +3615,25 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 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]] [[package]]
name = "peeking_take_while" name = "peeking_take_while"
version = "0.1.2" version = "0.1.2"
@ -3591,6 +3649,15 @@ dependencies = [
"base64ct", "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]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.3.1" version = "2.3.1"
@ -4251,6 +4318,20 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "rtmr-calc"
version = "0.3.0"
dependencies = [
"anyhow",
"clap 4.5.23",
"gpt",
"hex",
"pe-sign",
"sha2",
"teepot",
"tracing",
]
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.24" version = "0.1.24"
@ -4810,6 +4891,16 @@ dependencies = [
"keccak", "keccak",
] ]
[[package]]
name = "sha384-extend"
version = "0.3.0"
dependencies = [
"anyhow",
"clap 4.5.23",
"hex",
"sha2",
]
[[package]] [[package]]
name = "sha3_ce" name = "sha3_ce"
version = "0.10.6" version = "0.10.6"
@ -4864,6 +4955,12 @@ dependencies = [
"rand_core 0.6.4", "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]] [[package]]
name = "slab" name = "slab"
version = "0.4.9" version = "0.4.9"
@ -5100,6 +5197,17 @@ dependencies = [
"bindgen 0.59.2", "bindgen 0.59.2",
] ]
[[package]]
name = "tdx-extend"
version = "0.3.0"
dependencies = [
"anyhow",
"clap 4.5.23",
"hex",
"teepot",
"tracing",
]
[[package]] [[package]]
name = "tee-key-preexec" name = "tee-key-preexec"
version = "0.3.0" version = "0.3.0"

View file

@ -29,6 +29,7 @@ ctrlc = "3.4"
enumset = { version = "1.1", features = ["serde"] } enumset = { version = "1.1", features = ["serde"] }
futures-core = { version = "0.3.30", features = ["alloc"], default-features = false } futures-core = { version = "0.3.30", features = ["alloc"], default-features = false }
getrandom = "0.2.14" getrandom = "0.2.14"
gpt = "4.0.0"
hex = { version = "0.4.3", features = ["std"], default-features = false } 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-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" } 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-integer = "0.1.46"
num-traits = "0.2.18" num-traits = "0.2.18"
p256 = "0.13.2" p256 = "0.13.2"
pe-sign = "0.1.10"
pgp = "0.14.2" pgp = "0.14.2"
pkcs8 = { version = "0.10" } pkcs8 = { version = "0.10" }
rand = "0.8" rand = "0.8"

18
bin/rtmr-calc/Cargo.toml Normal file
View file

@ -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

237
bin/rtmr-calc/src/main.rs Normal file
View file

@ -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<u8>,
}
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::<u8>::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(())
}

View file

@ -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

View file

@ -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(())
}

16
bin/tdx-extend/Cargo.toml Normal file
View file

@ -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

View file

@ -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
}

View file

@ -13,3 +13,15 @@ pub mod quote;
pub mod server; pub mod server;
pub mod sgx; pub mod sgx;
pub mod tdx; pub mod tdx;
/// pad a byte slice to a fixed sized array
pub fn pad<const T: usize>(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
}

View file

@ -3,6 +3,8 @@
//! Intel TDX helper functions. //! Intel TDX helper functions.
pub mod rtmr;
pub use crate::sgx::tcblevel::{parse_tcb_levels, EnumSet, TcbLevel}; pub use crate::sgx::tcblevel::{parse_tcb_levels, EnumSet, TcbLevel};
use crate::sgx::QuoteError; use crate::sgx::QuoteError;
pub use intel_tee_quote_verification_rs::Collateral; pub use intel_tee_quote_verification_rs::Collateral;

View file

@ -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<u8>,
}
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<u8> = 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<TdxRtmrEvent> for Vec<u8> {
fn from(val: TdxRtmrEvent) -> Self {
let event_ptr = &val as *const TdxRtmrEvent as *const u8;
let event_data_size = std::mem::size_of::<u8>() * val.event_data_size as usize;
let res_size = std::mem::size_of::<u32>() * 3
+ std::mem::size_of::<u64>()
+ 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
}
}

View file

@ -17,6 +17,9 @@
outputs = [ outputs = [
"out" "out"
"rtmr_calc"
"sha384_extend"
"tdx_extend"
"tee_key_preexec" "tee_key_preexec"
"tee_ratls_preexec" "tee_ratls_preexec"
"tee_self_attestation_test" "tee_self_attestation_test"