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.
This commit is contained in:
Luca Boccassi 2024-02-10 23:51:57 +00:00
parent a73144bbdf
commit ed896a5b85
2 changed files with 133 additions and 28 deletions

View file

@ -158,6 +158,7 @@
<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>
@ -172,9 +173,26 @@
<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>

View 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;