From b3bca11c180ed300e6d3b97d6cb29d1b0b737aac Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 1 Nov 2024 13:48:58 +0100 Subject: [PATCH 1/6] ask-password: Use default timeout if SYSTEMD_ASK_PASSWORD_KEYRING_TIMEOUT_SEC is unset Follow-up for d9f4dad986dcebd51bdaeb8ba3d2c00cdc0d701e --- src/shared/ask-password-api.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/shared/ask-password-api.c b/src/shared/ask-password-api.c index 0c3156cd27..aa45468402 100644 --- a/src/shared/ask-password-api.c +++ b/src/shared/ask-password-api.c @@ -115,7 +115,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 +123,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 && !streq(e, "default")) { 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); From 01d138b990907ab202887a2df5eccc3a58f99ebf Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 1 Nov 2024 12:45:29 +0100 Subject: [PATCH 2/6] ask-password: Drop "default" for SYSTEMD_ASK_PASSWORD_KEYRING_TIMEOUT_SEC Users can simply unset the environment variable to achieve the same effect. --- docs/ENVIRONMENT.md | 11 +++++------ src/shared/ask-password-api.c | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index 48eeaa508c..b0b30949fb 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -738,9 +738,8 @@ 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. diff --git a/src/shared/ask-password-api.c b/src/shared/ask-password-api.c index aa45468402..eba647aaef 100644 --- a/src/shared/ask-password-api.c +++ b/src/shared/ask-password-api.c @@ -123,7 +123,7 @@ static usec_t keyring_cache_timeout(void) { return saved_timeout; const char *e = secure_getenv("SYSTEMD_ASK_PASSWORD_KEYRING_TIMEOUT_SEC"); - if (e && !streq(e, "default")) { + 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); From 36c6c696a78d9339ad415a3ad6bdd5f22c2140f2 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 30 Oct 2024 23:45:38 +0100 Subject: [PATCH 3/6] ask-password: Add $SYSTEMD_ASK_PASSWORD_KEYRING_TYPE Currently ask_password_auto() will always try to store the password into the user keyring. Let's make this configurable so that we can configure ask_password_auto() into the session keyring. This is required when working with user namespaces, as the user keyring is namespaced by user namespaces which makes it impossible to share cached keys across user namespaces by using the user namespace while this is possible with the session keyring. --- docs/ENVIRONMENT.md | 5 ++++ src/shared/ask-password-api.c | 46 ++++++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index b0b30949fb..30c987f834 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -743,3 +743,8 @@ Tools using the Varlink protocol (such as `varlinkctl`) or sd-bus (such as 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/src/shared/ask-password-api.c b/src/shared/ask-password-api.c index eba647aaef..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; @@ -134,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; @@ -168,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; From d5c12da904872671e98c991f33e8e80fd9c3db8e Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 31 Oct 2024 13:54:33 +0100 Subject: [PATCH 4/6] efivars: Remove STRINGIFY() helper macros The names of these conflict with macros from efi.h that we'll move to efi-fundamental.h in a later commit. Let's avoid the conflict by getting rid of these helpers. Arguably this also improves readability by clearly indicating we're passing arbitrary strings and not constants to the macros when we invoke them. --- src/basic/efivars.c | 24 ++++++++-------- src/basic/efivars.h | 4 --- src/boot/bless-boot-generator.c | 2 +- src/boot/bless-boot.c | 2 +- src/boot/bootctl-install.c | 14 +++++----- src/boot/bootctl-random-seed.c | 4 +-- src/boot/bootctl-set-efivar.c | 16 +++++------ src/boot/bootctl-status.c | 20 ++++++------- src/boot/bootctl-systemd-efi-options.c | 2 +- src/boot/bootctl.c | 4 +-- src/boot/measure.c | 2 +- src/core/efi-random.c | 2 +- .../hibernate-resume-config.c | 2 +- src/login/logind-dbus.c | 8 +++--- src/partition/repart.c | 4 +-- src/shared/bootspec.c | 8 +++--- src/shared/efi-api.c | 12 ++++---- src/shared/efi-loader.c | 28 +++++++++---------- src/shared/hibernate-util.c | 2 +- src/sleep/sleep.c | 2 +- 20 files changed, 79 insertions(+), 83 deletions(-) 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..b4f0da5ed8 100644 --- a/src/basic/efivars.h +++ b/src/basic/efivars.h @@ -37,10 +37,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/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..c3d32a1c6b 100644 --- a/src/boot/bootctl-install.c +++ b/src/boot/bootctl-install.c @@ -1015,13 +1015,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; 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..90232b3096 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -149,7 +149,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 +163,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) diff --git a/src/boot/measure.c b/src/boot/measure.c index 3ad49e9d74..557e06b094 100644 --- a/src/boot/measure.c +++ b/src/boot/measure.c @@ -1049,7 +1049,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/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/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..8070b02cab 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -8587,7 +8587,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 +8608,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; 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/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"); From eac5336c27e2c8a0024fedcde87f9bb26dec49aa Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 30 Oct 2024 15:47:58 +0100 Subject: [PATCH 5/6] openssl-util: Query engine/provider pin via ask-password In mkosi, we want to support signing via a hardware token. We already support this in systemd-repart and systemd-measure. However, if the hardware token is protected by a pin, the pin is asked as many as 20 times when building an image as the pin is not cached and thus requested again for every operation. Let's introduce a custom openssl ui when we use engines and providers and plug systemd-ask-password into the process. With systemd-ask-password, the pin can be cached in the kernel keyring, allowing us to reuse it without querying the user again every time to enter the pin. We use the private key URI as the keyring identifier so that the cached pin can be shared across multiple tools. Note that if the private key is pin protected, openssl will prompt both when loading the private key using the pkcs11 engine and when actually signing the roothash. To make sure our custom UI is used when signing the roothash, we have to also configure it with ENGINE_ctrl() which takes a non-owning pointer to the UI_METHOD object and its userdata object which we have to keep alive so we introduce a new AskPasswordUserInterface struct which we use to keep both objects alive together with the EVP_PKEY object. Because the AskPasswordRequest struct stores non-owning pointers to its fields, we change repart to store the private key URI as a global variable again instead of the EVP_PKEY object so that we can use the private key argument as the keyring field of the AskPasswordRequest instance without running into lifetime issues. --- src/boot/measure.c | 65 ++++------- src/partition/repart.c | 174 +++++++++++------------------- src/shared/openssl-util.c | 220 +++++++++++++++++++++++++++++++++++--- src/shared/openssl-util.h | 43 +++++++- 4 files changed, 334 insertions(+), 168 deletions(-) diff --git a/src/boot/measure.c b/src/boot/measure.c index 557e06b094..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) { diff --git a/src/partition/repart.c b/src/partition/repart.c index 8070b02cab..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; } @@ -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/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; From a07864a4fe8c7330ca0ccdc0213ee8e4f7e3721e Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 31 Oct 2024 23:33:36 +0100 Subject: [PATCH 6/6] bootctl: Add --secure-boot-auto-enroll When specified, bootctl install will also set up secure boot auto-enrollment. For now, We sign all variables using the same certificate and key pair. --- man/bootctl.xml | 25 +++ src/basic/efivars.h | 7 +- src/basic/utf8.c | 4 + src/basic/utf8.h | 1 + src/boot/bootctl-install.c | 202 ++++++++++++++++++++++++ src/boot/bootctl.c | 64 ++++++++ src/boot/bootctl.h | 6 + src/boot/meson.build | 2 +- src/fundamental/efi-fundamental.h | 61 +++++++ test/TEST-74-AUX-UTILS/test.sh | 1 + test/units/TEST-74-AUX-UTILS.bootctl.sh | 27 ++++ 11 files changed, 396 insertions(+), 4 deletions(-) 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.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 - 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 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 <