diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/basic/efivars.h | 7 | ||||
-rw-r--r-- | src/basic/utf8.c | 4 | ||||
-rw-r--r-- | src/basic/utf8.h | 1 | ||||
-rw-r--r-- | src/boot/bootctl-install.c | 202 | ||||
-rw-r--r-- | src/boot/bootctl.c | 64 | ||||
-rw-r--r-- | src/boot/bootctl.h | 6 | ||||
-rw-r--r-- | src/boot/meson.build | 2 | ||||
-rw-r--r-- | src/fundamental/efi-fundamental.h | 61 |
8 files changed, 343 insertions, 4 deletions
diff --git a/src/basic/efivars.h b/src/basic/efivars.h index b4f0da5ed8..f9167a2ee6 100644 --- a/src/basic/efivars.h +++ b/src/basic/efivars.h @@ -23,9 +23,10 @@ #define EFI_VENDOR_SYSTEMD SD_ID128_MAKE(8c,f2,64,4b,4b,0b,42,8f,93,87,6d,87,60,50,dc,67) #define EFI_VENDOR_SYSTEMD_STR SD_ID128_MAKE_UUID_STR(8c,f2,64,4b,4b,0b,42,8f,93,87,6d,87,60,50,dc,67) -#define EFI_VARIABLE_NON_VOLATILE UINT32_C(0x00000001) -#define EFI_VARIABLE_BOOTSERVICE_ACCESS UINT32_C(0x00000002) -#define EFI_VARIABLE_RUNTIME_ACCESS UINT32_C(0x00000004) +#define EFI_VARIABLE_NON_VOLATILE UINT32_C(0x00000001) +#define EFI_VARIABLE_BOOTSERVICE_ACCESS UINT32_C(0x00000002) +#define EFI_VARIABLE_RUNTIME_ACCESS UINT32_C(0x00000004) +#define EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS UINT32_C(0x00000020) /* Note that the <lowercaseuuid>-<varname> naming scheme is an efivarfs convention, i.e. part of the Linux * API file system implementation for EFI. EFI itself processes UIDS in binary form. diff --git a/src/basic/utf8.c b/src/basic/utf8.c index 0d904e53af..2a9da59881 100644 --- a/src/basic/utf8.c +++ b/src/basic/utf8.c @@ -504,6 +504,10 @@ size_t char16_strlen(const char16_t *s) { return n; } +size_t char16_strsize(const char16_t *s) { + return s ? (char16_strlen(s) + 1) * sizeof(*s) : 0; +} + /* expected size used to encode one unicode char */ static int utf8_unichar_to_encoded_len(char32_t unichar) { diff --git a/src/basic/utf8.h b/src/basic/utf8.h index 8a5e884a0a..221bc46a2d 100644 --- a/src/basic/utf8.h +++ b/src/basic/utf8.h @@ -42,6 +42,7 @@ char* utf16_to_utf8(const char16_t *s, size_t length /* bytes! */); char16_t *utf8_to_utf16(const char *s, size_t length); size_t char16_strlen(const char16_t *s); /* returns the number of 16-bit words in the string (not bytes!) */ +size_t char16_strsize(const char16_t *s); int utf8_encoded_valid_unichar(const char *str, size_t length); int utf8_encoded_to_unichar(const char *str, char32_t *ret_unichar); 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(×tamp); + 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, ×tamp, 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', diff --git a/src/fundamental/efi-fundamental.h b/src/fundamental/efi-fundamental.h index 2862932ec9..75cfd23b8c 100644 --- a/src/fundamental/efi-fundamental.h +++ b/src/fundamental/efi-fundamental.h @@ -10,4 +10,65 @@ typedef struct { uint16_t Data3; uint8_t Data4[8]; } EFI_GUID; + +typedef struct { + EFI_GUID SignatureOwner; + uint8_t SignatureData[]; +} EFI_SIGNATURE_DATA; + +typedef struct { + EFI_GUID SignatureType; + uint32_t SignatureListSize; + uint32_t SignatureHeaderSize; + uint32_t SignatureSize; + EFI_SIGNATURE_DATA Signatures[]; +} EFI_SIGNATURE_LIST; + +typedef struct { + uint32_t dwLength; + uint16_t wRevision; + uint16_t wCertificateType; + uint8_t bCertificate[]; +} WIN_CERTIFICATE; + +typedef struct { + WIN_CERTIFICATE Hdr; + EFI_GUID CertType; + uint8_t CertData[]; +} WIN_CERTIFICATE_UEFI_GUID; + +typedef struct { + uint16_t Year; + uint8_t Month; + uint8_t Day; + uint8_t Hour; + uint8_t Minute; + uint8_t Second; + uint8_t Pad1; + uint32_t Nanosecond; + int16_t TimeZone; + uint8_t Daylight; + uint8_t Pad2; +} EFI_TIME; + +typedef struct { + EFI_TIME TimeStamp; + WIN_CERTIFICATE_UEFI_GUID AuthInfo; +} EFI_VARIABLE_AUTHENTICATION_2; + +#define GUID_DEF(d1, d2, d3, d4_1, d4_2, d4_3, d4_4, d4_5, d4_6, d4_7, d4_8) \ + { d1, d2, d3, { d4_1, d4_2, d4_3, d4_4, d4_5, d4_6, d4_7, d4_8 } } + +#define MAKE_GUID_PTR(name) ((EFI_GUID *) &(const EFI_GUID) name##_GUID) + +#define EFI_GLOBAL_VARIABLE \ + GUID_DEF(0x8be4df61, 0x93ca, 0x11d2, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c) +#define EFI_IMAGE_SECURITY_DATABASE_GUID \ + GUID_DEF(0xd719b2cb, 0x3d3a, 0x4596, 0xa3, 0xbc, 0xda, 0xd0, 0x0e, 0x67, 0x65, 0x6f) + +#define EFI_CERT_X509_GUID \ + GUID_DEF(0xa5c059a1, 0x94e4, 0x4aa7, 0x87, 0xb5, 0xab, 0x15, 0x5c, 0x2b, 0xf0, 0x72) +#define EFI_CERT_TYPE_PKCS7_GUID \ + GUID_DEF(0x4aafd29d, 0x68df, 0x49ee, 0x8a, 0xa9, 0x34, 0x7d, 0x37, 0x56, 0x65, 0xa7) + #endif |