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/*.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/*.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/*.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
106 changes: 105 additions & 1 deletion lib/src/rootfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,27 @@ use std::fs;
use std::io;
use std::path;

use glob::glob;

const RELATIVE_KERNELS_PATH: &str = "usr/lib/modules/";
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_UKI_DISCOVERY_PATTERN: &str = "boot/EFI/Linux/*.efi";
const RELATIVE_SYSTEMDBOOT_PATH_X86: &str = "usr/lib/systemd/boot/efi/systemd-bootx64.efi";
const RELATIVE_SYSTEMDBOOT_PATH_AARCH64: &str = "usr/lib/systemd/boot/efi/systemd-bootx64.efi";
const RELATIVE_SYSTEMDBOOT_PATHS: [&str; 2] = [
RELATIVE_SYSTEMDBOOT_PATH_X86,
RELATIVE_SYSTEMDBOOT_PATH_AARCH64,
];

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 +38,94 @@ fn esp_path_absolute(rootfs_path: &path::Path) -> io::Result<path::PathBuf> {
}
}

fn uki_discover(rootfs_path: &path::Path) -> io::Result<Option<String>> {
let absolute_uki_discovery_pattern = rootfs_path.join(RELATIVE_UKI_DISCOVERY_PATTERN);
let mut discovered = glob(absolute_uki_discovery_pattern.to_str().unwrap())
.expect("invalid uki discovery glob pattern")
.filter_map(Result::ok);

let temptative = match discovered.next() {
Some(p) => p,
None => return Ok(None),
};

if discovered.next().is_some() {
return Err(io::Error::other(
"found more than 1 .efi during automatic UKI discovery",
));
}

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

fn uki_path_absolute(rootfs_path: &path::Path, uki: &str) -> io::Result<Option<String>> {
let relative_uki_path = if uki.is_empty() {
return uki_discover(rootfs_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>> {
for temptative in RELATIVE_SYSTEMDBOOT_PATHS
.iter()
.map(|p| rootfs_path.join(p))
{
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 +136,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,
Comment on lines +72 to +73

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if we really need to distinguish things here. They should all behave the same for PCR computation?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you refer to systemdboot from shim/grub? Are you referring to uki/addons and linux too? Could you elaborate your concern a bit more?

I really need to consider how this gears into the combine logic. I just mocked the "I'm running shim/grub and I upgrade into systemdboot" case but did not result in what I'd expect... combine.rs probably needs yet another tweak (or two).

TPMEventID::Pcr4Uki => TPMEG_UKI,
TPMEventID::Pcr4UkiAddon => TPMEG_UKI,
TPMEventID::Pcr4Vmlinuz => TPMEG_LINUX,
TPMEventID::Pcr7SecureBoot => TPMEG_SECUREBOOT,
TPMEventID::Pcr7Pk => TPMEG_SECUREBOOT,
Expand Down
Loading