]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
measure: add support for --certificate and --private-key-source for engine/provider...
authorLuca Boccassi <bluca@debian.org>
Sat, 10 Feb 2024 23:51:57 +0000 (23:51 +0000)
committerLuca Boccassi <bluca@debian.org>
Fri, 1 Mar 2024 17:32:19 +0000 (17:32 +0000)
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
src/boot/measure.c

index 6cbeac1e382b22194afc982dd2f8379490903274..4edfa747eafb67bbaf2cae2ece6ec44657219a89 100644 (file)
       <varlistentry>
         <term><option>--private-key=<replaceable>PATH</replaceable></option></term>
         <term><option>--public-key=<replaceable>PATH</replaceable></option></term>
+        <term><option>--certificate=<replaceable>PATH</replaceable></option></term>
 
         <listitem><para>These switches take paths to a pair of PEM encoded RSA key files, for use with
         the <command>sign</command> command.</para>
         <para>If the <option>--public-key=</option> is not specified but <option>--private-key=</option> is
         specified the public key is automatically derived from the private key.</para>
 
+        <para><option>--certificate=</option> can be used to specify an X.509 certificate as an alternative
+        to <option>--public-key=</option> since v256.</para>
+
         <xi:include href="version-info.xml" xpointer="v252"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--private-key=<replaceable>PATH/URI</replaceable></option></term>
+        <term><option>--private-key-source=<replaceable>TYPE[:NAME]</replaceable></option></term>
+        <term><option>--certificate=<replaceable>PATH</replaceable></option></term>
+
+        <listitem><para>As an alternative to <option>--public-key=</option> for the
+        <command>sign</command> 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 <option>--private-key-source=</option> as a type:name tuple, such as
+        engine:pkcs11. The specified OpenSSL signing engine or provider will be used to sign.</para>
+
+        <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>--tpm2-device=</option><replaceable>PATH</replaceable></term>
 
index dea112add0983ab8a37bb129f234bd5f11668345..789a3deb8ce276aee21cdb3c9fa65c2de45e9fc5 100644 (file)
@@ -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;