From effcec7e2e1aa9c68bae97fb95725288a1a53fea Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Wed, 11 Mar 2026 15:18:13 -0600 Subject: [PATCH] feat: add Secure Boot MOK key enrollment and module signing Generate DKMS MOK key pair during OS install, sign all unsigned kernel modules, and enroll the MOK certificate using the user's master password. On reboot, MokManager prompts the user to complete enrollment. Re-enrolls on every boot if the key exists but isn't enrolled yet. Adds setup wizard dialog to inform the user about the MokManager prompt. --- build/dpkg-deps/depends | 2 + build/image-recipe/build.sh | 10 ++ build/lib/scripts/sign-unsigned-modules | 76 +++++++++++ build/lib/scripts/upgrade | 9 ++ core/src/error.rs | 2 + core/src/init.rs | 5 + core/src/os_install/mod.rs | 63 ++++++--- core/src/setup.rs | 2 + core/src/util/mod.rs | 1 + core/src/util/mok.rs | 125 ++++++++++++++++++ sdk/base/lib/osBindings/SetupInfo.ts | 6 +- .../setup-wizard/src/app/app.component.ts | 1 + .../app/components/mok-enrollment.dialog.ts | 77 +++++++++++ .../setup-wizard/src/app/pages/drives.page.ts | 1 + .../src/app/pages/success.page.ts | 15 +++ .../src/app/services/mock-api.service.ts | 1 + .../src/app/services/state.service.ts | 2 + web/projects/setup-wizard/src/app/types.ts | 1 + .../shared/src/i18n/dictionaries/de.ts | 4 + .../shared/src/i18n/dictionaries/en.ts | 5 + .../shared/src/i18n/dictionaries/es.ts | 4 + .../shared/src/i18n/dictionaries/fr.ts | 4 + .../shared/src/i18n/dictionaries/pl.ts | 4 + 23 files changed, 400 insertions(+), 20 deletions(-) create mode 100755 build/lib/scripts/sign-unsigned-modules create mode 100644 core/src/util/mok.rs create mode 100644 web/projects/setup-wizard/src/app/components/mok-enrollment.dialog.ts diff --git a/build/dpkg-deps/depends b/build/dpkg-deps/depends index da2012ae2..3e527e4d9 100644 --- a/build/dpkg-deps/depends +++ b/build/dpkg-deps/depends @@ -11,6 +11,7 @@ cifs-utils conntrack cryptsetup curl +dkms dmidecode dnsutils dosfstools @@ -36,6 +37,7 @@ lvm2 lxc magic-wormhole man-db +mokutil ncdu net-tools network-manager diff --git a/build/image-recipe/build.sh b/build/image-recipe/build.sh index 787f71844..0b3024286 100755 --- a/build/image-recipe/build.sh +++ b/build/image-recipe/build.sh @@ -299,6 +299,16 @@ if [ "${NVIDIA}" = "1" ]; then echo "[nvidia-hook] Removed build dependencies." >&2 fi +# Install linux-kbuild for sign-file (Secure Boot module signing) +KVER_ALL="\$(ls -1t /boot/vmlinuz-* 2>/dev/null | head -n1 | sed 's|.*/vmlinuz-||')" +if [ -n "\${KVER_ALL}" ]; then + KBUILD_VER="\$(echo "\${KVER_ALL}" | grep -oP '^\d+\.\d+')" + if [ -n "\${KBUILD_VER}" ]; then + echo "[build] Installing linux-kbuild-\${KBUILD_VER} for Secure Boot support" >&2 + apt-get install -y "linux-kbuild-\${KBUILD_VER}" || echo "[build] WARNING: linux-kbuild-\${KBUILD_VER} not available" >&2 + fi +fi + cp /etc/resolv.conf /etc/resolv.conf.bak if [ "${IB_SUITE}" = trixie ] && [ "${IB_TARGET_ARCH}" != riscv64 ]; then diff --git a/build/lib/scripts/sign-unsigned-modules b/build/lib/scripts/sign-unsigned-modules new file mode 100755 index 000000000..fdaf11e88 --- /dev/null +++ b/build/lib/scripts/sign-unsigned-modules @@ -0,0 +1,76 @@ +#!/bin/bash + +# sign-unsigned-modules [--source --dest ] [--sign-file ] +# [--mok-key ] [--mok-pub ] +# +# Signs all unsigned kernel modules using the DKMS MOK key. +# +# Default (install) mode: +# Run inside a chroot. Finds and signs unsigned modules in /lib/modules in-place. +# sign-file and MOK key are auto-detected from standard paths. +# +# Overlay mode (--source/--dest): +# Finds unsigned modules in , copies to , signs the copies. +# Clears old signed modules in first. Used during upgrades where the +# overlay upper is tmpfs and writes would be lost. + +set -e + +SOURCE="" +DEST="" +SIGN_FILE="" +MOK_KEY="/var/lib/dkms/mok.key" +MOK_PUB="/var/lib/dkms/mok.pub" + +while [[ $# -gt 0 ]]; do + case $1 in + --source) SOURCE="$2"; shift 2;; + --dest) DEST="$2"; shift 2;; + --sign-file) SIGN_FILE="$2"; shift 2;; + --mok-key) MOK_KEY="$2"; shift 2;; + --mok-pub) MOK_PUB="$2"; shift 2;; + *) echo "Unknown option: $1" >&2; exit 1;; + esac +done + +# Auto-detect sign-file if not specified +if [ -z "$SIGN_FILE" ]; then + SIGN_FILE="$(ls -1 /usr/lib/linux-kbuild-*/scripts/sign-file 2>/dev/null | head -1)" +fi + +if [ -z "$SIGN_FILE" ] || [ ! -x "$SIGN_FILE" ]; then + exit 0 +fi + +if [ ! -f "$MOK_KEY" ] || [ ! -f "$MOK_PUB" ]; then + exit 0 +fi + +COUNT=0 + +if [ -n "$SOURCE" ] && [ -n "$DEST" ]; then + # Overlay mode: find unsigned in source, copy to dest, sign in dest + rm -rf "${DEST}"/lib/modules + + for ko in $(find "${SOURCE}"/lib/modules -name '*.ko' 2>/dev/null); do + if ! modinfo "$ko" 2>/dev/null | grep -q '^sig_id:'; then + rel_path="${ko#${SOURCE}}" + mkdir -p "${DEST}$(dirname "$rel_path")" + cp "$ko" "${DEST}${rel_path}" + "$SIGN_FILE" sha256 "$MOK_KEY" "$MOK_PUB" "${DEST}${rel_path}" + COUNT=$((COUNT + 1)) + fi + done +else + # In-place mode: sign modules directly + for ko in $(find /lib/modules -name '*.ko' 2>/dev/null); do + if ! modinfo "$ko" 2>/dev/null | grep -q '^sig_id:'; then + "$SIGN_FILE" sha256 "$MOK_KEY" "$MOK_PUB" "$ko" + COUNT=$((COUNT + 1)) + fi + done +fi + +if [ $COUNT -gt 0 ]; then + echo "[sign-modules] Signed $COUNT unsigned kernel modules" +fi diff --git a/build/lib/scripts/upgrade b/build/lib/scripts/upgrade index 35230eb0a..6945c7586 100755 --- a/build/lib/scripts/upgrade +++ b/build/lib/scripts/upgrade @@ -83,6 +83,15 @@ if [ -d /sys/firmware/efi ] && [ -f /media/startos/config/efi-installer-entry ]; fi fi +# Sign unsigned kernel modules for Secure Boot +SIGN_FILE="$(ls -1 /media/startos/next/usr/lib/linux-kbuild-*/scripts/sign-file 2>/dev/null | head -1)" +/media/startos/next/usr/lib/startos/scripts/sign-unsigned-modules \ + --source /media/startos/lower \ + --dest /media/startos/config/overlay \ + --sign-file "$SIGN_FILE" \ + --mok-key /media/startos/config/overlay/var/lib/dkms/mok.key \ + --mok-pub /media/startos/config/overlay/var/lib/dkms/mok.pub + sync umount -Rl /media/startos/next diff --git a/core/src/error.rs b/core/src/error.rs index 55b4494b1..88f664394 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -101,6 +101,7 @@ pub enum ErrorKind { UpdateFailed = 77, Smtp = 78, SetSysInfo = 79, + Bios = 80, } impl ErrorKind { pub fn as_str(&self) -> String { @@ -185,6 +186,7 @@ impl ErrorKind { UpdateFailed => t!("error.update-failed"), Smtp => t!("error.smtp"), SetSysInfo => t!("error.set-sys-info"), + Bios => t!("error.bios"), } .to_string() } diff --git a/core/src/init.rs b/core/src/init.rs index e5792adea..fe5f334f2 100644 --- a/core/src/init.rs +++ b/core/src/init.rs @@ -173,6 +173,11 @@ pub async fn init( RpcContext::init_auth_cookie().await?; local_auth.complete(); + // Re-enroll MOK on every boot if Secure Boot key exists but isn't enrolled yet + if let Err(e) = crate::util::mok::enroll_mok(std::path::Path::new(crate::util::mok::DKMS_MOK_PUB)).await { + tracing::warn!("MOK enrollment failed: {e}"); + } + load_database.start(); let db = cfg.db().await?; crate::version::Current::default().pre_init(&db).await?; diff --git a/core/src/os_install/mod.rs b/core/src/os_install/mod.rs index d04491acd..6a1c00f35 100644 --- a/core/src/os_install/mod.rs +++ b/core/src/os_install/mod.rs @@ -21,7 +21,7 @@ use crate::prelude::*; use crate::s9pk::merkle_archive::source::multi_cursor_file::MultiCursorFile; use crate::setup::SetupInfo; use crate::util::Invoke; -use crate::util::io::{TmpDir, delete_file, open_file, write_file_atomic}; +use crate::util::io::{TmpDir, delete_dir, delete_file, open_file, write_file_atomic}; use crate::util::serde::IoFormat; mod gpt; @@ -30,12 +30,7 @@ mod mbr; /// Get the EFI BootCurrent entry number (the entry firmware used to boot). /// Returns None on non-EFI systems or if BootCurrent is not set. async fn get_efi_boot_current() -> Result, Error> { - let efi_output = String::from_utf8( - Command::new("efibootmgr") - .invoke(ErrorKind::Grub) - .await?, - ) - .map_err(|e| Error::new(eyre!("efibootmgr output not valid UTF-8: {e}"), ErrorKind::Grub))?; + let efi_output = String::from_utf8(Command::new("efibootmgr").invoke(ErrorKind::Grub).await?)?; Ok(efi_output .lines() @@ -46,12 +41,7 @@ async fn get_efi_boot_current() -> Result, Error> { /// Promote a specific boot entry to first in the EFI boot order. async fn promote_efi_entry(entry: &str) -> Result<(), Error> { - let efi_output = String::from_utf8( - Command::new("efibootmgr") - .invoke(ErrorKind::Grub) - .await?, - ) - .map_err(|e| Error::new(eyre!("efibootmgr output not valid UTF-8: {e}"), ErrorKind::Grub))?; + let efi_output = String::from_utf8(Command::new("efibootmgr").invoke(ErrorKind::Grub).await?)?; let current_order = efi_output .lines() @@ -182,6 +172,7 @@ struct DataDrive { pub struct InstallOsResult { pub part_info: OsPartitionInfo, pub rootfs: TmpMountGuard, + pub mok_enrolled: bool, } pub async fn install_os_to( @@ -230,6 +221,7 @@ pub async fn install_os_to( delete_file(guard.path().join("config/upgrade")).await?; delete_file(guard.path().join("config/overlay/etc/hostname")).await?; delete_file(guard.path().join("config/disk.guid")).await?; + delete_dir(guard.path().join("config/lib/modules")).await?; Command::new("cp") .arg("-r") .arg(guard.path().join("config")) @@ -265,9 +257,7 @@ pub async fn install_os_to( let config_path = rootfs.path().join("config"); if tokio::fs::metadata("/tmp/config.bak").await.is_ok() { - if tokio::fs::metadata(&config_path).await.is_ok() { - tokio::fs::remove_dir_all(&config_path).await?; - } + crate::util::io::delete_dir(&config_path).await?; Command::new("cp") .arg("-r") .arg("/tmp/config.bak") @@ -402,6 +392,28 @@ pub async fn install_os_to( .invoke(crate::ErrorKind::OpenSsh) .await?; + // Secure Boot: generate MOK key, sign unsigned modules, enroll MOK + let mut mok_enrolled = false; + if use_efi && crate::util::mok::is_secure_boot_enabled().await { + let new_key = crate::util::mok::ensure_dkms_key(overlay.path()).await?; + tracing::info!( + "DKMS MOK key: {}", + if new_key { + "generated" + } else { + "already exists" + } + ); + + crate::util::mok::sign_unsigned_modules(overlay.path()).await?; + + let mok_pub = overlay.path().join(crate::util::mok::DKMS_MOK_PUB.trim_start_matches('/')); + match crate::util::mok::enroll_mok(&mok_pub).await { + Ok(enrolled) => mok_enrolled = enrolled, + Err(e) => tracing::warn!("MOK enrollment failed: {e}"), + } + } + let mut install = Command::new("chroot"); install.arg(overlay.path()).arg("grub-install"); if !use_efi { @@ -443,7 +455,11 @@ pub async fn install_os_to( tokio::fs::remove_dir_all(&work).await?; lower.unmount().await?; - Ok(InstallOsResult { part_info, rootfs }) + Ok(InstallOsResult { + part_info, + rootfs, + mok_enrolled, + }) } pub async fn install_os( @@ -500,7 +516,11 @@ pub async fn install_os( None }; - let InstallOsResult { part_info, rootfs } = install_os_to( + let InstallOsResult { + part_info, + rootfs, + mok_enrolled, + } = install_os_to( "/run/live/medium/live/filesystem.squashfs", &disk.logicalname, disk.capacity, @@ -529,6 +549,7 @@ pub async fn install_os( .mutate(|c| c.os_partitions = Some(part_info.clone())); let mut setup_info = SetupInfo::default(); + setup_info.mok_enrolled = mok_enrolled; if let Some(data_drive) = data_drive { let mut logicalname = &*data_drive.logicalname; @@ -612,7 +633,11 @@ pub async fn cli_install_os( let use_efi = efi.unwrap_or_else(|| !matches!(partition_table, Some(PartitionTable::Mbr))); - let InstallOsResult { part_info, rootfs } = install_os_to( + let InstallOsResult { + part_info, + rootfs, + mok_enrolled: _, + } = install_os_to( &squashfs, &disk, capacity, diff --git a/core/src/setup.rs b/core/src/setup.rs index 850647752..ebb177cbc 100644 --- a/core/src/setup.rs +++ b/core/src/setup.rs @@ -279,6 +279,7 @@ pub enum SetupStatusRes { pub struct SetupInfo { pub guid: Option, pub attach: bool, + pub mok_enrolled: bool, } #[derive(Debug, Deserialize, Serialize, TS)] @@ -630,6 +631,7 @@ async fn fresh_setup( }: SetupExecuteProgress, ) -> Result<(SetupResult, RpcContext), Error> { let account = AccountInfo::new(password, root_ca_start_time().await, hostname)?; + let db = ctx.db().await?; let kiosk = Some(kiosk.unwrap_or(true)).filter(|_| &*PLATFORM != "raspberrypi"); sync_kiosk(kiosk).await?; diff --git a/core/src/util/mod.rs b/core/src/util/mod.rs index 9aac08fd8..6cdc345a1 100644 --- a/core/src/util/mod.rs +++ b/core/src/util/mod.rs @@ -45,6 +45,7 @@ pub mod iter; pub mod logger; pub mod lshw; pub mod mime; +pub mod mok; pub mod net; pub mod rpc; pub mod rpc_client; diff --git a/core/src/util/mok.rs b/core/src/util/mok.rs new file mode 100644 index 000000000..129e30167 --- /dev/null +++ b/core/src/util/mok.rs @@ -0,0 +1,125 @@ +use std::path::Path; + +use tokio::process::Command; + +use crate::prelude::*; +use crate::util::Invoke; +use crate::util::io::{delete_file, maybe_open_file, write_file_atomic}; + +pub const DKMS_MOK_KEY: &str = "/var/lib/dkms/mok.key"; +pub const DKMS_MOK_PUB: &str = "/var/lib/dkms/mok.pub"; + +pub async fn is_secure_boot_enabled() -> bool { + String::from_utf8_lossy( + &Command::new("mokutil") + .arg("--sb-state") + .env("LANG", "C.UTF-8") + .invoke(ErrorKind::Bios) + .await + .unwrap_or_default(), + ) + .contains("SecureBoot enabled") +} + +/// Generate a DKMS MOK key pair if one doesn't exist. +pub async fn ensure_dkms_key(root: &Path) -> Result { + let key_path = root.join(DKMS_MOK_KEY.trim_start_matches('/')); + if maybe_open_file(&key_path).await?.is_some() { + return Ok(false); // Already exists + } + Command::new("chroot") + .arg(root) + .arg("dkms") + .arg("generate_mok") + .invoke(ErrorKind::Bios) + .await?; + Ok(true) // Newly generated +} + +/// Sign all unsigned kernel modules in the given root using the DKMS MOK key. +/// Calls the sign-unsigned-modules script inside the chroot. +pub async fn sign_unsigned_modules(root: &Path) -> Result<(), Error> { + Command::new("chroot") + .arg(root) + .arg("/usr/lib/startos/scripts/sign-unsigned-modules") + .invoke(ErrorKind::OpenSsl) + .await?; + Ok(()) +} + +/// Read the start9 user's password hash from /etc/shadow. +/// Returns None if the user doesn't exist or the password is locked. +async fn start9_shadow_hash() -> Result, Error> { + let shadow = tokio::fs::read_to_string("/etc/shadow").await?; + for line in shadow.lines() { + if let Some(("start9", rest)) = line.split_once(':') { + if let Some((hash, _)) = rest.split_once(':') { + let hash = hash.trim_start_matches("!"); + if hash.starts_with('$') { + return Ok(Some(hash.to_owned())); + } + // Locked or invalid password + return Ok(None); + } + } + } + Ok(None) +} + +/// Enroll the DKMS MOK certificate using the start9 user's password from /etc/shadow. +/// Idempotent: skips if already enrolled, or if the user's password is not yet set. +/// `mok_pub` is the path to the MOK public certificate (may be inside a chroot overlay during install). +/// Returns true if a new enrollment was staged. +pub async fn enroll_mok(mok_pub: &Path) -> Result { + tracing::info!("enroll_mok: checking EFI and mok_pub={}", mok_pub.display()); + if tokio::fs::metadata("/sys/firmware/efi").await.is_err() { + tracing::info!("enroll_mok: no EFI, skipping"); + return Ok(false); + } + if maybe_open_file(mok_pub).await?.is_none() { + tracing::info!("enroll_mok: mok_pub not found, skipping"); + return Ok(false); + } + + // Check if already enrolled in firmware + let test_output = Command::new("mokutil") + .arg("--test-key") + .arg(mok_pub) + .env("LANG", "C.UTF-8") + .invoke(ErrorKind::Bios) + .await?; + let test_str = String::from_utf8(test_output)?; + tracing::info!("enroll_mok: mokutil --test-key output: {test_str:?}"); + if test_str.contains("is enrolled") { + tracing::info!("enroll_mok: already enrolled, skipping"); + return Ok(false); + } + + let Some(hash) = start9_shadow_hash().await? else { + tracing::info!("enroll_mok: start9 user password not set, skipping"); + return Ok(false); + }; + + // Revoke any pending enrollment (so we can re-import with current password) + let _ = Command::new("mokutil") + .arg("--revoke-import") + .arg(mok_pub) + .invoke(ErrorKind::Bios) + .await; + + let hash_file = Path::new("/tmp/mok-password-hash"); + write_file_atomic(hash_file, &hash).await?; + + tracing::info!("Enrolling DKMS MOK certificate"); + let result = Command::new("mokutil") + .arg("--import") + .arg(mok_pub) + .arg("--hash-file") + .arg(hash_file) + .invoke(ErrorKind::Bios) + .await; + + delete_file(hash_file).await.log_err(); + result?; + Ok(true) +} diff --git a/sdk/base/lib/osBindings/SetupInfo.ts b/sdk/base/lib/osBindings/SetupInfo.ts index 06b6447e6..5f78d28c8 100644 --- a/sdk/base/lib/osBindings/SetupInfo.ts +++ b/sdk/base/lib/osBindings/SetupInfo.ts @@ -1,3 +1,7 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type SetupInfo = { guid: string | null; attach: boolean } +export type SetupInfo = { + guid: string | null + attach: boolean + mokEnrolled: boolean +} diff --git a/web/projects/setup-wizard/src/app/app.component.ts b/web/projects/setup-wizard/src/app/app.component.ts index 1f9b84705..6276aab5b 100644 --- a/web/projects/setup-wizard/src/app/app.component.ts +++ b/web/projects/setup-wizard/src/app/app.component.ts @@ -40,6 +40,7 @@ export class AppComponent { this.stateService.dataDriveGuid = status.guid } this.stateService.attach = status.attach + this.stateService.mokEnrolled = status.mokEnrolled await this.router.navigate(['/language']) break diff --git a/web/projects/setup-wizard/src/app/components/mok-enrollment.dialog.ts b/web/projects/setup-wizard/src/app/components/mok-enrollment.dialog.ts new file mode 100644 index 000000000..03da02d11 --- /dev/null +++ b/web/projects/setup-wizard/src/app/components/mok-enrollment.dialog.ts @@ -0,0 +1,77 @@ +import { Component } from '@angular/core' +import { i18nPipe } from '@start9labs/shared' +import { TuiButton, TuiDialogContext, TuiIcon } from '@taiga-ui/core' +import { injectContext } from '@taiga-ui/polymorpheus' + +@Component({ + standalone: true, + imports: [TuiButton, TuiIcon, i18nPipe], + template: ` +
+ +
+

