Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 57 additions & 12 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use clap::{Args, Parser, Subcommand};
use log::LevelFilter;
use serde::{Deserialize, Serialize};

use std::path::PathBuf;

use compute_pcrs_lib::*;

#[derive(Parser, Debug)]
Expand All @@ -36,7 +38,12 @@ struct SecureBootVarStores {

#[derive(Subcommand, Debug)]
enum Command {
/// Compute all possible PCR values from the binaries available in the current environment
/// Compute the Authentihash of a PE binary
Authentihash {
/// Path to a PE binary
binary: String,
},
/// Compute all possible PCR values from the binaries available in the current environment. Meant to be run inside a Bootable Container.
All {
#[arg(
long,
Expand All @@ -49,10 +56,16 @@ enum Command {
secureboot_variables: SecureBootVarStores,
#[arg(
long,
default_value_t = false,
help = "Indicates that the linux image is an UKI image (e.g. is not vmlinuz))"
short,
default_value = "",
help = "Path to the UKI binary. It will try to find it in ${rootfs}/boot/EFI/Linux/uki.efi by default."
)]
uki: String,
#[arg(
long,
help = "Path to a UKI addon relative to the root dir. It can be passed multiple times."
)]
uki: bool,
uki_addon: Vec<String>,
#[arg(
long = "secureboot-disabled",
default_value_t = false,
Expand All @@ -67,6 +80,11 @@ enum Command {
mok_variables: String,
},
/// Compute PCR 4
///
/// It will try to find the UKI in the user-provided path. If empty,
/// it will assume the default Bootable Container UKI path:
/// ${rootfs}/boot/EFI/Linux/uki.efi.
/// If not found, it will then assume that it is the non UKI case.
Pcr4 {
#[arg(
long,
Expand All @@ -77,10 +95,16 @@ enum Command {
rootfs: String,
#[arg(
long,
default_value_t = false,
help = "Indicates that the linux image is an UKI image (e.g. is not vmlinuz))"
short,
default_value = "",
help = "Path to the UKI binary. It will try to find it in ${rootfs}/boot/EFI/Linux/uki.efi by default."
)]
uki: bool,
uki: String,
#[arg(
long,
help = "Path to a UKI addon relative to the root dir. It can be passed multiple times."
)]
uki_addon: Vec<String>,
#[arg(
long = "secureboot-disabled",
default_value_t = false,
Expand Down Expand Up @@ -143,16 +167,28 @@ fn main() -> Result<()> {
.init();

match &cli.command {
Command::Authentihash { binary } => {
let hash = authentihash(&PathBuf::from(binary)).unwrap();
println!("{hash}");
Ok(())
}
Command::All {
rootfs,
secureboot_variables,
uki,
uki_addon,
no_secureboot,
mok_variables,
} => {
let rfs = rootfs::RootFSTree::new(rootfs).unwrap();
let rfs = rootfs::RootFSTree::new(rootfs, uki, uki_addon.clone()).unwrap();
let pcrs = vec![
compute_pcr4(rfs.vmlinuz(), rfs.esp(), *uki, !no_secureboot),
compute_pcr4(
rfs.vmlinuz(),
rfs.esp(),
rfs.uki(),
rfs.uki_addons(),
!no_secureboot,
),
compute_pcr7(
secureboot_variables.efivars.as_deref(),
rfs.esp(),
Expand All @@ -170,10 +206,19 @@ fn main() -> Result<()> {
Command::Pcr4 {
rootfs,
uki,
uki_addon,
no_secureboot,
} => {
let rfs = rootfs::RootFSTree::new(rootfs).unwrap();
let pcr = compute_pcr4(rfs.vmlinuz(), rfs.esp(), *uki, !no_secureboot);
log::debug!("{uki_addon:?}");
let rfs = rootfs::RootFSTree::new(rootfs, uki, uki_addon.clone()).unwrap();
let pcr = compute_pcr4(
rfs.vmlinuz(),
rfs.esp(),
rfs.uki(),
rfs.uki_addons(),
!no_secureboot,
);

println!("{}", serde_json::to_string_pretty(&pcr).unwrap());
Ok(())
}
Expand All @@ -182,7 +227,7 @@ fn main() -> Result<()> {
secureboot_variables,
no_secureboot,
} => {
let rfs = rootfs::RootFSTree::new(rootfs).unwrap();
let rfs = rootfs::RootFSTree::new(rootfs, "", vec![]).unwrap();
let pcr = compute_pcr7(
secureboot_variables.efivars.as_deref(),
rfs.esp(),
Expand Down
18 changes: 18 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,24 @@ extract-info-target-container-image: pull-target-container-image
cat /etc/os-release \
> {{target_container_osinfo_path}}

run container *args:
#!/bin/bash
set -euo pipefail
cargo build --release
set -x
podman run --rm -ti \
--security-opt label=disable \
--mount=type=image,source={{container}},destination=/var/srv/image,rw=false \
-v $PWD/target/release/compute-pcrs:/usr/bin/compute-pcrs \
-v $PWD/test-data/:/var/srv/test-data \
-v $PWD/systemd-bootx64.efi:/var/srv/image/usr/lib/bootupd/updates/EFI/fedora/grubx64.efi \
fedora:latest \
compute-pcrs pcr4 \
--shim /var/srv/image/usr/lib/bootupd/updates/EFI/fedora/shimx64.efi \
--bootloader /var/srv/image/usr/lib/bootupd/updates/EFI/fedora/grubx64.efi \
--uki /var/srv/image/boot/EFI/Linux/6.15.10-200.fc42.x86_64.efi \
--uki-addon /var/srv/image/boot/EFI/Linux/6.15.10-200.fc42.x86_64.efi.extra.d/ignition.addon.efi

build-container:
#!/bin/bash
set -euo pipefail
Expand Down
24 changes: 21 additions & 3 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
// SPDX-License-Identifier: MIT
pub use pcrs::Pcr;

use std::path::Path;

pub mod certs;
mod esp;
mod linux;
Expand All @@ -16,9 +18,25 @@ pub mod shim;
pub mod tpmevents;
pub mod uefi;

pub fn compute_pcr4(kernels_dir: &str, esp_path: &str, uki: bool, secureboot: bool) -> Pcr {
let events = tpmevents::compute::pcr4_events(kernels_dir, esp_path, uki, secureboot);
Pcr::compile_from(&events)
pub fn authentihash(binary: &Path) -> Option<String> {
let pe = pefile::PeFile::load_from_file(&binary.to_string_lossy(), false)?;
Some(hex::encode(pe.authenticode()))
}

pub fn compute_pcr4(
kernels_dir: &str,
esp_path: &str,
uki: Option<&String>,
uki_addons: &[String],
secureboot: bool,
) -> Pcr {
Pcr::compile_from(&tpmevents::compute::pcr4_events(
kernels_dir,
esp_path,
uki,
uki_addons,
secureboot,
))
}

pub fn compute_pcr11(uki: &str) -> Pcr {
Expand Down
57 changes: 56 additions & 1 deletion lib/src/rootfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ const RELATIVE_ESP_OLD: &str = "usr/lib/bootupd/updates/";
// From fcos-44 on shim/grub are stored in different directories
// see https://fedoraproject.org/wiki/Changes/BootLoaderUpdatesPhase1
const RELATIVE_ESP_NEW: &str = "usr/lib/efi";
const RELATIVE_DEFAULT_UKI_PATH: &str = "boot/EFI/Linux/uki.efi";

pub struct RootFSTree {
esp_path: String,
kernels_path: String,
uki_path: Option<String>,
uki_addons: Vec<String>,
}

fn esp_path_absolute(rootfs_path: &path::Path) -> io::Result<path::PathBuf> {
Expand All @@ -26,14 +29,58 @@ fn esp_path_absolute(rootfs_path: &path::Path) -> io::Result<path::PathBuf> {
}
}

fn uki_path_absolute(rootfs_path: &path::Path, uki: &str) -> io::Result<Option<String>> {
let relative_uki_path = if uki.is_empty() {
RELATIVE_DEFAULT_UKI_PATH
} else {
uki
};

let temptative = rootfs_path.join(relative_uki_path);
if fs::exists(&temptative)? {
return Ok(Some(temptative.to_str().unwrap().into()));
}
Ok(None)
}

fn uki_addons_absolute(
rootfs_path: &path::Path,
uki_addons: Vec<String>,
) -> io::Result<Vec<String>> {
let mut absolute_addon_paths = vec![];
for addon in uki_addons.iter() {
let temptative = rootfs_path.join(addon);
if !fs::exists(&temptative)? {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!("Couldn't find uki addon {temptative:?}"),
));
}
absolute_addon_paths.push(temptative.to_str().unwrap().into())
}

Ok(absolute_addon_paths)
}

impl RootFSTree {
pub fn new(rootfs_path: &str) -> io::Result<RootFSTree> {
pub fn new(rootfs_path: &str, uki: &str, uki_addons: Vec<String>) -> io::Result<RootFSTree> {
let rootfs_path = path::absolute(rootfs_path)?;
let kernels_path = rootfs_path.join(RELATIVE_KERNELS_PATH);
let esp_path = esp_path_absolute(&rootfs_path)?;

let uki_path = uki_path_absolute(&rootfs_path, uki)?;
if uki_path.is_none() && !uki_addons.is_empty() {
return Err(io::Error::other(
"uki addons were provided but a valid uki is not found",
));
}
let uki_addons_paths = uki_addons_absolute(&rootfs_path, uki_addons)?;

Ok(RootFSTree {
esp_path: esp_path.to_str().unwrap().into(),
kernels_path: kernels_path.to_str().unwrap().into(),
uki_path,
uki_addons: uki_addons_paths,
})
}

Expand All @@ -44,4 +91,12 @@ impl RootFSTree {
pub fn vmlinuz(&self) -> &str {
self.kernels_path.as_str()
}

pub fn uki(&self) -> Option<&String> {
self.uki_path.as_ref()
}

pub fn uki_addons(&self) -> &Vec<String> {
self.uki_addons.as_ref()
}
}
4 changes: 4 additions & 0 deletions lib/src/tpmevents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ pub enum TPMEventID {
Pcr4Separator,
Pcr4Shim,
Pcr4Grub,
Pcr4Uki,
Pcr4UkiAddon,
Pcr4Vmlinuz,
Pcr7SecureBoot,
Pcr7Pk,
Expand Down Expand Up @@ -66,6 +68,8 @@ impl TPMEventID {
TPMEventID::Pcr4Separator => TPMEG_NEVER,
TPMEventID::Pcr4Shim => TPMEG_BOOTLOADER,
TPMEventID::Pcr4Grub => TPMEG_BOOTLOADER,
TPMEventID::Pcr4Uki => TPMEG_UKI,
TPMEventID::Pcr4UkiAddon => TPMEG_UKI,
TPMEventID::Pcr4Vmlinuz => TPMEG_LINUX,
TPMEventID::Pcr7SecureBoot => TPMEG_SECUREBOOT,
TPMEventID::Pcr7Pk => TPMEG_SECUREBOOT,
Expand Down
35 changes: 32 additions & 3 deletions lib/src/tpmevents/compute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-FileCopyrightText: Beñat Gartzia Arruabarrena <bgartzia@redhat.com>
//
// SPDX-License-Identifier: MIT
use crate::pefile::PeFile;
use lief::generic::Section;
use sha2::{Digest, Sha256};
use std::collections::HashSet;
Expand Down Expand Up @@ -50,7 +51,8 @@ const MODELS_MOKVARS: [TPMEventID; 3] = [
pub fn pcr4_events(
kernels_dir: &str,
esp_path: &str,
uki: bool,
uki_path: Option<&String>,
uki_addons: &[String],
secureboot: bool,
) -> Vec<TPMEvent> {
let mut events: Vec<TPMEvent> = vec![];
Expand Down Expand Up @@ -88,7 +90,35 @@ pub fn pcr4_events(
id: TPMEventID::Pcr4Grub,
});

if secureboot && !uki {
if let Some(uki) = uki_path {
events.push(TPMEvent {
name: "EV_EFI_BOOT_SERVICES_APPLICATION".into(),
pcr: n_pcr,
hash: PeFile::load_from_file(uki, false)
.expect("Can't open binary")
.authenticode(),
id: TPMEventID::Pcr4Uki,
});
events.extend(uki_addons.iter().map(|addon| {
TPMEvent {
name: "EV_EFI_BOOT_SERVICES_APPLICATION".into(),
pcr: n_pcr,
hash: PeFile::load_from_file(addon, false)
.expect("Can't open binary")
.authenticode(),
id: TPMEventID::Pcr4UkiAddon,
}
}));

if !secureboot {
events.push(TPMEvent {
name: "EV_EFI_BOOT_SERVICES_APPLICATION".into(),
pcr: n_pcr,
hash: linux::load_vmlinuz(kernels_dir).unwrap().authenticode(),
id: TPMEventID::Pcr4Vmlinuz,
});
}
} else if secureboot {
events.push(TPMEvent {
name: "EV_EFI_BOOT_SERVICES_APPLICATION".into(),
pcr: n_pcr,
Expand All @@ -97,7 +127,6 @@ pub fn pcr4_events(
});
}

// TODO: write condition for uki and implement logic
events
}

Expand Down