summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVincent Dagonneau <v@vda.io>2022-05-09 14:13:28 -0400
committerLennart Poettering <lennart@poettering.net>2022-08-03 10:11:08 +0200
commite6b0cfad514f44d9b77380793b02302772a98654 (patch)
tree332444d7b0edbaf8534311bb5e232aa5bd617ea7
parent9e6e3379ba9a68e6d4bcb12c0d6742d0dee64b00 (diff)
downloadsystemd-e6b0cfad514f44d9b77380793b02302772a98654.tar.gz
This patch adds support for enrolling secure boot boot keys from sd-boot.
***DANGER*** NOTE ***DANGER*** This feature might result in your device becoming soft-brick as outlined below, please use this feature carefully. ***DANGER*** NOTE ***DANGER*** If secure-boot-enrollment is set to no, then no action whatsoever is performed, no matter the files on the ESP. If secure boot keys are found under $ESP/loader/keys and secure-boot-enrollment is set to either manual or force then sd-boot will generate enrollment entries named after the directories they are in. The entries are shown at the very bottom of the list and can be selected by the user from the menu. If the user selects it, the user is shown a screen allowing for cancellation before a timeout. The enrollment proceeds if the action is not cancelled after the timeout. Additionally, if the secure-boot-enroll option is set to 'force' then the keys located in the directory named 'auto' are going to be enrolled automatically. The user is still going to be shown a screen allowing them to cancel the action if they want to, however the enrollment will proceed automatically after a timeout without user cancellation. After keys are enrolled, the system reboots with secure boot enabled therefore, it is ***critical*** to ensure that everything needed for the system to boot is signed properly (sd-boot itself, kernel, initramfs, PCI option ROMs). This feature currently only allows loading the most simple set of variables: PK, KEK and db. The files need to be prepared with cert-to-efi-sig-list and then signed with sign-efi-sig-list. Here is a short example to generate your own keys and the right files for auto-enrollement. ` keys="PK KEK DB" uuid="{$(systemd-id128 new -u)}" for key in ${keys}; 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}.esl.nosign" done sign-efi-sig-list -c PK.crt -k PK.key PK PK.esl.nosign PK.esl sign-efi-sig-list -c PK.crt -k PK.key KEK KEK.esl.nosign KEK.esl sign-efi-sig-list -c KEK.crt -k KEK.key db db.esl.nosign db.esl ` Once these keys are enrolled, all the files needed for boot ***NEED*** to be signed in order to run. You can sign the binaries with the sbsign tool, for example: ` sbsign --key db.key --cert db.crt bzImage --output $ESP/bzImage ` Example: Assuming the system has been put in Setup Mode: ` $ESP/loader/keys/auto/db.esl $ESP/loader/keys/auto/KEK.esl $ESP/loader/keys/auto/PK.esl $ESP/loader/keys/Linux Only/db.esl $ESP/loader/keys/Linux Only/KEK.esl $ESP/loader/keys/Linux Only/PK.esl $ESP/loader/keys/Linux and Windows/db.esl $ESP/loader/keys/Linux and Windows/KEK.esl $ESP/loader/keys/Linux and Windows/PK.esl ` If auto-enroll is set, then the db, KEK and then PK are enrolled from the 'auto' directory. If not, three new boot entries are available to the user in order to enroll either the 'Linux Only', 'Linux And Windows' or 'auto' set of keys.
-rw-r--r--man/loader.conf.xml45
-rw-r--r--man/systemd-boot.xml12
-rw-r--r--src/boot/efi/boot.c89
-rw-r--r--src/boot/efi/meson.build2
-rw-r--r--src/boot/efi/missing_efi.h7
-rw-r--r--src/boot/efi/secure-boot.c88
-rw-r--r--src/boot/efi/secure-boot.h8
7 files changed, 250 insertions, 1 deletions
diff --git a/man/loader.conf.xml b/man/loader.conf.xml
index 3606de5704..acddb193d1 100644
--- a/man/loader.conf.xml
+++ b/man/loader.conf.xml
@@ -223,6 +223,51 @@
</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>Caveat: This feature is experimental, and is likely to be changed (or removed in its
diff --git a/man/systemd-boot.xml b/man/systemd-boot.xml
index 393c0312fa..02790370df 100644
--- a/man/systemd-boot.xml
+++ b/man/systemd-boot.xml
@@ -55,6 +55,9 @@
<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>
@@ -91,6 +94,9 @@
<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>
@@ -311,6 +317,12 @@
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>
diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c
index b3f424d8ba..db0bbab0f2 100644
--- a/src/boot/efi/boot.c
+++ b/src/boot/efi/boot.c
@@ -48,6 +48,7 @@ enum loader_type {
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 {
@@ -88,6 +89,7 @@ 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;
@@ -528,6 +530,17 @@ static void print_status(Config *config, char16_t *loaded_image_path) {
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;
@@ -1217,6 +1230,17 @@ static void config_defaults_load_from_file(Config *config, char *content) {
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;
}
@@ -1519,6 +1543,7 @@ static void config_load_defaults(Config *config, EFI_FILE *root_dir) {
.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,
@@ -2429,6 +2454,55 @@ static void save_selected_entry(const Config *config, const ConfigEntry *entry)
(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,
@@ -2518,6 +2592,13 @@ static void config_load_all_entries(
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;
@@ -2606,6 +2687,14 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
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) {
diff --git a/src/boot/efi/meson.build b/src/boot/efi/meson.build
index 0129fcd070..83f7384530 100644
--- a/src/boot/efi/meson.build
+++ b/src/boot/efi/meson.build
@@ -355,6 +355,7 @@ efi_headers = files(
common_sources = files(
'assert.c',
+ 'console.c',
'devicetree.c',
'disk.c',
'efi-string.c',
@@ -369,7 +370,6 @@ common_sources = files(
systemd_boot_sources = files(
'boot.c',
- 'console.c',
'drivers.c',
'random-seed.c',
'shim.c',
diff --git a/src/boot/efi/missing_efi.h b/src/boot/efi/missing_efi.h
index f9700e3422..4e80acca56 100644
--- a/src/boot/efi/missing_efi.h
+++ b/src/boot/efi/missing_efi.h
@@ -385,3 +385,10 @@ typedef struct _EFI_CONSOLE_CONTROL_PROTOCOL {
} 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
diff --git a/src/boot/efi/secure-boot.c b/src/boot/efi/secure-boot.c
index 31f634a4d7..1da1026df5 100644
--- a/src/boot/efi/secure-boot.c
+++ b/src/boot/efi/secure-boot.c
@@ -2,6 +2,7 @@
#include "sbat.h"
#include "secure-boot.h"
+#include "console.h"
#include "util.h"
bool secure_boot_enabled(void) {
@@ -33,3 +34,90 @@ SecureBootMode secure_boot_mode(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;
+}
diff --git a/src/boot/efi/secure-boot.h b/src/boot/efi/secure-boot.h
index ce43423fce..ff434ed1ad 100644
--- a/src/boot/efi/secure-boot.h
+++ b/src/boot/efi/secure-boot.h
@@ -4,5 +4,13 @@
#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);