{{ 'Secure Boot Key Enrollment' | i18n }}

+

+ {{ + 'A signing key was enrolled for Secure Boot. On the next reboot, a blue screen (MokManager) will appear.' + | i18n + }} +

+
    +
  1. Select "Enroll MOK"
  2. +
  3. Select "Continue"
  4. +
  5. {{ 'Enter your StartOS master password when prompted' | i18n }}
  6. +
  7. Select "Reboot"
  8. +
+
+ +
+ `, + styles: ` + :host { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + } + + .icon-container { + margin-bottom: 1rem; + } + + .mok-icon { + width: 3rem; + height: 3rem; + color: var(--tui-status-info); + } + + h3 { + margin: 0 0 0.5rem; + } + + p { + margin: 0 0 1rem; + color: var(--tui-text-secondary); + } + + ol { + text-align: left; + margin: 0 0 1.5rem; + padding-left: 1.5rem; + + li { + margin-bottom: 0.25rem; + } + } + + footer { + display: flex; + justify-content: center; + } + `, +}) +export class MokEnrollmentDialog { + protected readonly context = injectContext>() +} diff --git a/web/projects/setup-wizard/src/app/pages/drives.page.ts b/web/projects/setup-wizard/src/app/pages/drives.page.ts index 1d20848c1..b473d1763 100644 --- a/web/projects/setup-wizard/src/app/pages/drives.page.ts +++ b/web/projects/setup-wizard/src/app/pages/drives.page.ts @@ -390,6 +390,7 @@ export default class DrivesPage { this.stateService.dataDriveGuid = result.guid this.stateService.attach = result.attach + this.stateService.mokEnrolled = result.mokEnrolled loader.unsubscribe() diff --git a/web/projects/setup-wizard/src/app/pages/success.page.ts b/web/projects/setup-wizard/src/app/pages/success.page.ts index c27a539ed..2a9e74903 100644 --- a/web/projects/setup-wizard/src/app/pages/success.page.ts +++ b/web/projects/setup-wizard/src/app/pages/success.page.ts @@ -19,6 +19,7 @@ import { ApiService } from '../services/api.service' import { StateService } from '../services/state.service' import { DocumentationComponent } from '../components/documentation.component' import { MatrixComponent } from '../components/matrix.component' +import { MokEnrollmentDialog } from '../components/mok-enrollment.dialog' import { RemoveMediaDialog } from '../components/remove-media.dialog' import { T } from '@start9labs/start-sdk' import { PolymorpheusComponent } from '@taiga-ui/polymorpheus' @@ -275,6 +276,20 @@ export default class SuccessPage implements AfterViewInit { await this.api.exit() } } + + if (this.stateService.mokEnrolled && this.result.needsRestart) { + this.dialogs + .openComponent( + new PolymorpheusComponent(MokEnrollmentDialog), + { + label: 'Secure Boot', + size: 's', + dismissible: false, + closeable: true, + }, + ) + .subscribe() + } } catch (e: any) { this.errorService.handleError(e) } diff --git a/web/projects/setup-wizard/src/app/services/mock-api.service.ts b/web/projects/setup-wizard/src/app/services/mock-api.service.ts index 743977e94..b749e9853 100644 --- a/web/projects/setup-wizard/src/app/services/mock-api.service.ts +++ b/web/projects/setup-wizard/src/app/services/mock-api.service.ts @@ -116,6 +116,7 @@ export class MockApiService extends ApiService { return { guid: 'mock-data-guid', attach: !params.dataDrive.wipe, + mokEnrolled: false, } } diff --git a/web/projects/setup-wizard/src/app/services/state.service.ts b/web/projects/setup-wizard/src/app/services/state.service.ts index a3aba2c7b..4a5b5090f 100644 --- a/web/projects/setup-wizard/src/app/services/state.service.ts +++ b/web/projects/setup-wizard/src/app/services/state.service.ts @@ -42,6 +42,7 @@ export class StateService { // From install response or status response (incomplete) dataDriveGuid = '' attach = false + mokEnrolled = false // Set during setup flow setupType?: SetupType @@ -116,6 +117,7 @@ export class StateService { this.keyboard = '' this.dataDriveGuid = '' this.attach = false + this.mokEnrolled = false this.setupType = undefined this.recoverySource = undefined } diff --git a/web/projects/setup-wizard/src/app/types.ts b/web/projects/setup-wizard/src/app/types.ts index 5d860666a..c810a4492 100644 --- a/web/projects/setup-wizard/src/app/types.ts +++ b/web/projects/setup-wizard/src/app/types.ts @@ -13,6 +13,7 @@ export interface InstallOsParams { export interface InstallOsRes { guid: string // data drive guid attach: boolean + mokEnrolled: boolean } // === Disk Info Helpers === diff --git a/web/projects/shared/src/i18n/dictionaries/de.ts b/web/projects/shared/src/i18n/dictionaries/de.ts index 95f7152bb..d3b08a699 100644 --- a/web/projects/shared/src/i18n/dictionaries/de.ts +++ b/web/projects/shared/src/i18n/dictionaries/de.ts @@ -709,4 +709,8 @@ export default { 786: 'Automatisch', 787: 'Ausgehender Datenverkehr', 788: 'Gateway verwenden', + 789: 'Secure-Boot-Schlüsselregistrierung', + 790: 'Ein Signaturschlüssel wurde für Secure Boot registriert. Beim nächsten Neustart erscheint ein blauer Bildschirm (MokManager).', + 791: 'Geben Sie Ihr StartOS-Master-Passwort ein, wenn Sie dazu aufgefordert werden', + 792: 'Verstanden', } satisfies i18n diff --git a/web/projects/shared/src/i18n/dictionaries/en.ts b/web/projects/shared/src/i18n/dictionaries/en.ts index e7856c8fc..150d0b7f9 100644 --- a/web/projects/shared/src/i18n/dictionaries/en.ts +++ b/web/projects/shared/src/i18n/dictionaries/en.ts @@ -709,4 +709,9 @@ export const ENGLISH: Record = { 'Auto': 786, 'Outbound Traffic': 787, 'Use gateway': 788, + // Secure Boot MOK enrollment + 'Secure Boot Key Enrollment': 789, + 'A signing key was enrolled for Secure Boot. On the next reboot, a blue screen (MokManager) will appear.': 790, + 'Enter your StartOS master password when prompted': 791, + 'Got it': 792, } diff --git a/web/projects/shared/src/i18n/dictionaries/es.ts b/web/projects/shared/src/i18n/dictionaries/es.ts index 11cf98218..c08883135 100644 --- a/web/projects/shared/src/i18n/dictionaries/es.ts +++ b/web/projects/shared/src/i18n/dictionaries/es.ts @@ -709,4 +709,8 @@ export default { 786: 'Automático', 787: 'Tráfico saliente', 788: 'Usar gateway', + 789: 'Registro de clave de Secure Boot', + 790: 'Se registró una clave de firma para Secure Boot. En el próximo reinicio, aparecerá una pantalla azul (MokManager).', + 791: 'Ingrese su contraseña maestra de StartOS cuando se le solicite', + 792: 'Entendido', } satisfies i18n diff --git a/web/projects/shared/src/i18n/dictionaries/fr.ts b/web/projects/shared/src/i18n/dictionaries/fr.ts index 678f5961b..23dce8287 100644 --- a/web/projects/shared/src/i18n/dictionaries/fr.ts +++ b/web/projects/shared/src/i18n/dictionaries/fr.ts @@ -709,4 +709,8 @@ export default { 786: 'Automatique', 787: 'Trafic sortant', 788: 'Utiliser la passerelle', + 789: "Enregistrement de la clé Secure Boot", + 790: "Une clé de signature a été enregistrée pour Secure Boot. Au prochain redémarrage, un écran bleu (MokManager) apparaîtra.", + 791: 'Entrez votre mot de passe principal StartOS lorsque vous y êtes invité', + 792: 'Compris', } satisfies i18n diff --git a/web/projects/shared/src/i18n/dictionaries/pl.ts b/web/projects/shared/src/i18n/dictionaries/pl.ts index b26484268..deef38a37 100644 --- a/web/projects/shared/src/i18n/dictionaries/pl.ts +++ b/web/projects/shared/src/i18n/dictionaries/pl.ts @@ -709,4 +709,8 @@ export default { 786: 'Automatycznie', 787: 'Ruch wychodzący', 788: 'Użyj bramy', + 789: 'Rejestracja klucza Secure Boot', + 790: 'Klucz podpisu został zarejestrowany dla Secure Boot. Przy następnym uruchomieniu pojawi się niebieski ekran (MokManager).', + 791: 'Wprowadź swoje hasło główne StartOS po wyświetleniu monitu', + 792: 'Rozumiem', } satisfies i18n