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
71 changes: 59 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 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<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,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(),
Expand All @@ -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(())
}
Expand All @@ -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(),
Expand Down
40 changes: 40 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 Expand Up @@ -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
26 changes: 23 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,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<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,
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 {
Expand Down
72 changes: 71 additions & 1 deletion lib/src/rootfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
uki_addons: Vec<String>,
systemd_boot: Option<String>,
}

fn esp_path_absolute(rootfs_path: &path::Path) -> io::Result<path::PathBuf> {
Expand All @@ -26,14 +31,67 @@ 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)
}

fn systemdboot_path_absolute(rootfs_path: &path::Path) -> io::Result<Option<String>> {
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<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,
systemd_boot: systemdboot_path_absolute(&rootfs_path)?,
})
}

Expand All @@ -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<String> {
self.uki_addons.as_ref()
}

pub fn systemd_boot(&self) -> Option<&String> {
self.systemd_boot.as_ref()
}
}
7 changes: 7 additions & 0 deletions lib/src/tpmevents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ pub enum TPMEventID {
Pcr4Separator,
Pcr4Shim,
Pcr4Grub,
Pcr4SystemdBoot,
Pcr4Uki,
Pcr4UkiAddon,
Pcr4Vmlinuz,
Pcr7SecureBoot,
Pcr7Pk,
Expand Down Expand Up @@ -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,
Expand Down
Loading