diff --git a/cli/src/main.rs b/cli/src/main.rs index ef63254..1c4b8f4 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -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)] @@ -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, @@ -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 relative to the target container image's rootfs. 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, #[arg( long = "secureboot-disabled", default_value_t = false, @@ -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, @@ -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, #[arg( long = "secureboot-disabled", default_value_t = false, @@ -143,16 +167,29 @@ 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.systemd_boot(), + rfs.uki(), + rfs.uki_addons(), + !no_secureboot, + ), compute_pcr7( secureboot_variables.efivars.as_deref(), rfs.esp(), @@ -170,10 +207,20 @@ 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.systemd_boot(), + rfs.uki(), + rfs.uki_addons(), + !no_secureboot, + ); + println!("{}", serde_json::to_string_pretty(&pcr).unwrap()); Ok(()) } @@ -182,7 +229,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(), diff --git a/justfile b/justfile index f842cca..ef0a439 100644 --- a/justfile +++ b/justfile @@ -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 @@ -176,3 +194,25 @@ test-default-mok-keys: prepare-test-deps > test/result.json 2>/dev/null diff test-fixtures/{{host_platform}}/${ID}-${OSTREE_VERSION}/pcr14.json test/result.json || (echo "FAILED" && exit 1) echo "OK" + +test-ukidev: build-container + #!/bin/bash + set -euo pipefail + # set -x + podman run --rm \ + --security-opt label=disable \ + -v $PWD/test-data/:/var/srv/test-data \ + --mount=type=image,source=localhost/fcos-trustee-uki,destination={{target_container_mount_point}},rw=false \ + {{container_image_name}} \ + compute-pcrs pcr4 \ + --rootfs {{target_container_mount_point}} \ + --uki boot/EFI/Linux/6.19.12-200.fc43.x86_64.efi \ + --uki-addon boot/EFI/Linux/6.19.12-200.fc43.x86_64.efi.extra.d/ignition.addon.efi \ + --secureboot-disabled + podman run --rm \ + --security-opt label=disable \ + -v $PWD/test-data/:/var/srv/test-data \ + --mount=type=image,source=localhost/fcos-trustee-uki,destination={{target_container_mount_point}},rw=false \ + {{container_image_name}} \ + compute-pcrs pcr11 \ + {{target_container_mount_point}}/boot/EFI/Linux/6.19.12-200.fc43.x86_64.efi diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 83ce228..5295d79 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -5,6 +5,8 @@ // SPDX-License-Identifier: MIT pub use pcrs::Pcr; +use std::path::Path; + pub mod certs; mod esp; mod linux; @@ -16,9 +18,27 @@ 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 { + 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, + systemd_boot: Option<&String>, + uki: Option<&String>, + uki_addons: &[String], + secureboot: bool, +) -> Pcr { + Pcr::compile_from(&tpmevents::compute::pcr4_events( + kernels_dir, + esp_path, + systemd_boot, + uki, + uki_addons, + secureboot, + )) } pub fn compute_pcr11(uki: &str) -> Pcr { diff --git a/lib/src/rootfs.rs b/lib/src/rootfs.rs index 20071c2..92b2694 100644 --- a/lib/src/rootfs.rs +++ b/lib/src/rootfs.rs @@ -12,10 +12,15 @@ 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"; +const RELATIVE_DEFAULT_SYSTEMDBOOT_PATH: &str = "usr/lib/systemd/boot/efi/systemd-bootx64.efi"; pub struct RootFSTree { esp_path: String, kernels_path: String, + uki_path: Option, + uki_addons: Vec, + systemd_boot: Option, } fn esp_path_absolute(rootfs_path: &path::Path) -> io::Result { @@ -26,14 +31,67 @@ fn esp_path_absolute(rootfs_path: &path::Path) -> io::Result { } } +fn uki_path_absolute(rootfs_path: &path::Path, uki: &str) -> io::Result> { + 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, +) -> io::Result> { + 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) +} + +fn systemdboot_path_absolute(rootfs_path: &path::Path) -> io::Result> { + let temptative = rootfs_path.join(RELATIVE_DEFAULT_SYSTEMDBOOT_PATH); + if fs::exists(&temptative)? { + return Ok(Some(temptative.to_str().unwrap().into())); + } + Ok(None) +} + impl RootFSTree { - pub fn new(rootfs_path: &str) -> io::Result { + pub fn new(rootfs_path: &str, uki: &str, uki_addons: Vec) -> io::Result { 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, + systemd_boot: systemdboot_path_absolute(&rootfs_path)?, }) } @@ -44,4 +102,16 @@ 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 { + self.uki_addons.as_ref() + } + + pub fn systemd_boot(&self) -> Option<&String> { + self.systemd_boot.as_ref() + } } diff --git a/lib/src/tpmevents.rs b/lib/src/tpmevents.rs index a188d52..f149bfa 100644 --- a/lib/src/tpmevents.rs +++ b/lib/src/tpmevents.rs @@ -28,6 +28,9 @@ pub enum TPMEventID { Pcr4Separator, Pcr4Shim, Pcr4Grub, + Pcr4SystemdBoot, + Pcr4Uki, + Pcr4UkiAddon, Pcr4Vmlinuz, Pcr7SecureBoot, Pcr7Pk, @@ -66,6 +69,10 @@ impl TPMEventID { TPMEventID::Pcr4Separator => TPMEG_NEVER, TPMEventID::Pcr4Shim => TPMEG_BOOTLOADER, TPMEventID::Pcr4Grub => TPMEG_BOOTLOADER, + //TODO: should we create another TPMEG_ for systemdboot? + TPMEventID::Pcr4SystemdBoot => TPMEG_BOOTLOADER, + TPMEventID::Pcr4Uki => TPMEG_UKI, + TPMEventID::Pcr4UkiAddon => TPMEG_UKI, TPMEventID::Pcr4Vmlinuz => TPMEG_LINUX, TPMEventID::Pcr7SecureBoot => TPMEG_SECUREBOOT, TPMEventID::Pcr7Pk => TPMEG_SECUREBOOT, diff --git a/lib/src/tpmevents/compute.rs b/lib/src/tpmevents/compute.rs index 2c52970..bfe1212 100644 --- a/lib/src/tpmevents/compute.rs +++ b/lib/src/tpmevents/compute.rs @@ -2,6 +2,7 @@ // SPDX-FileCopyrightText: BeƱat Gartzia Arruabarrena // // SPDX-License-Identifier: MIT +use crate::pefile::PeFile; use lief::generic::Section; use sha2::{Digest, Sha256}; use std::collections::HashSet; @@ -50,12 +51,15 @@ const MODELS_MOKVARS: [TPMEventID; 3] = [ pub fn pcr4_events( kernels_dir: &str, esp_path: &str, - uki: bool, + systemd_boot_path: Option<&String>, + uki_path: Option<&String>, + uki_addons: &[String], secureboot: bool, ) -> Vec { let mut events: Vec = vec![]; let esp = esp::Esp::new(esp_path).unwrap(); let n_pcr = 4; + let mut is_systemd_boot = false; // Calling EFI App events.push(TPMEvent { @@ -73,22 +77,62 @@ pub fn pcr4_events( id: TPMEventID::Pcr4Separator, }); - // Binaries - events.push(TPMEvent { - name: "EV_EFI_BOOT_SERVICES_APPLICATION".into(), - pcr: n_pcr, - hash: esp.shim().authenticode(), - id: TPMEventID::Pcr4Shim, - }); + if let Some(sysd_boot) = systemd_boot_path { + events.push(TPMEvent { + name: "EV_EFI_BOOT_SERVICES_APPLICATION".into(), + pcr: n_pcr, + hash: PeFile::load_from_file(sysd_boot, false) + .expect("Can't open binary") + .authenticode(), + id: TPMEventID::Pcr4SystemdBoot, + }); + is_systemd_boot = true; + } else { + // Binaries + events.push(TPMEvent { + name: "EV_EFI_BOOT_SERVICES_APPLICATION".into(), + pcr: n_pcr, + hash: esp.shim().authenticode(), + id: TPMEventID::Pcr4Shim, + }); - events.push(TPMEvent { - name: "EV_EFI_BOOT_SERVICES_APPLICATION".into(), - pcr: n_pcr, - hash: esp.grub().authenticode(), - id: TPMEventID::Pcr4Grub, - }); + events.push(TPMEvent { + name: "EV_EFI_BOOT_SERVICES_APPLICATION".into(), + pcr: n_pcr, + hash: esp.grub().authenticode(), + 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 && !is_systemd_boot { + 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, @@ -97,7 +141,6 @@ pub fn pcr4_events( }); } - // TODO: write condition for uki and implement logic events }