Merge pull request #6 from blitz/lanzatool-install

lanzatool.install: init
This commit is contained in:
Julian Stecklina 2022-11-23 15:46:12 +01:00 committed by GitHub
commit 788a112050
10 changed files with 316 additions and 102 deletions

View File

@ -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"

View File

@ -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"

View File

@ -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"
}
}
}

View File

@ -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<String>,
/// 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<PathBuf>,
/// 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,
}

View File

@ -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))
}

View File

@ -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<Signature> {
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(())
}

22
rust/lanzatool/src/esp.rs Normal file
View File

@ -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"),
}
}
}

View File

@ -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(())
}

View File

@ -1,5 +1,8 @@
mod bootspec;
mod cli;
mod crypto;
mod esp;
mod install;
mod stub;
use std::process;

View File

@ -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<u64> {
Ok(fs::File::open(path)?.metadata()?.size())
}