summaryrefslogtreecommitdiff
path: root/src/boot
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 /src/boot
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.
Diffstat (limited to 'src/boot')
-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
5 files changed, 193 insertions, 1 deletions
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);