From 42212c61bcbae4e082f7fd9779615372ade839e7 Mon Sep 17 00:00:00 2001 From: Cappy Ishihara Date: Sat, 8 Feb 2025 23:44:56 +0700 Subject: [PATCH] Implement LUKS key prompt, and pass them to systemd-repart Co-authored-by: madonuko --- src/backend/install.rs | 24 +++- src/consts.rs | 5 + src/main.rs | 2 + src/pages/installationtype.rs | 259 ++++++++++++---------------------- 4 files changed, 116 insertions(+), 174 deletions(-) diff --git a/src/backend/install.rs b/src/backend/install.rs index ceda566..4e9958f 100644 --- a/src/backend/install.rs +++ b/src/backend/install.rs @@ -19,6 +19,7 @@ use std::{ }; use tee_readwrite::TeeReader; +use crate::consts; use crate::consts::repart_dir; use crate::util::sys::check_uefi; use crate::{ @@ -49,6 +50,7 @@ pub struct InstallationState { pub postinstall: Vec, pub encrypt: bool, pub tpm: bool, + pub encryption_key: Option, } // TODO: remove this after have support for anything other than chromebook @@ -67,6 +69,7 @@ impl Default for InstallationState { mounttags: Option::default(), postinstall: crate::CONFIG.read().postinstall.clone(), encrypt: false, + encryption_key: Option::default(), } } } @@ -195,11 +198,17 @@ impl InstallationState { .devpath; let cfgdir = inst_type.cfgdir(); + // Let's write the encryption key to the keyfile + let keyfile = std::path::Path::new(consts::LUKS_KEYFILE_PATH); + if let Some(key) = &self.encryption_key { + std::fs::write(keyfile, key)?; + } + // TODO: encryption self.enable_encryption(&cfgdir)?; let repart_out = stage!("Creating partitions and copying files" { // todo: not freeze on error, show error message as err handler? - Self::systemd_repart(blockdev, &cfgdir)? + Self::systemd_repart(blockdev, &cfgdir, self.encrypt && self.encryption_key.is_some())? }); tracing::info!("Copying files done, Setting up system..."); @@ -364,6 +373,7 @@ impl InstallationState { fn systemd_repart( blockdev: &Path, cfgdir: &Path, + use_keyfile: bool, ) -> Result { let copy_source = Self::determine_copy_source(); @@ -371,7 +381,7 @@ impl InstallationState { std::env::var("READYMADE_DRY_RUN").map_or(cfg!(debug_assertions), |v| v == "1"); let dry_run = if dry_run { "yes" } else { "no" }; - let args = [ + let mut args = vec![ "--dry-run", dry_run, "--definitions", @@ -384,8 +394,16 @@ impl InstallationState { ©_source, "--json", "pretty", - blockdev.to_str().unwrap(), ]; + + if use_keyfile { + let keyfile_path = consts::LUKS_KEYFILE_PATH; + tracing::debug!("Using keyfile for systemd-repart: {keyfile_path}"); + args.push("--key-file"); + args.push(keyfile_path); + } + + args.extend(&[blockdev.to_str().unwrap()]); tracing::debug!(?dry_run, ?args, "Running systemd-repart"); diff --git a/src/consts.rs b/src/consts.rs index 10cd54c..1dde52b 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -4,12 +4,17 @@ const EFI_SHIM_AA64: &str = "\\EFI\\fedora\\shimaa64.efi"; pub const OS_NAME: &str = "Ultramarine Linux"; pub const LIVE_BASE: &str = "/dev/mapper/live-base"; pub const ROOTFS_BASE: &str = "/run/rootfsbase"; +pub const LUKS_KEYFILE_PATH: &str = "/run/readymade-luks.key"; const REPART_DIR: &str = "/usr/share/readymade/repart-cfgs/"; pub fn repart_dir() -> PathBuf { PathBuf::from(std::env::var("READYMADE_REPART_DIR").unwrap_or_else(|_| REPART_DIR.into())) } +pub fn open_keyfile() -> std::io::Result { + std::fs::File::open(LUKS_KEYFILE_PATH) +} + pub const fn shim_path() -> &'static str { if cfg!(target_arch = "x86_64") { EFI_SHIM_X86_64 diff --git a/src/main.rs b/src/main.rs index 939a180..0252b41 100644 --- a/src/main.rs +++ b/src/main.rs @@ -213,6 +213,8 @@ fn main() -> Result<()> { IPC_CHANNEL.set(Mutex::new(channel)).unwrap(); let install_state: InstallationState = serde_json::from_reader(std::io::stdin())?; + + return install_state.install(); } diff --git a/src/pages/installationtype.rs b/src/pages/installationtype.rs index bf8775e..6604d5e 100644 --- a/src/pages/installationtype.rs +++ b/src/pages/installationtype.rs @@ -5,6 +5,8 @@ use relm4::{ComponentParts, ComponentSender, RelmWidgetExt, SimpleComponent}; #[derive(Default)] pub struct InstallationTypePage { can_encrypt: bool, + root: Option, + act: Option, } #[derive(Debug)] @@ -13,6 +15,7 @@ pub enum InstallationTypePageMsg { #[doc(hidden)] Navigate(NavigationAction), InstallationTypeSelected(InstallationType), + EncryptDialogue(bool), Next, } @@ -175,12 +178,14 @@ impl SimpleComponent for InstallationTypePage { root: Self::Root, sender: ComponentSender, ) -> ComponentParts { - let model = Self::default(); + let mut model = Self::default(); let widgets = view_output!(); INSTALLATION_STATE.subscribe(sender.input_sender(), |_| InstallationTypePageMsg::Update); + model.root = Some(root.clone()); + ComponentParts { model, widgets } } @@ -204,9 +209,29 @@ impl SimpleComponent for InstallationTypePage { Some(InstallationType::ChromebookInstall); self.can_encrypt = true; } - InstallationTypePageMsg::Navigate(action) => sender - .output(InstallationTypePageOutput::Navigate(action)) - .unwrap(), + InstallationTypePageMsg::EncryptDialogue(b) => { + INSTALLATION_STATE.write().encrypt = b; + sender + .output(InstallationTypePageOutput::Navigate( + self.act.take().unwrap(), + )) + .unwrap(); + } + InstallationTypePageMsg::Navigate(action) => { + if INSTALLATION_STATE.read().encrypt { + let dialogue = EncryptPassDialogue::builder() + .launch(self.root.as_ref().unwrap().toplevel_window().unwrap()); + dialogue.forward( + sender.input_sender(), + InstallationTypePageMsg::EncryptDialogue, + ); + self.act = Some(action); + return; + } + sender + .output(InstallationTypePageOutput::Navigate(action)) + .unwrap(); + } InstallationTypePageMsg::Next => { sender.input(InstallationTypePageMsg::Navigate(NavigationAction::GoTo({ let value = INSTALLATION_STATE.read().installation_type; @@ -224,181 +249,73 @@ impl SimpleComponent for InstallationTypePage { } } -/* -macro_rules! pagename { - () => { - "Installation Type" - }; -} - -page!(InstallationType { - can_encrypt: bool, +kurage::generate_component!(EncryptPassDialogue { + btn_confirm: libhelium::Button, + tf_repeat: gtk::PasswordEntry, }: - init(root, sender, model, widgets) { } + init[tf_repeat](root, sender, model, widgets) for root_window: gtk::Window { + libhelium::prelude::WindowExt::set_parent(&root, Some(&root_window)); + // model.btn_confirm = widgets.btn_confirm.clone(); + } update(self, message, sender) { - InstallationTypeSelected(inner: InstallationType) => match inner { - InstallationType::WholeDisk => { - INSTALLATION_STATE.write().installation_type = Some(InstallationType::WholeDisk); - self.can_encrypt = true; - INSTALLATION_STATE.write().encrypt = true; - }, - InstallationType::DualBoot(_) => { - self.can_encrypt = true; - INSTALLATION_STATE.write().encrypt = true; - }, - InstallationType::Custom => { - INSTALLATION_STATE.write().installation_type = Some(InstallationType::Custom); - self.can_encrypt = false; - INSTALLATION_STATE.write().encrypt = false; - }, - InstallationType::ChromebookInstall => { - INSTALLATION_STATE.write().installation_type = - Some(InstallationType::ChromebookInstall); - INSTALLATION_STATE.write().encrypt = true; - self.can_encrypt = true; - } - }, - Next => { - sender.input(InstallationTypePageMsg::Navigate(NavigationAction::GoTo({ - let value = INSTALLATION_STATE.read().installation_type; - match value.unwrap() { - InstallationType::DualBoot(_) => Page::InstallDual, - InstallationType::ChromebookInstall | InstallationType::WholeDisk => { - Page::Confirmation - } - InstallationType::Custom => Page::InstallCustom, - } - }))); + SetBtnSensitive(sensitive: bool) => { + self.btn_confirm.set_sensitive(sensitive); }, - } => {} - - set_spacing: 6, - - gtk::Box { - set_orientation: gtk::Orientation::Vertical, - set_valign: gtk::Align::Center, - set_spacing: 18, - - gtk::Box { - set_orientation: gtk::Orientation::Vertical, - set_spacing: 6, - set_vexpand: true, - set_hexpand: true, - set_valign: gtk::Align::Center, - set_halign: gtk::Align::Center, - - gtk::Image { - set_icon_name: Some("drive-harddisk-symbolic"), - inline_css: "-gtk-icon-size: 128px" - }, - - gtk::Label { - #[watch] - set_label: &INSTALLATION_STATE.read().destination_disk.clone().map(|d| d.disk_name).unwrap_or_default(), - inline_css: "font-size: 16px; font-weight: bold" - }, - - gtk::Label { - #[watch] - set_label: &INSTALLATION_STATE.read().destination_disk.clone().map(|d| d.os_name).unwrap_or_default(), + Enter => { + if self.btn_confirm.is_sensitive() { + sender.output(true).unwrap(); } }, - - gtk::Box { - set_spacing: 6, - set_halign: gtk::Align::Center, - set_valign: gtk::Align::End, - set_homogeneous: true, - libhelium::Button { - set_visible: crate::CONFIG.read().install.allowed_installtypes.contains(&InstallationType::WholeDisk), - #[watch] - set_is_fill: crate::INSTALLATION_STATE.read().installation_type == Some(InstallationType::WholeDisk), - #[watch] - set_is_outline: crate::INSTALLATION_STATE.read().installation_type != Some(InstallationType::WholeDisk), - #[watch] - set_label: &gettext("Entire Disk"), - add_css_class: "large-button", - connect_clicked => InstallationTypePageMsg::InstallationTypeSelected(InstallationType::WholeDisk) - }, - libhelium::Button { - set_visible: crate::CONFIG.read().install.allowed_installtypes.iter().any(|x| matches!(x, InstallationType::DualBoot(_))), - #[watch] - set_is_fill: matches!(crate::INSTALLATION_STATE.read().installation_type, Some(InstallationType::DualBoot(_))), - #[watch] - set_is_outline: !matches!(crate::INSTALLATION_STATE.read().installation_type, Some(InstallationType::DualBoot(_))), - #[watch] - set_label: &gettext("Dual Boot"), - add_css_class: "large-button", - connect_clicked => InstallationTypePageMsg::InstallationTypeSelected(InstallationType::DualBoot(0)), - }, - libhelium::Button { - set_visible: crate::CONFIG.read().install.allowed_installtypes.contains(&InstallationType::Custom), - #[watch] - set_is_fill: crate::INSTALLATION_STATE.read().installation_type == Some(InstallationType::Custom), - #[watch] - set_is_outline: crate::INSTALLATION_STATE.read().installation_type != Some(InstallationType::Custom), - #[watch] - set_label: &gettext("Custom"), - add_css_class: "large-button", - connect_clicked => InstallationTypePageMsg::InstallationTypeSelected(InstallationType::Custom) - }, - libhelium::Button { - set_visible: crate::CONFIG.read().install.allowed_installtypes.contains(&InstallationType::ChromebookInstall), - #[watch] - set_is_fill: crate::INSTALLATION_STATE.read().installation_type == Some(InstallationType::ChromebookInstall), - #[watch] - set_is_outline: crate::INSTALLATION_STATE.read().installation_type != Some(InstallationType::ChromebookInstall), - #[watch] - set_label: &gettext("Chromebook"), - add_css_class: "large-button", - connect_clicked => InstallationTypePageMsg::InstallationTypeSelected(InstallationType::ChromebookInstall) + } => bool + + libhelium::Dialog { + set_modal: true, + set_title: Some(&gettext("Disk Encryption")), + + #[wrap(Some)] + set_child = >k::Box { + #[name = "tf_passwd"] + gtk::PasswordEntry { + set_hexpand: true, + set_halign: gtk::Align::Fill, + set_show_peek_icon: true, + set_placeholder_text: Some(&gettext("Password")), + connect_changed[sender, tf_repeat] => move |en| { + sender.input(Self::Input::SetBtnSensitive(en.text() == tf_repeat.text() && !en.text().is_empty())); + INSTALLATION_STATE.write().encryption_key = Some(en.text().to_string()); + }, }, - }, - }, - gtk::Box { - set_orientation: gtk::Orientation::Vertical, - set_halign: gtk::Align::Center, - - gtk::CheckButton { - set_label: Some(&gettext("Enable disk encryption")), - #[watch] - set_sensitive: model.can_encrypt, - connect_toggled => |btn| INSTALLATION_STATE.write().encrypt = btn.is_active(), - }, - gtk::CheckButton { - set_label: Some(&gettext("Enable TPM")), - #[watch] - set_sensitive: INSTALLATION_STATE.read().encrypt && model.can_encrypt, - connect_toggled => |btn| INSTALLATION_STATE.write().tpm = btn.is_active(), + #[local_ref] tf_repeat -> + gtk::PasswordEntry { + set_hexpand: true, + set_halign: gtk::Align::Fill, + set_show_peek_icon: true, + set_placeholder_text: Some(&gettext("Repeat Password")), + connect_changed[sender] => move |en| { + let pass = en.text().to_string(); + sender.input(Self::Input::SetBtnSensitive(INSTALLATION_STATE.read().encryption_key.as_ref().is_some_and(|p| p == &pass && !pass.is_empty()))); + }, + connect_activate => Self::Input::Enter, + }, }, + + // FIXME: for some reason the libhelium crate does not contain these methods + // (actually DialogExt is just totally missing) + + // #[name(btn_confirm)] + // #[wrap(Some)] + // set_primary_button = &libhelium::Button { + // set_label: &gettext("Confirm"), + // set_sensitive: false, + // connect_activate => Self::Input::Enter, + // }, + + // #[wrap(Some)] + // set_secondary_button = &libhelium::Button { + // set_label: &gettext("Cancel"), + // connect_activate[sender] => move |_| sender.output(false).unwrap(), + // }, }, - - gtk::Box { - set_orientation: gtk::Orientation::Horizontal, - set_spacing: 6, - - libhelium::Button { - set_is_textual: true, - #[watch] - set_label: &gettext("Previous"), - connect_clicked => InstallationTypePageMsg::Navigate(NavigationAction::GoTo(crate::Page::Destination)) - }, - - gtk::Box { - set_hexpand: true, - }, - - libhelium::Button { - set_is_pill: true, - #[watch] - set_label: &gettext("Next"), - add_css_class: "large-button", - connect_clicked => InstallationTypePageMsg::Next, - #[watch] - set_sensitive: crate::INSTALLATION_STATE.read().installation_type.is_some(), - } - } ); -*/