[PATCH v13 03/12] pkcs7: Allow direct signing of data with ML-DSA

David Howells
2026-01-20T14:51:36Z

Allow the data part of a PKCS#7 or CMS messge to be passed directly to an
asymmetric cipher algorithm (e.g. ML-DSA) if it wants to do the digestion
itself. The normal digestion of the data is then skipped as that would be
ignored unless another signed info in the message has some other algorithm
that needs it.

This is done by setting the digest parameters to point to the data to be
verified rather than making public_key_verify_signature() access the data
directly. This is so that keyctl(KEYCTL_PKEY_VERIFY) will still work.

To test this with ML-DSA, sign-file must be built with openssl > v3.5 and
must include the following fix:

https://github.com/openssl/openssl/pull/28923

which will allow CMS_NOATTR to be used with CMS_sign() for an ML-DSA key.

sign-file will remove CMS_NOATTR if openssl is earlier than v4 and signed
attributes will be used.

Signed-off-by: David Howells <dhowells@redhat.com>
cc: Lukas Wunner <lukas@wunner.de>
cc: Ignat Korchagin <ignat@cloudflare.com>
cc: Stephan Mueller <smueller@chronox.de>
cc: Eric Biggers <ebiggers@kernel.org>
cc: Herbert Xu <herbert@gondor.apana.org.au>
cc: keyrings@vger.kernel.org
cc: linux-crypto@vger.kernel.org
---
crypto/asymmetric_keys/mscode_parser.c | 2 +-
crypto/asymmetric_keys/pkcs7_verify.c | 18 ++++++++++++++++++
crypto/asymmetric_keys/signature.c | 3 ++-
crypto/asymmetric_keys/verify_pefile.c | 3 ++-
crypto/asymmetric_keys/verify_pefile.h | 1 +
crypto/asymmetric_keys/x509_public_key.c | 1 +
include/crypto/public_key.h | 1 +
7 files changed, 26 insertions(+), 3 deletions(-)

