lanzatool: embed kernel and initrd hashes

This commit is contained in:
Julian Stecklina 2022-11-28 02:23:43 +01:00
parent ba119d398f
commit 3f78939d0a
4 changed files with 168 additions and 32 deletions

View File

@ -8,6 +8,18 @@ version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
[[package]]
name = "arrayref"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
[[package]]
name = "arrayvec"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
[[package]] [[package]]
name = "atty" name = "atty"
version = "0.2.14" version = "0.2.14"
@ -31,6 +43,35 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "blake3"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ae2468a89544a466886840aa467a25b766499f4f04bf7d9fcd10ecee9fccef"
dependencies = [
"arrayref",
"arrayvec",
"cc",
"cfg-if",
"constant_time_eq",
"digest",
]
[[package]]
name = "block-buffer"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
dependencies = [
"generic-array",
]
[[package]]
name = "cc"
version = "1.0.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.0" version = "1.0.0"
@ -74,6 +115,33 @@ dependencies = [
"os_str_bytes", "os_str_bytes",
] ]
[[package]]
name = "constant_time_eq"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3ad85c1f65dc7b37604eb0e89748faf0b9653065f2a8ef69f96a687ec1e9279"
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "digest"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
dependencies = [
"block-buffer",
"crypto-common",
"subtle",
]
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "1.8.0" version = "1.8.0"
@ -83,6 +151,16 @@ dependencies = [
"instant", "instant",
] ]
[[package]]
name = "generic-array"
version = "0.14.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
dependencies = [
"typenum",
"version_check",
]
[[package]] [[package]]
name = "goblin" name = "goblin"
version = "0.6.0" version = "0.6.0"
@ -129,6 +207,7 @@ name = "lanzatool"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"blake3",
"clap", "clap",
"goblin", "goblin",
"nix", "nix",
@ -305,6 +384,12 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "subtle"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.103" version = "1.0.103"
@ -339,6 +424,12 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "typenum"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.5" version = "1.0.5"

View File

@ -13,3 +13,4 @@ nix = { version = "0.25.0", default-features = false, features = [ "fs" ] }
serde = { version = "1.0.147", features = ["derive"] } serde = { version = "1.0.147", features = ["derive"] }
serde_json = "1.0.89" serde_json = "1.0.89"
tempfile = "3.3.0" tempfile = "3.3.0"
blake3 = "1.3.3"

View File

