From ed896a5b85ed6ef8b0abb79b0a76e4eb40d15233 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Sat, 10 Feb 2024 23:51:57 +0000 Subject: [PATCH] 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 6cbeac1e382..4edfa747eaf 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 dea112add09..789a3deb8ce 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; -- 2.47.3