From af69ea714d8e75055561acfd607970742136655f Mon Sep 17 00:00:00 2001 From: Dan Streetman Date: Fri, 21 Feb 2025 09:36:53 +0000 Subject: [PATCH] systemd-keyutil: add verb to conver PKCS#1 to PKCS#7 Add verb that takes a PKCS#1 signature (plain rsa) as input and a certificates, and outputs a PKCS#7 binary detached signature (p7s), which is what the kernel dm-verity driver expects. Co-authored-by: Luca Boccassi --- man/systemd-keyutil.xml | 26 +++++ src/keyutil/keyutil.c | 139 ++++++++++++++++++++++++ src/shared/openssl-util.h | 1 + test/units/TEST-74-AUX-UTILS.keyutil.sh | 7 ++ 4 files changed, 173 insertions(+) diff --git a/man/systemd-keyutil.xml b/man/systemd-keyutil.xml index d56d226112..7060433762 100644 --- a/man/systemd-keyutil.xml +++ b/man/systemd-keyutil.xml @@ -64,6 +64,16 @@ + + + pkcs7 + + This command generates a PKCS#7 signature that embeds the PKCS#1 signature (RSA) + provided with to an file in PKCS#7 format + (p7s) using the certificate given with . + + + @@ -90,6 +100,22 @@ + + + + Input PKCS#1 signature for the pkcs7 command. + + + + + + + + Output PKCS#7 signature for the pkcs7 command. + + + + diff --git a/src/keyutil/keyutil.c b/src/keyutil/keyutil.c index 23459e48f5..36bb33f9f1 100644 --- a/src/keyutil/keyutil.c +++ b/src/keyutil/keyutil.c @@ -7,6 +7,7 @@ #include "ask-password-api.h" #include "build.h" #include "fd-util.h" +#include "fileio.h" #include "main-func.h" #include "memstream-util.h" #include "openssl-util.h" @@ -20,11 +21,15 @@ static char *arg_private_key_source = NULL; static char *arg_certificate = NULL; static char *arg_certificate_source = NULL; static CertificateSourceType arg_certificate_source_type = OPENSSL_CERTIFICATE_SOURCE_FILE; +static char *arg_signature = NULL; +static char *arg_output = NULL; STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep); STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep); STATIC_DESTRUCTOR_REGISTER(arg_certificate, freep); STATIC_DESTRUCTOR_REGISTER(arg_certificate_source, freep); +STATIC_DESTRUCTOR_REGISTER(arg_signature, freep); +STATIC_DESTRUCTOR_REGISTER(arg_output, freep); static int help(int argc, char *argv[], void *userdata) { _cleanup_free_ char *link = NULL; @@ -39,6 +44,7 @@ static int help(int argc, char *argv[], void *userdata) { "\n%3$sCommands:%4$s\n" " validate Load and validate the given certificate and private key\n" " public Extract a public key\n" + " pkcs7 Generate a PKCS#7 signature\n" "\n%3$sOptions:%4$s\n" " -h --help Show this help\n" " --version Print version\n" @@ -53,6 +59,8 @@ static int help(int argc, char *argv[], void *userdata) { " Specify how to interpret the certificate from\n" " --certificate=. Allows the certificate to be loaded\n" " from an OpenSSL provider\n" + " --signature=PATH PKCS#1 signature to embed in PKCS#7 signature\n" + " --output=PATH Where to write the PKCS#7 signature\n" "\nSee the %2$s for details.\n", program_invocation_short_name, link, @@ -71,6 +79,8 @@ static int parse_argv(int argc, char *argv[]) { ARG_PRIVATE_KEY_SOURCE, ARG_CERTIFICATE, ARG_CERTIFICATE_SOURCE, + ARG_SIGNATURE, + ARG_OUTPUT, }; static const struct option options[] = { @@ -80,6 +90,8 @@ static int parse_argv(int argc, char *argv[]) { { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE }, { "certificate", required_argument, NULL, ARG_CERTIFICATE }, { "certificate-source", required_argument, NULL, ARG_CERTIFICATE_SOURCE }, + { "signature", required_argument, NULL, ARG_SIGNATURE }, + { "output", required_argument, NULL, ARG_OUTPUT }, {} }; @@ -130,6 +142,20 @@ static int parse_argv(int argc, char *argv[]) { return r; break; + case ARG_SIGNATURE: + r = parse_path_argument(optarg, /*suppress_root=*/ false, &arg_signature); + if (r < 0) + return r; + + break; + + case ARG_OUTPUT: + r = parse_path_argument(optarg, /*suppress_root=*/ false, &arg_output); + if (r < 0) + return r; + + break; + case '?': return -EINVAL; @@ -277,11 +303,124 @@ static int verb_public(int argc, char *argv[], void *userdata) { return 0; } +static int verb_pkcs7(int argc, char *argv[], void *userdata) { + _cleanup_(X509_freep) X509 *certificate = NULL; + _cleanup_free_ char *pkcs1 = NULL; + size_t pkcs1_len = 0; + int r; + + if (!arg_certificate) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--certificate= must be specified"); + + if (!arg_signature) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--signature= must be specified"); + + if (!arg_output) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--output= must be specified"); + + if (arg_certificate_source_type == OPENSSL_CERTIFICATE_SOURCE_FILE) { + r = parse_path_argument(arg_certificate, /*suppress_root=*/ false, &arg_certificate); + if (r < 0) + return r; + } + + r = openssl_load_x509_certificate( + arg_certificate_source_type, + arg_certificate_source, + arg_certificate, + &certificate); + if (r < 0) + return log_error_errno(r, "Failed to load X.509 certificate from %s: %m", arg_certificate); + + r = read_full_file(arg_signature, &pkcs1, &pkcs1_len); + if (r < 0) + return log_error_errno(r, "Failed to read PKCS#1 file %s: %m", arg_signature); + if (pkcs1_len == 0) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "PKCS#1 file %s is empty", arg_signature); + + /* Create PKCS7_SIGNER_INFO using X509 pubkey/digest NIDs */ + + _cleanup_(PKCS7_SIGNER_INFO_freep) PKCS7_SIGNER_INFO *signer_info = PKCS7_SIGNER_INFO_new(); + if (!signer_info) + return log_oom(); + + if (ASN1_INTEGER_set(signer_info->version, 1) == 0) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set ASN1 integer: %s", + ERR_error_string(ERR_get_error(), NULL)); + + if (X509_NAME_set(&signer_info->issuer_and_serial->issuer, X509_get_issuer_name(certificate)) == 0) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set issuer name: %s", + ERR_error_string(ERR_get_error(), NULL)); + + ASN1_INTEGER_free(signer_info->issuer_and_serial->serial); + signer_info->issuer_and_serial->serial = ASN1_INTEGER_dup(X509_get0_serialNumber(certificate)); + if (!signer_info->issuer_and_serial->serial) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set issuer serial: %s", + ERR_error_string(ERR_get_error(), NULL)); + + int x509_mdnid = 0, x509_pknid = 0; + if (X509_get_signature_info(certificate, &x509_mdnid, &x509_pknid, /* secbits= */ NULL, /* flags= */ NULL) == 0) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get X.509 digest NID/PK: %s", + ERR_error_string(ERR_get_error(), NULL)); + + if (X509_ALGOR_set0(signer_info->digest_alg, OBJ_nid2obj(x509_mdnid), V_ASN1_NULL, /* pval= */ NULL) == 0) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set digest alg: %s", + ERR_error_string(ERR_get_error(), NULL)); + + if (X509_ALGOR_set0(signer_info->digest_enc_alg, OBJ_nid2obj(x509_pknid), V_ASN1_NULL, /* pval= */ NULL) == 0) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set digest enc alg: %s", + ERR_error_string(ERR_get_error(), NULL)); + + /* Create new PKCS7 using X509 certificate */ + + _cleanup_(PKCS7_freep) PKCS7 *pkcs7 = PKCS7_new(); + if (!pkcs7) + return log_oom(); + + if (PKCS7_set_type(pkcs7, NID_pkcs7_signed) == 0) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS#7 type: %s", + ERR_error_string(ERR_get_error(), NULL)); + + if (PKCS7_content_new(pkcs7, NID_pkcs7_data) == 0) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS#7 content: %s", + ERR_error_string(ERR_get_error(), NULL)); + + if (PKCS7_set_detached(pkcs7, true) == 0) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS#7 detached attribute: %s", + ERR_error_string(ERR_get_error(), NULL)); + + if (PKCS7_add_certificate(pkcs7, certificate) == 0) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS#7 certificate: %s", + ERR_error_string(ERR_get_error(), NULL)); + + /* Add PKCS1 signature to PKCS7_SIGNER_INFO */ + + ASN1_STRING_set0(signer_info->enc_digest, TAKE_PTR(pkcs1), pkcs1_len); + + /* Add PKCS7_SIGNER_INFO to PKCS7 */ + + if (PKCS7_add_signer(pkcs7, signer_info) == 0) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS#7 signer info: %s", + ERR_error_string(ERR_get_error(), NULL)); + TAKE_PTR(signer_info); + + _cleanup_fclose_ FILE *output = fopen(arg_output, "we"); + if (!output) + return log_error_errno(errno, "Could not open PKCS#7 output file %s: %m", arg_output); + + if (!i2d_PKCS7_fp(output, pkcs7)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write PKCS#7 file: %s", + ERR_error_string(ERR_get_error(), NULL)); + + return 0; +} + static int run(int argc, char *argv[]) { static const Verb verbs[] = { { "help", VERB_ANY, VERB_ANY, 0, help }, { "validate", VERB_ANY, 1, 0, verb_validate }, { "public", VERB_ANY, 1, 0, verb_public }, + { "pkcs7", VERB_ANY, VERB_ANY, 0, verb_pkcs7 }, {} }; int r; diff --git a/src/shared/openssl-util.h b/src/shared/openssl-util.h index 7eb1ea15c0..85d622b5b9 100644 --- a/src/shared/openssl-util.h +++ b/src/shared/openssl-util.h @@ -62,6 +62,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIGNUM*, BN_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BN_CTX*, BN_CTX_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ECDSA_SIG*, ECDSA_SIG_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(PKCS7*, PKCS7_free, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(PKCS7_SIGNER_INFO*, PKCS7_SIGNER_INFO_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SSL*, SSL_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIO*, BIO_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIO*, BIO_free_all, NULL); diff --git a/test/units/TEST-74-AUX-UTILS.keyutil.sh b/test/units/TEST-74-AUX-UTILS.keyutil.sh index bbbbf9fd67..d08c86e697 100755 --- a/test/units/TEST-74-AUX-UTILS.keyutil.sh +++ b/test/units/TEST-74-AUX-UTILS.keyutil.sh @@ -47,4 +47,11 @@ testcase_public() { (! /usr/lib/systemd/systemd-keyutil public) } +testcase_pkcs7() { + echo -n "test" > /tmp/payload + openssl dgst -sha256 -sign /tmp/test.key -out /tmp/payload.sig /tmp/payload + /usr/lib/systemd/systemd-keyutil --certificate /tmp/test.crt --output /tmp/payload.p7s --signature /tmp/payload.sig pkcs7 + openssl smime -verify -binary -inform der -in /tmp/payload.p7s -content /tmp/payload -certfile /tmp/test.crt -nointern -noverify > /dev/null +} + run_testcases