@ -87,17 +87,6 @@ impl Installer {
// TODO(Raito): prove to niksnur this is actually acceptable. // TODO(Raito): prove to niksnur this is actually acceptable.
let secure_temp_dir = tempdir()?; let secure_temp_dir = tempdir()?;
let lanzaboote_image = pe::lanzaboote_image(
&secure_temp_dir,
&self.lanzaboote_stub,
&bootspec.extension.os_release,
&kernel_cmdline,
&esp_paths.kernel,
&esp_paths.initrd,
&esp_paths.esp,
)
.context("Failed to assemble stub")?;
println!("Wrapping initrd into a PE binary..."); println!("Wrapping initrd into a PE binary...");
let initrd_location = secure_temp_dir.path().join("initrd"); let initrd_location = secure_temp_dir.path().join("initrd");
@ -114,24 +103,37 @@ impl Installer {
.toplevel .toplevel
.join("systemd/lib/systemd/boot/efi/systemd-bootx64.efi"); .join("systemd/lib/systemd/boot/efi/systemd-bootx64.efi");
let files_to_copy_and_sign = [ [
(&systemd_boot, &esp_paths.efi_fallback), (&systemd_boot, &esp_paths.efi_fallback),
(&systemd_boot, &esp_paths.systemd_boot), (&systemd_boot, &esp_paths.systemd_boot),
(&lanzaboote_image, &esp_paths.lanzaboote_image),
(&bootspec.kernel, &esp_paths.kernel), (&bootspec.kernel, &esp_paths.kernel),
(&wrapped_initrd, &esp_paths.initrd), (&wrapped_initrd, &esp_paths.initrd),
]; ]
.into_iter()
.try_for_each(|(from, to)| install_signed(&self.key_pair, from, to))?;
for (from, to) in files_to_copy_and_sign { let lanzaboote_image = pe::lanzaboote_image(
println!("Signing {}...", to.display()); &secure_temp_dir,
&self.lanzaboote_stub,
&bootspec.extension.os_release,
&kernel_cmdline,
&esp_paths.kernel,
&esp_paths.initrd,
&esp_paths.esp,
)
.context("Failed to assemble stub")?;
ensure_parent_dir(to); install_signed(
self.key_pair.sign_and_copy(from, to).with_context(|| { &self.key_pair,
format!("Failed to copy and sign file from {:?} to {:?}", from, to) &lanzaboote_image,
})?; &esp_paths.lanzaboote_image,
// Call sync to improve the likelihood that file is actually written to disk )
sync(); .context("Failed to install lanzaboote")?;
}
// Sync files to persistent storage. This may improve the
// chance of a consistent boot directory in case the system
// crashes.
sync();
println!( println!(
"Successfully installed lanzaboote to '{}'", "Successfully installed lanzaboote to '{}'",
@ -142,6 +144,18 @@ impl Installer {
} }
} }
/// Install a PE file. The PE gets signed in the process.
fn install_signed(key_pair: &KeyPair, from: &Path, to: &Path) -> Result<()> {
println!("Signing {}...", to.display());
ensure_parent_dir(to);
key_pair
.sign_and_copy(from, to)
.with_context(|| format!("Failed to copy and sign file from {:?} to {:?}", from, to))?;
Ok(())
}
pub fn append_initrd_secrets( pub fn append_initrd_secrets(
append_initrd_secrets_path: &Path, append_initrd_secrets_path: &Path,
initrd_path: &PathBuf, initrd_path: &PathBuf,

View File

@ -12,6 +12,11 @@ use crate::utils;
use tempfile::TempDir; use tempfile::TempDir;
/// Attach all information that lanzaboote needs into the PE binary.
///
/// When this function is called the referenced files already need to
/// be present in the ESP. This is required, because we need to read
/// them to compute hashes.
pub fn lanzaboote_image( pub fn lanzaboote_image(
target_dir: &TempDir, target_dir: &TempDir,
lanzaboote_stub: &Path, lanzaboote_stub: &Path,
@ -21,36 +26,56 @@ pub fn lanzaboote_image(
initrd_path: &Path, initrd_path: &Path,
esp: &Path, esp: &Path,
) -> Result<PathBuf> { ) -> Result<PathBuf> {
// objcopy copies files into the PE binary. That's why we have to write the contents // objcopy can only copy files into the PE binary. That's why we
// of some bootspec properties to disk // have to write the contents of some bootspec properties to disk.
let (kernel_cmdline_file, _) = let kernel_cmdline_file = write_to_tmp(target_dir, "kernel-cmdline", kernel_cmdline.join(" "))?;
write_to_tmp(target_dir, "kernel-cmdline", kernel_cmdline.join(" "))?;
let (kernel_path_file, _) = write_to_tmp( let kernel_path_file = write_to_tmp(
target_dir, target_dir,
"kernel-esp-path", "kernel-esp-path",
esp_relative_path_string(esp, kernel_path), esp_relative_path_string(esp, kernel_path),
)?; )?;
let (initrd_path_file, _) = write_to_tmp( let kernel_hash_file = write_to_tmp(
target_dir,
"kernel-hash",
file_hash(kernel_path)?.as_bytes(),
)?;
let initrd_path_file = write_to_tmp(
target_dir, target_dir,
"initrd-esp-path", "initrd-esp-path",
esp_relative_path_string(esp, initrd_path), esp_relative_path_string(esp, initrd_path),
)?; )?;
let initrd_hash_file = write_to_tmp(
target_dir,
"initrd-hash",
file_hash(initrd_path)?.as_bytes(),
)?;
let os_release_offs = stub_offset(lanzaboote_stub)?; let os_release_offs = stub_offset(lanzaboote_stub)?;
let kernel_cmdline_offs = os_release_offs + file_size(os_release)?; let kernel_cmdline_offs = os_release_offs + file_size(os_release)?;
let initrd_path_offs = kernel_cmdline_offs + file_size(&kernel_cmdline_file)?; let initrd_path_offs = kernel_cmdline_offs + file_size(&kernel_cmdline_file)?;
let kernel_path_offs = initrd_path_offs + file_size(&initrd_path_file)?; let kernel_path_offs = initrd_path_offs + file_size(&initrd_path_file)?;
let initrd_hash_offs = kernel_path_offs + file_size(&kernel_path_file)?;
let kernel_hash_offs = initrd_hash_offs + file_size(&initrd_hash_file)?;
let sections = vec![ let sections = vec![
s(".osrel", os_release, os_release_offs), s(".osrel", os_release, os_release_offs),
s(".cmdline", kernel_cmdline_file, kernel_cmdline_offs), s(".cmdline", kernel_cmdline_file, kernel_cmdline_offs),
s(".initrdp", initrd_path_file, initrd_path_offs), s(".initrdp", initrd_path_file, initrd_path_offs),
s(".kernelp", kernel_path_file, kernel_path_offs), s(".kernelp", kernel_path_file, kernel_path_offs),
s(".initrdh", initrd_hash_file, initrd_hash_offs),
s(".kernelh", kernel_hash_file, kernel_hash_offs),
]; ];
wrap_in_pe(target_dir, "lanzaboote-stub.efi", lanzaboote_stub, sections) wrap_in_pe(target_dir, "lanzaboote-stub.efi", lanzaboote_stub, sections)
} }
/// Compute the blake3 hash of a file.
fn file_hash(file: &Path) -> Result<blake3::Hash> {
Ok(blake3::hash(&fs::read(file)?))
}
/// Wrap an initrd into a PE binary. /// Wrap an initrd into a PE binary.
/// ///
/// This is required for lanzaboote to verify the signature of the /// This is required for lanzaboote to verify the signature of the
@ -125,21 +150,26 @@ fn s(name: &'static str, file_path: impl AsRef<Path>, offset: u64) -> Section {
} }
} }
/// Write a `u8` slice to a temporary file.
fn write_to_tmp( fn write_to_tmp(
secure_temp: &TempDir, secure_temp: &TempDir,
filename: &str, filename: &str,
contents: impl AsRef<[u8]>, contents: impl AsRef<[u8]>,
) -> Result<(PathBuf, fs::File)> { ) -> Result<PathBuf> {
let path = secure_temp.path().join(filename);
let mut tmpfile = fs::OpenOptions::new() let mut tmpfile = fs::OpenOptions::new()
.create(true) .create(true)
.write(true) .write(true)
.mode(0o600) .mode(0o600)
.open(secure_temp.path().join(filename)) .open(&path)
.context("Failed to create tempfile")?; .context("Failed to create tempfile")?;
tmpfile tmpfile
.write_all(contents.as_ref()) .write_all(contents.as_ref())
.context("Failed to write to tempfile")?; .context("Failed to write to tempfile")?;
Ok((secure_temp.path().join(filename), tmpfile))
Ok(path)
} }
fn esp_relative_path_string(esp: &Path, path: &Path) -> String { fn esp_relative_path_string(esp: &Path, path: &Path) -> String {