Currently, only x86 is supported, where it uses the PC speaker.</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term>secure-boot-enroll</term>
+
+ <listitem><para>Danger: this feature might soft-brick your device if used improperly.</para>
+
+ <para>Takes one of <literal>off</literal>, <literal>manual</literal> or <literal>force</literal>.
+ Controls the enrollment of secure boot keys. If set to <literal>off</literal>, no action whatsoever
+ is taken. If set to <literal>manual</literal> (the default) and the UEFI firmware is in setup-mode
+ then entries to manually enroll Secure Boot variables are created in the boot menu. If set to
+ <literal>force</literal>, in addition, if a directory named <filename>/loader/keys/auto/</filename>
+ exists on the ESP then the keys in that directory are enrolled automatically.</para>
+
+ <para>The different sets of variables can be set up under <filename>/loader/keys/<replaceable>NAME</replaceable></filename>
+ where <replaceable>NAME</replaceable> is the name that is going to be used as the name of the entry.
+ This allows to ship multiple sets of Secure Boot variables and choose which one to enroll at runtime.</para>
+
+ <para>Supported secure boot variables are one database for authorized images, one key exchange key (KEK)
+ and one platform key (PK). For more information, refer to the <ulink url="https://uefi.org/specifications">UEFI specification</ulink>,
+ under Secure Boot and Driver Signing. Another resource that describe the interplay of the different variables is the
+ <ulink url="https://edk2-docs.gitbook.io/understanding-the-uefi-secure-boot-chain/secure_boot_chain_in_uefi/uefi_secure_boot">
+ EDK2 documentation</ulink>.</para>
+
+ <para>A complete set of UEFI variable includes <filename>db.esl</filename>, <filename>KEK.esl</filename>
+ and <filename>PK.esl</filename>. Note that these files need to be authenticated UEFI variables. See
+ below for an example of how to generate them from regular X.509 keys.</para>
+
+ <programlisting>uuid=$(systemd-id128 new --)
+for key in PK KEK db; do
+ openssl req -new -x509 -subj "/CN=${key}/ -keyout "${key}.key" -out "${key}.crt"
+ openssl x509 -outform DER -in "${key}.crt" -out "${key}.cer"
+ cert-to-efi-sig-list -g "${uuid}" "${key}.crt" "${key}.tmp"
+done
+
+sign-efi-sig-list -c PK.crt -k PK.key PK PK.tmp PK.esl
+sign-efi-sig-list -c PK.crt -k PK.key KEK KEK.tmp KEK.esl
+sign-efi-sig-list -c KEK.crt -k KEK.key db db.tmp db.esl
+ </programlisting>
+
+ <para>This feature is considered dangerous because even if all the required files are signed with the
+ keys being loaded, some files necessary for the system to function properly still won't be. This
+ is especially the case with Option ROMs (e.g. for storage controllers or graphics cards). See
+ <ulink url="https://github.com/Foxboron/sbctl/wiki/FAQ#option-rom">Secure Boot and Option ROMs</ulink>
+ for more details.</para></listitem>
+ </varlistentry>
+
<varlistentry>
<term>reboot-for-bitlocker</term>
<listitem><para>The EFI Shell binary, if installed.</para></listitem>
<listitem><para>A reboot into the UEFI firmware setup option, if supported by the firmware.</para></listitem>
+
+ <listitem><para>Secure boot variables enrollement if the UEFI firmware is in setup-mode and files are provided
+ on the ESP.</para></listitem>
</itemizedlist>
<para><command>systemd-boot</command> supports the following features:</para>
<listitem><para>The boot manager optionally reads a random seed from the ESP partition, combines it
with a 'system token' stored in a persistent EFI variable and derives a random seed to use by the OS as
entropy pool initialization, providing a full entropy pool during early boot.</para></listitem>
+
+ <listitem><para>The boot manager allows for secure boot variables to be enrolled if the UEFI firmware is
+ in setup-mode. Additionally, variables can be automatically enrolled if configured.</para></listitem>
</itemizedlist>
<para><citerefentry><refentrytitle>bootctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
extension of the EFI architecture ID followed by <filename>.efi</filename> (e.g. for x86-64 this means a
suffix of <filename>x64.efi</filename>). This may be used to automatically load file system drivers and
similar, to extend the native firmware support.</para>
+
+ <para>Enrollment of Secure Boot variables can be performed manually or automatically if files are available
+ under <filename>/keys/<replaceable>NAME</replaceable>/{db,KEK,PK}.esl</filename>, <replaceable>NAME</replaceable>
+ being the display name for the set of variables in the menu. If one of the sets is named <filename>auto</filename>
+ then it might be enrolled automatically depending on whether <literal>secure-boot-enroll</literal> is set
+ to force or not.</para>
</refsect1>
<refsect1>
LOADER_EFI,
LOADER_LINUX, /* Boot loader spec type #1 entries */
LOADER_UNIFIED_LINUX, /* Boot loader spec type #2 entries */
+ LOADER_SECURE_BOOT_KEYS,
};
typedef struct {
bool auto_entries;
bool auto_firmware;
bool reboot_for_bitlocker;
+ secure_boot_enroll secure_boot_enroll;
bool force_menu;
bool use_saved_entry;
bool use_saved_entry_efivar;
ps_bool(L" reboot-for-bitlocker: %s\n", config->reboot_for_bitlocker);
ps_string(L" random-seed-mode: %s\n", random_seed_modes_table[config->random_seed_mode]);
+ switch (config->secure_boot_enroll) {
+ case ENROLL_OFF:
+ Print(L" secure-boot-enroll: off\n"); break;
+ case ENROLL_MANUAL:
+ Print(L" secure-boot-enroll: manual\n"); break;
+ case ENROLL_FORCE:
+ Print(L" secure-boot-enroll: force\n"); break;
+ default:
+ assert_not_reached();
+ }
+
switch (config->console_mode) {
case CONSOLE_MODE_AUTO:
Print(L" console-mode (config): %s\n", L"auto"); break;
err = parse_boolean(value, &config->reboot_for_bitlocker);
if (err != EFI_SUCCESS)
log_error_stall(L"Error parsing 'reboot-for-bitlocker' config option: %a", value);
+ }
+
+ if (streq8(key, "secure-boot-enroll")) {
+ if (streq8(value, "manual"))
+ config->secure_boot_enroll = ENROLL_MANUAL;
+ else if (streq8(value, "force"))
+ config->secure_boot_enroll = ENROLL_FORCE;
+ else if (streq8(value, "off"))
+ config->secure_boot_enroll = ENROLL_OFF;
+ else
+ log_error_stall(L"Error parsing 'secure-boot-enroll' config option: %a", value);
continue;
}
.auto_entries = true,
.auto_firmware = true,
.reboot_for_bitlocker = false,
+ .secure_boot_enroll = ENROLL_MANUAL,
.random_seed_mode = RANDOM_SEED_WITH_SYSTEM_TOKEN,
.idx_default_efivar = IDX_INVALID,
.console_mode = CONSOLE_MODE_KEEP,
(void) efivar_set(LOADER_GUID, L"LoaderEntryLastBooted", NULL, EFI_VARIABLE_NON_VOLATILE);
}
+static EFI_STATUS secure_boot_discover_keys(Config *config, EFI_FILE *root_dir) {
+ EFI_STATUS err;
+ _cleanup_(file_closep) EFI_FILE *keys_basedir = NULL;
+
+ if (secure_boot_mode() != SECURE_BOOT_SETUP)
+ return EFI_SUCCESS;
+
+ /* the lack of a 'keys' directory is not fatal and is silently ignored */
+ err = open_directory(root_dir, u"\\loader\\keys", &keys_basedir);
+ if (err == EFI_NOT_FOUND)
+ return EFI_SUCCESS;
+ if (err != EFI_SUCCESS)
+ return err;
+
+ for (;;) {
+ _cleanup_free_ EFI_FILE_INFO *dirent = NULL;
+ size_t dirent_size = 0;
+ ConfigEntry *entry = NULL;
+
+ err = readdir_harder(keys_basedir, &dirent, &dirent_size);
+ if (err != EFI_SUCCESS || !dirent)
+ return err;
+
+ if (dirent->FileName[0] == '.')
+ continue;
+
+ if (!FLAGS_SET(dirent->Attribute, EFI_FILE_DIRECTORY))
+ continue;
+
+ entry = xnew(ConfigEntry, 1);
+ *entry = (ConfigEntry) {
+ .id = xpool_print(L"secure-boot-keys-%s", dirent->FileName),
+ .title = xpool_print(L"Enroll Secure Boot keys: %s", dirent->FileName),
+ .path = xpool_print(L"\\loader\\keys\\%s", dirent->FileName),
+ .type = LOADER_SECURE_BOOT_KEYS,
+ .tries_done = -1,
+ .tries_left = -1,
+ };
+ config_add_entry(config, entry);
+
+ if (config->secure_boot_enroll == ENROLL_FORCE && strcaseeq16(dirent->FileName, u"auto"))
+ /* if we auto enroll sucessfully this call does not return, if it fails we still
+ * want to add other potential entries to the menu */
+ secure_boot_enroll_at(root_dir, entry->path);
+ }
+
+ return EFI_SUCCESS;
+}
+
static void export_variables(
EFI_LOADED_IMAGE_PROTOCOL *loaded_image,
const char16_t *loaded_image_path,
config_add_entry(config, entry);
}
+ /* find if secure boot signing keys exist and autoload them if necessary
+ otherwise creates menu entries so that the user can load them manually
+ if the secure-boot-enroll variable is set to no (the default), we do not
+ even search for keys on the ESP */
+ if (config->secure_boot_enroll != ENROLL_OFF)
+ secure_boot_discover_keys(config, root_dir);
+
if (config->entry_count == 0)
return;
break;
}
+ /* if auto enrollment is activated, we try to load keys for the given entry. */
+ if (entry->type == LOADER_SECURE_BOOT_KEYS && config.secure_boot_enroll != ENROLL_OFF) {
+ err = secure_boot_enroll_at(root_dir, entry->path);
+ if (err == EFI_SUCCESS)
+ return EFI_SUCCESS;
+ continue;
+ }
+
/* Run special entry like "reboot" now. Those that have a loader
* will be handled by image_start() instead. */
if (entry->call && !entry->loader) {
common_sources = files(
'assert.c',
+ 'console.c',
'devicetree.c',
'disk.c',
'efi-string.c',
systemd_boot_sources = files(
'boot.c',
- 'console.c',
'drivers.c',
'random-seed.c',
'shim.c',
} EFI_CONSOLE_CONTROL_PROTOCOL;
#endif
+
+#ifndef EFI_IMAGE_SECURITY_DATABASE_VARIABLE
+
+#define EFI_IMAGE_SECURITY_DATABASE_VARIABLE \
+ { 0xd719b2cb, 0x3d3a, 0x4596, {0xa3, 0xbc, 0xda, 0xd0, 0xe, 0x67, 0x65, 0x6f }}
+
+#endif
#include "sbat.h"
#include "secure-boot.h"
+#include "console.h"
#include "util.h"
bool secure_boot_enabled(void) {
#ifdef SBAT_DISTRO
static const char sbat[] _used_ _section_(".sbat") = SBAT_SECTION_TEXT;
#endif
+
+EFI_STATUS secure_boot_enroll_at(EFI_FILE *root_dir, const char16_t *path) {
+ assert(root_dir);
+ assert(path);
+
+ EFI_STATUS err;
+
+ clear_screen(COLOR_NORMAL);
+
+ Print(L"Enrolling secure boot keys from directory: \\loader\\keys\\%s\n"
+ L"Warning: Enrolling custom Secure Boot keys might soft-brick your machine!\n",
+ path);
+
+ unsigned timeout_sec = 15;
+ for(;;) {
+ PrintAt(0, ST->ConOut->Mode->CursorRow, L"Enrolling in %2u s, press any key to abort.", timeout_sec);
+
+ uint64_t key;
+ err = console_key_read(&key, 1000 * 1000);
+ if (err == EFI_NOT_READY)
+ continue;
+ if (err == EFI_TIMEOUT) {
+ if (timeout_sec == 0) /* continue enrolling keys */
+ break;
+ timeout_sec--;
+ continue;
+ }
+ if (err != EFI_SUCCESS)
+ return log_error_status_stall(err, L"Error waiting for user input to enroll Secure Boot keys: %r", err);
+
+ /* user aborted, returning EFI_SUCCESS here allows the user to go back to the menu */
+ return EFI_SUCCESS;
+ }
+
+ _cleanup_(file_closep) EFI_FILE *dir = NULL;
+
+ err = open_directory(root_dir, path, &dir);
+ if (err != EFI_SUCCESS)
+ return log_error_status_stall(err, L"Failed opening keys directory %s: %r", path, err);
+
+ struct {
+ const char16_t *name;
+ const char16_t *filename;
+ const EFI_GUID vendor;
+ char *buffer;
+ size_t size;
+ } sb_vars[] = {
+ { u"db", u"db.esl", EFI_IMAGE_SECURITY_DATABASE_VARIABLE, NULL, 0 },
+ { u"KEK", u"KEK.esl", EFI_GLOBAL_VARIABLE, NULL, 0 },
+ { u"PK", u"PK.esl", EFI_GLOBAL_VARIABLE, NULL, 0 },
+ };
+
+ /* Make sure all keys files exist before we start enrolling them by loading them from the disk first. */
+ for (size_t i = 0; i < ELEMENTSOF(sb_vars); i++) {
+ err = file_read(dir, sb_vars[i].filename, 0, 0, &sb_vars[i].buffer, &sb_vars[i].size);
+ if (err != EFI_SUCCESS) {
+ log_error_stall(L"Failed reading file %s\\%s: %r", path, sb_vars[i].filename, err);
+ goto out_deallocate;
+ }
+ }
+
+ for (size_t i = 0; i < ELEMENTSOF(sb_vars); i++) {
+ uint32_t sb_vars_opts =
+ EFI_VARIABLE_NON_VOLATILE |
+ EFI_VARIABLE_BOOTSERVICE_ACCESS |
+ EFI_VARIABLE_RUNTIME_ACCESS |
+ EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS;
+
+ err = efivar_set_raw(&sb_vars[i].vendor, sb_vars[i].name, sb_vars[i].buffer, sb_vars[i].size, sb_vars_opts);
+ if (err != EFI_SUCCESS) {
+ log_error_stall(L"Failed to write %s secure boot variable: %r", sb_vars[i].name, err);
+ goto out_deallocate;
+ }
+ }
+
+ /* The system should be in secure boot mode now and we could continue a regular boot. But at least
+ * TPM PCR7 measurements should change on next boot. Reboot now so that any OS we load does not end
+ * up relying on the old PCR state. */
+ RT->ResetSystem(EfiResetCold, EFI_SUCCESS, 0, NULL);
+ assert_not_reached();
+
+out_deallocate:
+ for (size_t i = 0; i < ELEMENTSOF(sb_vars); i++)
+ FreePool(sb_vars[i].buffer);
+
+ return err;
+}
#include <efi.h>
#include "efivars-fundamental.h"
+typedef enum {
+ ENROLL_OFF, /* no Secure Boot key enrollment whatsoever, even manual entries are not generated */
+ ENROLL_MANUAL, /* Secure Boot key enrollment is strictly manual: manual entries are generated and need to be selected by the user */
+ ENROLL_FORCE, /* Secure Boot key enrollment may be automatic if it is available but might not be safe */
+} secure_boot_enroll;
+
bool secure_boot_enabled(void);
SecureBootMode secure_boot_mode(void);
+
+EFI_STATUS secure_boot_enroll_at(EFI_FILE *root_dir, const char16_t *path);