diff --git a/rust/lanzatool/Cargo.lock b/rust/lanzatool/Cargo.lock index 8532f0d..3a419ec 100644 --- a/rust/lanzatool/Cargo.lock +++ b/rust/lanzatool/Cargo.lock @@ -69,30 +69,14 @@ dependencies = [ ] [[package]] -name = "ct-codecs" -version = "1.1.1" +name = "goblin" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3b7eb4404b8195a9abb6356f4ac07d8ba267045c8d6d220ac4dc992e6cc75df" - -[[package]] -name = "ed25519-compact" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f2d21333b679bbbac680b3eb45c86937e42f69277028f4e97b599b80b86c253" +checksum = "572564d6cba7d09775202c8e7eebc4d534d5ae36578ab402fb21e182a0ac9505" dependencies = [ - "ct-codecs", - "getrandom", -] - -[[package]] -name = "getrandom" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" -dependencies = [ - "cfg-if", - "libc", - "wasi", + "log", + "plain", + "scroll", ] [[package]] @@ -110,13 +94,21 @@ dependencies = [ "libc", ] +[[package]] +name = "itoa" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" + [[package]] name = "lanztool" version = "0.1.0" dependencies = [ "anyhow", "clap", - "ed25519-compact", + "goblin", + "serde", + "serde_json", ] [[package]] @@ -125,6 +117,15 @@ version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + [[package]] name = "once_cell" version = "1.16.0" @@ -137,6 +138,12 @@ version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -179,6 +186,63 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "scroll" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdbda6ac5cd1321e724fa9cee216f3a61885889b896f073b8f82322789c5250e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "strsim" version = "0.10.0" @@ -217,12 +281,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - [[package]] name = "winapi" version = "0.3.9" diff --git a/rust/lanzatool/Cargo.toml b/rust/lanzatool/Cargo.toml index 339de02..2b612b9 100644 --- a/rust/lanzatool/Cargo.toml +++ b/rust/lanzatool/Cargo.toml @@ -8,4 +8,6 @@ edition = "2021" [dependencies] anyhow = "1.0.66" clap = { version = "4.0.26", features = ["derive"] } -ed25519-compact = "2.0.2" +goblin = "0.6.0" +serde = { version = "1.0.147", features = ["derive"] } +serde_json = "1.0.89" diff --git a/rust/lanzatool/bootspec.json b/rust/lanzatool/bootspec.json new file mode 100644 index 0000000..912982f --- /dev/null +++ b/rust/lanzatool/bootspec.json @@ -0,0 +1,24 @@ +{ + "v1": { + "init": "/nix/store/7zrsjhxi0c93m2l89rj8jdp9khm8fc6s-nixos-system-tuxedo-22.11.20221115.85d6b39/init", + "initrd": "/nix/store/7a4plccwni1sldhyra75f7m44xgsgiqw-initrd-linux-6.0.8/initrd", + "kernel": "/nix/store/nsw0422iwp4linayqx727pi4fdyja0wv-linux-6.0.8/bzImage", + "kernelParams": [ + "amd_iommu=on", + "amd_iommu=pt", + "iommu=pt", + "kvm.ignore_msrs=1", + "kvm.report_ignored_msrs=0", + "udev.log_priority=3", + "systemd.unified_cgroup_hierarchy=1", + "loglevel=4" + ], + "label": "NixOS 21.11.20210810.dirty (Linux 5.15.30)", + "toplevel": "/nix/store/7zrsjhxi0c93m2l89rj8jdp9khm8fc6s-nixos-system-tuxedo-22.11.20221115.85d6b39", + "extension": { + "esp": "test", + "bootctl": "/nix/store/89366aivz6v8a34yyni2m04ca9hwrl92-systemd-250.4/bin/bootctl", + "osRelease": "/nix/store/734vglb01ssz73wlihad7xa9yzvwlvx6-etc-os-release" + } + } +} \ No newline at end of file diff --git a/rust/lanzatool/src/bootspec.rs b/rust/lanzatool/src/bootspec.rs new file mode 100644 index 0000000..a2a0c15 --- /dev/null +++ b/rust/lanzatool/src/bootspec.rs @@ -0,0 +1,36 @@ +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +pub struct Bootspec { + pub v1: GenerationV1, +} + +#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct GenerationV1 { + /// Label for the system closure + pub label: String, + /// Path to kernel (bzImage) -- $toplevel/kernel + pub kernel: PathBuf, + /// list of kernel parameters + pub kernel_params: Vec, + /// Path to the init script + pub init: PathBuf, + /// Path to initrd -- $toplevel/initrd + pub initrd: PathBuf, + /// Path to "append-initrd-secrets" script -- $toplevel/append-initrd-secrets + pub initrd_secrets: Option, + /// config.system.build.toplevel path + pub toplevel: PathBuf, + pub extension: Extension, +} + +#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Extension { + pub esp: String, + pub bootctl: PathBuf, + pub os_release: PathBuf, +} diff --git a/rust/lanzatool/src/cli.rs b/rust/lanzatool/src/cli.rs index 6576b7c..2338ea9 100644 --- a/rust/lanzatool/src/cli.rs +++ b/rust/lanzatool/src/cli.rs @@ -1,10 +1,9 @@ -use std::fs; use std::path::{Path, PathBuf}; use anyhow::Result; use clap::{Parser, Subcommand}; -use crate::crypto; +use crate::install; #[derive(Parser)] pub struct Cli { @@ -14,12 +13,10 @@ pub struct Cli { #[derive(Subcommand)] pub enum Commands { - /// Generate key pair - Generate, - /// Sign - Sign { file: PathBuf, private_key: PathBuf }, - /// Sign - Verify { file: PathBuf, public_key: PathBuf }, + Install { + public_key: PathBuf, + bootspec: PathBuf, + }, } impl Cli { @@ -31,49 +28,15 @@ impl Cli { impl Commands { pub fn call(self) -> Result<()> { match self { - Commands::Generate => generate(), - Commands::Sign { file, private_key } => sign(&file, &private_key), - Commands::Verify { file, public_key } => verify(&file, &public_key), + Commands::Install { + public_key, + bootspec, + } => install(&public_key, &bootspec), } } } -fn generate() -> Result<()> { - let key_pair = crypto::generate_key(); - - fs::write("public_key.pem", key_pair.pk.to_pem())?; - fs::write("private_key.pem", key_pair.sk.to_pem())?; - - Ok(()) -} - -fn sign(file: &Path, private_key: &Path) -> Result<()> { - let message = fs::read(file)?; - let private_key = fs::read_to_string(private_key)?; - - let signature = crypto::sign(&message, &private_key)?; - - let file_path = with_extension(file, ".sig"); - fs::write(file_path, signature.as_slice())?; - - Ok(()) -} - -fn verify(file: &Path, public_key: &Path) -> Result<()> { - let message = fs::read(file)?; - - let signature_path = with_extension(file, ".sig"); - let signature = fs::read(signature_path)?; - - let public_key = fs::read_to_string(public_key)?; - - crypto::verify(&message, &signature, &public_key)?; - - Ok(()) -} - -fn with_extension(path: &Path, extension: &str) -> PathBuf { - let mut file_path = path.to_path_buf().into_os_string(); - file_path.push(extension); - PathBuf::from(file_path) +fn install(public_key: &Path, bootspec: &Path) -> Result<()> { + let lanzaboote_bin = std::env::var("LANZABOOTE")?; + install::install(public_key, bootspec, Path::new(&lanzaboote_bin)) } diff --git a/rust/lanzatool/src/crypto.rs b/rust/lanzatool/src/crypto.rs deleted file mode 100644 index 6433c1f..0000000 --- a/rust/lanzatool/src/crypto.rs +++ /dev/null @@ -1,22 +0,0 @@ -use anyhow::Result; -use ed25519_compact::{KeyPair, Noise, PublicKey, SecretKey, Seed, Signature}; - -pub fn generate_key() -> KeyPair { - KeyPair::from_seed(Seed::default()) -} - -pub fn sign(message: &[u8], private_key: &str) -> Result { - let private_key = SecretKey::from_pem(private_key)?; - let signature = private_key.sign(message, Some(Noise::generate())); - - Ok(signature) -} - -pub fn verify(message: &[u8], signature: &[u8], public_key: &str) -> Result<()> { - let signature = Signature::from_slice(signature)?; - let public_key = PublicKey::from_pem(public_key)?; - - public_key.verify(message, &signature)?; - - Ok(()) -} diff --git a/rust/lanzatool/src/esp.rs b/rust/lanzatool/src/esp.rs new file mode 100644 index 0000000..3112225 --- /dev/null +++ b/rust/lanzatool/src/esp.rs @@ -0,0 +1,22 @@ +use std::path::{Path, PathBuf}; + +pub struct EspPaths { + pub esp: PathBuf, + pub nixos: PathBuf, + pub kernel: PathBuf, + pub initrd: PathBuf, +} + +impl EspPaths { + pub fn new(esp: &str) -> Self { + let esp = Path::new(esp); + let esp_nixos = esp.join("EFI/nixos"); + + Self { + esp: esp.to_owned(), + nixos: esp_nixos.clone(), + kernel: esp_nixos.join("kernel"), + initrd: esp_nixos.join("initrd"), + } + } +} diff --git a/rust/lanzatool/src/install.rs b/rust/lanzatool/src/install.rs new file mode 100644 index 0000000..9f55cb6 --- /dev/null +++ b/rust/lanzatool/src/install.rs @@ -0,0 +1,52 @@ +use std::fs; + +use std::path::Path; +use std::process::Command; + +use anyhow::Result; + +use crate::bootspec::Bootspec; +use crate::esp::EspPaths; +use crate::stub; + +pub fn install(_: &Path, bootspec: &Path, lanzaboote_bin: &Path) -> Result<()> { + let bootspec_doc: Bootspec = serde_json::from_slice(&fs::read(bootspec)?)?; + + let esp_paths = EspPaths::new(&bootspec_doc.v1.extension.esp); + + stub::assemble( + lanzaboote_bin, + &bootspec_doc.v1.extension.os_release, + &bootspec_doc.v1.kernel_params, + &esp_paths.kernel, + &esp_paths.initrd, + ) + .unwrap(); + + // Copy the files to the ESP + fs::create_dir_all(&esp_paths.nixos)?; + fs::copy(bootspec_doc.v1.kernel, esp_paths.kernel)?; + fs::copy(bootspec_doc.v1.initrd, esp_paths.initrd)?; + // install_systemd_boot(bootctl, &esp)?; + + Ok(()) +} + +fn _install_systemd_boot(bootctl: &Path, esp: &Path) -> Result<()> { + let args = vec![ + String::from("install"), + String::from("--path"), + esp.display().to_string(), + ]; + + let status = Command::new(&bootctl).args(&args).status()?; + if !status.success() { + return Err(anyhow::anyhow!( + "Failed success run `{}` with args `{:?}`", + &bootctl.display(), + &args + ) + .into()); + } + Ok(()) +} diff --git a/rust/lanzatool/src/main.rs b/rust/lanzatool/src/main.rs index e09cbcc..e600556 100644 --- a/rust/lanzatool/src/main.rs +++ b/rust/lanzatool/src/main.rs @@ -1,5 +1,8 @@ +mod bootspec; mod cli; -mod crypto; +mod esp; +mod install; +mod stub; use std::process; diff --git a/rust/lanzatool/src/stub.rs b/rust/lanzatool/src/stub.rs new file mode 100644 index 0000000..54ad410 --- /dev/null +++ b/rust/lanzatool/src/stub.rs @@ -0,0 +1,76 @@ +use std::fs; +use std::os::unix::fs::MetadataExt; +use std::path::Path; +use std::process::Command; + +use anyhow::Result; +use goblin; + +pub fn assemble( + lanzaboote_bin: &Path, + os_release: &Path, + kernel_cmdline: &[String], + kernel_path: &Path, + initrd_path: &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 = Path::new("/tmp/kernel_cmdline"); + fs::write(kernel_cmdline_file, kernel_cmdline.join(" "))?; + let kernel_path_file = Path::new("/tmp/kernel_path"); + fs::write(kernel_path_file, kernel_path.to_str().unwrap())?; + let initrd_path_file = Path::new("/tmp/initrd_path"); + fs::write(initrd_path_file, initrd_path.to_str().unwrap())?; + + let pe_binary = fs::read(lanzaboote_bin)?; + let pe = goblin::pe::PE::parse(&pe_binary)?; + + let os_release_offs = u64::from( + pe.sections + .iter() + .find(|s| s.name().unwrap() == ".sdmagic") + .and_then(|s| Some(s.size_of_raw_data + s.virtual_address)) + .unwrap(), + ); + + 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 args = vec![ + String::from("--add-section"), + format!(".osrel={}", path_to_string(os_release)), + String::from("--change-section-vma"), + format!(".osrel={:#x}", os_release_offs), + String::from("--add-section"), + format!(".cmdline={}", path_to_string(kernel_cmdline_file)), + String::from("--change-section-vma"), + format!(".cmdline={:#x}", kernel_cmdline_offs), + String::from("--add-section"), + format!(".initrdp={}", path_to_string(initrd_path_file)), + String::from("--change-section-vma"), + format!(".initrdp={:#x}", initrd_path_offs), + String::from("--add-section"), + format!(".kernelp={}", path_to_string(kernel_path_file)), + String::from("--change-section-vma"), + format!(".kernelp={:#x}", kernel_path_offs), + lanzaboote_bin.to_str().unwrap().to_owned(), + String::from("stub.efi"), + ]; + + let status = Command::new("objcopy").args(&args).status()?; + if !status.success() { + return Err(anyhow::anyhow!("Failed to build stub with args `{:?}`", &args).into()); + } + + Ok(()) +} + +// All Linux file paths should be convertable to strings +fn path_to_string(path: &Path) -> String { + path.to_owned().into_os_string().into_string().unwrap() +} + +fn file_size(path: &Path) -> Result { + Ok(fs::File::open(path)?.metadata()?.size()) +}