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

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