Skip to content
This repository was archived by the owner on Oct 10, 2023. It is now read-only.

Commit 0d4e983

Browse files
pcr phases
1 parent 688442f commit 0d4e983

File tree

11 files changed

+332
-30
lines changed

11 files changed

+332
-30
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flake.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flake.nix

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
};
5757

5858
sbattach = import ./installer/patched-sbattach.nix { inherit pkgs; };
59+
60+
pcrTest = pkgs.callPackage ./pcr-test.nix { inherit inputs; };
5961
});
6062

6163
defaultPackage = forAllSystems ({ system, ... }: self.packages.${system}.package);

generator/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ doctest = false
1616
chrono = { version = "0.4.23", default-features = false, features = [ "std", "clock" ] }
1717
lazy_static = "1.4.0"
1818
regex = { version = "1.7.1" }
19+
serde = "1.0.137"
1920
serde_json = "1.0.94"
2021
tempfile = "3.3.0"
2122
structopt = { version = "0.3.26", default-features = false }

generator/src/bootable/efi.rs

Lines changed: 78 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
use std::io::Seek;
12
use std::io::Write;
2-
use std::path::Path;
3-
use std::process::Command;
3+
use std::path::{Path, PathBuf};
4+
use std::process::{Command, Stdio};
45

56
use tempfile::NamedTempFile;
67

7-
use super::BootableToplevel;
8+
use super::{BootableToplevel, PcrPhase};
89
use crate::Result;
910

