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