Skip to content
This repository was archived by the owner on Oct 10, 2023. It is now read-only.
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
};

sbattach = import ./installer/patched-sbattach.nix { inherit pkgs; };

pcrTest = pkgs.callPackage ./pcr-test.nix { inherit inputs; };
});

defaultPackage = forAllSystems ({ system, ... }: self.packages.${system}.package);
Expand Down
1 change: 1 addition & 0 deletions generator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ doctest = false
chrono = { version = "0.4.23", default-features = false, features = [ "std", "clock" ] }
lazy_static = "1.4.0"
regex = { version = "1.7.1" }
serde = "1.0.137"
serde_json = "1.0.94"
tempfile = "3.3.0"
structopt = { version = "0.3.26", default-features = false }
Expand Down
102 changes: 78 additions & 24 deletions generator/src/bootable/efi.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use std::io::Seek;
use std::io::Write;
use std::path::Path;
use std::process::Command;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};

use tempfile::NamedTempFile;

use super::BootableToplevel;
use super::{BootableToplevel, PcrPhase};
use crate::Result;

pub struct EfiProgram {
Expand All @@ -16,9 +17,17 @@ impl EfiProgram {
Self { source }
}

pub fn write_unified_efi(&self, objcopy: &Path, outpath: &Path, stub: &Path) -> Result<()> {
pub fn write_unified_efi(
&self,
objcopy: &Path,
systemd_measure: &Option<PathBuf>,
pcr_phases: &Option<Vec<PcrPhase>>,
outpath: &Path,
stub: &Path,
) -> Result<()> {
let generation_path = &self.source.toplevel.0;
let mut kernel_params = NamedTempFile::new()?;
let mut pcr_sig = NamedTempFile::new()?;

write!(
kernel_params,
Expand All @@ -27,30 +36,75 @@ impl EfiProgram {
self.source.kernel_params.join(" ")
)?;

write!(pcr_sig, "{{}}")?;

if let Some(pcr_phases) = pcr_phases {
for phase in pcr_phases {
let mut cmd = Command::new(systemd_measure.as_ref().unwrap());
for bank in &phase.banks {
cmd.args(["--bank", bank]);
}
cmd.args([
"--osrel",
&format!("{}/etc/os-release", generation_path.display()),
"--cmdline",
&format!("{}", kernel_params.path().display()),
"--linux",
&format!("{}/kernel", generation_path.display()),
"--initrd",
&format!("{}/initrd", generation_path.display()),
"--phase",
&phase.phase_path.to_string(),
"--private-key",
&format!("{}", phase.private_key_file.display()),
"--public-key",
&format!("{}", phase.public_key_file.display()),
"--append",
&format!("{}", pcr_sig.path().display()),
"sign",
]);
let output = cmd.stderr(Stdio::inherit()).output()?;

if !output.status.success() {
return Err("failed to sign measurement".into());
}
pcr_sig.rewind()?;
pcr_sig.as_file().set_len(0)?;
pcr_sig.write_all(&output.stdout)?;
}
}

// Offsets taken from one of systemd's EFI tests:
// https://github.com/systemd/systemd/blob/01d0123f044d6c090b6ac2f6d304de2bdb19ae3b/test/test-efi-create-disk.sh#L32-L38
let status = Command::new(objcopy)
.args(&[
"--add-section",
&format!(".osrel={}/etc/os-release", generation_path.display()),
"--change-section-vma",
".osrel=0x20000",
let mut cmd = Command::new(objcopy);
cmd.args([
"--add-section",
&format!(".osrel={}/etc/os-release", generation_path.display()),
"--change-section-vma",
".osrel=0x20000",
"--add-section",
&format!(".cmdline={}", kernel_params.path().display()),
"--change-section-vma",
".cmdline=0x30000",
"--add-section",
&format!(".linux={}/kernel", generation_path.display()),
"--change-section-vma",
".linux=0x2000000",
"--add-section",
&format!(".initrd={}/initrd", generation_path.display()),
"--change-section-vma",
".initrd=0x3000000",
]);
if pcr_phases.is_some() {
cmd.args([
"--add-section",
&format!(".cmdline={}", kernel_params.path().display()),
&format!(".pcrsig={}", pcr_sig.path().display()),
"--change-section-vma",
".cmdline=0x30000",
"--add-section",
&format!(".linux={}/kernel", generation_path.display()),
"--change-section-vma",
".linux=0x2000000",
"--add-section",
&format!(".initrd={}/initrd", generation_path.display()),
"--change-section-vma",
".initrd=0x3000000",
&stub.display().to_string(),
&outpath.display().to_string(),
])
.status()?;
".pcrsig=0x40000",
]);
}
cmd.args([&stub.display().to_string(), &outpath.display().to_string()]);
let status = cmd.status()?;

if !status.success() {
return Err("failed to write unified efi".into());
Expand Down
2 changes: 2 additions & 0 deletions generator/src/bootable/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ use bootspec::SpecialisationName;
use crate::{Generation, Result};

mod efi;
mod pcr;
mod toplevel;

pub use efi::EfiProgram;
pub use pcr::PcrPhase;
pub use toplevel::BootableToplevel;

pub enum Bootable {
Expand Down
11 changes: 11 additions & 0 deletions generator/src/bootable/pcr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct PcrPhase {
pub phase_path: String,
pub banks: Vec<String>,
pub private_key_file: PathBuf,
pub public_key_file: PathBuf,
}
16 changes: 15 additions & 1 deletion generator/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::fs;
use std::path::PathBuf;

use generator::bootable::{self, Bootable, EfiProgram};
use generator::bootable::{self, Bootable, EfiProgram, PcrPhase};
use generator::{systemd_boot, Generation, Result};
use structopt::StructOpt;

Expand All @@ -13,6 +14,12 @@ struct Args {
/// The `objcopy` binary
#[structopt(long, requires_all = &["systemd-efi-stub", "unified-efi"])]
objcopy: Option<PathBuf>,
/// The `systemd-measure` binary
#[structopt(long, requires_all = &["systemd-efi-stub", "unified-efi"])]
systemd_measure: Option<PathBuf>,
/// The pcr phase spec json file
#[structopt(long, requires_all = &["systemd-efi-stub", "unified-efi"])]
pcr_phases: Option<PathBuf>,
/// Whether or not to combine the initrd and kernel into a unified EFI file
#[structopt(long, requires_all = &["systemd-efi-stub", "objcopy"])]
unified_efi: bool,
Expand Down Expand Up @@ -58,9 +65,16 @@ fn main() -> Result<()> {
toplevels.into_iter().map(Bootable::Linux).collect()
};

let pcr_phases: Option<Vec<PcrPhase>> = args.pcr_phases.map(|json_path| {
let cont = fs::read_to_string(json_path).unwrap();
serde_json::from_str(&cont).unwrap()
});

systemd_boot::generate(
bootables,
args.objcopy,
args.systemd_measure,
pcr_phases,
args.systemd_efi_stub,
args.systemd_machine_id_setup,
)?;
Expand Down
12 changes: 10 additions & 2 deletions generator/src/systemd_boot/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::process::Command;

use bootspec::SpecialisationName;

use crate::bootable::{Bootable, BootableToplevel, EfiProgram};
use crate::bootable::{Bootable, BootableToplevel, EfiProgram, PcrPhase};
use crate::Result;

// FIXME: placeholder dir
Expand Down Expand Up @@ -38,6 +38,8 @@ pub struct Contents {
pub fn generate(
bootables: Vec<Bootable>,
objcopy: Option<PathBuf>,
systemd_measure: Option<PathBuf>,
pcr_phases: Option<Vec<PcrPhase>>,
systemd_efi_stub: Option<PathBuf>,
systemd_machine_id_setup: PathBuf,
) -> Result<()> {
Expand All @@ -58,7 +60,13 @@ pub fn generate(
let objcopy = objcopy.as_ref().unwrap();
let systemd_efi_stub = systemd_efi_stub.as_ref().unwrap();

efi.write_unified_efi(objcopy, Path::new(&unified_dest), systemd_efi_stub)?;
efi.write_unified_efi(
objcopy,
&systemd_measure,
&pcr_phases,
Path::new(&unified_dest),
systemd_efi_stub,
)?;
}
Bootable::Linux(toplevel) => {
let (path, contents) = self::linux_entry_impl(&toplevel, &machine_id)?;
Expand Down
64 changes: 64 additions & 0 deletions nixos-module.nix
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,68 @@ in
type = types.nullOr types.str;
default = null;
};

pcrPhases = {
enable = lib.mkEnableOption "pcr phases";

signatures = lib.mkOption {
default = { };
type = types.attrsOf (types.submodule ({ name, ... }: {
config.phasePath = lib.mkDefault name;
options = {
phasePath = lib.mkOption {
type = types.str;
};
banks = lib.mkOption {
type = types.listOf types.str;
default = [ ];
};
privateKeyFile = lib.mkOption {
type = types.path // { apply = toString; };
};
publicKeyFile = lib.mkOption {
type = types.path // { apply = toString; };
};
};
}));
};
};
};
};
config = {
boot.kernelParams = lib.mkIf config.boot.loader.secureboot.pcrPhases.enable [ "systemd.gpt_auto=false" ];
boot.initrd = lib.mkIf config.boot.loader.secureboot.pcrPhases.enable {
availableKernelModules = [ "efivarfs" ];
systemd = {
package = config.systemd.package;
additionalUpstreamUnits = [ "systemd-pcrphase-initrd.service" ];
services.systemd-pcrphase-initrd = {
wantedBy = [ "initrd.target" ];
after = [ "systemd-modules-load.service" ];

# TODO: How should this be pulled in?
wants = [ "cryptsetup-pre.target" ];
};

# TODO: This is sketchy, but works as long as no initrd FSes
# are ordered before local-fs.target (zfs currently needlessly
# does this in nixos)
targets.cryptsetup-pre.after = [ "systemd-tmpfiles-setup.service" ];

storePaths = [ "${config.boot.initrd.systemd.package}/lib/systemd/systemd-pcrphase" ];
contents."/etc/tmpfiles.d/90-tpm-pcr-signature.conf".text = ''
C /run/systemd/tpm2-pcr-signature.json - - - - /.extra/tpm2-pcr-signature.json
'';
};
};
systemd = lib.mkIf config.boot.loader.secureboot.pcrPhases.enable {
additionalUpstreamSystemUnits = [
"systemd-pcrphase-sysinit.service"
"systemd-pcrphase.service"
];
services.systemd-pcrphase-sysinit.wantedBy = [ "basic.target" ];
services.systemd-pcrphase.wantedBy = [ "multi-user.target" ];
};
boot.loader.external = {
enable = true;
installHook = pkgs.writeShellScript "install-bootloader"
Expand All @@ -34,6 +93,11 @@ in

"--systemd-efi-stub"
"${config.systemd.package}/lib/systemd/boot/efi/linuxx64.efi.stub"
] ++ lib.optionals config.boot.loader.secureboot.pcrPhases.enable [
"--systemd-measure"
"${config.systemd.package}/lib/systemd/systemd-measure"
"--pcr-phases"
(pkgs.writeText "pcr-phases" (builtins.toJSON (lib.mapAttrsToList (n: v: v) config.boot.loader.secureboot.pcrPhases.signatures)))
]));

installerArgs = lib.escapeShellArgs
Expand Down
Loading