From a73144bbdf0a31846d2b8db9b5043527996f4c0c Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sun, 11 Feb 2024 20:15:51 +0000 Subject: [PATCH 1/3] repart: add --private-key-source and drop --private-key-uri It turns out it's mostly PKCS11 that supports the URI format, and other engines just take files. For example the tpm2-tss-openssl engine just takes a sealed private key file path as the key input, and the engine needs to be specified separately. Add --private-key-source=file|engine:foo|provider:bar to manually specify how to use the private key parameter. Follow-up for 0a8264080a5d4b5e13e65eed80ac98a476f7fe43 --- docs/ENVIRONMENT.md | 8 ----- man/systemd-repart.xml | 12 ++++--- src/partition/repart.c | 48 +++++++++++++++---------- src/shared/openssl-util.c | 74 ++++++++++++++++++++++++--------------- src/shared/openssl-util.h | 19 ++++++++-- 5 files changed, 100 insertions(+), 61 deletions(-) diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index 302ca67b57..00492829bd 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -129,14 +129,6 @@ All tools: * `$SYSTEMD_VERITY_SHARING=0` — if set, sharing dm-verity devices by using a stable `-verity` device mapper name will be disabled. -* `$SYSTEMD_OPENSSL_KEY_LOADER`— when using OpenSSL to load a key via an engine - or a provider, can be used to force the usage of one or the other interface. - Set to 'engine' to force the usage of the old engine API, and to 'provider' - force the usage of the new provider API. If unset, the provider will be tried - first and the engine as a fallback if that fails. Providers are the new OpenSSL - 3 API, but there are very few if any in a production-ready state, so engines - are still needed. - `systemctl`: * `$SYSTEMCTL_FORCE_BUS=1` — if set, do not connect to PID 1's private D-Bus diff --git a/man/systemd-repart.xml b/man/systemd-repart.xml index 7dfcfc90b3..7e2730a33a 100644 --- a/man/systemd-repart.xml +++ b/man/systemd-repart.xml @@ -355,11 +355,13 @@ - + - Takes a URI-like string referring to a private key, that will be passed to OpenSSL's - "engine" or "provider" logic. Configures the signing key to use when creating verity signature - partitions with the Verity=signature setting in partition files. + Takes one of file, engine or + provider. In the latter two cases, it is followed by the name of a provider or + engine, separated by colon, that will be passed to OpenSSL's "engine" or "provider" logic. + Configures the signing mechanism to use when creating verity signature partitions with the + Verity=signature setting in partition files. @@ -662,7 +664,7 @@ VERSION_ID=38 IMAGE_ID=my-foo IMAGE_VERSION=7 EOF -systemd-repart --make-ddi=sysext --private-key-uri="pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=0123456789abcdef;token=Some%20Cert" --certificate=cert.crt -s tree/ /var/lib/extensions/my-foo.sysext.raw +systemd-repart --make-ddi=sysext --private-key-source=engine:pkcs11 --private-key="pkcs11:model=PKCS%2315%20emulated;manufacturer=piv_II;serial=0123456789abcdef;token=Some%20Cert" --certificate=cert.crt -s tree/ /var/lib/extensions/my-foo.sysext.raw systemd-sysext refresh The DDI generated that way may be applied to the system with diff --git a/src/partition/repart.c b/src/partition/repart.c index db1d00974e..54ded64aee 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -146,6 +146,8 @@ 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 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_tpm2_device = NULL; static uint32_t arg_tpm2_seal_key_handle = 0; @@ -177,6 +179,7 @@ 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_source, freep); STATIC_DESTRUCTOR_REGISTER(arg_certificate, X509_freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device_key, freep); @@ -6806,8 +6809,14 @@ static int help(void) { " Specify disk image dissection policy\n" " --definitions=DIR Find partition definitions in specified directory\n" " --key-file=PATH Key to use when encrypting partitions\n" - " --private-key=PATH Private key to use when generating verity roothash\n" - " signatures\n" + " --private-key=PATH|URI\n" + " Private key to use when generating verity roothash\n" + " signatures, 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 the --private-key=. Allows to use\n" + " an OpenSSL engine/provider when generating verity\n" + " roothash signatures\n" " --certificate=PATH PEM certificate to use when generating verity\n" " roothash signatures\n" " --tpm2-device=PATH Path to TPM2 device node to use\n" @@ -6857,7 +6866,7 @@ static int help(void) { } static int parse_argv(int argc, char *argv[]) { - _cleanup_free_ char *private_key = NULL, *private_key_uri = NULL; + _cleanup_free_ char *private_key = NULL; enum { ARG_VERSION = 0x100, @@ -6878,7 +6887,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_JSON, ARG_KEY_FILE, ARG_PRIVATE_KEY, - ARG_PRIVATE_KEY_URI, + ARG_PRIVATE_KEY_SOURCE, ARG_CERTIFICATE, ARG_TPM2_DEVICE, ARG_TPM2_DEVICE_KEY, @@ -6921,7 +6930,7 @@ static int parse_argv(int argc, char *argv[]) { { "json", required_argument, NULL, ARG_JSON }, { "key-file", required_argument, NULL, ARG_KEY_FILE }, { "private-key", required_argument, NULL, ARG_PRIVATE_KEY }, - { "private-key-uri", required_argument, NULL, ARG_PRIVATE_KEY_URI }, + { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE }, { "certificate", required_argument, NULL, ARG_CERTIFICATE }, { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, { "tpm2-device-key", required_argument, NULL, ARG_TPM2_DEVICE_KEY }, @@ -7115,12 +7124,14 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_PRIVATE_KEY_URI: { - r = free_and_strdup_warn(&private_key_uri, optarg); + 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 ARG_CERTIFICATE: { _cleanup_free_ char *cert = NULL; @@ -7465,12 +7476,7 @@ static int parse_argv(int argc, char *argv[]) { *p = gpt_partition_type_override_architecture(*p, arg_architecture); } - if (private_key && private_key_uri) - return log_error_errno( - SYNTHETIC_ERRNO(EINVAL), - "Cannot specify both --private-key= and --private-key-uri=."); - - if (private_key) { + if (private_key && arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) { _cleanup_(erase_and_freep) char *k = NULL; size_t n = 0; @@ -7485,16 +7491,22 @@ static int parse_argv(int argc, char *argv[]) { r = parse_private_key(k, n, &arg_private_key); if (r < 0) return r; - } else if (private_key_uri) { + } 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(private_key_uri, &arg_private_key); + r = openssl_load_key_from_token( + arg_private_key_source_type, + arg_private_key_source, + private_key, + &arg_private_key); if (r < 0) return log_error_errno( r, - "Failed to load key '%s' from OpenSSL provider: %m", - private_key); + "Failed to load key '%s' from OpenSSL private key source %s: %m", + private_key, + arg_private_key_source); } return 1; diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c index e2386d0513..63c443cbfd 100644 --- a/src/shared/openssl-util.c +++ b/src/shared/openssl-util.c @@ -1366,38 +1366,25 @@ static int load_key_from_engine(const char *engine, const char *private_key_uri, return 0; } -int openssl_load_key_from_token(const char *private_key_uri, EVP_PKEY **ret) { - _cleanup_free_ char *provider = NULL; - const char *colon, *e; - int r; +int openssl_load_key_from_token( + KeySourceType private_key_source_type, + const char *private_key_source, + const char *private_key, + EVP_PKEY **ret) { - assert(private_key_uri); + assert(IN_SET(private_key_source_type, OPENSSL_KEY_SOURCE_ENGINE, OPENSSL_KEY_SOURCE_PROVIDER)); + assert(private_key_source); + assert(private_key); - colon = strchr(private_key_uri, ':'); - if (!colon) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid URI '%s'", private_key_uri); + switch (private_key_source_type) { - provider = strndup(private_key_uri, colon - private_key_uri); - if (!provider) - return log_oom_debug(); - - e = secure_getenv("SYSTEMD_OPENSSL_KEY_LOADER"); - if (e) { - if (streq(e, "provider")) - r = load_key_from_provider(provider, private_key_uri, ret); - else if (streq(e, "engine")) - r = load_key_from_engine(provider, private_key_uri, ret); - else - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value for SYSTEMD_OPENSSL_KEY_LOADER: %s", e); - } else { - r = load_key_from_provider(provider, private_key_uri, ret); - if (r < 0) { - log_debug_errno(r, "Failed to load key from provider '%s', falling back to engine", provider); - r = load_key_from_engine(provider, private_key_uri, ret); - } + case OPENSSL_KEY_SOURCE_ENGINE: + return load_key_from_engine(private_key_source, private_key, ret); + case OPENSSL_KEY_SOURCE_PROVIDER: + return load_key_from_provider(private_key_source, private_key, ret); + default: + assert_not_reached(); } - - return r; } #endif @@ -1418,3 +1405,34 @@ int x509_fingerprint(X509 *cert, uint8_t buffer[static SHA256_DIGEST_SIZE]) { return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot calculate X509 fingerprint: %m"); #endif } + +int parse_openssl_key_source_argument( + const char *argument, + char **private_key_source, + KeySourceType *private_key_source_type) { + + KeySourceType type; + const char *e = NULL; + int r; + + assert(argument); + assert(private_key_source); + assert(private_key_source_type); + + if (streq(argument, "file")) + type = OPENSSL_KEY_SOURCE_FILE; + else if ((e = startswith(argument, "engine:"))) + type = OPENSSL_KEY_SOURCE_ENGINE; + else if ((e = startswith(argument, "provider:"))) + type = OPENSSL_KEY_SOURCE_PROVIDER; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid private key source '%s'", argument); + + r = free_and_strdup_warn(private_key_source, e); + if (r < 0) + return r; + + *private_key_source_type = type; + + return 0; +} diff --git a/src/shared/openssl-util.h b/src/shared/openssl-util.h index 8d7d4f8c9f..e5ecbad86d 100644 --- a/src/shared/openssl-util.h +++ b/src/shared/openssl-util.h @@ -5,6 +5,16 @@ #include "macro.h" #include "sha256.h" +typedef enum KeySourceType { + OPENSSL_KEY_SOURCE_FILE, + OPENSSL_KEY_SOURCE_ENGINE, + OPENSSL_KEY_SOURCE_PROVIDER, + _OPENSSL_KEY_SOURCE_MAX, + _OPENSSL_KEY_SOURCE_INVALID = -EINVAL, +} KeySourceType; + +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 #if HAVE_OPENSSL @@ -123,7 +133,7 @@ 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(const char *private_key_uri, EVP_PKEY **ret); +int openssl_load_key_from_token(KeySourceType private_key_source_type, const char *private_key_source, const char *private_key, EVP_PKEY **ret); #else @@ -140,7 +150,12 @@ static inline void *EVP_PKEY_free(EVP_PKEY *p) { return NULL; } -static inline int openssl_load_key_from_token(const char *private_key_uri, EVP_PKEY **ret) { +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) { + return -EOPNOTSUPP; } From ed896a5b85ed6ef8b0abb79b0a76e4eb40d15233 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 10 Feb 2024 23:51:57 +0000 Subject: [PATCH 2/3] measure: add support for --certificate and --private-key-source for engine/provider signing Allow signing with an OpenSSL engine/provider, such as PKCS11. A public key is not enough, a full certificate is needed for PKCS11, so a new parameter is added for that too. --- man/systemd-measure.xml | 18 +++++ src/boot/measure.c | 143 ++++++++++++++++++++++++++++++++-------- 2 files changed, 133 insertions(+), 28 deletions(-) diff --git a/man/systemd-measure.xml b/man/systemd-measure.xml index 6cbeac1e38..4edfa747ea 100644 --- a/man/systemd-measure.xml +++ b/man/systemd-measure.xml @@ -158,6 +158,7 @@ + These switches take paths to a pair of PEM encoded RSA key files, for use with the sign command. @@ -172,9 +173,26 @@ If the is not specified but is specified the public key is automatically derived from the private key. + can be used to specify an X.509 certificate as an alternative + to since v256. + + + + + + + As an alternative to for the + sign command, these switches can be used to sign with an hardware token. The + private key 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. + + + + PATH diff --git a/src/boot/measure.c b/src/boot/measure.c index dea112add0..789a3deb8c 100644 --- a/src/boot/measure.c +++ b/src/boot/measure.c @@ -30,7 +30,10 @@ static char *arg_sections[_UNIFIED_SECTION_MAX] = {}; static char **arg_banks = NULL; static char *arg_tpm2_device = 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 char *arg_public_key = NULL; +static char *arg_certificate = NULL; static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO|JSON_FORMAT_OFF; static PagerFlags arg_pager_flags = 0; static bool arg_current = false; @@ -40,7 +43,9 @@ static char *arg_append = NULL; STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep); +STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep); STATIC_DESTRUCTOR_REGISTER(arg_public_key, freep); +STATIC_DESTRUCTOR_REGISTER(arg_certificate, freep); STATIC_DESTRUCTOR_REGISTER(arg_phase, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_append, freep); @@ -74,7 +79,11 @@ static int help(int argc, char *argv[], void *userdata) { " --bank=DIGEST Select TPM bank (SHA1, SHA256, SHA384, SHA512)\n" " --tpm2-device=PATH Use specified TPM2 device\n" " --private-key=KEY Private key (PEM) to sign with\n" + " --private-key-source=file|provider:PROVIDER|engine:ENGINE\n" + " Specify how to use the --private-key=. Allows to use\n" + " an OpenSSL engine/provider when signing\n" " --public-key=KEY Public key (PEM) to validate against\n" + " --certificate=PATH PEM certificate to use when signing with a URI\n" " --json=MODE Output as JSON\n" " -j Same as --json=pretty on tty, --json=short otherwise\n" " --append=PATH Load specified JSON signature, and append new signature to it\n" @@ -133,7 +142,9 @@ static int parse_argv(int argc, char *argv[]) { ARG_PCRPKEY = _ARG_SECTION_LAST, ARG_BANK, ARG_PRIVATE_KEY, + ARG_PRIVATE_KEY_SOURCE, ARG_PUBLIC_KEY, + ARG_CERTIFICATE, ARG_TPM2_DEVICE, ARG_JSON, ARG_PHASE, @@ -141,26 +152,28 @@ static int parse_argv(int argc, char *argv[]) { }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "version", no_argument, NULL, ARG_VERSION }, - { "linux", required_argument, NULL, ARG_LINUX }, - { "osrel", required_argument, NULL, ARG_OSREL }, - { "cmdline", required_argument, NULL, ARG_CMDLINE }, - { "initrd", required_argument, NULL, ARG_INITRD }, - { "splash", required_argument, NULL, ARG_SPLASH }, - { "dtb", required_argument, NULL, ARG_DTB }, - { "uname", required_argument, NULL, ARG_UNAME }, - { "sbat", required_argument, NULL, ARG_SBAT }, - { "pcrpkey", required_argument, NULL, ARG_PCRPKEY }, - { "current", no_argument, NULL, 'c' }, - { "bank", required_argument, NULL, ARG_BANK }, - { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, - { "private-key", required_argument, NULL, ARG_PRIVATE_KEY }, - { "public-key", required_argument, NULL, ARG_PUBLIC_KEY }, - { "json", required_argument, NULL, ARG_JSON }, - { "phase", required_argument, NULL, ARG_PHASE }, - { "append", required_argument, NULL, ARG_APPEND }, + { "help", no_argument, NULL, 'h' }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "version", no_argument, NULL, ARG_VERSION }, + { "linux", required_argument, NULL, ARG_LINUX }, + { "osrel", required_argument, NULL, ARG_OSREL }, + { "cmdline", required_argument, NULL, ARG_CMDLINE }, + { "initrd", required_argument, NULL, ARG_INITRD }, + { "splash", required_argument, NULL, ARG_SPLASH }, + { "dtb", required_argument, NULL, ARG_DTB }, + { "uname", required_argument, NULL, ARG_UNAME }, + { "sbat", required_argument, NULL, ARG_SBAT }, + { "pcrpkey", required_argument, NULL, ARG_PCRPKEY }, + { "current", no_argument, NULL, 'c' }, + { "bank", required_argument, NULL, ARG_BANK }, + { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, + { "private-key", required_argument, NULL, ARG_PRIVATE_KEY }, + { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE }, + { "public-key", required_argument, NULL, ARG_PUBLIC_KEY }, + { "certificate", required_argument, NULL, ARG_CERTIFICATE }, + { "json", required_argument, NULL, ARG_JSON }, + { "phase", required_argument, NULL, ARG_PHASE }, + { "append", required_argument, NULL, ARG_APPEND }, {} }; @@ -213,7 +226,17 @@ static int parse_argv(int argc, char *argv[]) { } case ARG_PRIVATE_KEY: - r = parse_path_argument(optarg, /* suppress_root= */ false, &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; @@ -226,6 +249,13 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_CERTIFICATE: + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_certificate); + if (r < 0) + return r; + + break; + case ARG_TPM2_DEVICE: { _cleanup_free_ char *device = NULL; @@ -281,6 +311,12 @@ static int parse_argv(int argc, char *argv[]) { assert_not_reached(); } + if (arg_public_key && arg_certificate) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Both --public-key= and --certificate= specified, refusing."); + + if (arg_private_key_source && !arg_certificate) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "When using --private-key-source=, --certificate= must be specified."); + if (strv_isempty(arg_banks)) { /* If no banks are specifically selected, pick all known banks */ arg_banks = strv_new("SHA1", "SHA256", "SHA384", "SHA512"); @@ -731,7 +767,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) { _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL; _cleanup_(EVP_PKEY_freep) EVP_PKEY *privkey = NULL, *pubkey = NULL; - _cleanup_fclose_ FILE *privkeyf = NULL; + _cleanup_(X509_freep) X509 *certificate = NULL; size_t n; int r; @@ -759,13 +795,57 @@ static int verb_sign(int argc, char *argv[], void *userdata) { /* When signing we only support JSON output */ arg_json_format_flags &= ~JSON_FORMAT_OFF; - privkeyf = fopen(arg_private_key, "re"); - if (!privkeyf) - return log_error_errno(errno, "Failed to open private key file '%s': %m", arg_private_key); + /* 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; - privkey = PEM_read_PrivateKey(privkeyf, NULL, NULL, NULL); - if (!privkey) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse private key '%s'.", arg_private_key); + r = read_full_file_full( + AT_FDCWD, arg_certificate, UINT64_MAX, SIZE_MAX, + READ_FULL_FILE_CONNECT_SOCKET, + /* bind_name= */ NULL, + &crt, &n); + 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)); + } + + if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) { + _cleanup_fclose_ FILE *privkeyf = NULL; + _cleanup_free_ char *resolved_pkey = NULL; + + r = parse_path_argument(arg_private_key, /* suppress_root= */ false, &resolved_pkey); + 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); + } if (arg_public_key) { _cleanup_fclose_ FILE *pubkeyf = NULL; @@ -777,6 +857,13 @@ static int verb_sign(int argc, char *argv[], void *userdata) { pubkey = PEM_read_PUBKEY(pubkeyf, NULL, NULL, NULL); if (!pubkey) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse public key '%s'.", arg_public_key); + } else if (certificate) { + pubkey = X509_get_pubkey(certificate); + if (!pubkey) + return log_error_errno( + SYNTHETIC_ERRNO(EIO), + "Failed to extract public key from certificate %s.", + arg_certificate); } else { _cleanup_(memstream_done) MemStream m = {}; FILE *tf; From 419b25ddcac39cf967555c7a2eaa274fbf1ad03c Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sun, 11 Feb 2024 00:34:19 +0000 Subject: [PATCH 3/3] ukify: add support for engine signing of PCR signatures --- man/ukify.xml | 5 ++++- src/ukify/test/test_ukify.py | 11 +++++------ src/ukify/ukify.py | 24 ++++++++++++++---------- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/man/ukify.xml b/man/ukify.xml index 659c6ed0a3..886e4522e4 100644 --- a/man/ukify.xml +++ b/man/ukify.xml @@ -99,7 +99,10 @@ the n-th boot phase path set will be signed by the n-th key. This can be used to build different trust policies for different phases of the boot. In the config file, PCRPrivateKey=, PCRPublicKey=, and Phases= are grouped into separate sections, - describing separate boot phases. + describing separate boot phases. If SigningEngine=/ + is specified, then the private keys arguments will be passed verbatim to OpenSSL as URIs, and the public + key arguments will be loaded as X.509 certificates, so that signing can be perfomed with an OpenSSL + engine. If a SecureBoot signing key is provided via the SecureBootPrivateKey=/ option, the resulting diff --git a/src/ukify/test/test_ukify.py b/src/ukify/test/test_ukify.py index ec466393d3..0e3f932890 100755 --- a/src/ukify/test/test_ukify.py +++ b/src/ukify/test/test_ukify.py @@ -120,7 +120,7 @@ def test_apply_config(tmp_path): assert ns.sign_kernel is False assert ns._groups == ['NAME'] - assert ns.pcr_private_keys == [pathlib.Path('some/path7')] + assert ns.pcr_private_keys == ['some/path7'] assert ns.pcr_public_keys == [pathlib.Path('some/path8')] assert ns.phase_path_groups == [['enter-initrd:leave-initrd:sysinit:ready:shutdown:final']] @@ -143,7 +143,7 @@ def test_apply_config(tmp_path): assert ns.sign_kernel is False assert ns._groups == ['NAME'] - assert ns.pcr_private_keys == [pathlib.Path('some/path7')] + assert ns.pcr_private_keys == ['some/path7'] assert ns.pcr_public_keys == [pathlib.Path('some/path8')] assert ns.phase_path_groups == [['enter-initrd:leave-initrd:sysinit:ready:shutdown:final']] @@ -189,7 +189,7 @@ def test_parse_args_many_deprecated(): assert opts.pcrpkey == pathlib.Path('PATH') assert opts.uname == '1.2.3' assert opts.stub == pathlib.Path('STUBPATH') - assert opts.pcr_private_keys == [pathlib.Path('PKEY1')] + assert opts.pcr_private_keys == ['PKEY1'] assert opts.pcr_public_keys == [pathlib.Path('PKEY2')] assert opts.pcr_banks == ['SHA1', 'SHA256'] assert opts.signing_engine == 'ENGINE' @@ -235,7 +235,7 @@ def test_parse_args_many(): assert opts.pcrpkey == pathlib.Path('PATH') assert opts.uname == '1.2.3' assert opts.stub == pathlib.Path('STUBPATH') - assert opts.pcr_private_keys == [pathlib.Path('PKEY1')] + assert opts.pcr_private_keys == ['PKEY1'] assert opts.pcr_public_keys == [pathlib.Path('PKEY2')] assert opts.pcr_banks == ['SHA1', 'SHA256'] assert opts.signing_engine == 'ENGINE' @@ -342,8 +342,7 @@ def test_config_priority(tmp_path): assert opts.pcrpkey == pathlib.Path('PATH') assert opts.uname == '1.2.3' assert opts.stub == pathlib.Path('STUBPATH') - assert opts.pcr_private_keys == [pathlib.Path('PKEY1'), - pathlib.Path('some/path7')] + assert opts.pcr_private_keys == ['PKEY1', 'some/path7'] assert opts.pcr_public_keys == [pathlib.Path('PKEY2'), pathlib.Path('some/path8')] assert opts.pcr_banks == ['SHA1', 'SHA256'] diff --git a/src/ukify/ukify.py b/src/ukify/ukify.py index 8ca93543b5..f7d08590d9 100755 --- a/src/ukify/ukify.py +++ b/src/ukify/ukify.py @@ -449,7 +449,7 @@ def check_cert_and_keys_nonexistent(opts): *((priv_key, pub_key) for priv_key, pub_key, _ in key_path_groups(opts))) for path in paths: - if path and path.exists(): + if path and pathlib.Path(path).exists(): raise ValueError(f'{path} is present') @@ -539,7 +539,11 @@ def call_systemd_measure(uki, linux, opts): for priv_key, pub_key, group in key_path_groups(opts): extra = [f'--private-key={priv_key}'] - if pub_key: + if opts.signing_engine is not None: + assert pub_key + extra += [f'--private-key-source=engine:{opts.signing_engine}'] + extra += [f'--certificate={pub_key}'] + elif pub_key: extra += [f'--public-key={pub_key}'] extra += [f'--phase={phase_path}' for phase_path in group or ()] @@ -728,11 +732,13 @@ def sbsign_sign(sbsign_tool, input_f, output_f, opts=None): sbsign_tool, '--key', opts.sb_key, '--cert', opts.sb_cert, - input_f, - '--output', output_f, ] if opts.signing_engine is not None: sign_invocation += ['--engine', opts.signing_engine] + sign_invocation += [ + input_f, + '--output', output_f, + ] signer_sign(sign_invocation) def find_pesign(opts=None): @@ -820,7 +826,7 @@ def make_uki(opts): pcrpkey = opts.pcr_public_keys[0] elif opts.pcr_private_keys and len(opts.pcr_private_keys) == 1: from cryptography.hazmat.primitives import serialization - privkey = serialization.load_pem_private_key(opts.pcr_private_keys[0].read_bytes(), password=None) + privkey = serialization.load_pem_private_key(pathlib.Path(opts.pcr_private_keys[0]).read_bytes(), password=None) pcrpkey = privkey.public_key().public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo, @@ -1002,7 +1008,7 @@ def generate_keys(opts): print(f'Writing private key for PCR signing to {priv_key}') with temporary_umask(0o077): - priv_key.write_bytes(priv_key_pem) + pathlib.Path(priv_key).write_bytes(priv_key_pem) if pub_key: print(f'Writing public key for PCR signing to {pub_key}') pub_key.write_bytes(pub_key_pem) @@ -1405,10 +1411,8 @@ CONFIG_ITEMS = [ ConfigItem( '--pcr-private-key', dest = 'pcr_private_keys', - metavar = 'PATH', - type = pathlib.Path, action = 'append', - help = 'private part of the keypair for signing PCR signatures', + help = 'private part of the keypair or engine-specific designation for signing PCR signatures', config_key = 'PCRSignature:/PCRPrivateKey', config_push = ConfigItem.config_set_group, ), @@ -1418,7 +1422,7 @@ CONFIG_ITEMS = [ metavar = 'PATH', type = pathlib.Path, action = 'append', - help = 'public part of the keypair for signing PCR signatures', + help = 'public part of the keypair or engine-specific designation for signing PCR signatures', config_key = 'PCRSignature:/PCRPublicKey', config_push = ConfigItem.config_set_group, ),