1011
pub struct EfiProgram {
@@ -16,9 +17,17 @@ impl EfiProgram {
1617
Self { source }
1718
}
1819

19-
pub fn write_unified_efi(&self, objcopy: &Path, outpath: &Path, stub: &Path) -> Result<()> {
20+
pub fn write_unified_efi(
21+
&self,
22+
objcopy: &Path,
23+
systemd_measure: &Option<PathBuf>,
24+
pcr_phases: &Option<Vec<PcrPhase>>,
25+
outpath: &Path,
26+
stub: &Path,
27+
) -> Result<()> {
2028
let generation_path = &self.source.toplevel.0;
2129
let mut kernel_params = NamedTempFile::new()?;
30+
let mut pcr_sig = NamedTempFile::new()?;
2231

2332
write!(
2433
kernel_params,
@@ -27,30 +36,75 @@ impl EfiProgram {
2736
self.source.kernel_params.join(" ")
2837
)?;
2938

39+
write!(pcr_sig, "{{}}")?;
40+
41+
if let Some(pcr_phases) = pcr_phases {
42+
for phase in pcr_phases {
43+
let mut cmd = Command::new(systemd_measure.as_ref().unwrap());
44+
for bank in &phase.banks {
45+
cmd.args(["--bank", bank]);
46+
}
47+
cmd.args([
48+
"--osrel",
49+
&format!("{}/etc/os-release", generation_path.display()),
50+
"--cmdline",
51+
&format!("{}", kernel_params.path().display()),
52+
"--linux",
53+
&format!("{}/kernel", generation_path.display()),
54+
"--initrd",
55+
&format!("{}/initrd", generation_path.display()),
56+
"--phase",
57+
&phase.phase_path.to_string(),
58+
"--private-key",
59+
&format!("{}", phase.private_key_file.display()),
60+
"--public-key",
61+
&format!("{}", phase.public_key_file.display()),
62+
"--append",
63+
&format!("{}", pcr_sig.path().display()),
64+
"sign",
65+
]);
66+
let output = cmd.stderr(Stdio::inherit()).output()?;
67+
68+
if !output.status.success() {
69+
return Err("failed to sign measurement".into());
70+
}
71+
pcr_sig.rewind()?;
72+
pcr_sig.as_file().set_len(0)?;
73+
pcr_sig.write_all(&output.stdout)?;
74+
}
75+
}
76+
3077
// Offsets taken from one of systemd's EFI tests:
3178
// https://github.com/systemd/systemd/blob/01d0123f044d6c090b6ac2f6d304de2bdb19ae3b/test/test-efi-create-disk.sh#L32-L38
32-
let status = Command::new(objcopy)
33-
.args(&[
34-
"--add-section",
35-
&format!(".osrel={}/etc/os-release", generation_path.display()),
36-
"--change-section-vma",
37-
".osrel=0x20000",
79+
let mut cmd = Command::new(objcopy);
80+
cmd.args([
81+
"--add-section",
82+
&format!(".osrel={}/etc/os-release", generation_path.display()),
83+
"--change-section-vma",
84+
".osrel=0x20000",
85+
"--add-section",
86+
&format!(".cmdline={}", kernel_params.path().display()),
87+
"--change-section-vma",
88+
".cmdline=0x30000",
89+
"--add-section",
90+
&format!(".linux={}/kernel", generation_path.display()),
91+
"--change-section-vma",
92+
".linux=0x2000000",
93+
"--add-section",
94+
&format!(".initrd={}/initrd", generation_path.display()),
95+
"--change-section-vma",
96+
".initrd=0x3000000",
97+
]);
98+
if pcr_phases.is_some() {
99+
cmd.args([
38100
"--add-section",
39-
&format!(".cmdline={}", kernel_params.path().display()),
101+
&format!(".pcrsig={}", pcr_sig.path().display()),
40102
"--change-section-vma",
41-
".cmdline=0x30000",
42-
"--add-section",
43-
&format!(".linux={}/kernel", generation_path.display()),
44-
"--change-section-vma",
45-
".linux=0x2000000",
46-
"--add-section",
47-
&format!(".initrd={}/initrd", generation_path.display()),
48-
"--change-section-vma",
49-
".initrd=0x3000000",
50-
&stub.display().to_string(),
51-
&outpath.display().to_string(),
52-
])
53-
.status()?;
103+
".pcrsig=0x40000",
104+
]);
105+
}
106+
cmd.args([&stub.display().to_string(), &outpath.display().to_string()]);
107+
let status = cmd.status()?;
54108

55109
if !status.success() {
56110
return Err("failed to write unified efi".into());

generator/src/bootable/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ use bootspec::SpecialisationName;
55
use crate::{Generation, Result};
66

77
mod efi;
8+
mod pcr;
89
mod toplevel;
910

1011
pub use efi::EfiProgram;
12+
pub use pcr::PcrPhase;
1113
pub use toplevel::BootableToplevel;
1214

1315
pub enum Bootable {

generator/src/bootable/pcr.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
use serde::{Deserialize, Serialize};
2+
use std::path::PathBuf;
3+
4+
#[derive(Serialize, Deserialize, Debug)]
5+
#[serde(rename_all = "camelCase")]
6+
pub struct PcrPhase {
7+
pub phase_path: String,
8+
pub banks: Vec<String>,
9+
pub private_key_file: PathBuf,
10+
pub public_key_file: PathBuf,
11+
}

generator/src/main.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
use std::fs;
12
use std::path::PathBuf;
23

3-
use generator::bootable::{self, Bootable, EfiProgram};
4+
use generator::bootable::{self, Bootable, EfiProgram, PcrPhase};
45
use generator::{systemd_boot, Generation, Result};
56
use structopt::StructOpt;
67

@@ -13,6 +14,12 @@ struct Args {
1314
/// The `objcopy` binary
1415
#[structopt(long, requires_all = &["systemd-efi-stub", "unified-efi"])]
1516
objcopy: Option<PathBuf>,
17+
/// The `systemd-measure` binary
18+
#[structopt(long, requires_all = &["systemd-efi-stub", "unified-efi"])]
19+
systemd_measure: Option<PathBuf>,
20+
/// The pcr phase spec json file
21+
#[structopt(long, requires_all = &["systemd-efi-stub", "unified-efi"])]
22+
pcr_phases: Option<PathBuf>,
1623
/// Whether or not to combine the initrd and kernel into a unified EFI file
1724
#[structopt(long, requires_all = &["systemd-efi-stub", "objcopy"])]
1825
unified_efi: bool,
@@ -58,9 +65,16 @@ fn main() -> Result<()> {
5865
toplevels.into_iter().map(Bootable::Linux).collect()
5966
};
6067

68+
let pcr_phases: Option<Vec<PcrPhase>> = args.pcr_phases.map(|json_path| {
69+
let cont = fs::read_to_string(json_path).unwrap();
70+
serde_json::from_str(&cont).unwrap()
71+
});
72+
6173
systemd_boot::generate(
6274
bootables,
6375
args.objcopy,
76+
args.systemd_measure,
77+
pcr_phases,
6478
args.systemd_efi_stub,
6579
args.systemd_machine_id_setup,
6680
)?;

generator/src/systemd_boot/mod.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::process::Command;
66

77
use bootspec::SpecialisationName;
88

9-
use crate::bootable::{Bootable, BootableToplevel, EfiProgram};
9+
use crate::bootable::{Bootable, BootableToplevel, EfiProgram, PcrPhase};
1010
use crate::Result;
1111

1212
// FIXME: placeholder dir
@@ -38,6 +38,8 @@ pub struct Contents {
3838
pub fn generate(
3939
bootables: Vec<Bootable>,
4040
objcopy: Option<PathBuf>,
41+
systemd_measure: Option<PathBuf>,
42+
pcr_phases: Option<Vec<PcrPhase>>,
4143
systemd_efi_stub: Option<PathBuf>,
4244
systemd_machine_id_setup: PathBuf,
4345
) -> Result<()> {
@@ -58,7 +60,13 @@ pub fn generate(
5860
let objcopy = objcopy.as_ref().unwrap();
5961
let systemd_efi_stub = systemd_efi_stub.as_ref().unwrap();
6062

61-
efi.write_unified_efi(objcopy, Path::new(&unified_dest), systemd_efi_stub)?;
63+
efi.write_unified_efi(
64+
objcopy,
65+
&systemd_measure,
66+
&pcr_phases,
67+
Path::new(&unified_dest),
68+
systemd_efi_stub,
69+
)?;
6270
}
6371
Bootable::Linux(toplevel) => {
6472
let (path, contents) = self::linux_entry_impl(&toplevel, &machine_id)?;

nixos-module.nix

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,59 @@ in
1414
type = types.nullOr types.str;
1515
default = null;
1616
};
17+
18+
pcrPhases = {
19+
enable = lib.mkEnableOption "pcr phases";
20+
21+
signatures = lib.mkOption {
22+
default = {};
23+
type = types.attrsOf (types.submodule ({ name, ... }: {
24+
config.phasePath = lib.mkDefault name;
25+
options = {
26+
phasePath = lib.mkOption {
27+
type = types.str;
28+
};
29+
banks = lib.mkOption {
30+
type = types.listOf types.str;
31+
default = [];
32+
};
33+
privateKeyFile = lib.mkOption {
34+
type = types.path // { apply = toString; };
35+
};
36+
publicKeyFile = lib.mkOption {
37+
type = types.path // { apply = toString; };
38+
};
39+
};
40+
}));
41+
};
42+
};
1743
};
1844
};
1945
config = {
46+
boot.kernelParams = lib.mkIf config.boot.loader.secureboot.pcrPhases.enable ["systemd.gpt_auto=false"];
47+
boot.initrd = lib.mkIf config.boot.loader.secureboot.pcrPhases.enable {
48+
availableKernelModules = ["efivarfs"];
49+
systemd = {
50+
package = config.systemd.package;
51+
additionalUpstreamUnits = ["systemd-pcrphase-initrd.service"];
52+
services.systemd-pcrphase-initrd = {
53+
wantedBy = ["initrd.target"];
54+
after = ["systemd-modules-load.service"];
55+
};
56+
storePaths = ["${config.boot.initrd.systemd.package}/lib/systemd/systemd-pcrphase"];
57+
contents."/etc/tmpfiles.d/90-tpm-pcr-signature.conf".text = ''
58+
C /run/systemd/tpm2-pcr-signature.json - - - - /.extra/tpm2-pcr-signature.json
59+
'';
60+
};
61+
};
62+
systemd = lib.mkIf config.boot.loader.secureboot.pcrPhases.enable {
63+
additionalUpstreamSystemUnits = [
64+
"systemd-pcrphase-sysinit.service"
65+
"systemd-pcrphase.service"
66+
];
67+
services.systemd-pcrphase-sysinit.wantedBy = ["basic.target"];
68+
services.systemd-pcrphase.wantedBy = ["multi-user.target"];
69+
};
2070
boot.loader.external = {
2171
enable = true;
2272
installHook = pkgs.writeShellScript "install-bootloader"
@@ -34,6 +84,11 @@ in
3484

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

3994
installerArgs = lib.escapeShellArgs

0 commit comments

Comments
 (0)