diff --git a/crypto/asymmetric_keys/mscode_parser.c b/crypto/asymmetric_keys/mscode_parser.c
index 8aecbe4637f3..54dac17f19e2 100644
--- a/crypto/asymmetric_keys/mscode_parser.c
+++ b/crypto/asymmetric_keys/mscode_parser.c
@@ -124,6 +124,6 @@ int mscode_note_digest(void *context, size_t hdrlen,
return -ENOMEM;

ctx->digest_len = vlen;
-
+ ctx->digest_free = true;
return 0;
}
diff --git a/crypto/asymmetric_keys/pkcs7_verify.c b/crypto/asymmetric_keys/pkcs7_verify.c
index 0f9f515b784d..46eee9811023 100644
--- a/crypto/asymmetric_keys/pkcs7_verify.c
+++ b/crypto/asymmetric_keys/pkcs7_verify.c
@@ -30,6 +30,16 @@ static int pkcs7_digest(struct pkcs7_message *pkcs7,

kenter(",%u,%s", sinfo->index, sinfo->sig->hash_algo);

+ if (!sinfo->authattrs && sig->algo_does_hash) {
+ /* There's no intermediate digest and the signature algo
+ * doesn't want the data prehashing.
+ */
+ sig->digest = (void *)pkcs7->data;
+ sig->digest_size = pkcs7->data_len;
+ sig->digest_free = false;
+ return 0;
+ }
+
/* The digest was calculated already. */
if (sig->digest)
return 0;
@@ -51,6 +61,7 @@ static int pkcs7_digest(struct pkcs7_message *pkcs7,
sig->digest = kmalloc(sig->digest_size, GFP_KERNEL);
if (!sig->digest)
goto error_no_desc;
+ sig->digest_free = true;

desc = kzalloc(desc_size, GFP_KERNEL);
if (!desc)
@@ -103,6 +114,7 @@ static int pkcs7_digest(struct pkcs7_message *pkcs7,
*/
if (sig->algo_does_hash) {
kfree(sig->digest);
+ sig->digest_free = false;

ret = -ENOMEM;
sig->digest = kmalloc(umax(sinfo->authattrs_len, sig->digest_size),
@@ -110,6 +122,7 @@ static int pkcs7_digest(struct pkcs7_message *pkcs7,
if (!sig->digest)
goto error_no_desc;

+ sig->digest_free = true;
sig->digest_size = sinfo->authattrs_len;
memcpy(sig->digest, sinfo->authattrs, sinfo->authattrs_len);
((u8 *)sig->digest)[0] = ASN1_CONS_BIT | ASN1_SET;
@@ -155,6 +168,11 @@ int pkcs7_get_digest(struct pkcs7_message *pkcs7, const u8 **buf, u32 *len,
ret = pkcs7_digest(pkcs7, sinfo);
if (ret)
return ret;
+ if (!sinfo->sig->digest_free) {
+ pr_notice_once("%s: No digest available\n", __func__);
+ return -EINVAL; /* TODO: MLDSA doesn't necessarily calculate an
+ * intermediate digest. */
+ }

*buf = sinfo->sig->digest;
*len = sinfo->sig->digest_size;
diff --git a/crypto/asymmetric_keys/signature.c b/crypto/asymmetric_keys/signature.c
index 041d04b5c953..bea01cf27d0a 100644
--- a/crypto/asymmetric_keys/signature.c
+++ b/crypto/asymmetric_keys/signature.c
@@ -28,7 +28,8 @@ void public_key_signature_free(struct public_key_signature *sig)
for (i = 0; i < ARRAY_SIZE(sig->auth_ids); i++)
kfree(sig->auth_ids[i]);
kfree(sig->s);
- kfree(sig->digest);
+ if (sig->digest_free)
+ kfree(sig->digest);
kfree(sig);
}
}
diff --git a/crypto/asymmetric_keys/verify_pefile.c b/crypto/asymmetric_keys/verify_pefile.c
index 1f3b227ba7f2..30c23aea3b25 100644
--- a/crypto/asymmetric_keys/verify_pefile.c
+++ b/crypto/asymmetric_keys/verify_pefile.c
@@ -451,6 +451,7 @@ int verify_pefile_signature(const void *pebuf, unsigned pelen,
ret = pefile_digest_pe(pebuf, pelen, &ctx);

error:
- kfree_sensitive(ctx.digest);
+ if (ctx.digest_free)
+ kfree_sensitive(ctx.digest);
return ret;
}
diff --git a/crypto/asymmetric_keys/verify_pefile.h b/crypto/asymmetric_keys/verify_pefile.h
index e1628e100cde..f641437264b4 100644
--- a/crypto/asymmetric_keys/verify_pefile.h
+++ b/crypto/asymmetric_keys/verify_pefile.h
@@ -22,6 +22,7 @@ struct pefile_context {
/* PKCS#7 MS Individual Code Signing content */
const void *digest; /* Digest */
unsigned digest_len; /* Digest length */
+ bool digest_free; /* T if digest should be freed */
const char *digest_algo; /* Digest algorithm */
};

diff --git a/crypto/asymmetric_keys/x509_public_key.c b/crypto/asymmetric_keys/x509_public_key.c
index 12e3341e806b..2243add11d48 100644
--- a/crypto/asymmetric_keys/x509_public_key.c
+++ b/crypto/asymmetric_keys/x509_public_key.c
@@ -56,6 +56,7 @@ int x509_get_sig_params(struct x509_certificate *cert)
sig->digest = kmalloc(sig->digest_size, GFP_KERNEL);
if (!sig->digest)
goto error;
+ sig->digest_free = true;

desc = kzalloc(desc_size, GFP_KERNEL);
if (!desc)
diff --git a/include/crypto/public_key.h b/include/crypto/public_key.h
index e4ec8003a3a4..68899a49cd0d 100644
--- a/include/crypto/public_key.h
+++ b/include/crypto/public_key.h
@@ -46,6 +46,7 @@ struct public_key_signature {
u8 *digest;
u32 s_size; /* Number of bytes in signature */
u32 digest_size; /* Number of bytes in digest */
+ bool digest_free; /* T if digest needs freeing */
bool algo_does_hash; /* Public key algo does its own hashing */
const char *pkey_algo;
const char *hash_algo;