add parameter to specify hash alg used for PKCS#1 signature in PKCS#7 creation (#37794)

The pkcs7_new() function currently uses the hash algorithm from the X509
certificate's signature, but the content signature may use a different
hash algorithm. This adds a parameter to allow specifying what hash
algorithm the content signature generation used.
This commit is contained in:
Luca Boccassi
2025-06-10 23:07:26 +01:00
committed by GitHub
6 changed files with 73 additions and 30 deletions

View File

@@ -72,7 +72,9 @@
<option>--signature=</option> in a PKCS#7 signature using the certificate given with
<option>--certificate=</option> and writes it to the file specified with <option>--output=</option>
in PKCS#7 format (p7s). If <option>--content=</option> is provided it is included in the p7s,
otherwise a "detached" signature is created.</para>
otherwise a "detached" signature is created. The <option>--hash-algorithm=</option> option, which
defaults to <literal>SHA256</literal>, specifies what hash algorithm was used to generate the
signature.</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
@@ -119,6 +121,17 @@
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--hash-algorithm=<replaceable>ALGORITHM</replaceable></option></term>
<listitem><para>Hash algorithm used to generate the PKCS#1 signature for the <command>pkcs7</command>
command. This should be a valid openssl digest algorithm; use <literal>openssl list
-digest-algorithms</literal> to see a list of valid algorithms on your system. Defaults to
<literal>SHA256</literal>.</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--output=<replaceable>PATH</replaceable></option></term>

View File

