diff --git a/rust/lanzatool/Cargo.lock b/rust/lanzatool/Cargo.lock index 1c9e8b0..4ef3c12 100644 --- a/rust/lanzatool/Cargo.lock +++ b/rust/lanzatool/Cargo.lock @@ -8,6 +8,18 @@ version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "atty" version = "0.2.14" @@ -31,6 +43,35 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "cfg-if" version = "1.0.0" @@ -74,6 +115,33 @@ dependencies = [ "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]] name = "fastrand" version = "1.8.0" @@ -83,6 +151,16 @@ dependencies = [ "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]] name = "goblin" version = "0.6.0" @@ -129,6 +207,7 @@ name = "lanzatool" version = "0.1.0" dependencies = [ "anyhow", + "blake3", "clap", "goblin", "nix", @@ -305,6 +384,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + [[package]] name = "syn" version = "1.0.103" @@ -339,6 +424,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + [[package]] name = "unicode-ident" version = "1.0.5" diff --git a/rust/lanzatool/Cargo.toml b/rust/lanzatool/Cargo.toml index 6ec5d7f..5ca7a67 100644 --- a/rust/lanzatool/Cargo.toml +++ b/rust/lanzatool/Cargo.toml @@ -13,3 +13,4 @@ nix = { version = "0.25.0", default-features = false, features = [ "fs" ] } serde = { version = "1.0.147", features = ["derive"] } serde_json = "1.0.89" tempfile = "3.3.0" +blake3 = "1.3.3" diff --git a/rust/lanzatool/src/install.rs b/rust/lanzatool/src/install.rs index 64bba50..0044acb 100644 --- a/rust/lanzatool/src/install.rs +++ b/rust/lanzatool/src/install.rs @@ -87,17 +87,6 @@ impl Installer { // TODO(Raito): prove to niksnur this is actually acceptable. 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..."); let initrd_location = secure_temp_dir.path().join("initrd"); @@ -114,24 +103,37 @@ impl Installer { .toplevel .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.systemd_boot), - (&lanzaboote_image, &esp_paths.lanzaboote_image), (&bootspec.kernel, &esp_paths.kernel), (&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 { - println!("Signing {}...", to.display()); + 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")?; - ensure_parent_dir(to); - self.key_pair.sign_and_copy(from, to).with_context(|| { - format!("Failed to copy and sign file from {:?} to {:?}", from, to) - })?; - // Call sync to improve the likelihood that file is actually written to disk - sync(); - } + install_signed( + &self.key_pair, + &lanzaboote_image, + &esp_paths.lanzaboote_image, + ) + .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!( "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( append_initrd_secrets_path: &Path, initrd_path: &PathBuf, diff --git a/rust/lanzatool/src/pe.rs b/rust/lanzatool/src/pe.rs index 17122a2..994d3ea 100644 --- a/rust/lanzatool/src/pe.rs +++ b/rust/lanzatool/src/pe.rs @@ -12,6 +12,11 @@ use crate::utils; 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( target_dir: &TempDir, lanzaboote_stub: &Path, @@ -21,36 +26,56 @@ pub fn lanzaboote_image( initrd_path: &Path, esp: &Path, ) -> Result { - // objcopy copies files into the PE binary. That's why we have to write the contents - // of some bootspec properties to disk - let (kernel_cmdline_file, _) = - write_to_tmp(target_dir, "kernel-cmdline", kernel_cmdline.join(" "))?; - let (kernel_path_file, _) = write_to_tmp( + // objcopy can only copy files into the PE binary. That's why we + // have to write the contents of some bootspec properties to disk. + let kernel_cmdline_file = write_to_tmp(target_dir, "kernel-cmdline", kernel_cmdline.join(" "))?; + + let kernel_path_file = write_to_tmp( target_dir, "kernel-esp-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, "initrd-esp-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 kernel_cmdline_offs = os_release_offs + file_size(os_release)?; 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 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![ s(".osrel", os_release, os_release_offs), s(".cmdline", kernel_cmdline_file, kernel_cmdline_offs), s(".initrdp", initrd_path_file, initrd_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) } +/// Compute the blake3 hash of a file. +fn file_hash(file: &Path) -> Result { + Ok(blake3::hash(&fs::read(file)?)) +} + /// Wrap an initrd into a PE binary. /// /// This is required for lanzaboote to verify the signature of the @@ -125,21 +150,26 @@ fn s(name: &'static str, file_path: impl AsRef, offset: u64) -> Section { } } +/// Write a `u8` slice to a temporary file. fn write_to_tmp( secure_temp: &TempDir, filename: &str, contents: impl AsRef<[u8]>, -) -> Result<(PathBuf, fs::File)> { +) -> Result { + let path = secure_temp.path().join(filename); + let mut tmpfile = fs::OpenOptions::new() .create(true) .write(true) .mode(0o600) - .open(secure_temp.path().join(filename)) + .open(&path) .context("Failed to create tempfile")?; + tmpfile .write_all(contents.as_ref()) .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 {