initial commit
This commit is contained in:
commit
518ad3ba79
9 changed files with 1584 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
/eventlog
|
1155
Cargo.lock
generated
Normal file
1155
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
19
Cargo.toml
Normal file
19
Cargo.toml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
[package]
|
||||||
|
name = "calc-pcr4"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
authenticode = { version = "0.4.3", features = ["object"] }
|
||||||
|
clap = { version = "4.5.11", features = ["derive"] }
|
||||||
|
digest = "0.10.7"
|
||||||
|
fallible-iterator = "0.2.0"
|
||||||
|
hex = "0.4.3"
|
||||||
|
libc = "0.2.155"
|
||||||
|
object = "0.36.2"
|
||||||
|
sha1 = "0.10.6"
|
||||||
|
sha2 = "0.10.8"
|
||||||
|
thiserror = "1.0.63"
|
||||||
|
typed-path = "0.9.1"
|
||||||
|
uefi-eventlog = { git = "https://github.com/slonkazoid/uefi-eventlog-rs.git", rev = "a49027dc8eb7fcf09f7b797e08dbaa24d36dfc55" }
|
||||||
|
uuid = "1.10.0"
|
42
src/args.rs
Normal file
42
src/args.rs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
use clap::{Parser, ValueEnum};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[derive(ValueEnum, Clone, Copy)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum Bits {
|
||||||
|
_64,
|
||||||
|
_32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(ValueEnum, Clone, Copy)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum Hash {
|
||||||
|
Sha1,
|
||||||
|
Sha256,
|
||||||
|
Sha512,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
pub struct Args {
|
||||||
|
#[arg(
|
||||||
|
short,
|
||||||
|
long,
|
||||||
|
help = "Address width of the EFI executables",
|
||||||
|
default_value = "64"
|
||||||
|
)]
|
||||||
|
pub bits: Bits,
|
||||||
|
|
||||||
|
#[arg(short, long, help = "Hashing algorithm", default_value = "sha256")]
|
||||||
|
pub algo: Hash,
|
||||||
|
|
||||||
|
#[arg(
|
||||||
|
short,
|
||||||
|
long,
|
||||||
|
help = "Path of the event log",
|
||||||
|
default_value = "/sys/kernel/security/tpm0/binary_bios_measurements"
|
||||||
|
)]
|
||||||
|
pub event_log: PathBuf,
|
||||||
|
|
||||||
|
#[arg(short, long, help = "Path of a JSON file with drop-ins for the files")]
|
||||||
|
pub drop_ins: Option<PathBuf>,
|
||||||
|
}
|
42
src/device_path.rs
Normal file
42
src/device_path.rs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
use uefi_eventlog::parsed::{DevicePath, DevicePathInfo, DevicePathInfoHardDriveSignatureType};
|
||||||
|
|
||||||
|
use crate::util::fixup_uuid;
|
||||||
|
|
||||||
|
fn recurse_device_path(
|
||||||
|
a: DevicePath,
|
||||||
|
mut uuid: Option<u128>,
|
||||||
|
mut path: Option<String>,
|
||||||
|
) -> (Option<u128>, Option<String>) {
|
||||||
|
match a.next {
|
||||||
|
Some(next) => {
|
||||||
|
match &next.info {
|
||||||
|
DevicePathInfo::HardDrive {
|
||||||
|
partition_signature,
|
||||||
|
signature_type,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
if matches!(signature_type, DevicePathInfoHardDriveSignatureType::Guid) {
|
||||||
|
let mut sig = [0u8; 16];
|
||||||
|
partition_signature
|
||||||
|
.as_slice()
|
||||||
|
.read_exact(&mut sig)
|
||||||
|
.expect("expected 128-bit Guid");
|
||||||
|
uuid = Some(fixup_uuid(u128::from_be_bytes(sig)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DevicePathInfo::FilePath { path: _path } => {
|
||||||
|
path = Some(_path.clone());
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
return recurse_device_path(*next, uuid, path);
|
||||||
|
}
|
||||||
|
None => return (uuid, path),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn traverse_device_path(a: DevicePath) -> (Option<u128>, Option<String>) {
|
||||||
|
recurse_device_path(a, None, None)
|
||||||
|
}
|
38
src/find_mount_point.rs
Normal file
38
src/find_mount_point.rs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
use libc::*;
|
||||||
|
|
||||||
|
use std::ffi::{CStr, CString, OsStr};
|
||||||
|
use std::io;
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
pub fn by_partuuid(id: &str) -> io::Result<Option<PathBuf>> {
|
||||||
|
by_dev(PathBuf::from("/dev/disk/by-partuuid").join(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn by_dev(path: impl AsRef<Path>) -> io::Result<Option<PathBuf>> {
|
||||||
|
let path = std::fs::canonicalize(path)?;
|
||||||
|
unsafe {
|
||||||
|
let path_cstring =
|
||||||
|
CString::from_vec_unchecked(Vec::from(path.as_os_str().as_encoded_bytes()));
|
||||||
|
|
||||||
|
let mounts_file = setmntent(c"/proc/mounts".as_ptr(), c"r".as_ptr());
|
||||||
|
if mounts_file.is_null() {
|
||||||
|
return Err(io::Error::from_raw_os_error(*__errno_location()));
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let entry = getmntent(mounts_file);
|
||||||
|
if entry.is_null() {
|
||||||
|
return Err(io::Error::from_raw_os_error(*__errno_location()));
|
||||||
|
}
|
||||||
|
let entry = *entry;
|
||||||
|
|
||||||
|
let dev = CStr::from_ptr(entry.mnt_fsname);
|
||||||
|
|
||||||
|
if dev == path_cstring.as_c_str() {
|
||||||
|
let mount_point = OsStr::from_bytes(CStr::from_ptr(entry.mnt_dir).to_bytes());
|
||||||
|
return Ok(Some(PathBuf::from(mount_point)));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
105
src/hash.rs
Normal file
105
src/hash.rs
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
use std::fs::OpenOptions;
|
||||||
|
use std::io::Read;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use digest::{Digest, Update};
|
||||||
|
use object::pe::{ImageNtHeaders32, ImageNtHeaders64};
|
||||||
|
use object::read::pe::{ImageNtHeaders, PeFile};
|
||||||
|
|
||||||
|
use crate::args::{Bits, Hash};
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
|
pub enum Hasher {
|
||||||
|
Sha1(sha1::Sha1),
|
||||||
|
Sha256(sha2::Sha256),
|
||||||
|
Sha512(sha2::Sha512),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hasher {
|
||||||
|
pub fn finalize(self) -> Vec<u8> {
|
||||||
|
match self {
|
||||||
|
Hasher::Sha1(h) => h.finalize().into_iter().collect(),
|
||||||
|
Hasher::Sha256(h) => h.finalize().into_iter().collect(),
|
||||||
|
Hasher::Sha512(h) => h.finalize().into_iter().collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn output_size(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
Hasher::Sha1(_) => 20,
|
||||||
|
Hasher::Sha256(_) => 32,
|
||||||
|
Hasher::Sha512(_) => 64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Update for Hasher {
|
||||||
|
fn chain(self, data: impl AsRef<[u8]>) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Self::Sha1(h) => Self::Sha1(Update::chain(h, data)),
|
||||||
|
Self::Sha256(h) => Self::Sha256(Update::chain(h, data)),
|
||||||
|
Self::Sha512(h) => Self::Sha512(Update::chain(h, data)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, data: &[u8]) {
|
||||||
|
match self {
|
||||||
|
Self::Sha1(h) => Update::update(h, data),
|
||||||
|
Self::Sha256(h) => Update::update(h, data),
|
||||||
|
Self::Sha512(h) => Update::update(h, data),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Hash> for Hasher {
|
||||||
|
fn from(value: Hash) -> Self {
|
||||||
|
match value {
|
||||||
|
Hash::Sha1 => Self::Sha1(sha1::Sha1::new()),
|
||||||
|
Hash::Sha256 => Self::Sha256(sha2::Sha256::new()),
|
||||||
|
Hash::Sha512 => Self::Sha512(sha2::Sha512::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hash_generic<H: ImageNtHeaders>(buf: &[u8], update: &mut dyn Update) -> Result<(), Error> {
|
||||||
|
let pe: PeFile<H> = PeFile::parse(buf)?;
|
||||||
|
authenticode::authenticode_digest(&pe, update)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hash_by_path(
|
||||||
|
path: impl AsRef<Path>,
|
||||||
|
hasher: &mut dyn Update,
|
||||||
|
bits: Bits,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let mut file = OpenOptions::new().read(true).open(path)?;
|
||||||
|
let mut buf = Vec::with_capacity(4096);
|
||||||
|
file.read_to_end(&mut buf)?;
|
||||||
|
|
||||||
|
match bits {
|
||||||
|
Bits::_64 => hash_generic::<ImageNtHeaders64>(&buf, hasher),
|
||||||
|
Bits::_32 => hash_generic::<ImageNtHeaders32>(&buf, hasher),
|
||||||
|
}?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait MeasureInPlace {
|
||||||
|
fn measure(&mut self, data: &[u8], hasher: Hasher);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MeasureInPlace for Vec<u8> {
|
||||||
|
fn measure(&mut self, data: &[u8], mut hasher: Hasher) {
|
||||||
|
let len = hasher.output_size();
|
||||||
|
let buf = [self.as_slice(), data].concat();
|
||||||
|
hasher.update(&buf);
|
||||||
|
self.clear();
|
||||||
|
let mut hash = hasher.finalize();
|
||||||
|
let encoded = hex::encode(hash.as_slice());
|
||||||
|
eprintln!("new hash: {encoded:0>len$}", len = len * 2);
|
||||||
|
self.append(&mut hash);
|
||||||
|
}
|
||||||
|
}
|
168
src/main.rs
Normal file
168
src/main.rs
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
#![feature(let_chains)]
|
||||||
|
|
||||||
|
mod args;
|
||||||
|
mod device_path;
|
||||||
|
mod find_mount_point;
|
||||||
|
mod hash;
|
||||||
|
mod util;
|
||||||
|
|
||||||
|
use std::fs::OpenOptions;
|
||||||
|
use std::io;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use args::Args;
|
||||||
|
use clap::Parser;
|
||||||
|
use device_path::traverse_device_path;
|
||||||
|
use fallible_iterator::FallibleIterator;
|
||||||
|
use thiserror::Error;
|
||||||
|
use typed_path::{Utf8UnixEncoding, Utf8WindowsPathBuf};
|
||||||
|
use uefi_eventlog::parsed::ParsedEventData;
|
||||||
|
use uefi_eventlog::EventType;
|
||||||
|
|
||||||
|
use crate::hash::*;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
enum Error {
|
||||||
|
#[error("error while parsing PE file: {0}")]
|
||||||
|
PeParseError(#[from] object::Error),
|
||||||
|
#[error("error while generating authenticode hash: {0}")]
|
||||||
|
AuthenticodeError(authenticode::PeOffsetError),
|
||||||
|
#[error(transparent)]
|
||||||
|
IoError(#[from] io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<authenticode::PeOffsetError> for Error {
|
||||||
|
fn from(value: authenticode::PeOffsetError) -> Self {
|
||||||
|
Self::AuthenticodeError(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args = Args::parse();
|
||||||
|
let hash_len = Hasher::from(args.algo).output_size();
|
||||||
|
|
||||||
|
let mut file = OpenOptions::new().read(true).open(args.event_log).unwrap();
|
||||||
|
|
||||||
|
let fucking_settings = uefi_eventlog::ParseSettings::new();
|
||||||
|
let mut events = uefi_eventlog::Parser::new(&mut file, &fucking_settings);
|
||||||
|
|
||||||
|
let size = Hasher::from(args.algo).output_size();
|
||||||
|
|
||||||
|
let mut state = vec![0u8; size];
|
||||||
|
|
||||||
|
'process_event: loop {
|
||||||
|
let event = match events.next() {
|
||||||
|
Ok(Some(v)) => v,
|
||||||
|
Ok(None) => break,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("error while iterating over the event log?: {err}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if event.pcr_index == 4 {
|
||||||
|
eprintln!("processing {:?} event on pcr 4", event.event);
|
||||||
|
} else {
|
||||||
|
eprintln!(
|
||||||
|
"ignoring {:?} event on pcr {}",
|
||||||
|
event.event, event.pcr_index
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
'measure_file: {
|
||||||
|
match event.event {
|
||||||
|
EventType::EFIBootServicesApplication => {
|
||||||
|
let event_data = event.parsed_data.unwrap().unwrap();
|
||||||
|
if let ParsedEventData::ImageLoadEvent { device_path, .. } = event_data
|
||||||
|
&& let Some(device_path) = device_path
|
||||||
|
{
|
||||||
|
let (uuid, path) = traverse_device_path(device_path);
|
||||||
|
|
||||||
|
let windows_path_str = if let Some(windows_path_str) = path {
|
||||||
|
windows_path_str
|
||||||
|
} else {
|
||||||
|
break 'measure_file;
|
||||||
|
};
|
||||||
|
|
||||||
|
let windows_path = Utf8WindowsPathBuf::from(windows_path_str);
|
||||||
|
let unix_path = windows_path.with_encoding::<Utf8UnixEncoding>();
|
||||||
|
|
||||||
|
let esp;
|
||||||
|
|
||||||
|
'mnt: {
|
||||||
|
'get_mnt: {
|
||||||
|
let id = match uuid {
|
||||||
|
Some(id) => id,
|
||||||
|
None => {
|
||||||
|
eprintln!("uuid not in event log");
|
||||||
|
break 'get_mnt;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let maybe_point = match find_mount_point::by_partuuid(
|
||||||
|
&uuid::Uuid::from_u128(id).to_string(),
|
||||||
|
) {
|
||||||
|
Ok(maybe_point) => maybe_point,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("error while looking up mount point: {err}");
|
||||||
|
break 'get_mnt;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match maybe_point {
|
||||||
|
Some(point) => {
|
||||||
|
esp = point;
|
||||||
|
break 'mnt;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
eprintln!("device not mounted");
|
||||||
|
break 'get_mnt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback
|
||||||
|
eprintln!("couldn't find mount point, assuming \"/boot/efi\"");
|
||||||
|
esp = PathBuf::from("/boot/efi");
|
||||||
|
}
|
||||||
|
|
||||||
|
let full_path = esp.join(unix_path.strip_prefix("/").unwrap_or(&unix_path));
|
||||||
|
eprintln!("measuring file {full_path:?}");
|
||||||
|
|
||||||
|
let mut hasher = Hasher::from(args.algo);
|
||||||
|
let hash = match hash_by_path(&full_path, &mut hasher, args.bits) {
|
||||||
|
Ok(_) => {
|
||||||
|
let hash = hasher.finalize();
|
||||||
|
let encoded = hex::encode(hash.as_slice());
|
||||||
|
eprintln!("hash: {encoded:0>len$}", len = hash_len * 2);
|
||||||
|
hash
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("error while hashing file: {err}");
|
||||||
|
break 'measure_file;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
state.measure(&hash, args.algo.into());
|
||||||
|
eprintln!("measured into state");
|
||||||
|
continue 'process_event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(digest) = event.digests.first() {
|
||||||
|
let digest = digest.digest();
|
||||||
|
let encoded = hex::encode(digest.as_slice());
|
||||||
|
eprintln!("applying digest: {encoded:0>len$}", len = hash_len * 2);
|
||||||
|
state.measure(&digest, args.algo.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let hash = state.as_slice();
|
||||||
|
let hex = hex::encode(hash);
|
||||||
|
|
||||||
|
println!("{hex:0>len$}", len = hash_len * 2);
|
||||||
|
}
|
13
src/util.rs
Normal file
13
src/util.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
pub fn fixup_uuid(data: u128) -> u128 {
|
||||||
|
let (first, second, third, fourth): (u32, u16, u16, u64) =
|
||||||
|
unsafe { std::mem::transmute(data.to_be_bytes()) };
|
||||||
|
let real = unsafe {
|
||||||
|
std::mem::transmute((
|
||||||
|
first.swap_bytes(),
|
||||||
|
second.swap_bytes(),
|
||||||
|
third.swap_bytes(),
|
||||||
|
fourth,
|
||||||
|
))
|
||||||
|
};
|
||||||
|
u128::from_be_bytes(real)
|
||||||
|
}
|
Loading…
Reference in a new issue