@@ -26,6 +26,7 @@ static char *arg_certificate_source = NULL;
static CertificateSourceType arg_certificate_source_type = OPENSSL_CERTIFICATE_SOURCE_FILE;
static char *arg_signature = NULL;
static char *arg_content = NULL;
static char *arg_hash_algorithm = NULL;
static char *arg_output = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep);
@@ -66,6 +67,8 @@ static int help(int argc, char *argv[], void *userdata) {
" from an OpenSSL provider\n"
" --content=PATH Raw data content to embed in PKCS#7 signature\n"
" --signature=PATH PKCS#1 signature to embed in PKCS#7 signature\n"
" --hash-algorithm=ALGORITHM\n"
" Hash algorithm used to create the PKCS#1 signature\n"
" --output=PATH Where to write the PKCS#7 signature\n"
"\nSee the %2$s for details.\n",
program_invocation_short_name,
@@ -87,6 +90,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_CERTIFICATE_SOURCE,
ARG_SIGNATURE,
ARG_CONTENT,
ARG_HASH_ALGORITHM,
ARG_OUTPUT,
};
@@ -99,6 +103,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "certificate-source", required_argument, NULL, ARG_CERTIFICATE_SOURCE },
{ "signature", required_argument, NULL, ARG_SIGNATURE },
{ "content", required_argument, NULL, ARG_CONTENT },
{ "hash-algorithm", required_argument, NULL, ARG_HASH_ALGORITHM },
{ "output", required_argument, NULL, ARG_OUTPUT },
{}
};
@@ -164,6 +169,10 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_HASH_ALGORITHM:
arg_hash_algorithm = optarg;
break;
case ARG_OUTPUT:
r = parse_path_argument(optarg, /*suppress_root=*/ false, &arg_output);
if (r < 0)
@@ -355,7 +364,7 @@ static int verb_pkcs7(int argc, char *argv[], void *userdata) {
_cleanup_(PKCS7_freep) PKCS7 *pkcs7 = NULL;
PKCS7_SIGNER_INFO *signer_info;
r = pkcs7_new(certificate, /* private_key= */ NULL, &pkcs7, &signer_info);
r = pkcs7_new(certificate, /* private_key= */ NULL, arg_hash_algorithm, &pkcs7, &signer_info);
if (r < 0)
return log_error_errno(r, "Failed to allocate PKCS#7 context: %m");

View File

@@ -321,6 +321,7 @@ static int asn1_timestamp(ASN1_TIME **ret) {
static int pkcs7_new_with_attributes(
X509 *certificate,
EVP_PKEY *private_key,
const char *hash_algorithm,
STACK_OF(X509_ATTRIBUTE) *signed_attributes,
PKCS7 **ret_p7,
PKCS7_SIGNER_INFO **ret_si) {
@@ -336,7 +337,7 @@ static int pkcs7_new_with_attributes(
_cleanup_(PKCS7_freep) PKCS7 *p7 = NULL;
PKCS7_SIGNER_INFO *si = NULL; /* avoid false maybe-uninitialized warning */
r = pkcs7_new(certificate, private_key, &p7, &si);
r = pkcs7_new(certificate, private_key, hash_algorithm, &p7, &si);
if (r < 0)
return log_error_errno(r, "Failed to allocate PKCS# context: %m");
@@ -562,7 +563,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
_cleanup_(PKCS7_freep) PKCS7 *p7 = NULL;
PKCS7_SIGNER_INFO *si = NULL; /* avoid false maybe-uninitialized warning */
r = pkcs7_new_with_attributes(certificate, private_key, signed_attributes, &p7, &si);
r = pkcs7_new_with_attributes(certificate, private_key, /* hash_algorithm= */ NULL, signed_attributes, &p7, &si);
if (r < 0)
return r;

View File

@@ -1126,14 +1126,15 @@ int digest_and_sign(
return 0;
}
int pkcs7_new(X509 *certificate, EVP_PKEY *private_key, PKCS7 **ret_p7, PKCS7_SIGNER_INFO **ret_si) {
int pkcs7_new(X509 *certificate, EVP_PKEY *private_key, const char *hash_algorithm, PKCS7 **ret_p7, PKCS7_SIGNER_INFO **ret_si) {
assert(certificate);
assert(ret_p7);
/* This function sets up a new PKCS7 signing context. If a private key is provided, the context is
* set up for "in-band" signing with PKCS7_dataFinal(). If a private key is not provided, the context
* is set up for "out-of-band" signing, meaning the signature has to be provided by the user and
* copied into the signer info's "enc_digest" field. */
* copied into the signer info's "enc_digest" field. If the signing hash algorithm is not provided,
* SHA-256 is used. */
_cleanup_(PKCS7_freep) PKCS7 *p7 = PKCS7_new();
if (!p7)
@@ -1151,21 +1152,22 @@ int pkcs7_new(X509 *certificate, EVP_PKEY *private_key, PKCS7 **ret_p7, PKCS7_SI
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set PKCS7 certificate: %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, NULL, NULL) == 0)
int x509_pknid = 0;
if (X509_get_signature_info(certificate, NULL, &x509_pknid, NULL, NULL) == 0)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get X509 digest NID: %s",
ERR_error_string(ERR_get_error(), NULL));
const EVP_MD *md = EVP_get_digestbynid(x509_mdnid);
const EVP_MD *md = EVP_get_digestbyname(hash_algorithm ?: "SHA256");
if (!md)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get digest algorithm via digest NID");
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get digest algorithm '%s'",
hash_algorithm ?: "SHA256");
_cleanup_(PKCS7_SIGNER_INFO_freep) PKCS7_SIGNER_INFO *si = PKCS7_SIGNER_INFO_new();
if (!si)
return log_oom();
if (private_key) {
if (PKCS7_SIGNER_INFO_set(si, certificate, private_key, EVP_get_digestbynid(x509_mdnid)) <= 0)
if (PKCS7_SIGNER_INFO_set(si, certificate, private_key, md) <= 0)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to configure signer info: %s",
ERR_error_string(ERR_get_error(), NULL));
} else {
@@ -1183,7 +1185,7 @@ int pkcs7_new(X509 *certificate, EVP_PKEY *private_key, PKCS7 **ret_p7, PKCS7_SI
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set signer info serial: %s",
ERR_error_string(ERR_get_error(), NULL));
if (X509_ALGOR_set0(si->digest_alg, OBJ_nid2obj(x509_mdnid), V_ASN1_NULL, NULL) == 0)
if (X509_ALGOR_set0(si->digest_alg, OBJ_nid2obj(EVP_MD_type(md)), V_ASN1_NULL, NULL) == 0)
return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set signer info digest algorithm: %s",
ERR_error_string(ERR_get_error(), NULL));

View File

@@ -165,7 +165,7 @@ int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_s
int digest_and_sign(const EVP_MD *md, EVP_PKEY *privkey, const void *data, size_t size, void **ret, size_t *ret_size);
int pkcs7_new(X509 *certificate, EVP_PKEY *private_key, PKCS7 **ret_p7, PKCS7_SIGNER_INFO **ret_si);
int pkcs7_new(X509 *certificate, EVP_PKEY *private_key, const char *hash_algorithm, PKCS7 **ret_p7, PKCS7_SIGNER_INFO **ret_si);
int string_hashsum(const char *s, size_t len, const char *md_algorithm, char **ret);

View File

@@ -47,31 +47,49 @@ testcase_public() {
(! /usr/lib/systemd/systemd-keyutil public)
}
verify_pkcs7() {
# Verify using internal certificate
openssl smime -verify -binary -inform der -in /tmp/payload.p7s -content /tmp/payload -noverify > /dev/null
# Verify using external (original) certificate
openssl smime -verify -binary -inform der -in /tmp/payload.p7s -content /tmp/payload -noverify -certfile /tmp/test.crt -nointern > /dev/null
}
verify_pkcs7_fail() {
# Verify using internal certificate
(! openssl smime -verify -binary -inform der -in /tmp/payload.p7s -content /tmp/payload -noverify > /dev/null)
# Verify using external (original) certificate
(! openssl smime -verify -binary -inform der -in /tmp/payload.p7s -content /tmp/payload -noverify -certfile /tmp/test.crt -nointern > /dev/null)
}
testcase_pkcs7() {
echo -n "test" > /tmp/payload
# Generate PKCS#1 signature
openssl dgst -sha256 -sign /tmp/test.key -out /tmp/payload.sig /tmp/payload
for hashalg in sha256 sha384 sha512; do
# shellcheck disable=SC2086
openssl dgst -$hashalg -sign /tmp/test.key -out /tmp/payload.p1s /tmp/payload
# Generate PKCS#7 "detached" signature
/usr/lib/systemd/systemd-keyutil --certificate /tmp/test.crt --output /tmp/payload.p7s --signature /tmp/payload.sig pkcs7
# Test with and without content in the PKCS7
for content_param in "" "--content /tmp/payload"; do
# Test with and without specifying signing hash alg
for hashalg_param in "" "--hash-algorithm $hashalg"; do
# shellcheck disable=SC2086
/usr/lib/systemd/systemd-keyutil --certificate /tmp/test.crt --output /tmp/payload.p7s --signature /tmp/payload.p1s $content_param $hashalg_param pkcs7
# Verify using internal x509 certificate
openssl smime -verify -binary -inform der -in /tmp/payload.p7s -content /tmp/payload -noverify > /dev/null
# Should always pass, except when not specifying hash alg and hash alg != sha256
if [ -z "$hashalg_param" ] && [ "$hashalg" != "sha256" ]; then
verify_pkcs7_fail
else
verify_pkcs7
fi
# Verify using external (original) x509 certificate
openssl smime -verify -binary -inform der -in /tmp/payload.p7s -content /tmp/payload -certificate /tmp/test.crt -nointern -noverify > /dev/null
rm -f /tmp/payload.p7s
done
done
rm -f /tmp/payload.p7s
rm -f /tmp/payload.p1s
done
# Generate PKCS#7 non-"detached" signature
/usr/lib/systemd/systemd-keyutil --certificate /tmp/test.crt --output /tmp/payload.p7s --signature /tmp/payload.sig --content /tmp/payload pkcs7
# Verify using internal x509 certificate
openssl smime -verify -binary -inform der -in /tmp/payload.p7s -noverify > /dev/null
# Verify using external (original) x509 certificate
openssl smime -verify -binary -inform der -in /tmp/payload.p7s -certificate /tmp/test.crt -nointern -noverify > /dev/null
rm -f /tmp/payload
}
run_testcases