diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index 48eeaa508c..30c987f834 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -738,9 +738,13 @@ Tools using the Varlink protocol (such as `varlinkctl`) or sd-bus (such as `systemd-ask-password`: -* `$SYSTEMD_ASK_PASSWORD_KEYRING_TIMEOUT_SEC` - takes a timespan or `default`, - which controls the expiration time of keys stored in the kernel keyring by - `systemd-ask-password`. If unset or set to `default`, the default expiration - of 150 seconds is used. If set to `0`, keys are not cached in the kernel - keyring. If set to `infinity`, keys are cached without an expiration time in - the kernel keyring. +* `$SYSTEMD_ASK_PASSWORD_KEYRING_TIMEOUT_SEC` - takes a timespan, which controls + the expiration time of keys stored in the kernel keyring by `systemd-ask-password`. + If unset, the default expiration of 150 seconds is used. If set to `0`, keys are + not cached in the kernel keyring. If set to `infinity`, keys are cached without an + expiration time in the kernel keyring. + +* `SYSTEMD_ASK_PASSWORD_KEYRING_TYPE` - takes a keyring ID or one of `thread`, + `process`, `session`, `user`, `user-session`, or `group`. Controls the kernel + keyring in which `systemd-ask-password` caches the queried password. Defaults + to `user`. diff --git a/man/bootctl.xml b/man/bootctl.xml index 6fa279aa35..eab18f7575 100644 --- a/man/bootctl.xml +++ b/man/bootctl.xml @@ -526,6 +526,31 @@ + + + + + + + Configure the ESP for secure boot auto-enrollment when invoking the + install command. Takes a boolean argument. Disabled by default. Enabling this + option will make bootctl populate the ESP with signed PK, + KEK and db signature databases, each containing the given + certificate in DER format as their only entry. These secure boot signature + databases will be picked up and enrolled by systemd-boot if secure boot is in + setup mode and secure boot auto-enrollment is enabled. + + When specifying this option, a certificate and private key have to be provided as well using + the and options. The + option takes a path to a PEM encoded X.509 certificate. The + option can take a path or a URI that will be passed to the OpenSSL + engine or provider, as specified by as a + type:name tuple, such as engine:pkcs11. The specified OpenSSL + signing engine or provider will be used to sign the EFI signature lists. + + + + diff --git a/src/basic/efivars.c b/src/basic/efivars.c index 0ff07fa113..5e04c32212 100644 --- a/src/basic/efivars.c +++ b/src/basic/efivars.c @@ -330,7 +330,7 @@ bool is_efi_secure_boot(void) { int r; if (cache < 0) { - r = read_flag(EFI_GLOBAL_VARIABLE(SecureBoot)); + r = read_flag(EFI_GLOBAL_VARIABLE_STR("SecureBoot")); if (r == -ENOENT) cache = false; else if (r < 0) @@ -348,7 +348,7 @@ SecureBootMode efi_get_secure_boot_mode(void) { if (cache != _SECURE_BOOT_INVALID) return cache; - int secure = read_flag(EFI_GLOBAL_VARIABLE(SecureBoot)); + int secure = read_flag(EFI_GLOBAL_VARIABLE_STR("SecureBoot")); if (secure < 0) { if (secure != -ENOENT) log_debug_errno(secure, "Error reading SecureBoot EFI variable, assuming not in SecureBoot mode: %m"); @@ -358,9 +358,9 @@ SecureBootMode efi_get_secure_boot_mode(void) { /* We can assume false for all these if they are abscent (AuditMode and * DeployedMode may not exist on older firmware). */ - int audit = read_flag(EFI_GLOBAL_VARIABLE(AuditMode)); - int deployed = read_flag(EFI_GLOBAL_VARIABLE(DeployedMode)); - int setup = read_flag(EFI_GLOBAL_VARIABLE(SetupMode)); + int audit = read_flag(EFI_GLOBAL_VARIABLE_STR("AuditMode")); + int deployed = read_flag(EFI_GLOBAL_VARIABLE_STR("DeployedMode")); + int setup = read_flag(EFI_GLOBAL_VARIABLE_STR("SetupMode")); log_debug("Secure boot variables: SecureBoot=%d AuditMode=%d DeployedMode=%d SetupMode=%d", secure, audit, deployed, setup); @@ -381,13 +381,13 @@ static int read_efi_options_variable(char **ret) { /* Let's be helpful with the returned error and check if the variable exists at all. If it * does, let's return a recognizable error (EPERM), and if not ENODATA. */ - if (access(EFIVAR_PATH(EFI_SYSTEMD_VARIABLE(SystemdOptions)), F_OK) < 0) + if (access(EFIVAR_PATH(EFI_SYSTEMD_VARIABLE_STR("SystemdOptions")), F_OK) < 0) return errno == ENOENT ? -ENODATA : -errno; return -EPERM; } - r = efi_get_variable_string(EFI_SYSTEMD_VARIABLE(SystemdOptions), ret); + r = efi_get_variable_string(EFI_SYSTEMD_VARIABLE_STR("SystemdOptions"), ret); if (r == -ENOENT) return -ENODATA; return r; @@ -401,7 +401,7 @@ int cache_efi_options_variable(void) { if (r < 0) return r; - return write_string_file(EFIVAR_CACHE_PATH(EFI_SYSTEMD_VARIABLE(SystemdOptions)), line, + return write_string_file(EFIVAR_CACHE_PATH(EFI_SYSTEMD_VARIABLE_STR("SystemdOptions")), line, WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755); } @@ -418,7 +418,7 @@ int systemd_efi_options_variable(char **ret) { if (e) return strdup_to(ret, e); - r = read_one_line_file(EFIVAR_CACHE_PATH(EFI_SYSTEMD_VARIABLE(SystemdOptions)), ret); + r = read_one_line_file(EFIVAR_CACHE_PATH(EFI_SYSTEMD_VARIABLE_STR("SystemdOptions")), ret); if (r == -ENOENT) return -ENODATA; return r; @@ -432,12 +432,12 @@ int systemd_efi_options_efivarfs_if_newer(char **ret) { struct stat a = {}, b; int r; - if (stat(EFIVAR_PATH(EFI_SYSTEMD_VARIABLE(SystemdOptions)), &a) < 0 && errno != ENOENT) + if (stat(EFIVAR_PATH(EFI_SYSTEMD_VARIABLE_STR("SystemdOptions")), &a) < 0 && errno != ENOENT) return log_debug_errno(errno, "Failed to stat EFI variable SystemdOptions: %m"); - if (stat(EFIVAR_CACHE_PATH(EFI_SYSTEMD_VARIABLE(SystemdOptions)), &b) < 0) { + if (stat(EFIVAR_CACHE_PATH(EFI_SYSTEMD_VARIABLE_STR("SystemdOptions")), &b) < 0) { if (errno != ENOENT) - log_debug_errno(errno, "Failed to stat "EFIVAR_CACHE_PATH(EFI_SYSTEMD_VARIABLE(SystemdOptions))": %m"); + log_debug_errno(errno, "Failed to stat "EFIVAR_CACHE_PATH(EFI_SYSTEMD_VARIABLE_STR("SystemdOptions"))": %m"); } else if (compare_stat_mtime(&a, &b) > 0) log_debug("Variable SystemdOptions in evifarfs is newer than in cache."); else { diff --git a/src/basic/efivars.h b/src/basic/efivars.h index fe2be3107a..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 - 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. @@ -37,10 +38,6 @@ #define EFI_LOADER_VARIABLE_STR(name) EFI_VENDOR_VARIABLE_STR(EFI_VENDOR_LOADER_STR, name) #define EFI_SYSTEMD_VARIABLE_STR(name) EFI_VENDOR_VARIABLE_STR(EFI_VENDOR_SYSTEMD_STR, name) -#define EFI_GLOBAL_VARIABLE(name) EFI_GLOBAL_VARIABLE_STR(STRINGIFY(name)) -#define EFI_LOADER_VARIABLE(name) EFI_LOADER_VARIABLE_STR(STRINGIFY(name)) -#define EFI_SYSTEMD_VARIABLE(name) EFI_SYSTEMD_VARIABLE_STR(STRINGIFY(name)) - #define EFIVAR_PATH(variable) "/sys/firmware/efi/efivars/" variable #define EFIVAR_CACHE_PATH(variable) "/run/systemd/efivars/" variable 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/bless-boot-generator.c b/src/boot/bless-boot-generator.c index cf645e2416..2c3d54173d 100644 --- a/src/boot/bless-boot-generator.c +++ b/src/boot/bless-boot-generator.c @@ -33,7 +33,7 @@ static int run(const char *dest, const char *dest_early, const char *dest_late) return 0; } - if (access(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderBootCountPath)), F_OK) < 0) { + if (access(EFIVAR_PATH(EFI_LOADER_VARIABLE_STR("LoaderBootCountPath")), F_OK) < 0) { if (errno == ENOENT) { log_debug_errno(errno, "Skipping generator, not booted with boot counting in effect."); return 0; diff --git a/src/boot/bless-boot.c b/src/boot/bless-boot.c index 4853aaefe9..61b882c505 100644 --- a/src/boot/bless-boot.c +++ b/src/boot/bless-boot.c @@ -219,7 +219,7 @@ static int acquire_boot_count_path( uint64_t left, done; int r; - r = efi_get_variable_path(EFI_LOADER_VARIABLE(LoaderBootCountPath), &path); + r = efi_get_variable_path(EFI_LOADER_VARIABLE_STR("LoaderBootCountPath"), &path); if (r == -ENOENT) return -EUNATCH; /* in this case, let the caller print a message */ if (r < 0) diff --git a/src/boot/bootctl-install.c b/src/boot/bootctl-install.c index 0612791e07..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()); @@ -1015,13 +1207,13 @@ static int remove_loader_variables(void) { /* Remove all persistent loader variables we define */ FOREACH_STRING(var, - EFI_LOADER_VARIABLE(LoaderConfigConsoleMode), - EFI_LOADER_VARIABLE(LoaderConfigTimeout), - EFI_LOADER_VARIABLE(LoaderConfigTimeoutOneShot), - EFI_LOADER_VARIABLE(LoaderEntryDefault), - EFI_LOADER_VARIABLE(LoaderEntryLastBooted), - EFI_LOADER_VARIABLE(LoaderEntryOneShot), - EFI_LOADER_VARIABLE(LoaderSystemToken)) { + EFI_LOADER_VARIABLE_STR("LoaderConfigConsoleMode"), + EFI_LOADER_VARIABLE_STR("LoaderConfigTimeout"), + EFI_LOADER_VARIABLE_STR("LoaderConfigTimeoutOneShot"), + EFI_LOADER_VARIABLE_STR("LoaderEntryDefault"), + EFI_LOADER_VARIABLE_STR("LoaderEntryLastBooted"), + EFI_LOADER_VARIABLE_STR("LoaderEntryOneShot"), + EFI_LOADER_VARIABLE_STR("LoaderSystemToken")) { int q; @@ -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-random-seed.c b/src/boot/bootctl-random-seed.c index cfe10c4115..93cb4f9493 100644 --- a/src/boot/bootctl-random-seed.c +++ b/src/boot/bootctl-random-seed.c @@ -79,7 +79,7 @@ static int set_system_token(void) { return 0; } - r = efi_get_variable(EFI_LOADER_VARIABLE(LoaderSystemToken), NULL, NULL, &token_size); + r = efi_get_variable(EFI_LOADER_VARIABLE_STR("LoaderSystemToken"), NULL, NULL, &token_size); if (r == -ENODATA) log_debug_errno(r, "LoaderSystemToken EFI variable is invalid (too short?), replacing."); else if (r < 0) { @@ -103,7 +103,7 @@ static int set_system_token(void) { * and possibly get identification information or too much insight into the kernel's entropy pool * state. */ WITH_UMASK(0077) { - r = efi_set_variable(EFI_LOADER_VARIABLE(LoaderSystemToken), buffer, sizeof(buffer)); + r = efi_set_variable(EFI_LOADER_VARIABLE_STR("LoaderSystemToken"), buffer, sizeof(buffer)); if (r < 0) { if (!arg_graceful) return log_error_errno(r, "Failed to write 'LoaderSystemToken' EFI variable: %m"); diff --git a/src/boot/bootctl-set-efivar.c b/src/boot/bootctl-set-efivar.c index 10867b2257..f23fcc713e 100644 --- a/src/boot/bootctl-set-efivar.c +++ b/src/boot/bootctl-set-efivar.c @@ -74,17 +74,17 @@ static int parse_loader_entry_target_arg(const char *arg1, char16_t **ret_target assert(ret_target_size); if (streq(arg1, "@current")) { - r = efi_get_variable(EFI_LOADER_VARIABLE(LoaderEntrySelected), NULL, (void *) ret_target, ret_target_size); + r = efi_get_variable(EFI_LOADER_VARIABLE_STR("LoaderEntrySelected"), NULL, (void *) ret_target, ret_target_size); if (r < 0) return log_error_errno(r, "Failed to get EFI variable 'LoaderEntrySelected': %m"); } else if (streq(arg1, "@oneshot")) { - r = efi_get_variable(EFI_LOADER_VARIABLE(LoaderEntryOneShot), NULL, (void *) ret_target, ret_target_size); + r = efi_get_variable(EFI_LOADER_VARIABLE_STR("LoaderEntryOneShot"), NULL, (void *) ret_target, ret_target_size); if (r < 0) return log_error_errno(r, "Failed to get EFI variable 'LoaderEntryOneShot': %m"); } else if (streq(arg1, "@default")) { - r = efi_get_variable(EFI_LOADER_VARIABLE(LoaderEntryDefault), NULL, (void *) ret_target, ret_target_size); + r = efi_get_variable(EFI_LOADER_VARIABLE_STR("LoaderEntryDefault"), NULL, (void *) ret_target, ret_target_size); if (r < 0) return log_error_errno(r, "Failed to get EFI variable 'LoaderEntryDefault': %m"); @@ -114,7 +114,7 @@ int verb_set_efivar(int argc, char *argv[], void *userdata) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Not booted with UEFI."); - if (access(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderInfo)), F_OK) < 0) { + if (access(EFIVAR_PATH(EFI_LOADER_VARIABLE_STR("LoaderInfo")), F_OK) < 0) { if (errno == ENOENT) { log_error_errno(errno, "Not booted with a supported boot loader."); return -EOPNOTSUPP; @@ -137,16 +137,16 @@ int verb_set_efivar(int argc, char *argv[], void *userdata) { int (* arg_parser)(const char *, char16_t **, size_t *); if (streq(argv[0], "set-default")) { - variable = EFI_LOADER_VARIABLE(LoaderEntryDefault); + variable = EFI_LOADER_VARIABLE_STR("LoaderEntryDefault"); arg_parser = parse_loader_entry_target_arg; } else if (streq(argv[0], "set-oneshot")) { - variable = EFI_LOADER_VARIABLE(LoaderEntryOneShot); + variable = EFI_LOADER_VARIABLE_STR("LoaderEntryOneShot"); arg_parser = parse_loader_entry_target_arg; } else if (streq(argv[0], "set-timeout")) { - variable = EFI_LOADER_VARIABLE(LoaderConfigTimeout); + variable = EFI_LOADER_VARIABLE_STR("LoaderConfigTimeout"); arg_parser = parse_timeout; } else if (streq(argv[0], "set-timeout-oneshot")) { - variable = EFI_LOADER_VARIABLE(LoaderConfigTimeoutOneShot); + variable = EFI_LOADER_VARIABLE_STR("LoaderConfigTimeoutOneShot"); arg_parser = parse_timeout; } else assert_not_reached(); diff --git a/src/boot/bootctl-status.c b/src/boot/bootctl-status.c index c548ddcee5..229c1ad535 100644 --- a/src/boot/bootctl-status.c +++ b/src/boot/bootctl-status.c @@ -413,17 +413,17 @@ int verb_status(int argc, char *argv[], void *userdata) { uint64_t loader_features = 0, stub_features = 0; int have; - (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE(LoaderFirmwareType), &fw_type); - (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE(LoaderFirmwareInfo), &fw_info); - (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE(LoaderInfo), &loader); - (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE(StubInfo), &stub); - (void) efi_get_variable_path_and_warn(EFI_LOADER_VARIABLE(LoaderImageIdentifier), &loader_path); - (void) efi_get_variable_path_and_warn(EFI_LOADER_VARIABLE(StubImageIdentifier), &stub_path); + (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderFirmwareType"), &fw_type); + (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderFirmwareInfo"), &fw_info); + (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderInfo"), &loader); + (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("StubInfo"), &stub); + (void) efi_get_variable_path_and_warn(EFI_LOADER_VARIABLE_STR("LoaderImageIdentifier"), &loader_path); + (void) efi_get_variable_path_and_warn(EFI_LOADER_VARIABLE_STR("StubImageIdentifier"), &stub_path); (void) efi_loader_get_features(&loader_features); (void) efi_stub_get_features(&stub_features); - (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE(LoaderEntrySelected), ¤t_entry); - (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE(LoaderEntryOneShot), &oneshot_entry); - (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE(LoaderEntryDefault), &default_entry); + (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderEntrySelected"), ¤t_entry); + (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderEntryOneShot"), &oneshot_entry); + (void) efi_get_variable_string_and_warn(EFI_LOADER_VARIABLE_STR("LoaderEntryDefault"), &default_entry); SecureBootMode secure = efi_get_secure_boot_mode(); printf("%sSystem:%s\n", ansi_underline(), ansi_normal()); @@ -525,7 +525,7 @@ int verb_status(int argc, char *argv[], void *userdata) { } printf("%sRandom Seed:%s\n", ansi_underline(), ansi_normal()); - have = access(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderSystemToken)), F_OK) >= 0; + have = access(EFIVAR_PATH(EFI_LOADER_VARIABLE_STR("LoaderSystemToken")), F_OK) >= 0; printf(" System Token: %s\n", have ? "set" : "not set"); if (arg_esp_path) { diff --git a/src/boot/bootctl-systemd-efi-options.c b/src/boot/bootctl-systemd-efi-options.c index 7f8308fc3d..d626d87abc 100644 --- a/src/boot/bootctl-systemd-efi-options.c +++ b/src/boot/bootctl-systemd-efi-options.c @@ -34,7 +34,7 @@ int verb_systemd_efi_options(int argc, char *argv[], void *userdata) { log_notice("Note: SystemdOptions EFI variable has been modified since boot. New value: %s", new); } else { - r = efi_set_variable_string(EFI_SYSTEMD_VARIABLE(SystemdOptions), argv[1]); + r = efi_set_variable_string(EFI_SYSTEMD_VARIABLE_STR("SystemdOptions"), argv[1]); if (r < 0) return log_error_errno(r, "Failed to set SystemdOptions EFI variable: %m"); } diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index 176de8d55d..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, @@ -149,7 +157,7 @@ static int print_loader_or_stub_path(void) { if (r < 0) return log_error_errno(r, "Unable to determine loader partition UUID: %m"); - r = efi_get_variable_path(EFI_LOADER_VARIABLE(LoaderImageIdentifier), &p); + r = efi_get_variable_path(EFI_LOADER_VARIABLE_STR("LoaderImageIdentifier"), &p); if (r == -ENOENT) return log_error_errno(r, "No loader EFI binary path passed."); if (r < 0) @@ -163,7 +171,7 @@ static int print_loader_or_stub_path(void) { if (r < 0) return log_error_errno(r, "Unable to determine stub partition UUID: %m"); - r = efi_get_variable_path(EFI_LOADER_VARIABLE(StubImageIdentifier), &p); + r = efi_get_variable_path(EFI_LOADER_VARIABLE_STR("StubImageIdentifier"), &p); if (r == -ENOENT) return log_error_errno(r, "No stub EFI binary path passed."); if (r < 0) @@ -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/measure.c b/src/boot/measure.c index 3ad49e9d74..3c409f8bd9 100644 --- a/src/boot/measure.c +++ b/src/boot/measure.c @@ -6,6 +6,7 @@ #include "sd-json.h" #include "alloc-util.h" +#include "ask-password-api.h" #include "build.h" #include "efi-loader.h" #include "fd-util.h" @@ -803,6 +804,7 @@ static int verb_calculate(int argc, char *argv[], void *userdata) { static int verb_sign(int argc, char *argv[], void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL; + _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL; _cleanup_(EVP_PKEY_freep) EVP_PKEY *privkey = NULL, *pubkey = NULL; _cleanup_(X509_freep) X509 *certificate = NULL; size_t n; @@ -834,54 +836,31 @@ static int verb_sign(int argc, char *argv[], void *userdata) { /* This must be done before openssl_load_key_from_token() otherwise it will get stuck */ if (arg_certificate) { - _cleanup_(BIO_freep) BIO *cb = NULL; - _cleanup_free_ char *crt = NULL; - - r = read_full_file_full( - AT_FDCWD, arg_certificate, UINT64_MAX, SIZE_MAX, - READ_FULL_FILE_CONNECT_SOCKET, - /* bind_name= */ NULL, - &crt, &n); + r = openssl_load_x509_certificate(arg_certificate, &certificate); if (r < 0) - return log_error_errno(r, "Failed to read certificate file '%s': %m", arg_certificate); - - cb = BIO_new_mem_buf(crt, n); - if (!cb) - return log_oom(); - - certificate = PEM_read_bio_X509(cb, NULL, NULL, NULL); - if (!certificate) - return log_error_errno( - SYNTHETIC_ERRNO(EBADMSG), - "Failed to parse X.509 certificate: %s", - ERR_error_string(ERR_get_error(), NULL)); + return log_error_errno(r, "Failed to load X.509 certificate from %s: %m", arg_certificate); } - if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) { - _cleanup_fclose_ FILE *privkeyf = NULL; - _cleanup_free_ char *resolved_pkey = NULL; + if (arg_private_key) { + if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) { + r = parse_path_argument(arg_private_key, /* suppress_root= */ false, &arg_private_key); + if (r < 0) + return log_error_errno(r, "Failed to parse private key path %s: %m", arg_private_key); + } - r = parse_path_argument(arg_private_key, /* suppress_root= */ false, &resolved_pkey); + r = openssl_load_private_key( + arg_private_key_source_type, + arg_private_key_source, + arg_private_key, + &(AskPasswordRequest) { + .id = "measure-private-key-pin", + .keyring = arg_private_key, + .credential = "measure.private-key-pin", + }, + &privkey, + &ui); if (r < 0) - return log_error_errno(r, "Failed to parse private key path %s: %m", arg_private_key); - - privkeyf = fopen(resolved_pkey, "re"); - if (!privkeyf) - return log_error_errno(errno, "Failed to open private key file '%s': %m", resolved_pkey); - - privkey = PEM_read_PrivateKey(privkeyf, NULL, NULL, NULL); - if (!privkey) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse private key '%s'.", resolved_pkey); - } else if (arg_private_key_source && - IN_SET(arg_private_key_source_type, OPENSSL_KEY_SOURCE_ENGINE, OPENSSL_KEY_SOURCE_PROVIDER)) { - r = openssl_load_key_from_token( - arg_private_key_source_type, arg_private_key_source, arg_private_key, &privkey); - if (r < 0) - return log_error_errno( - r, - "Failed to load key '%s' from OpenSSL key source %s: %m", - arg_private_key, - arg_private_key_source); + return log_error_errno(r, "Failed to load private key from %s: %m", arg_private_key); } if (arg_public_key) { @@ -1049,7 +1028,7 @@ static int validate_stub(void) { log_warning("Warning: current kernel image does not support measuring itself, the command line or initrd system extension images.\n" "The PCR measurements seen are unlikely to be valid."); - r = compare_reported_pcr_nr(TPM2_PCR_KERNEL_BOOT, EFI_LOADER_VARIABLE(StubPcrKernelImage), "kernel image"); + r = compare_reported_pcr_nr(TPM2_PCR_KERNEL_BOOT, EFI_LOADER_VARIABLE_STR("StubPcrKernelImage"), "kernel image"); if (r < 0) return r; 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/core/efi-random.c b/src/core/efi-random.c index dffde57c91..f3af300825 100644 --- a/src/core/efi-random.c +++ b/src/core/efi-random.c @@ -16,7 +16,7 @@ void lock_down_efi_variables(void) { _cleanup_close_ int fd = -EBADF; int r; - fd = open(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderSystemToken)), O_RDONLY|O_CLOEXEC); + fd = open(EFIVAR_PATH(EFI_LOADER_VARIABLE_STR("LoaderSystemToken")), O_RDONLY|O_CLOEXEC); if (fd < 0) { if (errno != ENOENT) log_warning_errno(errno, "Unable to open LoaderSystemToken EFI variable, ignoring: %m"); 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 diff --git a/src/hibernate-resume/hibernate-resume-config.c b/src/hibernate-resume/hibernate-resume-config.c index edf7a70e7c..d93919956b 100644 --- a/src/hibernate-resume/hibernate-resume-config.c +++ b/src/hibernate-resume/hibernate-resume-config.c @@ -161,7 +161,7 @@ int get_efi_hibernate_location(EFIHibernateLocation **ret) { if (!is_efi_boot()) goto skip; - r = efi_get_variable_string(EFI_SYSTEMD_VARIABLE(HibernateLocation), &location_str); + r = efi_get_variable_string(EFI_SYSTEMD_VARIABLE_STR("HibernateLocation"), &location_str); if (r == -ENOENT) { log_debug_errno(r, "EFI variable HibernateLocation is not set, skipping."); goto skip; diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index 467c5f78a0..1b6e358b74 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -3286,12 +3286,12 @@ static int method_set_reboot_to_boot_loader_menu( if (use_efi) { if (x == UINT64_MAX) - r = efi_set_variable(EFI_LOADER_VARIABLE(LoaderConfigTimeoutOneShot), NULL, 0); + r = efi_set_variable(EFI_LOADER_VARIABLE_STR("LoaderConfigTimeoutOneShot"), NULL, 0); else { char buf[DECIMAL_STR_MAX(uint64_t) + 1]; xsprintf(buf, "%" PRIu64, DIV_ROUND_UP(x, USEC_PER_SEC)); /* second granularity */ - r = efi_set_variable_string(EFI_LOADER_VARIABLE(LoaderConfigTimeoutOneShot), buf); + r = efi_set_variable_string(EFI_LOADER_VARIABLE_STR("LoaderConfigTimeoutOneShot"), buf); } if (r < 0) return r; @@ -3481,9 +3481,9 @@ static int method_set_reboot_to_boot_loader_entry( if (use_efi) { if (isempty(v)) /* Delete item */ - r = efi_set_variable(EFI_LOADER_VARIABLE(LoaderEntryOneShot), NULL, 0); + r = efi_set_variable(EFI_LOADER_VARIABLE_STR("LoaderEntryOneShot"), NULL, 0); else - r = efi_set_variable_string(EFI_LOADER_VARIABLE(LoaderEntryOneShot), v); + r = efi_set_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntryOneShot"), v); if (r < 0) return r; } else { diff --git a/src/partition/repart.c b/src/partition/repart.c index 5427e0a69f..be5171d5e1 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -17,6 +17,7 @@ #include "sd-json.h" #include "alloc-util.h" +#include "ask-password-api.h" #include "blkid-util.h" #include "blockdev-list.h" #include "blockdev-util.h" @@ -149,10 +150,10 @@ static PagerFlags arg_pager_flags = 0; static bool arg_legend = true; static void *arg_key = NULL; static size_t arg_key_size = 0; -static EVP_PKEY *arg_private_key = NULL; +static char *arg_private_key = NULL; static KeySourceType arg_private_key_source_type = OPENSSL_KEY_SOURCE_FILE; static char *arg_private_key_source = NULL; -static X509 *arg_certificate = NULL; +static char *arg_certificate = NULL; static char *arg_tpm2_device = NULL; static uint32_t arg_tpm2_seal_key_handle = 0; static char *arg_tpm2_device_key = NULL; @@ -182,9 +183,9 @@ STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep); STATIC_DESTRUCTOR_REGISTER(arg_definitions, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_key, erase_and_freep); -STATIC_DESTRUCTOR_REGISTER(arg_private_key, EVP_PKEY_freep); +STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep); STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep); -STATIC_DESTRUCTOR_REGISTER(arg_certificate, X509_freep); +STATIC_DESTRUCTOR_REGISTER(arg_certificate, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device_key, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_hash_pcr_values, freep); @@ -429,6 +430,9 @@ typedef struct Context { int backing_fd; bool from_scratch; + + X509 *certificate; + EVP_PKEY *private_key; } Context; static const char *empty_mode_table[_EMPTY_MODE_MAX] = { @@ -709,9 +713,11 @@ static Partition* partition_unlink_and_free(Context *context, Partition *p) { DEFINE_TRIVIAL_CLEANUP_FUNC(Partition*, partition_free); -static Context* context_new(sd_id128_t seed) { +static Context* context_new(sd_id128_t seed, X509 *certificate, EVP_PKEY *private_key) { Context *context; + /* Note: This function takes ownership of the certificate and private_key arguments. */ + context = new(Context, 1); if (!context) return NULL; @@ -721,6 +727,8 @@ static Context* context_new(sd_id128_t seed) { .end = UINT64_MAX, .total = UINT64_MAX, .seed = seed, + .certificate = certificate, + .private_key = private_key, }; return context; @@ -755,6 +763,9 @@ static Context* context_free(Context *context) { else free(context->node); + X509_free(context->certificate); + EVP_PKEY_free(context->private_key); + return mfree(context); } @@ -4979,6 +4990,8 @@ static int partition_format_verity_hash( static int sign_verity_roothash( const struct iovec *roothash, + X509 *certificate, + EVP_PKEY *private_key, struct iovec *ret_signature) { #if HAVE_OPENSSL @@ -4989,6 +5002,7 @@ static int sign_verity_roothash( int sigsz; assert(roothash); + assert(private_key); assert(iovec_is_set(roothash)); assert(ret_signature); @@ -5000,7 +5014,7 @@ static int sign_verity_roothash( if (!rb) return log_oom(); - p7 = PKCS7_sign(arg_certificate, arg_private_key, NULL, rb, PKCS7_DETACHED|PKCS7_NOATTR|PKCS7_BINARY); + p7 = PKCS7_sign(certificate, private_key, NULL, rb, PKCS7_DETACHED|PKCS7_NOATTR|PKCS7_BINARY); if (!p7) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to calculate PKCS7 signature: %s", ERR_error_string(ERR_get_error(), NULL)); @@ -5039,15 +5053,13 @@ static int partition_format_verity_sig(Context *context, Partition *p) { assert_se(hp = p->siblings[VERITY_HASH]); assert(!hp->dropped); - assert(arg_certificate); - assert_se((whole_fd = fdisk_get_devfd(context->fdisk_context)) >= 0); - r = sign_verity_roothash(&hp->roothash, &sig); + r = sign_verity_roothash(&hp->roothash, context->certificate, context->private_key, &sig); if (r < 0) return r; - r = x509_fingerprint(arg_certificate, fp); + r = x509_fingerprint(context->certificate, fp); if (r < 0) return log_error_errno(r, "Unable to calculate X509 certificate fingerprint: %m"); @@ -6040,60 +6052,6 @@ static int context_mkfs(Context *context) { return 0; } -static int parse_x509_certificate(const char *certificate, size_t certificate_size, X509 **ret) { -#if HAVE_OPENSSL - _cleanup_(X509_freep) X509 *cert = NULL; - _cleanup_(BIO_freep) BIO *cb = NULL; - - assert(certificate); - assert(certificate_size > 0); - assert(ret); - - cb = BIO_new_mem_buf(certificate, certificate_size); - if (!cb) - return log_oom(); - - cert = PEM_read_bio_X509(cb, NULL, NULL, NULL); - if (!cert) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse X.509 certificate: %s", - ERR_error_string(ERR_get_error(), NULL)); - - if (ret) - *ret = TAKE_PTR(cert); - - return 0; -#else - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot parse X509 certificate."); -#endif -} - -static int parse_private_key(const char *key, size_t key_size, EVP_PKEY **ret) { -#if HAVE_OPENSSL - _cleanup_(BIO_freep) BIO *kb = NULL; - _cleanup_(EVP_PKEY_freep) EVP_PKEY *pk = NULL; - - assert(key); - assert(key_size > 0); - assert(ret); - - kb = BIO_new_mem_buf(key, key_size); - if (!kb) - return log_oom(); - - pk = PEM_read_bio_PrivateKey(kb, NULL, NULL, NULL); - if (!pk) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse PEM private key: %s", - ERR_error_string(ERR_get_error(), NULL)); - - if (ret) - *ret = TAKE_PTR(pk); - - return 0; -#else - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot parse private key."); -#endif -} - static int partition_acquire_uuid(Context *context, Partition *p, sd_id128_t *ret) { struct { sd_id128_t type_uuid; @@ -7898,9 +7856,7 @@ static int help(void) { return 0; } -static int parse_argv(int argc, char *argv[]) { - _cleanup_free_ char *private_key = NULL; - +static int parse_argv(int argc, char *argv[], X509 **ret_certificate, EVP_PKEY **ret_private_key, OpenSSLAskPasswordUI **ret_ui) { enum { ARG_VERSION = 0x100, ARG_NO_PAGER, @@ -7989,11 +7945,17 @@ static int parse_argv(int argc, char *argv[]) { {} }; + _cleanup_(X509_freep) X509 *certificate = NULL; + _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL; + _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = NULL; bool auto_hash_pcr_values = true, auto_public_key_pcr_mask = true, auto_pcrlock = true; int c, r; assert(argc >= 0); assert(argv); + assert(ret_certificate); + assert(ret_private_key); + assert(ret_ui); while ((c = getopt_long(argc, argv, "hs:SCP", options, NULL)) >= 0) @@ -8153,7 +8115,7 @@ static int parse_argv(int argc, char *argv[]) { } case ARG_PRIVATE_KEY: { - r = free_and_strdup_warn(&private_key, optarg); + r = free_and_strdup_warn(&arg_private_key, optarg); if (r < 0) return r; break; @@ -8169,20 +8131,7 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_CERTIFICATE: { - _cleanup_free_ char *cert = NULL; - size_t n = 0; - - r = read_full_file_full( - AT_FDCWD, optarg, UINT64_MAX, SIZE_MAX, - READ_FULL_FILE_CONNECT_SOCKET, - NULL, - &cert, &n); - if (r < 0) - return log_error_errno(r, "Failed to read certificate file '%s': %m", optarg); - - X509_free(arg_certificate); - arg_certificate = NULL; - r = parse_x509_certificate(cert, n, &arg_certificate); + r = parse_path_argument(optarg, /*suppress_root=*/ false, &arg_certificate); if (r < 0) return r; break; @@ -8518,39 +8467,38 @@ static int parse_argv(int argc, char *argv[]) { *p = gpt_partition_type_override_architecture(*p, arg_architecture); } - if (private_key && arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) { - _cleanup_(erase_and_freep) char *k = NULL; - size_t n = 0; - - r = read_full_file_full( - AT_FDCWD, private_key, UINT64_MAX, SIZE_MAX, - READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET, - NULL, - &k, &n); + if (arg_certificate) { + r = openssl_load_x509_certificate(arg_certificate, &certificate); if (r < 0) - return log_error_errno(r, "Failed to read key file '%s': %m", private_key); + return log_error_errno(r, "Failed to load X.509 certificate from %s: %m", arg_certificate); + } - r = parse_private_key(k, n, &arg_private_key); - if (r < 0) - return r; - } else if (private_key && - IN_SET(arg_private_key_source_type, OPENSSL_KEY_SOURCE_ENGINE, OPENSSL_KEY_SOURCE_PROVIDER)) { - /* This must happen after parse_x509_certificate() is called above, otherwise - * signing later will get stuck as the parsed private key won't have the - * certificate, so this block cannot be inline in ARG_PRIVATE_KEY. */ - r = openssl_load_key_from_token( + if (arg_private_key) { + if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) { + r = parse_path_argument(arg_private_key, /*suppress_root=*/ false, &arg_private_key); + if (r < 0) + return r; + } + + r = openssl_load_private_key( arg_private_key_source_type, arg_private_key_source, - private_key, - &arg_private_key); + arg_private_key, + &(AskPasswordRequest) { + .id = "repart-private-key-pin", + .keyring = arg_private_key, + .credential = "repart.private-key-pin", + }, + &private_key, + &ui); if (r < 0) - return log_error_errno( - r, - "Failed to load key '%s' from OpenSSL private key source %s: %m", - private_key, - arg_private_key_source); + return log_error_errno(r, "Failed to load private key from %s: %m", arg_private_key); } + *ret_certificate = TAKE_PTR(certificate); + *ret_private_key = TAKE_PTR(private_key); + *ret_ui = TAKE_PTR(ui); + return 1; } @@ -8587,7 +8535,7 @@ static int parse_efi_variable_factory_reset(void) { if (!in_initrd()) /* Never honour EFI variable factory reset request outside of the initrd */ return 0; - r = efi_get_variable_string(EFI_SYSTEMD_VARIABLE(FactoryReset), &value); + r = efi_get_variable_string(EFI_SYSTEMD_VARIABLE_STR("FactoryReset"), &value); if (r < 0) { if (r == -ENOENT || ERRNO_IS_NOT_SUPPORTED(r)) return 0; @@ -8608,7 +8556,7 @@ static int parse_efi_variable_factory_reset(void) { static int remove_efi_variable_factory_reset(void) { int r; - r = efi_set_variable(EFI_SYSTEMD_VARIABLE(FactoryReset), NULL, 0); + r = efi_set_variable(EFI_SYSTEMD_VARIABLE_STR("FactoryReset"), NULL, 0); if (r < 0) { if (r == -ENOENT || ERRNO_IS_NOT_SUPPORTED(r)) return 0; @@ -8967,6 +8915,9 @@ static int determine_auto_size(Context *c) { } static int run(int argc, char *argv[]) { + _cleanup_(X509_freep) X509 *certificate = NULL; + _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL; + _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = NULL; _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; _cleanup_(umount_and_freep) char *mounted_dir = NULL; _cleanup_(context_freep) Context* context = NULL; @@ -8975,7 +8926,7 @@ static int run(int argc, char *argv[]) { log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &certificate, &private_key, &ui); if (r <= 0) return r; @@ -9034,10 +8985,13 @@ static int run(int argc, char *argv[]) { return log_oom(); } - context = context_new(arg_seed); + context = context_new(arg_seed, certificate, private_key); if (!context) return log_oom(); + TAKE_PTR(certificate); + TAKE_PTR(private_key); + r = context_read_seed(context, arg_root); if (r < 0) return r; diff --git a/src/shared/ask-password-api.c b/src/shared/ask-password-api.c index 0c3156cd27..2e49096f82 100644 --- a/src/shared/ask-password-api.c +++ b/src/shared/ask-password-api.c @@ -44,6 +44,7 @@ #include "random-util.h" #include "signal-util.h" #include "socket-util.h" +#include "string-table.h" #include "string-util.h" #include "strv.h" #include "terminal-util.h" @@ -54,6 +55,17 @@ #define KEYRING_TIMEOUT_USEC ((5 * USEC_PER_MINUTE) / 2) +static const char* keyring_table[] = { + [-KEY_SPEC_THREAD_KEYRING] = "thread", + [-KEY_SPEC_PROCESS_KEYRING] = "process", + [-KEY_SPEC_SESSION_KEYRING] = "session", + [-KEY_SPEC_USER_KEYRING] = "user", + [-KEY_SPEC_USER_SESSION_KEYRING] = "user-session", + [-KEY_SPEC_GROUP_KEYRING] = "group", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(keyring, int); + static int lookup_key(const char *keyname, key_serial_t *ret) { key_serial_t serial; @@ -115,7 +127,7 @@ static int touch_ask_password_directory(AskPasswordFlags flags) { } static usec_t keyring_cache_timeout(void) { - static usec_t saved_timeout = USEC_INFINITY; + static usec_t saved_timeout = KEYRING_TIMEOUT_USEC; static bool saved_timeout_set = false; int r; @@ -123,9 +135,7 @@ static usec_t keyring_cache_timeout(void) { return saved_timeout; const char *e = secure_getenv("SYSTEMD_ASK_PASSWORD_KEYRING_TIMEOUT_SEC"); - if (streq_ptr(e, "default")) - saved_timeout = KEYRING_TIMEOUT_USEC; - else if (e) { + if (e) { r = parse_sec(e, &saved_timeout); if (r < 0) log_debug_errno(r, "Invalid value in $SYSTEMD_ASK_PASSWORD_KEYRING_TIMEOUT_SEC, ignoring: %s", e); @@ -136,6 +146,38 @@ static usec_t keyring_cache_timeout(void) { return saved_timeout; } +static key_serial_t keyring_cache_type(void) { + static key_serial_t saved_keyring = KEY_SPEC_USER_KEYRING; + static bool saved_keyring_set = false; + int r; + + if (saved_keyring_set) + return saved_keyring; + + const char *e = secure_getenv("SYSTEMD_ASK_PASSWORD_KEYRING_TYPE"); + if (e) { + key_serial_t keyring; + + r = safe_atoi32(e, &keyring); + if (r >= 0) + if (keyring < 0) + log_debug_errno(keyring, "Invalid value in $SYSTEMD_ASK_PASSWORD_KEYRING_TYPE, ignoring: %s", e); + else + saved_keyring = keyring; + else { + keyring = keyring_from_string(e); + if (keyring < 0) + log_debug_errno(keyring, "Invalid value in $SYSTEMD_ASK_PASSWORD_KEYRING_TYPE, ignoring: %s", e); + else + saved_keyring = -keyring; + } + } + + saved_keyring_set = true; + + return saved_keyring; +} + static int add_to_keyring(const char *keyname, AskPasswordFlags flags, char **passwords) { _cleanup_strv_free_erase_ char **l = NULL; _cleanup_(erase_and_freep) char *p = NULL; @@ -170,7 +212,7 @@ static int add_to_keyring(const char *keyname, AskPasswordFlags flags, char **pa * have multiple passwords. */ n = LESS_BY(n, (size_t) 1); - serial = add_key("user", keyname, p, n, KEY_SPEC_USER_KEYRING); + serial = add_key("user", keyname, p, n, keyring_cache_type()); if (serial == -1) return -errno; diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index 4c0195a41e..584515a297 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -1382,19 +1382,19 @@ static int boot_load_efi_entry_pointers(BootConfig *config, bool skip_efivars) { /* Loads the three "pointers" to boot loader entries from their EFI variables */ - r = efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderEntryOneShot), &config->entry_oneshot); + r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntryOneShot"), &config->entry_oneshot); if (r == -ENOMEM) return log_oom(); if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA)) log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryOneShot\", ignoring: %m"); - r = efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderEntryDefault), &config->entry_default); + r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntryDefault"), &config->entry_default); if (r == -ENOMEM) return log_oom(); if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA)) log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryDefault\", ignoring: %m"); - r = efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderEntrySelected), &config->entry_selected); + r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntrySelected"), &config->entry_selected); if (r == -ENOMEM) return log_oom(); if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA)) @@ -1560,7 +1560,7 @@ int boot_config_augment_from_loader( break; } - p = strdup(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderEntries))); + p = strdup(EFIVAR_PATH(EFI_LOADER_VARIABLE_STR("LoaderEntries"))); if (!p) return log_oom(); diff --git a/src/shared/efi-api.c b/src/shared/efi-api.c index 3b9954be9b..37720f896b 100644 --- a/src/shared/efi-api.c +++ b/src/shared/efi-api.c @@ -79,7 +79,7 @@ int efi_reboot_to_firmware_supported(void) { if (!is_efi_boot()) goto not_supported; - r = efi_get_variable(EFI_GLOBAL_VARIABLE(OsIndicationsSupported), NULL, &v, &s); + r = efi_get_variable(EFI_GLOBAL_VARIABLE_STR("OsIndicationsSupported"), NULL, &v, &s); if (r == -ENOENT) goto not_supported; /* variable doesn't exist? it's not supported then */ if (r < 0) @@ -115,7 +115,7 @@ static int get_os_indications(uint64_t *ret) { return r; /* stat() the EFI variable, to see if the mtime changed. If it did we need to cache again. */ - if (stat(EFIVAR_PATH(EFI_GLOBAL_VARIABLE(OsIndications)), &new_stat) < 0) { + if (stat(EFIVAR_PATH(EFI_GLOBAL_VARIABLE_STR("OsIndications")), &new_stat) < 0) { if (errno != ENOENT) return -errno; @@ -129,7 +129,7 @@ static int get_os_indications(uint64_t *ret) { return 0; } - r = efi_get_variable(EFI_GLOBAL_VARIABLE(OsIndications), NULL, &v, &s); + r = efi_get_variable(EFI_GLOBAL_VARIABLE_STR("OsIndications"), NULL, &v, &s); if (r == -ENOENT) { /* Some firmware implementations that do support OsIndications and report that with * OsIndicationsSupported will remove the OsIndications variable when it is unset. Let's @@ -172,7 +172,7 @@ int efi_set_reboot_to_firmware(bool value) { /* Avoid writing to efi vars store if we can due to firmware bugs. */ if (b != b_new) - return efi_set_variable(EFI_GLOBAL_VARIABLE(OsIndications), &b_new, sizeof(uint64_t)); + return efi_set_variable(EFI_GLOBAL_VARIABLE_STR("OsIndications"), &b_new, sizeof(uint64_t)); return 0; } @@ -399,7 +399,7 @@ int efi_get_boot_order(uint16_t **ret_order) { if (!is_efi_boot()) return -EOPNOTSUPP; - r = efi_get_variable(EFI_GLOBAL_VARIABLE(BootOrder), NULL, &buf, &l); + r = efi_get_variable(EFI_GLOBAL_VARIABLE_STR("BootOrder"), NULL, &buf, &l); if (r < 0) return r; @@ -419,7 +419,7 @@ int efi_set_boot_order(const uint16_t *order, size_t n) { if (!is_efi_boot()) return -EOPNOTSUPP; - return efi_set_variable(EFI_GLOBAL_VARIABLE(BootOrder), order, n * sizeof(uint16_t)); + return efi_set_variable(EFI_GLOBAL_VARIABLE_STR("BootOrder"), order, n * sizeof(uint16_t)); } static int boot_id_hex(const char s[static 4]) { diff --git a/src/shared/efi-loader.c b/src/shared/efi-loader.c index ae0b2b1fdc..3eddc49bf9 100644 --- a/src/shared/efi-loader.c +++ b/src/shared/efi-loader.c @@ -43,11 +43,11 @@ int efi_loader_get_boot_usec(usec_t *ret_firmware, usec_t *ret_loader) { if (!is_efi_boot()) return -EOPNOTSUPP; - r = read_usec(EFI_LOADER_VARIABLE(LoaderTimeInitUSec), &x); + r = read_usec(EFI_LOADER_VARIABLE_STR("LoaderTimeInitUSec"), &x); if (r < 0) return log_debug_errno(r, "Failed to read LoaderTimeInitUSec: %m"); - r = read_usec(EFI_LOADER_VARIABLE(LoaderTimeExecUSec), &y); + r = read_usec(EFI_LOADER_VARIABLE_STR("LoaderTimeExecUSec"), &y); if (r < 0) return log_debug_errno(r, "Failed to read LoaderTimeExecUSec: %m"); @@ -69,11 +69,11 @@ static int get_device_part_uuid(const char *variable, sd_id128_t *ret) { } int efi_loader_get_device_part_uuid(sd_id128_t *ret) { - return get_device_part_uuid(EFI_LOADER_VARIABLE(LoaderDevicePartUUID), ret); + return get_device_part_uuid(EFI_LOADER_VARIABLE_STR("LoaderDevicePartUUID"), ret); } int efi_stub_get_device_part_uuid(sd_id128_t *ret) { - return get_device_part_uuid(EFI_LOADER_VARIABLE(StubDevicePartUUID), ret); + return get_device_part_uuid(EFI_LOADER_VARIABLE_STR("StubDevicePartUUID"), ret); } int efi_loader_get_entries(char ***ret) { @@ -87,7 +87,7 @@ int efi_loader_get_entries(char ***ret) { if (!is_efi_boot()) return -EOPNOTSUPP; - r = efi_get_variable(EFI_LOADER_VARIABLE(LoaderEntries), NULL, (void**) &entries, &size); + r = efi_get_variable(EFI_LOADER_VARIABLE_STR("LoaderEntries"), NULL, (void**) &entries, &size); if (r < 0) return r; @@ -148,12 +148,12 @@ int efi_loader_get_features(uint64_t *ret) { return 0; } - r = efi_get_variable(EFI_LOADER_VARIABLE(LoaderFeatures), NULL, &v, &s); + r = efi_get_variable(EFI_LOADER_VARIABLE_STR("LoaderFeatures"), NULL, &v, &s); if (r == -ENOENT) { _cleanup_free_ char *info = NULL; /* The new (v240+) LoaderFeatures variable is not supported, let's see if it's systemd-boot at all */ - r = efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderInfo), &info); + r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderInfo"), &info); if (r < 0) { if (r != -ENOENT) return r; @@ -199,12 +199,12 @@ int efi_stub_get_features(uint64_t *ret) { return 0; } - r = efi_get_variable(EFI_LOADER_VARIABLE(StubFeatures), NULL, &v, &s); + r = efi_get_variable(EFI_LOADER_VARIABLE_STR("StubFeatures"), NULL, &v, &s); if (r == -ENOENT) { _cleanup_free_ char *info = NULL; /* The new (v252+) StubFeatures variable is not supported, let's see if it's systemd-stub at all */ - r = efi_get_variable_string(EFI_LOADER_VARIABLE(StubInfo), &info); + r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("StubInfo"), &info); if (r < 0) { if (r != -ENOENT) return r; @@ -261,7 +261,7 @@ int efi_measured_uki(int log_level) { if (!efi_has_tpm2()) return (cached = 0); - r = efi_get_variable_string(EFI_LOADER_VARIABLE(StubPcrKernelImage), &pcr_string); + r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("StubPcrKernelImage"), &pcr_string); if (r == -ENOENT) return (cached = 0); if (r < 0) @@ -291,7 +291,7 @@ int efi_loader_get_config_timeout_one_shot(usec_t *ret) { assert(ret); /* stat() the EFI variable, to see if the mtime changed. If it did, we need to cache again. */ - if (stat(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderConfigTimeoutOneShot)), &new_stat) < 0) + if (stat(EFIVAR_PATH(EFI_LOADER_VARIABLE_STR("LoaderConfigTimeoutOneShot")), &new_stat) < 0) return -errno; if (stat_inode_unmodified(&new_stat, &cache_stat)) { @@ -299,7 +299,7 @@ int efi_loader_get_config_timeout_one_shot(usec_t *ret) { return 0; } - r = efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderConfigTimeoutOneShot), &v); + r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderConfigTimeoutOneShot"), &v); if (r < 0) return r; @@ -323,13 +323,13 @@ int efi_loader_update_entry_one_shot_cache(char **cache, struct stat *cache_stat assert(cache_stat); /* stat() the EFI variable, to see if the mtime changed. If it did we need to cache again. */ - if (stat(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderEntryOneShot)), &new_stat) < 0) + if (stat(EFIVAR_PATH(EFI_LOADER_VARIABLE_STR("LoaderEntryOneShot")), &new_stat) < 0) return -errno; if (stat_inode_unmodified(&new_stat, cache_stat)) return 0; - r = efi_get_variable_string(EFI_LOADER_VARIABLE(LoaderEntryOneShot), &v); + r = efi_get_variable_string(EFI_LOADER_VARIABLE_STR("LoaderEntryOneShot"), &v); if (r < 0) return r; diff --git a/src/shared/hibernate-util.c b/src/shared/hibernate-util.c index 7c21157580..1213fdc2c7 100644 --- a/src/shared/hibernate-util.c +++ b/src/shared/hibernate-util.c @@ -527,7 +527,7 @@ int clear_efi_hibernate_location_and_warn(void) { if (!is_efi_boot()) return 0; - r = efi_set_variable(EFI_SYSTEMD_VARIABLE(HibernateLocation), NULL, 0); + r = efi_set_variable(EFI_SYSTEMD_VARIABLE_STR("HibernateLocation"), NULL, 0); if (r == -ENOENT) return 0; if (r < 0) diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c index 2ab89c7639..818f4a5f19 100644 --- a/src/shared/openssl-util.c +++ b/src/shared/openssl-util.c @@ -3,12 +3,15 @@ #include #include "alloc-util.h" +#include "ask-password-api.h" #include "fd-util.h" +#include "fileio.h" #include "hexdecoct.h" #include "memory-util.h" #include "openssl-util.h" #include "random-util.h" #include "string-util.h" +#include "strv.h" #if HAVE_OPENSSL # include @@ -1309,10 +1312,15 @@ int pkey_generate_volume_keys( } } -static int load_key_from_provider(const char *provider, const char *private_key_uri, EVP_PKEY **ret) { +static int load_key_from_provider( + const char *provider, + const char *private_key_uri, + OpenSSLAskPasswordUI *ui, + EVP_PKEY **ret) { assert(provider); assert(private_key_uri); + assert(ui); assert(ret); #if OPENSSL_VERSION_MAJOR >= 3 @@ -1325,8 +1333,8 @@ static int load_key_from_provider(const char *provider, const char *private_key_ _cleanup_(OSSL_STORE_closep) OSSL_STORE_CTX *store = OSSL_STORE_open( private_key_uri, - /* ui_method= */ NULL, - /* ui_data= */ NULL, + ui->method, + &ui->request, /* post_process= */ NULL, /* post_process_data= */ NULL); if (!store) @@ -1348,10 +1356,10 @@ static int load_key_from_provider(const char *provider, const char *private_key_ #endif } -static int load_key_from_engine(const char *engine, const char *private_key_uri, EVP_PKEY **ret) { - +static int load_key_from_engine(const char *engine, const char *private_key_uri, OpenSSLAskPasswordUI *ui, EVP_PKEY **ret) { assert(engine); assert(private_key_uri); + assert(ui); assert(ret); #if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0) @@ -1363,11 +1371,13 @@ static int load_key_from_engine(const char *engine, const char *private_key_uri, if (ENGINE_init(e) == 0) return log_openssl_errors("Failed to initialize signing engine '%s'", engine); - _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = ENGINE_load_private_key( - e, - private_key_uri, - /* ui_method= */ NULL, - /* callback_data= */ NULL); + if (ENGINE_ctrl(e, ENGINE_CTRL_SET_USER_INTERFACE, /*i=*/ 0, ui->method, /*f=*/ NULL) <= 0) + return log_openssl_errors("Failed to set engine user interface"); + + if (ENGINE_ctrl(e, ENGINE_CTRL_SET_CALLBACK_DATA, /*i=*/ 0, &ui->request, /*f=*/ NULL) <= 0) + return log_openssl_errors("Failed to set engine user interface data"); + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = ENGINE_load_private_key(e, private_key_uri, ui->method, &ui->request); if (!private_key) return log_openssl_errors("Failed to load private key from '%s'", private_key_uri); REENABLE_WARNING; @@ -1384,24 +1394,88 @@ int openssl_load_key_from_token( KeySourceType private_key_source_type, const char *private_key_source, const char *private_key, - EVP_PKEY **ret) { + OpenSSLAskPasswordUI *ui, + EVP_PKEY **ret_private_key) { assert(IN_SET(private_key_source_type, OPENSSL_KEY_SOURCE_ENGINE, OPENSSL_KEY_SOURCE_PROVIDER)); assert(private_key_source); + assert(ui); assert(private_key); switch (private_key_source_type) { case OPENSSL_KEY_SOURCE_ENGINE: - return load_key_from_engine(private_key_source, private_key, ret); + return load_key_from_engine(private_key_source, private_key, ui, ret_private_key); case OPENSSL_KEY_SOURCE_PROVIDER: - return load_key_from_provider(private_key_source, private_key, ret); + return load_key_from_provider(private_key_source, private_key, ui, ret_private_key); default: assert_not_reached(); } } + +static int openssl_ask_password_ui_read(UI *ui, UI_STRING *uis) { + int r; + + switch(UI_get_string_type(uis)) { + case UIT_PROMPT: { + /* If no ask password request was configured use the default openssl UI. */ + AskPasswordRequest *req = UI_get0_user_data(ui); + if (!req) + return (UI_method_get_reader(UI_OpenSSL()))(ui, uis); + + req->message = UI_get0_output_string(uis); + + _cleanup_(strv_freep) char **l = NULL; + r = ask_password_auto(req, /*until=*/ 0, ASK_PASSWORD_ACCEPT_CACHED|ASK_PASSWORD_PUSH_CACHE, &l); + if (r < 0) { + log_error_errno(r, "Failed to query for PIN: %m"); + return 0; + } + + if (strv_length(l) != 1) { + log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected only a single password/pin."); + return 0; + } + + if (UI_set_result(ui, uis, *l) != 0) { + log_openssl_errors("Failed to set user interface result"); + return 0; + } + + return 1; + } + default: + return (UI_method_get_reader(UI_OpenSSL()))(ui, uis); + } +} #endif +int openssl_ask_password_ui_new(OpenSSLAskPasswordUI **ret) { +#if HAVE_OPENSSL + assert(ret); + + _cleanup_(UI_destroy_methodp) UI_METHOD *method = UI_create_method("systemd-ask-password"); + if (!method) + return log_openssl_errors("Failed to initialize openssl user interface"); + + if (UI_method_set_reader(method, openssl_ask_password_ui_read) != 0) + return log_openssl_errors("Failed to set openssl user interface reader"); + + OpenSSLAskPasswordUI *ui = new(OpenSSLAskPasswordUI, 1); + if (!ui) + return log_oom_debug(); + + *ui = (OpenSSLAskPasswordUI) { + .method = TAKE_PTR(method), + }; + + *ret = TAKE_PTR(ui); + return 0; +#else + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot create ask-password user interface."); +#endif +} + int x509_fingerprint(X509 *cert, uint8_t buffer[static SHA256_DIGEST_SIZE]) { #if HAVE_OPENSSL _cleanup_free_ uint8_t *der = NULL; @@ -1420,6 +1494,126 @@ int x509_fingerprint(X509 *cert, uint8_t buffer[static SHA256_DIGEST_SIZE]) { #endif } +int openssl_load_x509_certificate(const char *path, X509 **ret) { +#if HAVE_OPENSSL + _cleanup_free_ char *rawcert = NULL; + _cleanup_(X509_freep) X509 *cert = NULL; + _cleanup_(BIO_freep) BIO *cb = NULL; + size_t rawcertsz; + int r; + + assert(path); + assert(ret); + + r = read_full_file_full( + AT_FDCWD, path, UINT64_MAX, SIZE_MAX, + READ_FULL_FILE_CONNECT_SOCKET, + NULL, + &rawcert, &rawcertsz); + if (r < 0) + return log_debug_errno(r, "Failed to read certificate file '%s': %m", path); + + cb = BIO_new_mem_buf(rawcert, rawcertsz); + if (!cb) + return log_oom_debug(); + + cert = PEM_read_bio_X509(cb, NULL, NULL, NULL); + if (!cert) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse X.509 certificate: %s", + ERR_error_string(ERR_get_error(), NULL)); + + if (ret) + *ret = TAKE_PTR(cert); + + return 0; +#else + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot load X509 certificate."); +#endif +} + +static int openssl_load_private_key_from_file(const char *path, EVP_PKEY **ret) { +#if HAVE_OPENSSL + _cleanup_(erase_and_freep) char *rawkey = NULL; + _cleanup_(BIO_freep) BIO *kb = NULL; + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pk = NULL; + size_t rawkeysz; + int r; + + assert(path); + assert(ret); + + r = read_full_file_full( + AT_FDCWD, path, UINT64_MAX, SIZE_MAX, + READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET, + NULL, + &rawkey, &rawkeysz); + if (r < 0) + return log_debug_errno(r, "Failed to read key file '%s': %m", path); + + kb = BIO_new_mem_buf(rawkey, rawkeysz); + if (!kb) + return log_oom_debug(); + + pk = PEM_read_bio_PrivateKey(kb, NULL, NULL, NULL); + if (!pk) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse PEM private key: %s", + ERR_error_string(ERR_get_error(), NULL)); + + if (ret) + *ret = TAKE_PTR(pk); + + return 0; +#else + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot load private key."); +#endif +} + +int openssl_load_private_key( + KeySourceType private_key_source_type, + const char *private_key_source, + const char *private_key, + const AskPasswordRequest *request, + EVP_PKEY **ret_private_key, + OpenSSLAskPasswordUI **ret_user_interface) { + + int r; + + assert(private_key); + assert(request); + + if (private_key_source_type == OPENSSL_KEY_SOURCE_FILE) { + r = openssl_load_private_key_from_file(private_key, ret_private_key); + if (r < 0) + return r; + + *ret_user_interface = NULL; + } else { + _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL; + r = openssl_ask_password_ui_new(&ui); + if (r < 0) + return log_debug_errno(r, "Failed to allocate ask-password user interface: %m"); + + ui->request = *request; + + r = openssl_load_key_from_token( + private_key_source_type, + private_key_source, + private_key, + ui, + ret_private_key); + if (r < 0) + return log_debug_errno( + r, + "Failed to load key '%s' from OpenSSL private key source %s: %m", + private_key, + private_key_source); + + *ret_user_interface = TAKE_PTR(ui); + } + + return 0; +} + int parse_openssl_key_source_argument( const char *argument, char **private_key_source, diff --git a/src/shared/openssl-util.h b/src/shared/openssl-util.h index 1a89fcc2bd..c9acd40f22 100644 --- a/src/shared/openssl-util.h +++ b/src/shared/openssl-util.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "ask-password-api.h" #include "iovec-util.h" #include "macro.h" #include "sha256.h" @@ -13,6 +14,8 @@ typedef enum KeySourceType { _OPENSSL_KEY_SOURCE_INVALID = -EINVAL, } KeySourceType; +typedef struct OpenSSLAskPasswordUI OpenSSLAskPasswordUI; + int parse_openssl_key_source_argument(const char *argument, char **private_key_source, KeySourceType *private_key_source_type); #define X509_FINGERPRINT_SIZE SHA256_DIGEST_SIZE @@ -26,6 +29,7 @@ int parse_openssl_key_source_argument(const char *argument, char **private_key_s # include # include # include +# include # include # ifndef OPENSSL_VERSION_MAJOR /* OPENSSL_VERSION_MAJOR macro was added in OpenSSL 3. Thus, if it doesn't exist, we must be before OpenSSL 3. */ @@ -130,12 +134,13 @@ int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_s int digest_and_sign(const EVP_MD *md, EVP_PKEY *privkey, const void *data, size_t size, void **ret, size_t *ret_size); -int openssl_load_key_from_token(KeySourceType private_key_source_type, const char *private_key_source, const char *private_key, EVP_PKEY **ret); +int openssl_load_key_from_token(KeySourceType private_key_source_type, const char *private_key_source, const char *private_key, OpenSSLAskPasswordUI *ui, EVP_PKEY **ret_private_key); #else typedef struct X509 X509; typedef struct EVP_PKEY EVP_PKEY; +typedef struct UI_METHOD UI_METHOD; static inline void *X509_free(X509 *p) { assert(p == NULL); @@ -147,11 +152,17 @@ static inline void *EVP_PKEY_free(EVP_PKEY *p) { return NULL; } +static inline void* UI_destroy_method(UI_METHOD *p) { + assert(p == NULL); + return NULL; +} + static inline int openssl_load_key_from_token( KeySourceType private_key_source_type, const char *private_key_source, const char *private_key, - EVP_PKEY **ret) { + OpenSSLAskPasswordUI *ui, + EVP_PKEY **ret_private_key) { return -EOPNOTSUPP; } @@ -160,9 +171,37 @@ static inline int openssl_load_key_from_token( DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509*, X509_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_PKEY*, EVP_PKEY_free, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(UI_METHOD*, UI_destroy_method, NULL); + +struct OpenSSLAskPasswordUI { + AskPasswordRequest request; + UI_METHOD *method; +}; + +int openssl_ask_password_ui_new(OpenSSLAskPasswordUI **ret); + +static inline OpenSSLAskPasswordUI* openssl_ask_password_ui_free(OpenSSLAskPasswordUI *ui) { + if (!ui) + return NULL; + + UI_destroy_method(ui->method); + return mfree(ui); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OpenSSLAskPasswordUI*, openssl_ask_password_ui_free, NULL); int x509_fingerprint(X509 *cert, uint8_t buffer[static X509_FINGERPRINT_SIZE]); +int openssl_load_x509_certificate(const char *path, X509 **ret); + +int openssl_load_private_key( + KeySourceType private_key_source_type, + const char *private_key_source, + const char *private_key, + const AskPasswordRequest *request, + EVP_PKEY **ret_private_key, + OpenSSLAskPasswordUI **ret_user_interface); + #if PREFER_OPENSSL /* The openssl definition */ typedef const EVP_MD* hash_md_t; diff --git a/src/sleep/sleep.c b/src/sleep/sleep.c index 3f113fff74..181bb4ccef 100644 --- a/src/sleep/sleep.c +++ b/src/sleep/sleep.c @@ -114,7 +114,7 @@ static int write_efi_hibernate_location(const HibernationDevice *hibernation_dev if (r < 0) return log_full_errno(log_level, r, "Failed to format JSON object: %m"); - r = efi_set_variable_string(EFI_SYSTEMD_VARIABLE(HibernateLocation), formatted); + r = efi_set_variable_string(EFI_SYSTEMD_VARIABLE_STR("HibernateLocation"), formatted); if (r < 0) return log_full_errno(log_level, r, "Failed to set EFI variable HibernateLocation: %m"); diff --git a/test/TEST-74-AUX-UTILS/test.sh b/test/TEST-74-AUX-UTILS/test.sh index b10965040a..e90c55c28d 100755 --- a/test/TEST-74-AUX-UTILS/test.sh +++ b/test/TEST-74-AUX-UTILS/test.sh @@ -38,6 +38,7 @@ test_append_files() { instmods vmw_vsock_vmci_transport inst_binary gcc generate_module_dependencies + inst_binary openssl } do_test "$@" diff --git a/test/units/TEST-74-AUX-UTILS.bootctl.sh b/test/units/TEST-74-AUX-UTILS.bootctl.sh index 1767f1fc89..46fd5d1f2d 100755 --- a/test/units/TEST-74-AUX-UTILS.bootctl.sh +++ b/test/units/TEST-74-AUX-UTILS.bootctl.sh @@ -276,4 +276,31 @@ testcase_bootctl_varlink() { SYSTEMD_LOG_TARGET=console varlinkctl call --json=short /run/systemd/io.systemd.BootControl io.systemd.BootControl.SetRebootToFirmware '{"state":false}' --graceful=io.systemd.BootControl.RebootToFirmwareNotSupported } +testcase_bootctl_secure_boot_auto_enroll() { + cat >/tmp/openssl.conf <