summaryrefslogtreecommitdiffstats
path: root/src/boot
diff options
context:
space:
mode:
authorDaan De Meyer <daan.j.demeyer@gmail.com>2024-10-31 23:33:36 +0100
committerDaan De Meyer <daan.j.demeyer@gmail.com>2024-11-03 10:46:17 +0100
commita07864a4fe8c7330ca0ccdc0213ee8e4f7e3721e (patch)
tree2aa1779ac0bf55b9521f3753977faffca2fa5f30 /src/boot
parentopenssl-util: Query engine/provider pin via ask-password (diff)
downloadsystemd-a07864a4fe8c7330ca0ccdc0213ee8e4f7e3721e.tar.xz
systemd-a07864a4fe8c7330ca0ccdc0213ee8e4f7e3721e.zip
bootctl: Add --secure-boot-auto-enroll
When specified, bootctl install will also set up secure boot auto-enrollment. For now, We sign all variables using the same certificate and key pair.
Diffstat (limited to 'src/boot')
-rw-r--r--src/boot/bootctl-install.c202
-rw-r--r--src/boot/bootctl.c64
-rw-r--r--src/boot/bootctl.h6
-rw-r--r--src/boot/meson.build2
4 files changed, 273 insertions, 1 deletions
diff --git a/src/boot/bootctl-install.c b/src/boot/bootctl-install.c
index c3d32a1c6b..a9e2eea486 100644
--- a/src/boot/bootctl-install.c
+++ b/src/boot/bootctl-install.c
@@ -8,14 +8,17 @@
#include "copy.h"
#include "dirent-util.h"
#include "efi-api.h"
+#include "efi-fundamental.h"
#include "env-file.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "glyph-util.h"
#include "id128-util.h"
+#include "io-util.h"
#include "kernel-config.h"
#include "os-util.h"
+#include "parse-argument.h"
#include "path-util.h"
#include "rm-rf.h"
#include "stat-util.h"
@@ -295,6 +298,8 @@ static const char *const esp_subdirs[] = {
"EFI/systemd",
"EFI/BOOT",
"loader",
+ "loader/keys",
+ "loader/keys/auto",
NULL
};
@@ -569,6 +574,164 @@ static int install_entry_token(void) {
return 0;
}
+#if HAVE_OPENSSL
+static int efi_timestamp(EFI_TIME *ret) {
+ uint64_t epoch = UINT64_MAX;
+ struct tm tm = {};
+ int r;
+
+ assert(ret);
+
+ r = secure_getenv_uint64("SOURCE_DATE_EPOCH", &epoch);
+ if (r != -ENXIO)
+ log_debug_errno(r, "Failed to parse $SOURCE_DATE_EPOCH, ignoring: %m");
+
+ r = localtime_or_gmtime_usec(epoch != UINT64_MAX ? epoch : now(CLOCK_REALTIME), /*utc=*/ true, &tm);
+ if (r < 0)
+ return log_error_errno(r, "Failed to convert timestamp to calendar time: %m");
+
+ *ret = (EFI_TIME) {
+ .Year = 1900 + tm.tm_year,
+ /* tm_mon starts at 0, EFI_TIME months start at 1. */
+ .Month = tm.tm_mon + 1,
+ .Day = tm.tm_mday,
+ .Hour = tm.tm_hour,
+ .Minute = tm.tm_min,
+ .Second = tm.tm_sec,
+ };
+
+ return 0;
+}
+#endif
+
+static int install_secure_boot_auto_enroll(const char *esp, X509 *certificate, EVP_PKEY *private_key) {
+#if HAVE_OPENSSL
+ int r;
+
+ _cleanup_free_ uint8_t *dercert = NULL;
+ int dercertsz;
+ dercertsz = i2d_X509(certificate, &dercert);
+ if (dercertsz < 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert X.509 certificate to DER: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ _cleanup_close_ int keys_fd = chase_and_open("loader/keys/auto", esp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, O_DIRECTORY, NULL);
+ if (keys_fd < 0)
+ return log_error_errno(keys_fd, "Failed to chase loader/keys/auto in the ESP: %m");
+
+ uint32_t siglistsz = offsetof(EFI_SIGNATURE_LIST, Signatures) + offsetof(EFI_SIGNATURE_DATA, SignatureData) + dercertsz;
+ /* We use malloc0() to zero-initialize the SignatureOwner field of Signatures[0]. */
+ _cleanup_free_ EFI_SIGNATURE_LIST *siglist = malloc0(siglistsz);
+ if (!siglist)
+ return log_oom();
+
+ *siglist = (EFI_SIGNATURE_LIST) {
+ .SignatureType = EFI_CERT_X509_GUID,
+ .SignatureListSize = siglistsz,
+ .SignatureSize = offsetof(EFI_SIGNATURE_DATA, SignatureData) + dercertsz,
+ };
+
+ memcpy(siglist->Signatures[0].SignatureData, dercert, dercertsz);
+
+ EFI_TIME timestamp;
+ r = efi_timestamp(&timestamp);
+ if (r < 0)
+ return r;
+
+ uint32_t attrs =
+ EFI_VARIABLE_NON_VOLATILE|
+ EFI_VARIABLE_BOOTSERVICE_ACCESS|
+ EFI_VARIABLE_RUNTIME_ACCESS|
+ EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS;
+
+ FOREACH_STRING(db, "PK", "KEK", "db") {
+ _cleanup_(BIO_freep) BIO *bio = NULL;
+
+ bio = BIO_new(BIO_s_mem());
+ if (!bio)
+ return log_oom();
+
+ _cleanup_free_ char16_t *db16 = utf8_to_utf16(db, SIZE_MAX);
+ if (!db16)
+ return log_oom();
+
+ /* Don't count the trailing NUL terminator. */
+ if (BIO_write(bio, db16, char16_strsize(db16) - sizeof(char16_t)) < 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write variable name to bio");
+
+ EFI_GUID *guid = STR_IN_SET(db, "PK", "KEK") ? &(EFI_GUID) EFI_GLOBAL_VARIABLE : &(EFI_GUID) EFI_IMAGE_SECURITY_DATABASE_GUID;
+
+ if (BIO_write(bio, guid, sizeof(*guid)) < 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write variable GUID to bio");
+
+ if (BIO_write(bio, &attrs, sizeof(attrs)) < 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write variable attributes to bio");
+
+ if (BIO_write(bio, &timestamp, sizeof(timestamp)) < 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write timestamp to bio");
+
+ if (BIO_write(bio, siglist, siglistsz) < 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write signature list to bio");
+
+ _cleanup_(PKCS7_freep) PKCS7 *p7 = NULL;
+ p7 = PKCS7_sign(certificate, private_key, /*certs=*/ NULL, bio, PKCS7_DETACHED|PKCS7_NOATTR|PKCS7_BINARY|PKCS7_NOSMIMECAP);
+ if (!p7)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to calculate PKCS7 signature: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ _cleanup_free_ uint8_t *sig = NULL;
+ int sigsz = i2d_PKCS7_SIGNED(p7->d.sign, &sig);
+ if (sigsz < 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert PKCS7 signature to DER: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ size_t authsz = offsetof(EFI_VARIABLE_AUTHENTICATION_2, AuthInfo.CertData) + sigsz;
+ _cleanup_free_ EFI_VARIABLE_AUTHENTICATION_2 *auth = malloc(authsz);
+ if (!auth)
+ return log_oom();
+
+ *auth = (EFI_VARIABLE_AUTHENTICATION_2) {
+ .TimeStamp = timestamp,
+ .AuthInfo = {
+ .Hdr = {
+ .dwLength = offsetof(WIN_CERTIFICATE_UEFI_GUID, CertData) + sigsz,
+ .wRevision = 0x0200,
+ .wCertificateType = 0x0EF1, /* WIN_CERT_TYPE_EFI_GUID */
+ },
+ .CertType = EFI_CERT_TYPE_PKCS7_GUID,
+ }
+ };
+
+ memcpy(auth->AuthInfo.CertData, sig, sigsz);
+
+ _cleanup_free_ char *filename = strjoin(db, ".auth");
+ if (!filename)
+ return log_oom();
+
+ _cleanup_close_ int fd = openat(keys_fd, filename, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|O_WRONLY|O_CLOEXEC, 0600);
+ if (fd < 0)
+ return log_error_errno(fd, "Failed to open secure boot auto-enrollment file for writing: %m");
+
+ r = loop_write(fd, auth, authsz);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write authentication descriptor to secure boot auto-enrollment file: %m");
+
+ r = loop_write(fd, siglist, siglistsz);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write signature list to secure boot auto-enrollment file: %m");
+
+ if (fsync(fd) < 0 || fsync(keys_fd) < 0)
+ return log_error_errno(errno, "Failed to sync secure boot auto-enrollment file: %m");
+
+ log_info("Secure boot auto-enrollment file %s/loader/keys/auto/%s successfully written.", esp, filename);
+ }
+
+ return 0;
+#else
+ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot set up secure boot auto-enrollment.");
+#endif
+}
+
static bool same_entry(uint16_t id, sd_id128_t uuid, const char *path) {
_cleanup_free_ char *opath = NULL;
sd_id128_t ouuid;
@@ -778,6 +941,9 @@ static int are_we_installed(const char *esp_path) {
}
int verb_install(int argc, char *argv[], void *userdata) {
+ _cleanup_(X509_freep) X509 *certificate = NULL;
+ _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL;
+ _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = NULL;
sd_id128_t uuid = SD_ID128_NULL;
uint64_t pstart = 0, psize = 0;
uint32_t part = 0;
@@ -789,6 +955,26 @@ int verb_install(int argc, char *argv[], void *userdata) {
install = streq(argv[0], "install");
graceful = !install && arg_graceful; /* support graceful mode for updates */
+ if (arg_secure_boot_auto_enroll) {
+ r = openssl_load_x509_certificate(arg_certificate, &certificate);
+ if (r < 0)
+ return log_error_errno(r, "Failed to load X.509 certificate from %s: %m", arg_certificate);
+
+ r = openssl_load_private_key(
+ arg_private_key_source_type,
+ arg_private_key_source,
+ arg_private_key,
+ &(AskPasswordRequest) {
+ .id = "bootctl-private-key-pin",
+ .keyring = arg_private_key,
+ .credential = "bootctl.private-key-pin",
+ },
+ &private_key,
+ &ui);
+ if (r < 0)
+ return log_error_errno(r, "Failed to load private key from %s: %m", arg_private_key);
+ }
+
r = acquire_esp(/* unprivileged_mode= */ false, graceful, &part, &pstart, &psize, &uuid, NULL);
if (graceful && r == -ENOKEY)
return 0; /* If --graceful is specified and we can't find an ESP, handle this cleanly */
@@ -852,6 +1038,12 @@ int verb_install(int argc, char *argv[], void *userdata) {
if (r < 0)
return r;
}
+
+ if (arg_secure_boot_auto_enroll) {
+ r = install_secure_boot_auto_enroll(arg_esp_path, certificate, private_key);
+ if (r < 0)
+ return r;
+ }
}
r = install_loader_specification(arg_dollar_boot_path());
@@ -1069,6 +1261,16 @@ int verb_remove(int argc, char *argv[], void *userdata) {
if (q < 0 && r >= 0)
r = q;
+ FOREACH_STRING(db, "PK.auth", "KEK.auth", "db.auth") {
+ _cleanup_free_ char *p = path_join("/loader/keys/auto", db);
+ if (!p)
+ return log_oom();
+
+ q = remove_file(arg_esp_path, p);
+ if (q < 0 && r >= 0)
+ r = q;
+ }
+
q = remove_subdirs(arg_esp_path, esp_subdirs);
if (q < 0 && r >= 0)
r = q;
diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c
index 90232b3096..23a3d2f922 100644
--- a/src/boot/bootctl.c
+++ b/src/boot/bootctl.c
@@ -62,6 +62,11 @@ char *arg_efi_boot_option_description = NULL;
bool arg_dry_run = false;
ImagePolicy *arg_image_policy = NULL;
bool arg_varlink = false;
+bool arg_secure_boot_auto_enroll = false;
+char *arg_certificate = NULL;
+char *arg_private_key = NULL;
+KeySourceType arg_private_key_source_type = OPENSSL_KEY_SOURCE_FILE;
+char *arg_private_key_source = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep);
STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep);
@@ -71,6 +76,9 @@ STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_efi_boot_option_description, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_certificate, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep);
int acquire_esp(
int unprivileged_mode,
@@ -277,6 +285,19 @@ static int help(int argc, char *argv[], void *userdata) {
" --efi-boot-option-description=DESCRIPTION\n"
" Description of the entry in the boot option list\n"
" --dry-run Dry run (unlink and cleanup)\n"
+ " --secure-boot-auto-enroll\n"
+ " Set up secure boot auto-enrollment\n"
+ " --private-key=PATH|URI\n"
+ " Private key to use when setting up secure boot\n"
+ " auto-enrollment or an engine or provider specific\n"
+ " designation if --private-key-source= is used\n"
+ " --private-key-source=file|provider:PROVIDER|engine:ENGINE\n"
+ " Specify how to use KEY for --private-key=. Allows\n"
+ " an OpenSSL engine/provider to be used when setting\n"
+ " up secure boot auto-enrollment\n"
+ " --certificate=PATH\n"
+ " PEM certificate to use when setting up secure boot\n"
+ " auto-enrollment\n"
"\nSee the %2$s for details.\n",
program_invocation_short_name,
link,
@@ -309,6 +330,10 @@ static int parse_argv(int argc, char *argv[]) {
ARG_DRY_RUN,
ARG_PRINT_LOADER_PATH,
ARG_PRINT_STUB_PATH,
+ ARG_SECURE_BOOT_AUTO_ENROLL,
+ ARG_CERTIFICATE,
+ ARG_PRIVATE_KEY,
+ ARG_PRIVATE_KEY_SOURCE,
};
static const struct option options[] = {
@@ -339,6 +364,10 @@ static int parse_argv(int argc, char *argv[]) {
{ "all-architectures", no_argument, NULL, ARG_ARCH_ALL },
{ "efi-boot-option-description", required_argument, NULL, ARG_EFI_BOOT_OPTION_DESCRIPTION },
{ "dry-run", no_argument, NULL, ARG_DRY_RUN },
+ { "secure-boot-auto-enroll", required_argument, NULL, ARG_SECURE_BOOT_AUTO_ENROLL },
+ { "certificate", required_argument, NULL, ARG_CERTIFICATE },
+ { "private-key", required_argument, NULL, ARG_PRIVATE_KEY },
+ { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE },
{}
};
@@ -491,6 +520,35 @@ static int parse_argv(int argc, char *argv[]) {
arg_dry_run = true;
break;
+ case ARG_SECURE_BOOT_AUTO_ENROLL:
+ r = parse_boolean_argument("--secure-boot-auto-enroll=", optarg, &arg_secure_boot_auto_enroll);
+ if (r < 0)
+ return r;
+ break;
+
+ case ARG_CERTIFICATE: {
+ r = parse_path_argument(optarg, /*suppress_root=*/ false, &arg_certificate);
+ if (r < 0)
+ return r;
+ break;
+ }
+
+ case ARG_PRIVATE_KEY: {
+ r = free_and_strdup_warn(&arg_private_key, optarg);
+ if (r < 0)
+ return r;
+ break;
+ }
+
+ case ARG_PRIVATE_KEY_SOURCE:
+ r = parse_openssl_key_source_argument(
+ optarg,
+ &arg_private_key_source,
+ &arg_private_key_source_type);
+ if (r < 0)
+ return r;
+ break;
+
case '?':
return -EINVAL;
@@ -517,6 +575,12 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_dry_run && argv[optind] && !STR_IN_SET(argv[optind], "unlink", "cleanup"))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--dry is only supported with --unlink or --cleanup");
+ if (arg_secure_boot_auto_enroll && !arg_certificate)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Secure boot auto-enrollment requested but no certificate provided");
+
+ if (arg_secure_boot_auto_enroll && !arg_private_key)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Secure boot auto-enrollment requested but no private key provided");
+
r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT);
if (r < 0)
return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
diff --git a/src/boot/bootctl.h b/src/boot/bootctl.h
index 19eb93c2b1..8a67f5d8f8 100644
--- a/src/boot/bootctl.h
+++ b/src/boot/bootctl.h
@@ -6,6 +6,7 @@
#include "boot-entry.h"
#include "image-policy.h"
+#include "openssl-util.h"
#include "pager.h"
typedef enum InstallSource {
@@ -38,6 +39,11 @@ extern char *arg_efi_boot_option_description;
extern bool arg_dry_run;
extern ImagePolicy *arg_image_policy;
extern bool arg_varlink;
+extern bool arg_secure_boot_auto_enroll;
+extern char *arg_certificate;
+extern char *arg_private_key;
+extern KeySourceType arg_private_key_source_type;
+extern char *arg_private_key_source;
static inline const char* arg_dollar_boot_path(void) {
/* $BOOT shall be the XBOOTLDR partition if it exists, and otherwise the ESP */
diff --git a/src/boot/meson.build b/src/boot/meson.build
index 55b9bd6294..9c28c623d4 100644
--- a/src/boot/meson.build
+++ b/src/boot/meson.build
@@ -30,7 +30,7 @@ executables += [
],
'sources' : bootctl_sources,
'link_with' : boot_link_with,
- 'dependencies' : libblkid,
+ 'dependencies' : [libblkid, libopenssl],
},
libexec_template + {
'name' : 'systemd-bless-boot',