]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
systemd-keyutil: add verb to conver PKCS#1 to PKCS#7
authorDan Streetman <ddstreet@ieee.org>
Fri, 21 Feb 2025 09:36:53 +0000 (09:36 +0000)
committerLuca Boccassi <luca.boccassi@gmail.com>
Sun, 23 Feb 2025 14:14:09 +0000 (14:14 +0000)
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 <bluca@debian.org>
man/systemd-keyutil.xml
src/keyutil/keyutil.c
src/shared/openssl-util.h
test/units/TEST-74-AUX-UTILS.keyutil.sh

index d56d2261127a3b219b49232e3a652b2965cec518..7060433762c5f4fc6e218b211cc34e6a7e72f31a 100644 (file)
 
         <xi:include href="version-info.xml" xpointer="v257"/></listitem>
       </varlistentry>
+
+      <varlistentry>
+        <term><command>pkcs7</command></term>
+
+        <listitem><para>This command generates a PKCS#7 signature that embeds the PKCS#1 signature (RSA)
+        provided with <option>--signature=</option> to an <option>--output=</option> file in PKCS#7 format
+        (p7s) using the certificate given with <option>--certificate=</option>.</para>
+
+        <xi:include href="version-info.xml" xpointer="v258"/></listitem>
+      </varlistentry>
     </variablelist>
   </refsect1>
 
         <xi:include href="version-info.xml" xpointer="v257"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--signature=<replaceable>PATH</replaceable></option></term>
+
+        <listitem><para>Input PKCS#1 signature for the <command>pkcs7</command> command.</para>
+
+        <xi:include href="version-info.xml" xpointer="v258"/></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--output=<replaceable>PATH</replaceable></option></term>
+
+        <listitem><para>Output PKCS#7 signature for the <command>pkcs7</command> command.</para>
+
+        <xi:include href="version-info.xml" xpointer="v258"/></listitem>
+      </varlistentry>
+
       <xi:include href="standard-options.xml" xpointer="help"/>
       <xi:include href="standard-options.xml" xpointer="version"/>
     </variablelist>
index 23459e48f590a0182cdd7bce82b7051361da18fb..36bb33f9f114702f7893fb614f0856cc591f02bc 100644 (file)
@@ -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;
index 7eb1ea15c0fb83b13f3f1a9176eb54cefd0e30ab..85d622b5b9a6c2966c761640b8fa46292233bc15 100644 (file)
@@ -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);
index bbbbf9fd6763c72c1c1afa772744bfe97ededf5b..d08c86e6972109a4e236dc2f0b5ddc6289010da8 100755 (executable)
@@ -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