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