* [PATCH v14 0/5] x509, pkcs7, crypto: Add ML-DSA and RSASSA-PSS signing
@ 2026-01-21 22:36 David Howells
2026-01-21 22:36 ` [PATCH v14 1/5] crypto: Add ML-DSA crypto_sig support David Howells
` (5 more replies)
0 siblings, 6 replies; 15+ messages in thread
From: David Howells @ 2026-01-21 22:36 UTC (permalink / raw)
To: Lukas Wunner, Ignat Korchagin
Cc: David Howells, Jarkko Sakkinen, Herbert Xu, Eric Biggers,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Jason A . Donenfeld, Ard Biesheuvel, Stephan Mueller,
linux-crypto, keyrings, linux-modules, linux-kernel
Hi Lukas, Ignat,
[Note this is based on Eric Bigger's libcrypto-next branch].
These patches add ML-DSA module signing and RSASSA-PSS module signing. The
first half of the set adds ML-DSA signing:
(1) Add a crypto_sig interface for ML-DSA, verification only.
(2) Modify PKCS#7 support to allow kernel module signatures to carry
authenticatedAttributes as OpenSSL refuses to let them be opted out of
for ML-DSA (CMS_NOATTR). This adds an extra digest calculation to the
process.
Modify PKCS#7 to pass the authenticatedAttributes directly to the
ML-DSA algorithm rather than passing over a digest as is done with RSA
as ML-DSA wants to do its own hashing and will add other stuff into
the hash. We could use hashML-DSA or an external mu instead, but they
aren't standardised for CMS yet.
(3) Add support to the PKCS#7 and X.509 parsers for ML-DSA.
(4) Modify sign-file to handle OpenSSL not permitting CMS_NOATTR with
ML-DSA and add ML-DSA to the choice of algorithm with which to sign
modules. Note that this might need some more 'select' lines in the
Kconfig to select the lib stuff as well.
This is based on Eric's libcrypto-next branch which has the core
implementation of ML-DSA.
The second half of the set adds RSASSA-PSS signing:
(5) Add an info string parameter to the internal signature verification
routines where that does not already exist. This is necessary to pass
extra parameters and is already supported in the KEYCTL_PKEY_VERIFY
keyctl.
Both X.509 and PKCS#7 provide for these parameters to be supplied, but
it is tricky to pass the parameters in a blob with the signature or
key data as there are checks on these sizes that are then violated;
further, the way the parameters are laid out in the ASN.1 doesn't lend
itself easily to simply extracting out a larger blob.
(6) Add RSASSA-PSS support to the RSA driver in crypto/. This parses the
info string to get the verification parameters.
(7) Add support to the PKCS#7 and X.509 parsers for RSASSA-PSS.
(8) Modify sign-file to pass the extra parameters necessary to be able
generate RSASSA-PSS. For the moment, only select MGF1 with the same
hash algorithm as for the data for the mask function. Add RSASSA-PSS
to the choice of algorithm with which to sign modules.
Note that I do still need to add some FIPS tests for ML-DSA in the form of
X.509 certs, data and detached PKCS#7 signatures. I'm not sure if I can
use FIPS-standard tests for that.
The patches can also be found here:
https://git.kernel.org/pub/scm/linux/kernel/git/dhowells/linux-fs.git/log/?h=keys-pqc
David
Changes
=======
ver #14)
- public_key:
- Rename public_key::digest to public_key::m.
- X.509:
- Independently calculate the SHA256 hash for the blacklist check as
an ML-DSA-signed X.509 cert doesn't generate a digest we can use.
- Point public_key::m at the TBS data for ML-DSA.
- PKCS#7:
- Allocate a big enough digest buffer rather than reallocating in order
to store the authattrs/signedattrs instead.
- Merge the two patches that add direct signing support.
- ML-DSA:
- Use bool instead of u8.
- Remove references to SHAKE in Kconfig and mention OpenSSL requirements
there.
- Limit ML-DSA with an intermediate hash (e.g. signedAttrs) to using
SHA512 only.
- Don't select CRYPTO_LIB_SHA3 for CRYPTO_MLDSA.
- RSASSA-PSS:
- Allow use with SHA256 and SHA384.
- Fix calculation of emBits to be number of bits in the RSA modulus 'n'.
- Use strncmp() not memcmp() to avoid reading beyond end of string.
- Use correct destructor in rsassa_params_parse().
- Drop this algo for the moment.
- Drop the pefile_context::digest_free for now - it's only set to true and
is unrelated to public_key::digest_free.
ver #13)
- Allow a zero-length salt in RSASSA-PSS.
- Don't reject ECDSA/ECRDSA with SHA256 and SHA384 otherwise the FIPS
selftest panics when used.
- Add a FIPS test for RSASSA-PSS (from NIST's SigVerPSS_186-3.rsp).
- Add a FIPS test for ML-DSA (from NIST's FIPS204 JSON set).
ver #12)
- Rebased on Eric's libcrypto-next branch.
- Delete references to Dilithium (ML-DSA derived from this).
- Made sign-file supply CMS_NOATTR for ML-DSA if openssl >= v4.
- Made it possible to do ML-DSA over the data without signedAttrs.
- Made RSASSA-PSS info parser use strsep() and match_token().
- Cleaned the RSASSA-PSS param parsing.
- Added limitation on what hashes can be used with what algos.
- Moved __free()-marked variables to the point of setting.
ver #11)
- Rebased on Eric's libcrypto-next branch.
- Added RSASSA-PSS support patches.
ver #10)
- Replaced the Leancrypto ML-DSA implementation with Eric's.
- Fixed Eric's implementation to have MODULE_* info.
- Added a patch to drive Eric's ML-DSA implementation from crypto_sig.
- Removed SHAKE256 from the list of available module hash algorithms.
- Changed a some more ML_DSA to MLDSA in config symbols.
ver #9)
- ML-DSA changes:
- Separate output into four modules (1 common, 3 strength-specific).
- Solves Kconfig issue with needing to select at least one strength.
- Separate the strength-specific crypto-lib APIs.
- This is now generated by preprocessor-templating.
- Remove the multiplexor code.
- Multiplex the crypto-lib APIs by C type.
- Fix the PKCS#7/X.509 code to have the correct algo names.
ver #8)
- Moved the ML-DSA code to lib/crypto/mldsa/.
- Renamed some bits from ml-dsa to mldsa.
- Created a simplified API and placed that in include/crypto/mldsa.h.
- Made the testing code use the simplified API.
- Fixed a warning about implicitly casting between uint16_t and __le16.
ver #7)
- Rebased on Eric's tree as that now contains all the necessary SHA-3
infrastructure and drop the SHA-3 patches from here.
- Added a minimal patch to provide shake256 support for crypto_sig.
- Got rid of the memory allocation wrappers.
- Removed the ML-DSA keypair generation code and the signing code, leaving
only the signature verification code.
- Removed the secret key handling code.
- Removed the secret keys from the kunit tests and the signing testing.
- Removed some unused bits from the ML-DSA code.
- Downgraded the kdoc comments to ordinary comments, but keep the markup
for easier comparison to Leancrypto.
ver #6)
- Added a patch to make the jitterentropy RNG use lib/sha3.
- Added back the crypto/sha3_generic changes.
- Added ML-DSA implementation (still needs more cleanup).
- Added kunit test for ML-DSA.
- Modified PKCS#7 to accommodate ML-DSA.
- Modified PKCS#7 and X.509 to allow ML-DSA to be specified and used.
- Modified sign-file to not use CMS_NOATTR with ML-DSA.
- Allowed SHA3 and SHAKE* algorithms for module signing default.
- Allowed ML-DSA-{44,65,87} to be selected as the module signing default.
ver #5)
- Fix gen-hash-testvecs.py to correctly handle algo names that contain a
dash.
- Fix gen-hash-testvecs.py to not generate HMAC for SHA3-* or SHAKE* as
these don't currently have HMAC variants implemented.
- Fix algo names to be correct.
- Fix kunit module description as it now tests all SHA3 variants.
ver #4)
- Fix a couple of arm64 build problems.
- Doc fixes:
- Fix the description of the algorithm to be closer to the NIST spec's
terminology.
- Don't talk of finialising the context for XOFs.
- Don't say "Return: None".
- Declare the "Context" to be "Any context" and make no mention of the
fact that it might use the FPU.
- Change "initialise" to "initialize".
- Don't warn that the context is relatively large for stack use.
- Use size_t for size parameters/variables.
- Make the module_exit unconditional.
- Dropped the crypto/ dir-affecting patches for the moment.
ver #3)
- Renamed conflicting arm64 functions.
- Made a separate wrapper API for each algorithm in the family.
- Removed sha3_init(), sha3_reinit() and sha3_final().
- Removed sha3_ctx::digest_size.
- Renamed sha3_ctx::partial to sha3_ctx::absorb_offset.
- Refer to the output of SHAKE* as "output" not "digest".
- Moved the Iota transform into the one-round function.
- Made sha3_update() warn if called after sha3_squeeze().
- Simplified the module-load test to not do update after squeeze.
- Added Return: and Context: kdoc statements and expanded the kdoc
headers.
- Added an API description document.
- Overhauled the kunit tests.
- Only have one kunit test.
- Only call the general hash tester on one algo.
- Add separate simple cursory checks for the other algos.
- Add resqueezing tests.
- Add some NIST example tests.
- Changed crypto/sha3_generic to use this
- Added SHAKE128/256 to crypto/sha3_generic and crypto/testmgr
- Folded struct sha3_state into struct sha3_ctx.
ver #2)
- Simplify the endianness handling.
- Rename sha3_final() to sha3_squeeze() and don't clear the context at the
end as it's permitted to continue calling sha3_final() to extract
continuations of the digest (needed by ML-DSA).
- Don't reapply the end marker to the hash state in continuation
sha3_squeeze() unless sha3_update() gets called again (needed by
ML-DSA).
- Give sha3_squeeze() the amount of digest to produce as a parameter
rather than using ctx->digest_size and don't return the amount digested.
- Reimplement sha3_final() as a wrapper around sha3_squeeze() that
extracts ctx->digest_size amount of digest and then zeroes out the
context. The latter is necessary to avoid upsetting
hash-test-template.h.
- Provide a sha3_reinit() function to clear the state, but to leave the
parameters that indicate the hash properties unaffected, allowing for
reuse.
- Provide a sha3_set_digestsize() function to change the size of the
digest to be extracted by sha3_final(). sha3_squeeze() takes a
parameter for this instead.
- Don't pass the digest size as a parameter to shake128/256_init() but
rather default to 128/256 bits as per the function name.
- Provide a sha3_clear() function to zero out the context.
David Howells (5):
crypto: Add ML-DSA crypto_sig support
x509: Separately calculate sha256 for blacklist
pkcs7: Allow the signing algo to do whatever digestion it wants itself
pkcs7, x509: Add ML-DSA support
modsign: Enable ML-DSA module signing
Documentation/admin-guide/module-signing.rst | 16 +-
certs/Kconfig | 30 +++
certs/Makefile | 3 +
crypto/Kconfig | 9 +
crypto/Makefile | 2 +
crypto/asymmetric_keys/asymmetric_type.c | 4 +-
crypto/asymmetric_keys/pkcs7_parser.c | 28 ++-
crypto/asymmetric_keys/pkcs7_verify.c | 83 +++++---
crypto/asymmetric_keys/public_key.c | 13 +-
crypto/asymmetric_keys/signature.c | 3 +-
crypto/asymmetric_keys/x509_cert_parser.c | 27 ++-
crypto/asymmetric_keys/x509_parser.h | 2 +
crypto/asymmetric_keys/x509_public_key.c | 42 ++--
crypto/mldsa.c | 201 +++++++++++++++++++
include/crypto/public_key.h | 6 +-
include/linux/oid_registry.h | 5 +
scripts/sign-file.c | 34 +++-
security/integrity/digsig_asymmetric.c | 4 +-
18 files changed, 438 insertions(+), 74 deletions(-)
create mode 100644 crypto/mldsa.c
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v14 1/5] crypto: Add ML-DSA crypto_sig support
2026-01-21 22:36 [PATCH v14 0/5] x509, pkcs7, crypto: Add ML-DSA and RSASSA-PSS signing David Howells
@ 2026-01-21 22:36 ` David Howells
2026-01-21 22:36 ` [PATCH v14 2/5] x509: Separately calculate sha256 for blacklist David Howells
` (4 subsequent siblings)
5 siblings, 0 replies; 15+ messages in thread
From: David Howells @ 2026-01-21 22:36 UTC (permalink / raw)
To: Lukas Wunner, Ignat Korchagin
Cc: David Howells, Jarkko Sakkinen, Herbert Xu, Eric Biggers,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Jason A . Donenfeld, Ard Biesheuvel, Stephan Mueller,
linux-crypto, keyrings, linux-modules, linux-kernel
Add verify-only public key crypto support for ML-DSA so that the
X.509/PKCS#7 signature verification code, as used by module signing,
amongst other things, can make use of it through the common crypto_sig API.
Signed-off-by: David Howells <dhowells@redhat.com>
Reviewed-by: Jarkko Sakkinen <jarkko@kernel.org>
cc: Eric Biggers <ebiggers@kernel.org>
cc: Lukas Wunner <lukas@wunner.de>
cc: Ignat Korchagin <ignat@cloudflare.com>
cc: Stephan Mueller <smueller@chronox.de>
cc: Herbert Xu <herbert@gondor.apana.org.au>
cc: keyrings@vger.kernel.org
cc: linux-crypto@vger.kernel.org
---
crypto/Kconfig | 9 +++
crypto/Makefile | 2 +
crypto/mldsa.c | 201 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 212 insertions(+)
create mode 100644 crypto/mldsa.c
diff --git a/crypto/Kconfig b/crypto/Kconfig
index 12a87f7cf150..a210575fa5e0 100644
--- a/crypto/Kconfig
+++ b/crypto/Kconfig
@@ -344,6 +344,15 @@ config CRYPTO_ECRDSA
One of the Russian cryptographic standard algorithms (called GOST
algorithms). Only signature verification is implemented.
+config CRYPTO_MLDSA
+ tristate "ML-DSA (Module-Lattice-Based Digital Signature Algorithm)"
+ select CRYPTO_SIG
+ select CRYPTO_LIB_MLDSA
+ help
+ ML-DSA (Module-Lattice-Based Digital Signature Algorithm) (FIPS-204).
+
+ Only signature verification is implemented.
+
endmenu
menu "Block ciphers"
diff --git a/crypto/Makefile b/crypto/Makefile
index 23d3db7be425..267d5403045b 100644
--- a/crypto/Makefile
+++ b/crypto/Makefile
@@ -60,6 +60,8 @@ ecdsa_generic-y += ecdsa-p1363.o
ecdsa_generic-y += ecdsasignature.asn1.o
obj-$(CONFIG_CRYPTO_ECDSA) += ecdsa_generic.o
+obj-$(CONFIG_CRYPTO_MLDSA) += mldsa.o
+
crypto_acompress-y := acompress.o
crypto_acompress-y += scompress.o
obj-$(CONFIG_CRYPTO_ACOMP2) += crypto_acompress.o
diff --git a/crypto/mldsa.c b/crypto/mldsa.c
new file mode 100644
index 000000000000..d8de082cc67a
--- /dev/null
+++ b/crypto/mldsa.c
@@ -0,0 +1,201 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * crypto_sig wrapper around ML-DSA library.
+ */
+#include <linux/init.h>
+#include <linux/module.h>
+#include <crypto/internal/sig.h>
+#include <crypto/mldsa.h>
+
+struct crypto_mldsa_ctx {
+ u8 pk[MAX(MAX(MLDSA44_PUBLIC_KEY_SIZE,
+ MLDSA65_PUBLIC_KEY_SIZE),
+ MLDSA87_PUBLIC_KEY_SIZE)];
+ unsigned int pk_len;
+ enum mldsa_alg strength;
+ bool key_set;
+};
+
+static int crypto_mldsa_sign(struct crypto_sig *tfm,
+ const void *msg, unsigned int msg_len,
+ void *sig, unsigned int sig_len)
+{
+ return -EOPNOTSUPP;
+}
+
+static int crypto_mldsa_verify(struct crypto_sig *tfm,
+ const void *sig, unsigned int sig_len,
+ const void *msg, unsigned int msg_len)
+{
+ const struct crypto_mldsa_ctx *ctx = crypto_sig_ctx(tfm);
+
+ if (unlikely(!ctx->key_set))
+ return -EINVAL;
+
+ return mldsa_verify(ctx->strength, sig, sig_len, msg, msg_len,
+ ctx->pk, ctx->pk_len);
+}
+
+static unsigned int crypto_mldsa_key_size(struct crypto_sig *tfm)
+{
+ struct crypto_mldsa_ctx *ctx = crypto_sig_ctx(tfm);
+
+ switch (ctx->strength) {
+ case MLDSA44:
+ return MLDSA44_PUBLIC_KEY_SIZE;
+ case MLDSA65:
+ return MLDSA65_PUBLIC_KEY_SIZE;
+ case MLDSA87:
+ return MLDSA87_PUBLIC_KEY_SIZE;
+ default:
+ WARN_ON_ONCE(1);
+ return 0;
+ }
+}
+
+static int crypto_mldsa_set_pub_key(struct crypto_sig *tfm,
+ const void *key, unsigned int keylen)
+{
+ struct crypto_mldsa_ctx *ctx = crypto_sig_ctx(tfm);
+ unsigned int expected_len = crypto_mldsa_key_size(tfm);
+
+ if (keylen != expected_len)
+ return -EINVAL;
+
+ ctx->pk_len = keylen;
+ memcpy(ctx->pk, key, keylen);
+ ctx->key_set = true;
+ return 0;
+}
+
+static int crypto_mldsa_set_priv_key(struct crypto_sig *tfm,
+ const void *key, unsigned int keylen)
+{
+ return -EOPNOTSUPP;
+}
+
+static unsigned int crypto_mldsa_max_size(struct crypto_sig *tfm)
+{
+ struct crypto_mldsa_ctx *ctx = crypto_sig_ctx(tfm);
+
+ switch (ctx->strength) {
+ case MLDSA44:
+ return MLDSA44_SIGNATURE_SIZE;
+ case MLDSA65:
+ return MLDSA65_SIGNATURE_SIZE;
+ case MLDSA87:
+ return MLDSA87_SIGNATURE_SIZE;
+ default:
+ WARN_ON_ONCE(1);
+ return 0;
+ }
+}
+
+static int crypto_mldsa44_alg_init(struct crypto_sig *tfm)
+{
+ struct crypto_mldsa_ctx *ctx = crypto_sig_ctx(tfm);
+
+ ctx->strength = MLDSA44;
+ ctx->key_set = false;
+ return 0;
+}
+
+static int crypto_mldsa65_alg_init(struct crypto_sig *tfm)
+{
+ struct crypto_mldsa_ctx *ctx = crypto_sig_ctx(tfm);
+
+ ctx->strength = MLDSA65;
+ ctx->key_set = false;
+ return 0;
+}
+
+static int crypto_mldsa87_alg_init(struct crypto_sig *tfm)
+{
+ struct crypto_mldsa_ctx *ctx = crypto_sig_ctx(tfm);
+
+ ctx->strength = MLDSA87;
+ ctx->key_set = false;
+ return 0;
+}
+
+static void crypto_mldsa_alg_exit(struct crypto_sig *tfm)
+{
+}
+
+static struct sig_alg crypto_mldsa_algs[] = {
+ {
+ .sign = crypto_mldsa_sign,
+ .verify = crypto_mldsa_verify,
+ .set_pub_key = crypto_mldsa_set_pub_key,
+ .set_priv_key = crypto_mldsa_set_priv_key,
+ .key_size = crypto_mldsa_key_size,
+ .max_size = crypto_mldsa_max_size,
+ .init = crypto_mldsa44_alg_init,
+ .exit = crypto_mldsa_alg_exit,
+ .base.cra_name = "mldsa44",
+ .base.cra_driver_name = "mldsa44-lib",
+ .base.cra_ctxsize = sizeof(struct crypto_mldsa_ctx),
+ .base.cra_module = THIS_MODULE,
+ .base.cra_priority = 5000,
+ }, {
+ .sign = crypto_mldsa_sign,
+ .verify = crypto_mldsa_verify,
+ .set_pub_key = crypto_mldsa_set_pub_key,
+ .set_priv_key = crypto_mldsa_set_priv_key,
+ .key_size = crypto_mldsa_key_size,
+ .max_size = crypto_mldsa_max_size,
+ .init = crypto_mldsa65_alg_init,
+ .exit = crypto_mldsa_alg_exit,
+ .base.cra_name = "mldsa65",
+ .base.cra_driver_name = "mldsa65-lib",
+ .base.cra_ctxsize = sizeof(struct crypto_mldsa_ctx),
+ .base.cra_module = THIS_MODULE,
+ .base.cra_priority = 5000,
+ }, {
+ .sign = crypto_mldsa_sign,
+ .verify = crypto_mldsa_verify,
+ .set_pub_key = crypto_mldsa_set_pub_key,
+ .set_priv_key = crypto_mldsa_set_priv_key,
+ .key_size = crypto_mldsa_key_size,
+ .max_size = crypto_mldsa_max_size,
+ .init = crypto_mldsa87_alg_init,
+ .exit = crypto_mldsa_alg_exit,
+ .base.cra_name = "mldsa87",
+ .base.cra_driver_name = "mldsa87-lib",
+ .base.cra_ctxsize = sizeof(struct crypto_mldsa_ctx),
+ .base.cra_module = THIS_MODULE,
+ .base.cra_priority = 5000,
+ },
+};
+
+static int __init mldsa_init(void)
+{
+ int ret, i;
+
+ for (i = 0; i < ARRAY_SIZE(crypto_mldsa_algs); i++) {
+ ret = crypto_register_sig(&crypto_mldsa_algs[i]);
+ if (ret < 0)
+ goto error;
+ }
+ return 0;
+
+error:
+ pr_err("Failed to register (%d)\n", ret);
+ for (i--; i >= 0; i--)
+ crypto_unregister_sig(&crypto_mldsa_algs[i]);
+ return ret;
+}
+module_init(mldsa_init);
+
+static void mldsa_exit(void)
+{
+ for (int i = 0; i < ARRAY_SIZE(crypto_mldsa_algs); i++)
+ crypto_unregister_sig(&crypto_mldsa_algs[i]);
+}
+module_exit(mldsa_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Crypto API support for ML-DSA signature verification");
+MODULE_ALIAS_CRYPTO("mldsa44");
+MODULE_ALIAS_CRYPTO("mldsa65");
+MODULE_ALIAS_CRYPTO("mldsa87");
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v14 2/5] x509: Separately calculate sha256 for blacklist
2026-01-21 22:36 [PATCH v14 0/5] x509, pkcs7, crypto: Add ML-DSA and RSASSA-PSS signing David Howells
2026-01-21 22:36 ` [PATCH v14 1/5] crypto: Add ML-DSA crypto_sig support David Howells
@ 2026-01-21 22:36 ` David Howells
2026-01-25 14:34 ` Jarkko Sakkinen
2026-01-21 22:36 ` [PATCH v14 3/5] pkcs7: Allow the signing algo to do whatever digestion it wants itself David Howells
` (3 subsequent siblings)
5 siblings, 1 reply; 15+ messages in thread
From: David Howells @ 2026-01-21 22:36 UTC (permalink / raw)
To: Lukas Wunner, Ignat Korchagin
Cc: David Howells, Jarkko Sakkinen, Herbert Xu, Eric Biggers,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Jason A . Donenfeld, Ard Biesheuvel, Stephan Mueller,
linux-crypto, keyrings, linux-modules, linux-kernel
Calculate the SHA256 hash for blacklisting purposes independently of the
signature hash (which may be something other than SHA256).
This is necessary because when ML-DSA is used, no digest is calculated.
Note that this represents a change of behaviour in that the hash used for
the blacklist check would previously have been whatever digest was used
for, say, RSA-based signatures. It may be that this is inadvisable.
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/x509_parser.h | 2 ++
crypto/asymmetric_keys/x509_public_key.c | 23 +++++++++++++----------
2 files changed, 15 insertions(+), 10 deletions(-)
diff --git a/crypto/asymmetric_keys/x509_parser.h b/crypto/asymmetric_keys/x509_parser.h
index 0688c222806b..b7aeebdddb36 100644
--- a/crypto/asymmetric_keys/x509_parser.h
+++ b/crypto/asymmetric_keys/x509_parser.h
@@ -9,12 +9,14 @@
#include <linux/time.h>
#include <crypto/public_key.h>
#include <keys/asymmetric-type.h>
+#include <crypto/sha2.h>
struct x509_certificate {
struct x509_certificate *next;
struct x509_certificate *signer; /* Certificate that signed this one */
struct public_key *pub; /* Public key details */
struct public_key_signature *sig; /* Signature parameters */
+ u8 sha256[SHA256_DIGEST_SIZE]; /* Hash for blacklist purposes */
char *issuer; /* Name of certificate issuer */
char *subject; /* Name of certificate subject */
struct asymmetric_key_id *id; /* Issuer + Serial number */
diff --git a/crypto/asymmetric_keys/x509_public_key.c b/crypto/asymmetric_keys/x509_public_key.c
index 12e3341e806b..6d002e3b20c5 100644
--- a/crypto/asymmetric_keys/x509_public_key.c
+++ b/crypto/asymmetric_keys/x509_public_key.c
@@ -31,6 +31,19 @@ int x509_get_sig_params(struct x509_certificate *cert)
pr_devel("==>%s()\n", __func__);
+ /* Calculate a SHA256 hash of the TBS and check it against the
+ * blacklist.
+ */
+ sha256(cert->tbs, cert->tbs_size, cert->sha256);
+ ret = is_hash_blacklisted(cert->sha256, sizeof(cert->sha256),
+ BLACKLIST_HASH_X509_TBS);
+ if (ret == -EKEYREJECTED) {
+ pr_err("Cert %*phN is blacklisted\n",
+ (int)sizeof(cert->sha256), cert->sha256);
+ cert->blacklisted = true;
+ ret = 0;
+ }
+
sig->s = kmemdup(cert->raw_sig, cert->raw_sig_size, GFP_KERNEL);
if (!sig->s)
return -ENOMEM;
@@ -65,19 +78,9 @@ int x509_get_sig_params(struct x509_certificate *cert)
ret = crypto_shash_digest(desc, cert->tbs, cert->tbs_size,
sig->digest);
-
if (ret < 0)
goto error_2;
- ret = is_hash_blacklisted(sig->digest, sig->digest_size,
- BLACKLIST_HASH_X509_TBS);
- if (ret == -EKEYREJECTED) {
- pr_err("Cert %*phN is blacklisted\n",
- sig->digest_size, sig->digest);
- cert->blacklisted = true;
- ret = 0;
- }
-
error_2:
kfree(desc);
error:
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v14 3/5] pkcs7: Allow the signing algo to do whatever digestion it wants itself
2026-01-21 22:36 [PATCH v14 0/5] x509, pkcs7, crypto: Add ML-DSA and RSASSA-PSS signing David Howells
2026-01-21 22:36 ` [PATCH v14 1/5] crypto: Add ML-DSA crypto_sig support David Howells
2026-01-21 22:36 ` [PATCH v14 2/5] x509: Separately calculate sha256 for blacklist David Howells
@ 2026-01-21 22:36 ` David Howells
2026-01-25 14:38 ` Jarkko Sakkinen
2026-01-21 22:36 ` [PATCH v14 4/5] pkcs7, x509: Add ML-DSA support David Howells
` (2 subsequent siblings)
5 siblings, 1 reply; 15+ messages in thread
From: David Howells @ 2026-01-21 22:36 UTC (permalink / raw)
To: Lukas Wunner, Ignat Korchagin
Cc: David Howells, Jarkko Sakkinen, Herbert Xu, Eric Biggers,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Jason A . Donenfeld, Ard Biesheuvel, Stephan Mueller,
linux-crypto, keyrings, linux-modules, linux-kernel
Allow the data to be verified in a PKCS#7 or CMS message to be passed
directly to an asymmetric cipher algorithm (e.g. ML-DSA) if it wants to do
whatever passes for hashing/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.
The 'data to be verified' may be the content of the PKCS#7 message or it
will be the authenticatedAttributes (signedAttrs if CMS), modified, if
those are present.
This is done by:
(1) Rename ->digest and ->digest_len to ->m and ->m_size to represent the
input to the signature verification algorithm, reflecting that
->digest may no longer actually *be* a digest.
(2) Make ->m and ->m_size 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.
(3) Add a flag, ->algo_takes_data, to indicate that the verification
algorithm wants to access the data to be verified directly rather than
having it digested first.
(4) If the PKCS#7 message has authenticatedAttributes (or CMS signedAtts),
then the digest contained therein will be validated as now, and the
modified attrs blob will either be digested or assigned to ->m as
appropriate.
(5) For ML-DSA, point ->m to the TBSCertificate instead of digesting it
and using the digest.
Note that whilst ML-DSA does allow for an "external mu", CMS doesn't yet
have that standardised.
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/asymmetric_type.c | 4 +-
crypto/asymmetric_keys/pkcs7_parser.c | 4 +-
crypto/asymmetric_keys/pkcs7_verify.c | 79 ++++++++++++++++--------
crypto/asymmetric_keys/public_key.c | 3 +-
crypto/asymmetric_keys/signature.c | 3 +-
crypto/asymmetric_keys/x509_public_key.c | 19 ++++--
include/crypto/public_key.h | 6 +-
security/integrity/digsig_asymmetric.c | 4 +-
8 files changed, 79 insertions(+), 43 deletions(-)
diff --git a/crypto/asymmetric_keys/asymmetric_type.c b/crypto/asymmetric_keys/asymmetric_type.c
index 348966ea2175..2326743310b1 100644
--- a/crypto/asymmetric_keys/asymmetric_type.c
+++ b/crypto/asymmetric_keys/asymmetric_type.c
@@ -593,10 +593,10 @@ static int asymmetric_key_verify_signature(struct kernel_pkey_params *params,
{
struct public_key_signature sig = {
.s_size = params->in2_len,
- .digest_size = params->in_len,
+ .m_size = params->in_len,
.encoding = params->encoding,
.hash_algo = params->hash_algo,
- .digest = (void *)in,
+ .m = (void *)in,
.s = (void *)in2,
};
diff --git a/crypto/asymmetric_keys/pkcs7_parser.c b/crypto/asymmetric_keys/pkcs7_parser.c
index 423d13c47545..3cdbab3b9f50 100644
--- a/crypto/asymmetric_keys/pkcs7_parser.c
+++ b/crypto/asymmetric_keys/pkcs7_parser.c
@@ -599,8 +599,8 @@ int pkcs7_sig_note_set_of_authattrs(void *context, size_t hdrlen,
}
/* We need to switch the 'CONT 0' to a 'SET OF' when we digest */
- sinfo->authattrs = value - (hdrlen - 1);
- sinfo->authattrs_len = vlen + (hdrlen - 1);
+ sinfo->authattrs = value - hdrlen;
+ sinfo->authattrs_len = vlen + hdrlen;
return 0;
}
diff --git a/crypto/asymmetric_keys/pkcs7_verify.c b/crypto/asymmetric_keys/pkcs7_verify.c
index 6d6475e3a9bf..a5b2ed4d53fd 100644
--- a/crypto/asymmetric_keys/pkcs7_verify.c
+++ b/crypto/asymmetric_keys/pkcs7_verify.c
@@ -30,8 +30,18 @@ static int pkcs7_digest(struct pkcs7_message *pkcs7,
kenter(",%u,%s", sinfo->index, sinfo->sig->hash_algo);
+ if (!sinfo->authattrs && sig->algo_takes_data) {
+ /* There's no intermediate digest and the signature algo
+ * doesn't want the data prehashing.
+ */
+ sig->m = (void *)pkcs7->data;
+ sig->m_size = pkcs7->data_len;
+ sig->m_free = false;
+ return 0;
+ }
+
/* The digest was calculated already. */
- if (sig->digest)
+ if (sig->m)
return 0;
if (!sinfo->sig->hash_algo)
@@ -45,12 +55,13 @@ static int pkcs7_digest(struct pkcs7_message *pkcs7,
return (PTR_ERR(tfm) == -ENOENT) ? -ENOPKG : PTR_ERR(tfm);
desc_size = crypto_shash_descsize(tfm) + sizeof(*desc);
- sig->digest_size = crypto_shash_digestsize(tfm);
+ sig->m_size = crypto_shash_digestsize(tfm);
ret = -ENOMEM;
- sig->digest = kmalloc(sig->digest_size, GFP_KERNEL);
- if (!sig->digest)
+ sig->m = kmalloc(umax(sinfo->authattrs_len, sig->m_size), GFP_KERNEL);
+ if (!sig->m)
goto error_no_desc;
+ sig->m_free = true;
desc = kzalloc(desc_size, GFP_KERNEL);
if (!desc)
@@ -59,33 +70,30 @@ static int pkcs7_digest(struct pkcs7_message *pkcs7,
desc->tfm = tfm;
/* Digest the message [RFC2315 9.3] */
- ret = crypto_shash_digest(desc, pkcs7->data, pkcs7->data_len,
- sig->digest);
+ ret = crypto_shash_digest(desc, pkcs7->data, pkcs7->data_len, sig->m);
if (ret < 0)
goto error;
- pr_devel("MsgDigest = [%*ph]\n", 8, sig->digest);
+ pr_devel("MsgDigest = [%*ph]\n", 8, sig->m);
/* However, if there are authenticated attributes, there must be a
* message digest attribute amongst them which corresponds to the
* digest we just calculated.
*/
if (sinfo->authattrs) {
- u8 tag;
-
if (!sinfo->msgdigest) {
pr_warn("Sig %u: No messageDigest\n", sinfo->index);
ret = -EKEYREJECTED;
goto error;
}
- if (sinfo->msgdigest_len != sig->digest_size) {
+ if (sinfo->msgdigest_len != sig->m_size) {
pr_warn("Sig %u: Invalid digest size (%u)\n",
sinfo->index, sinfo->msgdigest_len);
ret = -EBADMSG;
goto error;
}
- if (memcmp(sig->digest, sinfo->msgdigest,
+ if (memcmp(sig->m, sinfo->msgdigest,
sinfo->msgdigest_len) != 0) {
pr_warn("Sig %u: Message digest doesn't match\n",
sinfo->index);
@@ -97,21 +105,33 @@ static int pkcs7_digest(struct pkcs7_message *pkcs7,
* as the contents of the digest instead. Note that we need to
* convert the attributes from a CONT.0 into a SET before we
* hash it.
+ *
+ * However, for certain algorithms, such as ML-DSA, the digest
+ * is integrated into the signing algorithm. In such a case,
+ * we copy the authattrs, modifying the tag type, and set that
+ * as the digest.
*/
- memset(sig->digest, 0, sig->digest_size);
-
- ret = crypto_shash_init(desc);
- if (ret < 0)
- goto error;
- tag = ASN1_CONS_BIT | ASN1_SET;
- ret = crypto_shash_update(desc, &tag, 1);
- if (ret < 0)
- goto error;
- ret = crypto_shash_finup(desc, sinfo->authattrs,
- sinfo->authattrs_len, sig->digest);
- if (ret < 0)
- goto error;
- pr_devel("AADigest = [%*ph]\n", 8, sig->digest);
+ if (sig->algo_takes_data) {
+ sig->m_size = sinfo->authattrs_len;
+ memcpy(sig->m, sinfo->authattrs, sinfo->authattrs_len);
+ sig->m[0] = ASN1_CONS_BIT | ASN1_SET;
+ ret = 0;
+ } else {
+ u8 tag = ASN1_CONS_BIT | ASN1_SET;
+
+ ret = crypto_shash_init(desc);
+ if (ret < 0)
+ goto error;
+ ret = crypto_shash_update(desc, &tag, 1);
+ if (ret < 0)
+ goto error;
+ ret = crypto_shash_finup(desc, sinfo->authattrs + 1,
+ sinfo->authattrs_len - 1,
+ sig->m);
+ if (ret < 0)
+ goto error;
+ }
+ pr_devel("AADigest = [%*ph]\n", 8, sig->m);
}
error:
@@ -137,9 +157,14 @@ 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->m_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;
+ *buf = sinfo->sig->m;
+ *len = sinfo->sig->m_size;
i = match_string(hash_algo_name, HASH_ALGO__LAST,
sinfo->sig->hash_algo);
diff --git a/crypto/asymmetric_keys/public_key.c b/crypto/asymmetric_keys/public_key.c
index e5b177c8e842..a46356e0c08b 100644
--- a/crypto/asymmetric_keys/public_key.c
+++ b/crypto/asymmetric_keys/public_key.c
@@ -425,8 +425,7 @@ int public_key_verify_signature(const struct public_key *pkey,
if (ret)
goto error_free_key;
- ret = crypto_sig_verify(tfm, sig->s, sig->s_size,
- sig->digest, sig->digest_size);
+ ret = crypto_sig_verify(tfm, sig->s, sig->s_size, sig->m, sig->m_size);
error_free_key:
kfree_sensitive(key);
diff --git a/crypto/asymmetric_keys/signature.c b/crypto/asymmetric_keys/signature.c
index 041d04b5c953..a5ac7a53b670 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->m_free)
+ kfree(sig->m);
kfree(sig);
}
}
diff --git a/crypto/asymmetric_keys/x509_public_key.c b/crypto/asymmetric_keys/x509_public_key.c
index 6d002e3b20c5..27b4fea37845 100644
--- a/crypto/asymmetric_keys/x509_public_key.c
+++ b/crypto/asymmetric_keys/x509_public_key.c
@@ -50,6 +50,14 @@ int x509_get_sig_params(struct x509_certificate *cert)
sig->s_size = cert->raw_sig_size;
+ if (sig->algo_takes_data) {
+ /* The signature algorithm does whatever passes for hashing. */
+ sig->m = (u8 *)cert->tbs;
+ sig->m_size = cert->tbs_size;
+ sig->m_free = false;
+ goto out;
+ }
+
/* Allocate the hashing algorithm we're going to need and find out how
* big the hash operational data will be.
*/
@@ -63,12 +71,13 @@ int x509_get_sig_params(struct x509_certificate *cert)
}
desc_size = crypto_shash_descsize(tfm) + sizeof(*desc);
- sig->digest_size = crypto_shash_digestsize(tfm);
+ sig->m_size = crypto_shash_digestsize(tfm);
ret = -ENOMEM;
- sig->digest = kmalloc(sig->digest_size, GFP_KERNEL);
- if (!sig->digest)
+ sig->m = kmalloc(sig->m_size, GFP_KERNEL);
+ if (!sig->m)
goto error;
+ sig->m_free = true;
desc = kzalloc(desc_size, GFP_KERNEL);
if (!desc)
@@ -76,8 +85,7 @@ int x509_get_sig_params(struct x509_certificate *cert)
desc->tfm = tfm;
- ret = crypto_shash_digest(desc, cert->tbs, cert->tbs_size,
- sig->digest);
+ ret = crypto_shash_digest(desc, cert->tbs, cert->tbs_size, sig->m);
if (ret < 0)
goto error_2;
@@ -85,6 +93,7 @@ int x509_get_sig_params(struct x509_certificate *cert)
kfree(desc);
error:
crypto_free_shash(tfm);
+out:
pr_devel("<==%s() = %d\n", __func__, ret);
return ret;
}
diff --git a/include/crypto/public_key.h b/include/crypto/public_key.h
index 81098e00c08f..4c5199b20338 100644
--- a/include/crypto/public_key.h
+++ b/include/crypto/public_key.h
@@ -43,9 +43,11 @@ extern void public_key_free(struct public_key *key);
struct public_key_signature {
struct asymmetric_key_id *auth_ids[3];
u8 *s; /* Signature */
- u8 *digest;
+ u8 *m; /* Message data to pass to verifier */
u32 s_size; /* Number of bytes in signature */
- u32 digest_size; /* Number of bytes in digest */
+ u32 m_size; /* Number of bytes in ->m */
+ bool m_free; /* T if ->m needs freeing */
+ bool algo_takes_data; /* T if public key algo operates on data, not a hash */
const char *pkey_algo;
const char *hash_algo;
const char *encoding;
diff --git a/security/integrity/digsig_asymmetric.c b/security/integrity/digsig_asymmetric.c
index 457c0a396caf..87be85f477d1 100644
--- a/security/integrity/digsig_asymmetric.c
+++ b/security/integrity/digsig_asymmetric.c
@@ -121,8 +121,8 @@ int asymmetric_verify(struct key *keyring, const char *sig,
goto out;
}
- pks.digest = (u8 *)data;
- pks.digest_size = datalen;
+ pks.m = (u8 *)data;
+ pks.m_size = datalen;
pks.s = hdr->sig;
pks.s_size = siglen;
ret = verify_signature(key, &pks);
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v14 4/5] pkcs7, x509: Add ML-DSA support
2026-01-21 22:36 [PATCH v14 0/5] x509, pkcs7, crypto: Add ML-DSA and RSASSA-PSS signing David Howells
` (2 preceding siblings ...)
2026-01-21 22:36 ` [PATCH v14 3/5] pkcs7: Allow the signing algo to do whatever digestion it wants itself David Howells
@ 2026-01-21 22:36 ` David Howells
2026-01-25 14:42 ` Jarkko Sakkinen
2026-01-21 22:36 ` [PATCH v14 5/5] modsign: Enable ML-DSA module signing David Howells
2026-01-23 11:13 ` [PATCH v14 0/5] x509, pkcs7, crypto: Add ML-DSA and RSASSA-PSS signing David Howells
5 siblings, 1 reply; 15+ messages in thread
From: David Howells @ 2026-01-21 22:36 UTC (permalink / raw)
To: Lukas Wunner, Ignat Korchagin
Cc: David Howells, Jarkko Sakkinen, Herbert Xu, Eric Biggers,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Jason A . Donenfeld, Ard Biesheuvel, Stephan Mueller,
linux-crypto, keyrings, linux-modules, linux-kernel
Add support for ML-DSA keys and signatures to the CMS/PKCS#7 and X.509
implementations. ML-DSA-44, -65 and -87 are all supported. For X.509
certificates, the TBSCertificate is required to be signed directly; for CMS,
direct signing of the data is preferred, though use of SHA512 (and only that)
as an intermediate hash of the content is permitted with signedAttrs.
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/pkcs7_parser.c | 24 +++++++++++++++++++-
crypto/asymmetric_keys/public_key.c | 10 +++++++++
crypto/asymmetric_keys/x509_cert_parser.c | 27 ++++++++++++++++++++++-
include/linux/oid_registry.h | 5 +++++
4 files changed, 64 insertions(+), 2 deletions(-)
diff --git a/crypto/asymmetric_keys/pkcs7_parser.c b/crypto/asymmetric_keys/pkcs7_parser.c
index 3cdbab3b9f50..594a8f1d9dfb 100644
--- a/crypto/asymmetric_keys/pkcs7_parser.c
+++ b/crypto/asymmetric_keys/pkcs7_parser.c
@@ -95,11 +95,18 @@ static int pkcs7_check_authattrs(struct pkcs7_message *msg)
if (sinfo->authattrs) {
want = true;
msg->have_authattrs = true;
+ } else if (sinfo->sig->algo_takes_data) {
+ sinfo->sig->hash_algo = "none";
}
- for (sinfo = sinfo->next; sinfo; sinfo = sinfo->next)
+ for (sinfo = sinfo->next; sinfo; sinfo = sinfo->next) {
if (!!sinfo->authattrs != want)
goto inconsistent;
+
+ if (!sinfo->authattrs &&
+ sinfo->sig->algo_takes_data)
+ sinfo->sig->hash_algo = "none";
+ }
return 0;
inconsistent:
@@ -297,6 +304,21 @@ int pkcs7_sig_note_pkey_algo(void *context, size_t hdrlen,
ctx->sinfo->sig->pkey_algo = "ecrdsa";
ctx->sinfo->sig->encoding = "raw";
break;
+ case OID_id_ml_dsa_44:
+ ctx->sinfo->sig->pkey_algo = "mldsa44";
+ ctx->sinfo->sig->encoding = "raw";
+ ctx->sinfo->sig->algo_takes_data = true;
+ break;
+ case OID_id_ml_dsa_65:
+ ctx->sinfo->sig->pkey_algo = "mldsa65";
+ ctx->sinfo->sig->encoding = "raw";
+ ctx->sinfo->sig->algo_takes_data = true;
+ break;
+ case OID_id_ml_dsa_87:
+ ctx->sinfo->sig->pkey_algo = "mldsa87";
+ ctx->sinfo->sig->encoding = "raw";
+ ctx->sinfo->sig->algo_takes_data = true;
+ break;
default:
printk("Unsupported pkey algo: %u\n", ctx->last_oid);
return -ENOPKG;
diff --git a/crypto/asymmetric_keys/public_key.c b/crypto/asymmetric_keys/public_key.c
index a46356e0c08b..09a0b83d5d77 100644
--- a/crypto/asymmetric_keys/public_key.c
+++ b/crypto/asymmetric_keys/public_key.c
@@ -142,6 +142,16 @@ software_key_determine_akcipher(const struct public_key *pkey,
if (strcmp(hash_algo, "streebog256") != 0 &&
strcmp(hash_algo, "streebog512") != 0)
return -EINVAL;
+ } else if (strcmp(pkey->pkey_algo, "mldsa44") == 0 ||
+ strcmp(pkey->pkey_algo, "mldsa65") == 0 ||
+ strcmp(pkey->pkey_algo, "mldsa87") == 0) {
+ if (strcmp(encoding, "raw") != 0)
+ return -EINVAL;
+ if (!hash_algo)
+ return -EINVAL;
+ if (strcmp(hash_algo, "none") != 0 &&
+ strcmp(hash_algo, "sha512") != 0)
+ return -EINVAL;
} else {
/* Unknown public key algorithm */
return -ENOPKG;
diff --git a/crypto/asymmetric_keys/x509_cert_parser.c b/crypto/asymmetric_keys/x509_cert_parser.c
index b37cae914987..2fe094f5caf3 100644
--- a/crypto/asymmetric_keys/x509_cert_parser.c
+++ b/crypto/asymmetric_keys/x509_cert_parser.c
@@ -257,6 +257,15 @@ int x509_note_sig_algo(void *context, size_t hdrlen, unsigned char tag,
case OID_gost2012Signature512:
ctx->cert->sig->hash_algo = "streebog512";
goto ecrdsa;
+ case OID_id_ml_dsa_44:
+ ctx->cert->sig->pkey_algo = "mldsa44";
+ goto ml_dsa;
+ case OID_id_ml_dsa_65:
+ ctx->cert->sig->pkey_algo = "mldsa65";
+ goto ml_dsa;
+ case OID_id_ml_dsa_87:
+ ctx->cert->sig->pkey_algo = "mldsa87";
+ goto ml_dsa;
}
rsa_pkcs1:
@@ -274,6 +283,12 @@ int x509_note_sig_algo(void *context, size_t hdrlen, unsigned char tag,
ctx->cert->sig->encoding = "x962";
ctx->sig_algo = ctx->last_oid;
return 0;
+ml_dsa:
+ ctx->cert->sig->algo_takes_data = true;
+ ctx->cert->sig->hash_algo = "none";
+ ctx->cert->sig->encoding = "raw";
+ ctx->sig_algo = ctx->last_oid;
+ return 0;
}
/*
@@ -300,7 +315,8 @@ int x509_note_signature(void *context, size_t hdrlen,
if (strcmp(ctx->cert->sig->pkey_algo, "rsa") == 0 ||
strcmp(ctx->cert->sig->pkey_algo, "ecrdsa") == 0 ||
- strcmp(ctx->cert->sig->pkey_algo, "ecdsa") == 0) {
+ strcmp(ctx->cert->sig->pkey_algo, "ecdsa") == 0 ||
+ strncmp(ctx->cert->sig->pkey_algo, "mldsa", 5) == 0) {
/* Discard the BIT STRING metadata */
if (vlen < 1 || *(const u8 *)value != 0)
return -EBADMSG;
@@ -524,6 +540,15 @@ int x509_extract_key_data(void *context, size_t hdrlen,
return -ENOPKG;
}
break;
+ case OID_id_ml_dsa_44:
+ ctx->cert->pub->pkey_algo = "mldsa44";
+ break;
+ case OID_id_ml_dsa_65:
+ ctx->cert->pub->pkey_algo = "mldsa65";
+ break;
+ case OID_id_ml_dsa_87:
+ ctx->cert->pub->pkey_algo = "mldsa87";
+ break;
default:
return -ENOPKG;
}
diff --git a/include/linux/oid_registry.h b/include/linux/oid_registry.h
index 6de479ebbe5d..ebce402854de 100644
--- a/include/linux/oid_registry.h
+++ b/include/linux/oid_registry.h
@@ -145,6 +145,11 @@ enum OID {
OID_id_rsassa_pkcs1_v1_5_with_sha3_384, /* 2.16.840.1.101.3.4.3.15 */
OID_id_rsassa_pkcs1_v1_5_with_sha3_512, /* 2.16.840.1.101.3.4.3.16 */
+ /* NIST FIPS-204 ML-DSA */
+ OID_id_ml_dsa_44, /* 2.16.840.1.101.3.4.3.17 */
+ OID_id_ml_dsa_65, /* 2.16.840.1.101.3.4.3.18 */
+ OID_id_ml_dsa_87, /* 2.16.840.1.101.3.4.3.19 */
+
OID__NR
};
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v14 5/5] modsign: Enable ML-DSA module signing
2026-01-21 22:36 [PATCH v14 0/5] x509, pkcs7, crypto: Add ML-DSA and RSASSA-PSS signing David Howells
` (3 preceding siblings ...)
2026-01-21 22:36 ` [PATCH v14 4/5] pkcs7, x509: Add ML-DSA support David Howells
@ 2026-01-21 22:36 ` David Howells
2026-01-25 14:44 ` Jarkko Sakkinen
2026-01-23 11:13 ` [PATCH v14 0/5] x509, pkcs7, crypto: Add ML-DSA and RSASSA-PSS signing David Howells
5 siblings, 1 reply; 15+ messages in thread
From: David Howells @ 2026-01-21 22:36 UTC (permalink / raw)
To: Lukas Wunner, Ignat Korchagin
Cc: David Howells, Jarkko Sakkinen, Herbert Xu, Eric Biggers,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Jason A . Donenfeld, Ard Biesheuvel, Stephan Mueller,
linux-crypto, keyrings, linux-modules, linux-kernel
Allow ML-DSA module signing to be enabled.
Note that OpenSSL's CMS_*() function suite does not, as of OpenSSL-3.6,
support the use of CMS_NOATTR with ML-DSA, so the prohibition against using
signedAttrs with module signing has to be removed. The selected digest
then applies only to the algorithm used to calculate the digest stored in
the messageDigest attribute. The OpenSSL development branch has patches
applied that fix this[1], but it appears that that will only be available
in OpenSSL-4.
[1] https://github.com/openssl/openssl/pull/28923
sign-file won't set CMS_NOATTR if openssl is earlier than v4, resulting in
the use of signed attributes.
The ML-DSA algorithm takes the raw data to be signed without regard to what
digest algorithm is specified in the CMS message. The CMS specified digest
algorithm is ignored unless signedAttrs are used; in such a case, only
SHA512 is permitted.
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Eric Biggers <ebiggers@kernel.org>
cc: Lukas Wunner <lukas@wunner.de>
cc: Ignat Korchagin <ignat@cloudflare.com>
cc: Stephan Mueller <smueller@chronox.de>
cc: Herbert Xu <herbert@gondor.apana.org.au>
cc: keyrings@vger.kernel.org
cc: linux-crypto@vger.kernel.org
---
Documentation/admin-guide/module-signing.rst | 16 +++++----
certs/Kconfig | 30 +++++++++++++++++
certs/Makefile | 3 ++
crypto/asymmetric_keys/pkcs7_verify.c | 4 ---
scripts/sign-file.c | 34 +++++++++++++++-----
5 files changed, 68 insertions(+), 19 deletions(-)
diff --git a/Documentation/admin-guide/module-signing.rst b/Documentation/admin-guide/module-signing.rst
index a8667a777490..7f2f127dc76f 100644
--- a/Documentation/admin-guide/module-signing.rst
+++ b/Documentation/admin-guide/module-signing.rst
@@ -28,10 +28,12 @@ trusted userspace bits.
This facility uses X.509 ITU-T standard certificates to encode the public keys
involved. The signatures are not themselves encoded in any industrial standard
-type. The built-in facility currently only supports the RSA & NIST P-384 ECDSA
-public key signing standard (though it is pluggable and permits others to be
-used). The possible hash algorithms that can be used are SHA-2 and SHA-3 of
-sizes 256, 384, and 512 (the algorithm is selected by data in the signature).
+type. The built-in facility currently only supports the RSA, NIST P-384 ECDSA
+and NIST FIPS-204 ML-DSA public key signing standards (though it is pluggable
+and permits others to be used). For RSA and ECDSA, the possible hash
+algorithms that can be used are SHA-2 and SHA-3 of sizes 256, 384, and 512 (the
+algorithm is selected by data in the signature); ML-DSA does its own hashing,
+but is allowed to be used with a SHA512 hash for signed attributes.
==========================
@@ -146,9 +148,9 @@ into vmlinux) using parameters in the::
file (which is also generated if it does not already exist).
-One can select between RSA (``MODULE_SIG_KEY_TYPE_RSA``) and ECDSA
-(``MODULE_SIG_KEY_TYPE_ECDSA``) to generate either RSA 4k or NIST
-P-384 keypair.
+One can select between RSA (``MODULE_SIG_KEY_TYPE_RSA``), ECDSA
+(``MODULE_SIG_KEY_TYPE_ECDSA``) and ML-DSA (``MODULE_SIG_KEY_TYPE_MLDSA_*``) to
+generate an RSA 4k, a NIST P-384 keypair or an ML-DSA 44, 65 or 87 keypair.
It is strongly recommended that you provide your own x509.genkey file.
diff --git a/certs/Kconfig b/certs/Kconfig
index 78307dc25559..2b088ef58373 100644
--- a/certs/Kconfig
+++ b/certs/Kconfig
@@ -39,6 +39,36 @@ config MODULE_SIG_KEY_TYPE_ECDSA
Note: Remove all ECDSA signing keys, e.g. certs/signing_key.pem,
when falling back to building Linux 5.14 and older kernels.
+config MODULE_SIG_KEY_TYPE_MLDSA_44
+ bool "ML-DSA-44"
+ select CRYPTO_MLDSA
+ help
+ Use an ML-DSA-44 key (NIST FIPS 204) for module signing. ML-DSA
+ support requires OpenSSL-3.5 minimum; preferably OpenSSL-4+. With
+ the latter, the entire module body will be signed; with the former,
+ signedAttrs will be used as it lacks support for CMS_NOATTR with
+ ML-DSA.
+
+config MODULE_SIG_KEY_TYPE_MLDSA_65
+ bool "ML-DSA-65"
+ select CRYPTO_MLDSA
+ help
+ Use an ML-DSA-65 key (NIST FIPS 204) for module signing. ML-DSA
+ support requires OpenSSL-3.5 minimum; preferably OpenSSL-4+. With
+ the latter, the entire module body will be signed; with the former,
+ signedAttrs will be used as it lacks support for CMS_NOATTR with
+ ML-DSA.
+
+config MODULE_SIG_KEY_TYPE_MLDSA_87
+ bool "ML-DSA-87"
+ select CRYPTO_MLDSA
+ help
+ Use an ML-DSA-87 key (NIST FIPS 204) for module signing. ML-DSA
+ support requires OpenSSL-3.5 minimum; preferably OpenSSL-4+. With
+ the latter, the entire module body will be signed; with the former,
+ signedAttrs will be used as it lacks support for CMS_NOATTR with
+ ML-DSA.
+
endchoice
config SYSTEM_TRUSTED_KEYRING
diff --git a/certs/Makefile b/certs/Makefile
index f6fa4d8d75e0..3ee1960f9f4a 100644
--- a/certs/Makefile
+++ b/certs/Makefile
@@ -43,6 +43,9 @@ targets += x509_certificate_list
ifeq ($(CONFIG_MODULE_SIG_KEY),certs/signing_key.pem)
keytype-$(CONFIG_MODULE_SIG_KEY_TYPE_ECDSA) := -newkey ec -pkeyopt ec_paramgen_curve:secp384r1
+keytype-$(CONFIG_MODULE_SIG_KEY_TYPE_MLDSA_44) := -newkey ml-dsa-44
+keytype-$(CONFIG_MODULE_SIG_KEY_TYPE_MLDSA_65) := -newkey ml-dsa-65
+keytype-$(CONFIG_MODULE_SIG_KEY_TYPE_MLDSA_87) := -newkey ml-dsa-87
quiet_cmd_gen_key = GENKEY $@
cmd_gen_key = openssl req -new -nodes -utf8 -$(CONFIG_MODULE_SIG_HASH) -days 36500 \
diff --git a/crypto/asymmetric_keys/pkcs7_verify.c b/crypto/asymmetric_keys/pkcs7_verify.c
index a5b2ed4d53fd..75d1d694dc7b 100644
--- a/crypto/asymmetric_keys/pkcs7_verify.c
+++ b/crypto/asymmetric_keys/pkcs7_verify.c
@@ -431,10 +431,6 @@ int pkcs7_verify(struct pkcs7_message *pkcs7,
pr_warn("Invalid module sig (not pkcs7-data)\n");
return -EKEYREJECTED;
}
- if (pkcs7->have_authattrs) {
- pr_warn("Invalid module sig (has authattrs)\n");
- return -EKEYREJECTED;
- }
break;
case VERIFYING_FIRMWARE_SIGNATURE:
if (pkcs7->data_type != OID_data) {
diff --git a/scripts/sign-file.c b/scripts/sign-file.c
index 7070245edfc1..547b97097230 100644
--- a/scripts/sign-file.c
+++ b/scripts/sign-file.c
@@ -315,18 +315,36 @@ int main(int argc, char **argv)
ERR(!digest_algo, "EVP_get_digestbyname");
#ifndef USE_PKCS7
+
+ unsigned int flags =
+ CMS_NOCERTS |
+ CMS_PARTIAL |
+ CMS_BINARY |
+ CMS_DETACHED |
+ CMS_STREAM |
+ CMS_NOSMIMECAP |
+ CMS_NO_SIGNING_TIME |
+ use_keyid;
+
+ if ((EVP_PKEY_is_a(private_key, "ML-DSA-44") ||
+ EVP_PKEY_is_a(private_key, "ML-DSA-65") ||
+ EVP_PKEY_is_a(private_key, "ML-DSA-87")) &&
+ OPENSSL_VERSION_MAJOR < 4) {
+ /* ML-DSA + CMS_NOATTR is not supported in openssl-3.5
+ * and before.
+ */
+ use_signed_attrs = 0;
+ }
+
+ flags |= use_signed_attrs;
+
/* Load the signature message from the digest buffer. */
- cms = CMS_sign(NULL, NULL, NULL, NULL,
- CMS_NOCERTS | CMS_PARTIAL | CMS_BINARY |
- CMS_DETACHED | CMS_STREAM);
+ cms = CMS_sign(NULL, NULL, NULL, NULL, flags);
ERR(!cms, "CMS_sign");
- ERR(!CMS_add1_signer(cms, x509, private_key, digest_algo,
- CMS_NOCERTS | CMS_BINARY |
- CMS_NOSMIMECAP | use_keyid |
- use_signed_attrs),
+ ERR(!CMS_add1_signer(cms, x509, private_key, digest_algo, flags),
"CMS_add1_signer");
- ERR(CMS_final(cms, bm, NULL, CMS_NOCERTS | CMS_BINARY) != 1,
+ ERR(CMS_final(cms, bm, NULL, flags) != 1,
"CMS_final");
#else
^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH v14 0/5] x509, pkcs7, crypto: Add ML-DSA and RSASSA-PSS signing
2026-01-21 22:36 [PATCH v14 0/5] x509, pkcs7, crypto: Add ML-DSA and RSASSA-PSS signing David Howells
` (4 preceding siblings ...)
2026-01-21 22:36 ` [PATCH v14 5/5] modsign: Enable ML-DSA module signing David Howells
@ 2026-01-23 11:13 ` David Howells
5 siblings, 0 replies; 15+ messages in thread
From: David Howells @ 2026-01-23 11:13 UTC (permalink / raw)
Cc: dhowells, Lukas Wunner, Ignat Korchagin, Jarkko Sakkinen,
Herbert Xu, Eric Biggers, Luis Chamberlain, Petr Pavlu,
Daniel Gomez, Sami Tolvanen, Jason A . Donenfeld, Ard Biesheuvel,
Stephan Mueller, linux-crypto, keyrings, linux-modules,
linux-kernel
David Howells <dhowells@redhat.com> wrote:
> Subject: [PATCH v14 0/5] x509, pkcs7, crypto: Add ML-DSA and RSASSA-PSS
> signing
I forgot to edit the cover to reflect I dropped RSASSA-PSS support from the
patchset for now.
David
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v14 2/5] x509: Separately calculate sha256 for blacklist
2026-01-21 22:36 ` [PATCH v14 2/5] x509: Separately calculate sha256 for blacklist David Howells
@ 2026-01-25 14:34 ` Jarkko Sakkinen
0 siblings, 0 replies; 15+ messages in thread
From: Jarkko Sakkinen @ 2026-01-25 14:34 UTC (permalink / raw)
To: David Howells
Cc: Lukas Wunner, Ignat Korchagin, Herbert Xu, Eric Biggers,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Jason A . Donenfeld, Ard Biesheuvel, Stephan Mueller,
linux-crypto, keyrings, linux-modules, linux-kernel
On Wed, Jan 21, 2026 at 10:36:04PM +0000, David Howells wrote:
> Calculate the SHA256 hash for blacklisting purposes independently of the
> signature hash (which may be something other than SHA256).
>
> This is necessary because when ML-DSA is used, no digest is calculated.
>
> Note that this represents a change of behaviour in that the hash used for
> the blacklist check would previously have been whatever digest was used
> for, say, RSA-based signatures. It may be that this is inadvisable.
>
> 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/x509_parser.h | 2 ++
> crypto/asymmetric_keys/x509_public_key.c | 23 +++++++++++++----------
> 2 files changed, 15 insertions(+), 10 deletions(-)
>
> diff --git a/crypto/asymmetric_keys/x509_parser.h b/crypto/asymmetric_keys/x509_parser.h
> index 0688c222806b..b7aeebdddb36 100644
> --- a/crypto/asymmetric_keys/x509_parser.h
> +++ b/crypto/asymmetric_keys/x509_parser.h
> @@ -9,12 +9,14 @@
> #include <linux/time.h>
> #include <crypto/public_key.h>
> #include <keys/asymmetric-type.h>
> +#include <crypto/sha2.h>
>
> struct x509_certificate {
> struct x509_certificate *next;
> struct x509_certificate *signer; /* Certificate that signed this one */
> struct public_key *pub; /* Public key details */
> struct public_key_signature *sig; /* Signature parameters */
> + u8 sha256[SHA256_DIGEST_SIZE]; /* Hash for blacklist purposes */
> char *issuer; /* Name of certificate issuer */
> char *subject; /* Name of certificate subject */
> struct asymmetric_key_id *id; /* Issuer + Serial number */
> diff --git a/crypto/asymmetric_keys/x509_public_key.c b/crypto/asymmetric_keys/x509_public_key.c
> index 12e3341e806b..6d002e3b20c5 100644
> --- a/crypto/asymmetric_keys/x509_public_key.c
> +++ b/crypto/asymmetric_keys/x509_public_key.c
> @@ -31,6 +31,19 @@ int x509_get_sig_params(struct x509_certificate *cert)
>
> pr_devel("==>%s()\n", __func__);
>
> + /* Calculate a SHA256 hash of the TBS and check it against the
> + * blacklist.
> + */
> + sha256(cert->tbs, cert->tbs_size, cert->sha256);
> + ret = is_hash_blacklisted(cert->sha256, sizeof(cert->sha256),
> + BLACKLIST_HASH_X509_TBS);
> + if (ret == -EKEYREJECTED) {
> + pr_err("Cert %*phN is blacklisted\n",
> + (int)sizeof(cert->sha256), cert->sha256);
> + cert->blacklisted = true;
> + ret = 0;
> + }
> +
> sig->s = kmemdup(cert->raw_sig, cert->raw_sig_size, GFP_KERNEL);
> if (!sig->s)
> return -ENOMEM;
> @@ -65,19 +78,9 @@ int x509_get_sig_params(struct x509_certificate *cert)
>
> ret = crypto_shash_digest(desc, cert->tbs, cert->tbs_size,
> sig->digest);
> -
nit: spurious diff unrelated to change
> if (ret < 0)
> goto error_2;
>
> - ret = is_hash_blacklisted(sig->digest, sig->digest_size,
> - BLACKLIST_HASH_X509_TBS);
> - if (ret == -EKEYREJECTED) {
> - pr_err("Cert %*phN is blacklisted\n",
> - sig->digest_size, sig->digest);
> - cert->blacklisted = true;
> - ret = 0;
> - }
> -
> error_2:
> kfree(desc);
> error:
>
I hold on for commentary (nit was is not reason to not give
reviewed-by) tho it looks to me acceptable.
BR, Jarkko
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v14 3/5] pkcs7: Allow the signing algo to do whatever digestion it wants itself
2026-01-21 22:36 ` [PATCH v14 3/5] pkcs7: Allow the signing algo to do whatever digestion it wants itself David Howells
@ 2026-01-25 14:38 ` Jarkko Sakkinen
2026-01-26 11:11 ` David Howells
0 siblings, 1 reply; 15+ messages in thread
From: Jarkko Sakkinen @ 2026-01-25 14:38 UTC (permalink / raw)
To: David Howells
Cc: Lukas Wunner, Ignat Korchagin, Herbert Xu, Eric Biggers,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Jason A . Donenfeld, Ard Biesheuvel, Stephan Mueller,
linux-crypto, keyrings, linux-modules, linux-kernel
On Wed, Jan 21, 2026 at 10:36:05PM +0000, David Howells wrote:
> Allow the data to be verified in a PKCS#7 or CMS message to be passed
> directly to an asymmetric cipher algorithm (e.g. ML-DSA) if it wants to do
> whatever passes for hashing/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.
>
> The 'data to be verified' may be the content of the PKCS#7 message or it
> will be the authenticatedAttributes (signedAttrs if CMS), modified, if
> those are present.
>
> This is done by:
>
> (1) Rename ->digest and ->digest_len to ->m and ->m_size to represent the
> input to the signature verification algorithm, reflecting that
> ->digest may no longer actually *be* a digest.
>
> (2) Make ->m and ->m_size 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.
These renames emit enough noise to be split into a separate patch.
>
> (3) Add a flag, ->algo_takes_data, to indicate that the verification
> algorithm wants to access the data to be verified directly rather than
> having it digested first.
>
> (4) If the PKCS#7 message has authenticatedAttributes (or CMS signedAtts),
> then the digest contained therein will be validated as now, and the
> modified attrs blob will either be digested or assigned to ->m as
> appropriate.
>
> (5) For ML-DSA, point ->m to the TBSCertificate instead of digesting it
> and using the digest.
>
> Note that whilst ML-DSA does allow for an "external mu", CMS doesn't yet
> have that standardised.
>
> 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/asymmetric_type.c | 4 +-
> crypto/asymmetric_keys/pkcs7_parser.c | 4 +-
> crypto/asymmetric_keys/pkcs7_verify.c | 79 ++++++++++++++++--------
> crypto/asymmetric_keys/public_key.c | 3 +-
> crypto/asymmetric_keys/signature.c | 3 +-
> crypto/asymmetric_keys/x509_public_key.c | 19 ++++--
> include/crypto/public_key.h | 6 +-
> security/integrity/digsig_asymmetric.c | 4 +-
> 8 files changed, 79 insertions(+), 43 deletions(-)
>
> diff --git a/crypto/asymmetric_keys/asymmetric_type.c b/crypto/asymmetric_keys/asymmetric_type.c
> index 348966ea2175..2326743310b1 100644
> --- a/crypto/asymmetric_keys/asymmetric_type.c
> +++ b/crypto/asymmetric_keys/asymmetric_type.c
> @@ -593,10 +593,10 @@ static int asymmetric_key_verify_signature(struct kernel_pkey_params *params,
> {
> struct public_key_signature sig = {
> .s_size = params->in2_len,
> - .digest_size = params->in_len,
> + .m_size = params->in_len,
> .encoding = params->encoding,
> .hash_algo = params->hash_algo,
> - .digest = (void *)in,
> + .m = (void *)in,
> .s = (void *)in2,
> };
>
> diff --git a/crypto/asymmetric_keys/pkcs7_parser.c b/crypto/asymmetric_keys/pkcs7_parser.c
> index 423d13c47545..3cdbab3b9f50 100644
> --- a/crypto/asymmetric_keys/pkcs7_parser.c
> +++ b/crypto/asymmetric_keys/pkcs7_parser.c
> @@ -599,8 +599,8 @@ int pkcs7_sig_note_set_of_authattrs(void *context, size_t hdrlen,
> }
>
> /* We need to switch the 'CONT 0' to a 'SET OF' when we digest */
> - sinfo->authattrs = value - (hdrlen - 1);
> - sinfo->authattrs_len = vlen + (hdrlen - 1);
> + sinfo->authattrs = value - hdrlen;
> + sinfo->authattrs_len = vlen + hdrlen;
> return 0;
> }
>
> diff --git a/crypto/asymmetric_keys/pkcs7_verify.c b/crypto/asymmetric_keys/pkcs7_verify.c
> index 6d6475e3a9bf..a5b2ed4d53fd 100644
> --- a/crypto/asymmetric_keys/pkcs7_verify.c
> +++ b/crypto/asymmetric_keys/pkcs7_verify.c
> @@ -30,8 +30,18 @@ static int pkcs7_digest(struct pkcs7_message *pkcs7,
>
> kenter(",%u,%s", sinfo->index, sinfo->sig->hash_algo);
>
> + if (!sinfo->authattrs && sig->algo_takes_data) {
> + /* There's no intermediate digest and the signature algo
> + * doesn't want the data prehashing.
> + */
> + sig->m = (void *)pkcs7->data;
> + sig->m_size = pkcs7->data_len;
> + sig->m_free = false;
> + return 0;
> + }
> +
> /* The digest was calculated already. */
> - if (sig->digest)
> + if (sig->m)
> return 0;
>
> if (!sinfo->sig->hash_algo)
> @@ -45,12 +55,13 @@ static int pkcs7_digest(struct pkcs7_message *pkcs7,
> return (PTR_ERR(tfm) == -ENOENT) ? -ENOPKG : PTR_ERR(tfm);
>
> desc_size = crypto_shash_descsize(tfm) + sizeof(*desc);
> - sig->digest_size = crypto_shash_digestsize(tfm);
> + sig->m_size = crypto_shash_digestsize(tfm);
>
> ret = -ENOMEM;
> - sig->digest = kmalloc(sig->digest_size, GFP_KERNEL);
> - if (!sig->digest)
> + sig->m = kmalloc(umax(sinfo->authattrs_len, sig->m_size), GFP_KERNEL);
> + if (!sig->m)
> goto error_no_desc;
> + sig->m_free = true;
>
> desc = kzalloc(desc_size, GFP_KERNEL);
> if (!desc)
> @@ -59,33 +70,30 @@ static int pkcs7_digest(struct pkcs7_message *pkcs7,
> desc->tfm = tfm;
>
> /* Digest the message [RFC2315 9.3] */
> - ret = crypto_shash_digest(desc, pkcs7->data, pkcs7->data_len,
> - sig->digest);
> + ret = crypto_shash_digest(desc, pkcs7->data, pkcs7->data_len, sig->m);
> if (ret < 0)
> goto error;
> - pr_devel("MsgDigest = [%*ph]\n", 8, sig->digest);
> + pr_devel("MsgDigest = [%*ph]\n", 8, sig->m);
>
> /* However, if there are authenticated attributes, there must be a
> * message digest attribute amongst them which corresponds to the
> * digest we just calculated.
> */
> if (sinfo->authattrs) {
> - u8 tag;
> -
> if (!sinfo->msgdigest) {
> pr_warn("Sig %u: No messageDigest\n", sinfo->index);
> ret = -EKEYREJECTED;
> goto error;
> }
>
> - if (sinfo->msgdigest_len != sig->digest_size) {
> + if (sinfo->msgdigest_len != sig->m_size) {
> pr_warn("Sig %u: Invalid digest size (%u)\n",
> sinfo->index, sinfo->msgdigest_len);
> ret = -EBADMSG;
> goto error;
> }
>
> - if (memcmp(sig->digest, sinfo->msgdigest,
> + if (memcmp(sig->m, sinfo->msgdigest,
> sinfo->msgdigest_len) != 0) {
> pr_warn("Sig %u: Message digest doesn't match\n",
> sinfo->index);
> @@ -97,21 +105,33 @@ static int pkcs7_digest(struct pkcs7_message *pkcs7,
> * as the contents of the digest instead. Note that we need to
> * convert the attributes from a CONT.0 into a SET before we
> * hash it.
> + *
> + * However, for certain algorithms, such as ML-DSA, the digest
> + * is integrated into the signing algorithm. In such a case,
> + * we copy the authattrs, modifying the tag type, and set that
> + * as the digest.
> */
> - memset(sig->digest, 0, sig->digest_size);
> -
> - ret = crypto_shash_init(desc);
> - if (ret < 0)
> - goto error;
> - tag = ASN1_CONS_BIT | ASN1_SET;
> - ret = crypto_shash_update(desc, &tag, 1);
> - if (ret < 0)
> - goto error;
> - ret = crypto_shash_finup(desc, sinfo->authattrs,
> - sinfo->authattrs_len, sig->digest);
> - if (ret < 0)
> - goto error;
> - pr_devel("AADigest = [%*ph]\n", 8, sig->digest);
> + if (sig->algo_takes_data) {
> + sig->m_size = sinfo->authattrs_len;
> + memcpy(sig->m, sinfo->authattrs, sinfo->authattrs_len);
> + sig->m[0] = ASN1_CONS_BIT | ASN1_SET;
> + ret = 0;
> + } else {
> + u8 tag = ASN1_CONS_BIT | ASN1_SET;
> +
> + ret = crypto_shash_init(desc);
> + if (ret < 0)
> + goto error;
> + ret = crypto_shash_update(desc, &tag, 1);
> + if (ret < 0)
> + goto error;
> + ret = crypto_shash_finup(desc, sinfo->authattrs + 1,
> + sinfo->authattrs_len - 1,
> + sig->m);
> + if (ret < 0)
> + goto error;
> + }
> + pr_devel("AADigest = [%*ph]\n", 8, sig->m);
> }
>
> error:
> @@ -137,9 +157,14 @@ 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->m_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;
> + *buf = sinfo->sig->m;
> + *len = sinfo->sig->m_size;
>
> i = match_string(hash_algo_name, HASH_ALGO__LAST,
> sinfo->sig->hash_algo);
> diff --git a/crypto/asymmetric_keys/public_key.c b/crypto/asymmetric_keys/public_key.c
> index e5b177c8e842..a46356e0c08b 100644
> --- a/crypto/asymmetric_keys/public_key.c
> +++ b/crypto/asymmetric_keys/public_key.c
> @@ -425,8 +425,7 @@ int public_key_verify_signature(const struct public_key *pkey,
> if (ret)
> goto error_free_key;
>
> - ret = crypto_sig_verify(tfm, sig->s, sig->s_size,
> - sig->digest, sig->digest_size);
> + ret = crypto_sig_verify(tfm, sig->s, sig->s_size, sig->m, sig->m_size);
>
> error_free_key:
> kfree_sensitive(key);
> diff --git a/crypto/asymmetric_keys/signature.c b/crypto/asymmetric_keys/signature.c
> index 041d04b5c953..a5ac7a53b670 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->m_free)
> + kfree(sig->m);
> kfree(sig);
> }
> }
> diff --git a/crypto/asymmetric_keys/x509_public_key.c b/crypto/asymmetric_keys/x509_public_key.c
> index 6d002e3b20c5..27b4fea37845 100644
> --- a/crypto/asymmetric_keys/x509_public_key.c
> +++ b/crypto/asymmetric_keys/x509_public_key.c
> @@ -50,6 +50,14 @@ int x509_get_sig_params(struct x509_certificate *cert)
>
> sig->s_size = cert->raw_sig_size;
>
> + if (sig->algo_takes_data) {
> + /* The signature algorithm does whatever passes for hashing. */
> + sig->m = (u8 *)cert->tbs;
> + sig->m_size = cert->tbs_size;
> + sig->m_free = false;
> + goto out;
> + }
> +
> /* Allocate the hashing algorithm we're going to need and find out how
> * big the hash operational data will be.
> */
> @@ -63,12 +71,13 @@ int x509_get_sig_params(struct x509_certificate *cert)
> }
>
> desc_size = crypto_shash_descsize(tfm) + sizeof(*desc);
> - sig->digest_size = crypto_shash_digestsize(tfm);
> + sig->m_size = crypto_shash_digestsize(tfm);
>
> ret = -ENOMEM;
> - sig->digest = kmalloc(sig->digest_size, GFP_KERNEL);
> - if (!sig->digest)
> + sig->m = kmalloc(sig->m_size, GFP_KERNEL);
> + if (!sig->m)
> goto error;
> + sig->m_free = true;
>
> desc = kzalloc(desc_size, GFP_KERNEL);
> if (!desc)
> @@ -76,8 +85,7 @@ int x509_get_sig_params(struct x509_certificate *cert)
>
> desc->tfm = tfm;
>
> - ret = crypto_shash_digest(desc, cert->tbs, cert->tbs_size,
> - sig->digest);
> + ret = crypto_shash_digest(desc, cert->tbs, cert->tbs_size, sig->m);
> if (ret < 0)
> goto error_2;
>
> @@ -85,6 +93,7 @@ int x509_get_sig_params(struct x509_certificate *cert)
> kfree(desc);
> error:
> crypto_free_shash(tfm);
> +out:
> pr_devel("<==%s() = %d\n", __func__, ret);
> return ret;
> }
> diff --git a/include/crypto/public_key.h b/include/crypto/public_key.h
> index 81098e00c08f..4c5199b20338 100644
> --- a/include/crypto/public_key.h
> +++ b/include/crypto/public_key.h
> @@ -43,9 +43,11 @@ extern void public_key_free(struct public_key *key);
> struct public_key_signature {
> struct asymmetric_key_id *auth_ids[3];
> u8 *s; /* Signature */
> - u8 *digest;
> + u8 *m; /* Message data to pass to verifier */
> u32 s_size; /* Number of bytes in signature */
> - u32 digest_size; /* Number of bytes in digest */
> + u32 m_size; /* Number of bytes in ->m */
> + bool m_free; /* T if ->m needs freeing */
> + bool algo_takes_data; /* T if public key algo operates on data, not a hash */
> const char *pkey_algo;
> const char *hash_algo;
> const char *encoding;
> diff --git a/security/integrity/digsig_asymmetric.c b/security/integrity/digsig_asymmetric.c
> index 457c0a396caf..87be85f477d1 100644
> --- a/security/integrity/digsig_asymmetric.c
> +++ b/security/integrity/digsig_asymmetric.c
> @@ -121,8 +121,8 @@ int asymmetric_verify(struct key *keyring, const char *sig,
> goto out;
> }
>
> - pks.digest = (u8 *)data;
> - pks.digest_size = datalen;
> + pks.m = (u8 *)data;
> + pks.m_size = datalen;
> pks.s = hdr->sig;
> pks.s_size = siglen;
> ret = verify_signature(key, &pks);
>
BR, Jarkko
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v14 4/5] pkcs7, x509: Add ML-DSA support
2026-01-21 22:36 ` [PATCH v14 4/5] pkcs7, x509: Add ML-DSA support David Howells
@ 2026-01-25 14:42 ` Jarkko Sakkinen
2026-01-26 11:25 ` David Howells
2026-01-26 12:02 ` Christophe Leroy (CS GROUP)
0 siblings, 2 replies; 15+ messages in thread
From: Jarkko Sakkinen @ 2026-01-25 14:42 UTC (permalink / raw)
To: David Howells
Cc: Lukas Wunner, Ignat Korchagin, Herbert Xu, Eric Biggers,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Jason A . Donenfeld, Ard Biesheuvel, Stephan Mueller,
linux-crypto, keyrings, linux-modules, linux-kernel
On Wed, Jan 21, 2026 at 10:36:06PM +0000, David Howells wrote:
> Add support for ML-DSA keys and signatures to the CMS/PKCS#7 and X.509
> implementations. ML-DSA-44, -65 and -87 are all supported. For X.509
> certificates, the TBSCertificate is required to be signed directly; for CMS,
> direct signing of the data is preferred, though use of SHA512 (and only that)
> as an intermediate hash of the content is permitted with signedAttrs.
>
> 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/pkcs7_parser.c | 24 +++++++++++++++++++-
> crypto/asymmetric_keys/public_key.c | 10 +++++++++
> crypto/asymmetric_keys/x509_cert_parser.c | 27 ++++++++++++++++++++++-
> include/linux/oid_registry.h | 5 +++++
> 4 files changed, 64 insertions(+), 2 deletions(-)
>
> diff --git a/crypto/asymmetric_keys/pkcs7_parser.c b/crypto/asymmetric_keys/pkcs7_parser.c
> index 3cdbab3b9f50..594a8f1d9dfb 100644
> --- a/crypto/asymmetric_keys/pkcs7_parser.c
> +++ b/crypto/asymmetric_keys/pkcs7_parser.c
> @@ -95,11 +95,18 @@ static int pkcs7_check_authattrs(struct pkcs7_message *msg)
> if (sinfo->authattrs) {
> want = true;
> msg->have_authattrs = true;
> + } else if (sinfo->sig->algo_takes_data) {
> + sinfo->sig->hash_algo = "none";
> }
>
> - for (sinfo = sinfo->next; sinfo; sinfo = sinfo->next)
> + for (sinfo = sinfo->next; sinfo; sinfo = sinfo->next) {
> if (!!sinfo->authattrs != want)
> goto inconsistent;
> +
> + if (!sinfo->authattrs &&
> + sinfo->sig->algo_takes_data)
> + sinfo->sig->hash_algo = "none";
Why don't we have a constant for "none"?
$ git grep "\"none\"" security/
security/apparmor/audit.c: "none",
security/apparmor/lib.c: { "none", DEBUG_NONE },
security/security.c: [LOCKDOWN_NONE] = "none",
$ git grep "\"none\"" crypto
crypto/asymmetric_keys/public_key.c: hash_algo = "none";
crypto/asymmetric_keys/public_key.c: hash_algo = "none";
crypto/testmgr.h: * PKCS#1 RSA test vectors for hash algorithm "none"
IMHO, this a bad practice.
> + }
> return 0;
>
> inconsistent:
> @@ -297,6 +304,21 @@ int pkcs7_sig_note_pkey_algo(void *context, size_t hdrlen,
> ctx->sinfo->sig->pkey_algo = "ecrdsa";
> ctx->sinfo->sig->encoding = "raw";
> break;
> + case OID_id_ml_dsa_44:
> + ctx->sinfo->sig->pkey_algo = "mldsa44";
> + ctx->sinfo->sig->encoding = "raw";
> + ctx->sinfo->sig->algo_takes_data = true;
> + break;
> + case OID_id_ml_dsa_65:
> + ctx->sinfo->sig->pkey_algo = "mldsa65";
> + ctx->sinfo->sig->encoding = "raw";
> + ctx->sinfo->sig->algo_takes_data = true;
> + break;
> + case OID_id_ml_dsa_87:
> + ctx->sinfo->sig->pkey_algo = "mldsa87";
> + ctx->sinfo->sig->encoding = "raw";
> + ctx->sinfo->sig->algo_takes_data = true;
> + break;
> default:
> printk("Unsupported pkey algo: %u\n", ctx->last_oid);
> return -ENOPKG;
> diff --git a/crypto/asymmetric_keys/public_key.c b/crypto/asymmetric_keys/public_key.c
> index a46356e0c08b..09a0b83d5d77 100644
> --- a/crypto/asymmetric_keys/public_key.c
> +++ b/crypto/asymmetric_keys/public_key.c
> @@ -142,6 +142,16 @@ software_key_determine_akcipher(const struct public_key *pkey,
> if (strcmp(hash_algo, "streebog256") != 0 &&
> strcmp(hash_algo, "streebog512") != 0)
> return -EINVAL;
> + } else if (strcmp(pkey->pkey_algo, "mldsa44") == 0 ||
> + strcmp(pkey->pkey_algo, "mldsa65") == 0 ||
> + strcmp(pkey->pkey_algo, "mldsa87") == 0) {
> + if (strcmp(encoding, "raw") != 0)
> + return -EINVAL;
> + if (!hash_algo)
> + return -EINVAL;
> + if (strcmp(hash_algo, "none") != 0 &&
> + strcmp(hash_algo, "sha512") != 0)
> + return -EINVAL;
> } else {
> /* Unknown public key algorithm */
> return -ENOPKG;
> diff --git a/crypto/asymmetric_keys/x509_cert_parser.c b/crypto/asymmetric_keys/x509_cert_parser.c
> index b37cae914987..2fe094f5caf3 100644
> --- a/crypto/asymmetric_keys/x509_cert_parser.c
> +++ b/crypto/asymmetric_keys/x509_cert_parser.c
> @@ -257,6 +257,15 @@ int x509_note_sig_algo(void *context, size_t hdrlen, unsigned char tag,
> case OID_gost2012Signature512:
> ctx->cert->sig->hash_algo = "streebog512";
> goto ecrdsa;
> + case OID_id_ml_dsa_44:
> + ctx->cert->sig->pkey_algo = "mldsa44";
> + goto ml_dsa;
> + case OID_id_ml_dsa_65:
> + ctx->cert->sig->pkey_algo = "mldsa65";
> + goto ml_dsa;
> + case OID_id_ml_dsa_87:
> + ctx->cert->sig->pkey_algo = "mldsa87";
> + goto ml_dsa;
> }
>
> rsa_pkcs1:
> @@ -274,6 +283,12 @@ int x509_note_sig_algo(void *context, size_t hdrlen, unsigned char tag,
> ctx->cert->sig->encoding = "x962";
> ctx->sig_algo = ctx->last_oid;
> return 0;
> +ml_dsa:
> + ctx->cert->sig->algo_takes_data = true;
> + ctx->cert->sig->hash_algo = "none";
> + ctx->cert->sig->encoding = "raw";
> + ctx->sig_algo = ctx->last_oid;
> + return 0;
> }
>
> /*
> @@ -300,7 +315,8 @@ int x509_note_signature(void *context, size_t hdrlen,
>
> if (strcmp(ctx->cert->sig->pkey_algo, "rsa") == 0 ||
> strcmp(ctx->cert->sig->pkey_algo, "ecrdsa") == 0 ||
> - strcmp(ctx->cert->sig->pkey_algo, "ecdsa") == 0) {
> + strcmp(ctx->cert->sig->pkey_algo, "ecdsa") == 0 ||
> + strncmp(ctx->cert->sig->pkey_algo, "mldsa", 5) == 0) {
> /* Discard the BIT STRING metadata */
> if (vlen < 1 || *(const u8 *)value != 0)
> return -EBADMSG;
> @@ -524,6 +540,15 @@ int x509_extract_key_data(void *context, size_t hdrlen,
> return -ENOPKG;
> }
> break;
> + case OID_id_ml_dsa_44:
> + ctx->cert->pub->pkey_algo = "mldsa44";
> + break;
> + case OID_id_ml_dsa_65:
> + ctx->cert->pub->pkey_algo = "mldsa65";
> + break;
> + case OID_id_ml_dsa_87:
> + ctx->cert->pub->pkey_algo = "mldsa87";
> + break;
> default:
> return -ENOPKG;
> }
> diff --git a/include/linux/oid_registry.h b/include/linux/oid_registry.h
> index 6de479ebbe5d..ebce402854de 100644
> --- a/include/linux/oid_registry.h
> +++ b/include/linux/oid_registry.h
> @@ -145,6 +145,11 @@ enum OID {
> OID_id_rsassa_pkcs1_v1_5_with_sha3_384, /* 2.16.840.1.101.3.4.3.15 */
> OID_id_rsassa_pkcs1_v1_5_with_sha3_512, /* 2.16.840.1.101.3.4.3.16 */
>
> + /* NIST FIPS-204 ML-DSA */
> + OID_id_ml_dsa_44, /* 2.16.840.1.101.3.4.3.17 */
> + OID_id_ml_dsa_65, /* 2.16.840.1.101.3.4.3.18 */
> + OID_id_ml_dsa_87, /* 2.16.840.1.101.3.4.3.19 */
> +
> OID__NR
> };
>
>
BR, Jarkko
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v14 5/5] modsign: Enable ML-DSA module signing
2026-01-21 22:36 ` [PATCH v14 5/5] modsign: Enable ML-DSA module signing David Howells
@ 2026-01-25 14:44 ` Jarkko Sakkinen
0 siblings, 0 replies; 15+ messages in thread
From: Jarkko Sakkinen @ 2026-01-25 14:44 UTC (permalink / raw)
To: David Howells
Cc: Lukas Wunner, Ignat Korchagin, Herbert Xu, Eric Biggers,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Jason A . Donenfeld, Ard Biesheuvel, Stephan Mueller,
linux-crypto, keyrings, linux-modules, linux-kernel
On Wed, Jan 21, 2026 at 10:36:07PM +0000, David Howells wrote:
> Allow ML-DSA module signing to be enabled.
>
> Note that OpenSSL's CMS_*() function suite does not, as of OpenSSL-3.6,
> support the use of CMS_NOATTR with ML-DSA, so the prohibition against using
> signedAttrs with module signing has to be removed. The selected digest
> then applies only to the algorithm used to calculate the digest stored in
> the messageDigest attribute. The OpenSSL development branch has patches
> applied that fix this[1], but it appears that that will only be available
> in OpenSSL-4.
>
> [1] https://github.com/openssl/openssl/pull/28923
>
> sign-file won't set CMS_NOATTR if openssl is earlier than v4, resulting in
> the use of signed attributes.
>
> The ML-DSA algorithm takes the raw data to be signed without regard to what
> digest algorithm is specified in the CMS message. The CMS specified digest
> algorithm is ignored unless signedAttrs are used; in such a case, only
> SHA512 is permitted.
>
> Signed-off-by: David Howells <dhowells@redhat.com>
> cc: Eric Biggers <ebiggers@kernel.org>
> cc: Lukas Wunner <lukas@wunner.de>
> cc: Ignat Korchagin <ignat@cloudflare.com>
> cc: Stephan Mueller <smueller@chronox.de>
> cc: Herbert Xu <herbert@gondor.apana.org.au>
> cc: keyrings@vger.kernel.org
> cc: linux-crypto@vger.kernel.org
> ---
> Documentation/admin-guide/module-signing.rst | 16 +++++----
> certs/Kconfig | 30 +++++++++++++++++
> certs/Makefile | 3 ++
> crypto/asymmetric_keys/pkcs7_verify.c | 4 ---
> scripts/sign-file.c | 34 +++++++++++++++-----
> 5 files changed, 68 insertions(+), 19 deletions(-)
>
> diff --git a/Documentation/admin-guide/module-signing.rst b/Documentation/admin-guide/module-signing.rst
> index a8667a777490..7f2f127dc76f 100644
> --- a/Documentation/admin-guide/module-signing.rst
> +++ b/Documentation/admin-guide/module-signing.rst
> @@ -28,10 +28,12 @@ trusted userspace bits.
>
> This facility uses X.509 ITU-T standard certificates to encode the public keys
> involved. The signatures are not themselves encoded in any industrial standard
> -type. The built-in facility currently only supports the RSA & NIST P-384 ECDSA
> -public key signing standard (though it is pluggable and permits others to be
> -used). The possible hash algorithms that can be used are SHA-2 and SHA-3 of
> -sizes 256, 384, and 512 (the algorithm is selected by data in the signature).
> +type. The built-in facility currently only supports the RSA, NIST P-384 ECDSA
> +and NIST FIPS-204 ML-DSA public key signing standards (though it is pluggable
> +and permits others to be used). For RSA and ECDSA, the possible hash
> +algorithms that can be used are SHA-2 and SHA-3 of sizes 256, 384, and 512 (the
> +algorithm is selected by data in the signature); ML-DSA does its own hashing,
> +but is allowed to be used with a SHA512 hash for signed attributes.
>
>
> ==========================
> @@ -146,9 +148,9 @@ into vmlinux) using parameters in the::
>
> file (which is also generated if it does not already exist).
>
> -One can select between RSA (``MODULE_SIG_KEY_TYPE_RSA``) and ECDSA
> -(``MODULE_SIG_KEY_TYPE_ECDSA``) to generate either RSA 4k or NIST
> -P-384 keypair.
> +One can select between RSA (``MODULE_SIG_KEY_TYPE_RSA``), ECDSA
> +(``MODULE_SIG_KEY_TYPE_ECDSA``) and ML-DSA (``MODULE_SIG_KEY_TYPE_MLDSA_*``) to
> +generate an RSA 4k, a NIST P-384 keypair or an ML-DSA 44, 65 or 87 keypair.
>
> It is strongly recommended that you provide your own x509.genkey file.
>
> diff --git a/certs/Kconfig b/certs/Kconfig
> index 78307dc25559..2b088ef58373 100644
> --- a/certs/Kconfig
> +++ b/certs/Kconfig
> @@ -39,6 +39,36 @@ config MODULE_SIG_KEY_TYPE_ECDSA
> Note: Remove all ECDSA signing keys, e.g. certs/signing_key.pem,
> when falling back to building Linux 5.14 and older kernels.
>
> +config MODULE_SIG_KEY_TYPE_MLDSA_44
> + bool "ML-DSA-44"
> + select CRYPTO_MLDSA
> + help
> + Use an ML-DSA-44 key (NIST FIPS 204) for module signing. ML-DSA
> + support requires OpenSSL-3.5 minimum; preferably OpenSSL-4+. With
> + the latter, the entire module body will be signed; with the former,
> + signedAttrs will be used as it lacks support for CMS_NOATTR with
> + ML-DSA.
> +
> +config MODULE_SIG_KEY_TYPE_MLDSA_65
> + bool "ML-DSA-65"
> + select CRYPTO_MLDSA
> + help
> + Use an ML-DSA-65 key (NIST FIPS 204) for module signing. ML-DSA
> + support requires OpenSSL-3.5 minimum; preferably OpenSSL-4+. With
> + the latter, the entire module body will be signed; with the former,
> + signedAttrs will be used as it lacks support for CMS_NOATTR with
> + ML-DSA.
> +
> +config MODULE_SIG_KEY_TYPE_MLDSA_87
> + bool "ML-DSA-87"
> + select CRYPTO_MLDSA
> + help
> + Use an ML-DSA-87 key (NIST FIPS 204) for module signing. ML-DSA
> + support requires OpenSSL-3.5 minimum; preferably OpenSSL-4+. With
> + the latter, the entire module body will be signed; with the former,
> + signedAttrs will be used as it lacks support for CMS_NOATTR with
> + ML-DSA.
> +
> endchoice
>
> config SYSTEM_TRUSTED_KEYRING
> diff --git a/certs/Makefile b/certs/Makefile
> index f6fa4d8d75e0..3ee1960f9f4a 100644
> --- a/certs/Makefile
> +++ b/certs/Makefile
> @@ -43,6 +43,9 @@ targets += x509_certificate_list
> ifeq ($(CONFIG_MODULE_SIG_KEY),certs/signing_key.pem)
>
> keytype-$(CONFIG_MODULE_SIG_KEY_TYPE_ECDSA) := -newkey ec -pkeyopt ec_paramgen_curve:secp384r1
> +keytype-$(CONFIG_MODULE_SIG_KEY_TYPE_MLDSA_44) := -newkey ml-dsa-44
> +keytype-$(CONFIG_MODULE_SIG_KEY_TYPE_MLDSA_65) := -newkey ml-dsa-65
> +keytype-$(CONFIG_MODULE_SIG_KEY_TYPE_MLDSA_87) := -newkey ml-dsa-87
>
> quiet_cmd_gen_key = GENKEY $@
> cmd_gen_key = openssl req -new -nodes -utf8 -$(CONFIG_MODULE_SIG_HASH) -days 36500 \
> diff --git a/crypto/asymmetric_keys/pkcs7_verify.c b/crypto/asymmetric_keys/pkcs7_verify.c
> index a5b2ed4d53fd..75d1d694dc7b 100644
> --- a/crypto/asymmetric_keys/pkcs7_verify.c
> +++ b/crypto/asymmetric_keys/pkcs7_verify.c
> @@ -431,10 +431,6 @@ int pkcs7_verify(struct pkcs7_message *pkcs7,
> pr_warn("Invalid module sig (not pkcs7-data)\n");
> return -EKEYREJECTED;
> }
> - if (pkcs7->have_authattrs) {
> - pr_warn("Invalid module sig (has authattrs)\n");
> - return -EKEYREJECTED;
> - }
> break;
> case VERIFYING_FIRMWARE_SIGNATURE:
> if (pkcs7->data_type != OID_data) {
> diff --git a/scripts/sign-file.c b/scripts/sign-file.c
> index 7070245edfc1..547b97097230 100644
> --- a/scripts/sign-file.c
> +++ b/scripts/sign-file.c
> @@ -315,18 +315,36 @@ int main(int argc, char **argv)
> ERR(!digest_algo, "EVP_get_digestbyname");
>
> #ifndef USE_PKCS7
> +
> + unsigned int flags =
> + CMS_NOCERTS |
> + CMS_PARTIAL |
> + CMS_BINARY |
> + CMS_DETACHED |
> + CMS_STREAM |
> + CMS_NOSMIMECAP |
> + CMS_NO_SIGNING_TIME |
> + use_keyid;
> +
> + if ((EVP_PKEY_is_a(private_key, "ML-DSA-44") ||
> + EVP_PKEY_is_a(private_key, "ML-DSA-65") ||
> + EVP_PKEY_is_a(private_key, "ML-DSA-87")) &&
> + OPENSSL_VERSION_MAJOR < 4) {
> + /* ML-DSA + CMS_NOATTR is not supported in openssl-3.5
> + * and before.
> + */
> + use_signed_attrs = 0;
> + }
> +
> + flags |= use_signed_attrs;
> +
> /* Load the signature message from the digest buffer. */
> - cms = CMS_sign(NULL, NULL, NULL, NULL,
> - CMS_NOCERTS | CMS_PARTIAL | CMS_BINARY |
> - CMS_DETACHED | CMS_STREAM);
> + cms = CMS_sign(NULL, NULL, NULL, NULL, flags);
> ERR(!cms, "CMS_sign");
>
> - ERR(!CMS_add1_signer(cms, x509, private_key, digest_algo,
> - CMS_NOCERTS | CMS_BINARY |
> - CMS_NOSMIMECAP | use_keyid |
> - use_signed_attrs),
> + ERR(!CMS_add1_signer(cms, x509, private_key, digest_algo, flags),
> "CMS_add1_signer");
> - ERR(CMS_final(cms, bm, NULL, CMS_NOCERTS | CMS_BINARY) != 1,
> + ERR(CMS_final(cms, bm, NULL, flags) != 1,
> "CMS_final");
>
> #else
>
LGTM
Reviewed-by: Jarkko Sakkinen <jarkko@kernel.org>
BR, Jarkko
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v14 3/5] pkcs7: Allow the signing algo to do whatever digestion it wants itself
2026-01-25 14:38 ` Jarkko Sakkinen
@ 2026-01-26 11:11 ` David Howells
0 siblings, 0 replies; 15+ messages in thread
From: David Howells @ 2026-01-26 11:11 UTC (permalink / raw)
To: Jarkko Sakkinen
Cc: dhowells, Lukas Wunner, Ignat Korchagin, Herbert Xu, Eric Biggers,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Jason A . Donenfeld, Ard Biesheuvel, Stephan Mueller,
linux-crypto, keyrings, linux-modules, linux-kernel
Jarkko Sakkinen <jarkko@kernel.org> wrote:
> > (1) Rename ->digest and ->digest_len to ->m and ->m_size to represent the
> > input to the signature verification algorithm, reflecting that
> > ->digest may no longer actually *be* a digest.
> ...
> These renames emit enough noise to be split into a separate patch.
Yeah, I had considered that, so I've now done that.
> > + if (sig->algo_takes_data) {
> > + sig->m_size = sinfo->authattrs_len;
> > + memcpy(sig->m, sinfo->authattrs, sinfo->authattrs_len);
> > + sig->m[0] = ASN1_CONS_BIT | ASN1_SET;
> > + ret = 0;
> > + } else {
> > + u8 tag = ASN1_CONS_BIT | ASN1_SET;
> > +
> > + ret = crypto_shash_init(desc);
> > + if (ret < 0)
> > + goto error;
> > + ret = crypto_shash_update(desc, &tag, 1);
> > + if (ret < 0)
> > + goto error;
> > + ret = crypto_shash_finup(desc, sinfo->authattrs + 1,
> > + sinfo->authattrs_len - 1,
> > + sig->m);
> > + if (ret < 0)
> > + goto error;
> > + }
Thinking further on this, I think it's better just to do the copy and modify
unconditionally and then in the second case here just call
crypto_hash_digest(). That means we end up doing a single crypto call on an
aligned buffer. It's not like expect the authattrs to be particularly big for
an RSA signature.
David
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v14 4/5] pkcs7, x509: Add ML-DSA support
2026-01-25 14:42 ` Jarkko Sakkinen
@ 2026-01-26 11:25 ` David Howells
2026-01-26 13:56 ` James Bottomley
2026-01-26 12:02 ` Christophe Leroy (CS GROUP)
1 sibling, 1 reply; 15+ messages in thread
From: David Howells @ 2026-01-26 11:25 UTC (permalink / raw)
To: Jarkko Sakkinen
Cc: dhowells, Lukas Wunner, Ignat Korchagin, Herbert Xu, Eric Biggers,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Jason A . Donenfeld, Ard Biesheuvel, Stephan Mueller,
linux-crypto, keyrings, linux-modules, linux-kernel
Jarkko Sakkinen <jarkko@kernel.org> wrote:
> Why don't we have a constant for "none"?
>
> $ git grep "\"none\"" security/
> security/apparmor/audit.c: "none",
> security/apparmor/lib.c: { "none", DEBUG_NONE },
> security/security.c: [LOCKDOWN_NONE] = "none",
>
> $ git grep "\"none\"" crypto
> crypto/asymmetric_keys/public_key.c: hash_algo = "none";
> crypto/asymmetric_keys/public_key.c: hash_algo = "none";
> crypto/testmgr.h: * PKCS#1 RSA test vectors for hash algorithm "none"
>
> IMHO, this a bad practice.
You'd think that the compiler and linker ought to be able to deal with
read-only string sharing within compilation units. I don't particularly want
to deal with combining every "none" string within the kernel into one within
this patchset.
I could move back to using an enum of algorithms, I suppose - though again,
I'd rather not do that in this patchset.
David
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v14 4/5] pkcs7, x509: Add ML-DSA support
2026-01-25 14:42 ` Jarkko Sakkinen
2026-01-26 11:25 ` David Howells
@ 2026-01-26 12:02 ` Christophe Leroy (CS GROUP)
1 sibling, 0 replies; 15+ messages in thread
From: Christophe Leroy (CS GROUP) @ 2026-01-26 12:02 UTC (permalink / raw)
To: Jarkko Sakkinen, David Howells
Cc: Lukas Wunner, Ignat Korchagin, Herbert Xu, Eric Biggers,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Jason A . Donenfeld, Ard Biesheuvel, Stephan Mueller,
linux-crypto, keyrings, linux-modules, linux-kernel
Le 25/01/2026 à 15:42, Jarkko Sakkinen a écrit :
> On Wed, Jan 21, 2026 at 10:36:06PM +0000, David Howells wrote:
>> Add support for ML-DSA keys and signatures to the CMS/PKCS#7 and X.509
>> implementations. ML-DSA-44, -65 and -87 are all supported. For X.509
>> certificates, the TBSCertificate is required to be signed directly; for CMS,
>> direct signing of the data is preferred, though use of SHA512 (and only that)
>> as an intermediate hash of the content is permitted with signedAttrs.
>>
>> 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/pkcs7_parser.c | 24 +++++++++++++++++++-
>> crypto/asymmetric_keys/public_key.c | 10 +++++++++
>> crypto/asymmetric_keys/x509_cert_parser.c | 27 ++++++++++++++++++++++-
>> include/linux/oid_registry.h | 5 +++++
>> 4 files changed, 64 insertions(+), 2 deletions(-)
>>
>> diff --git a/crypto/asymmetric_keys/pkcs7_parser.c b/crypto/asymmetric_keys/pkcs7_parser.c
>> index 3cdbab3b9f50..594a8f1d9dfb 100644
>> --- a/crypto/asymmetric_keys/pkcs7_parser.c
>> +++ b/crypto/asymmetric_keys/pkcs7_parser.c
>> @@ -95,11 +95,18 @@ static int pkcs7_check_authattrs(struct pkcs7_message *msg)
>> if (sinfo->authattrs) {
>> want = true;
>> msg->have_authattrs = true;
>> + } else if (sinfo->sig->algo_takes_data) {
>> + sinfo->sig->hash_algo = "none";
>> }
>>
>> - for (sinfo = sinfo->next; sinfo; sinfo = sinfo->next)
>> + for (sinfo = sinfo->next; sinfo; sinfo = sinfo->next) {
>> if (!!sinfo->authattrs != want)
>> goto inconsistent;
>> +
>> + if (!sinfo->authattrs &&
>> + sinfo->sig->algo_takes_data)
>> + sinfo->sig->hash_algo = "none";
>
> Why don't we have a constant for "none"?
>
> $ git grep "\"none\"" security/
> security/apparmor/audit.c: "none",
> security/apparmor/lib.c: { "none", DEBUG_NONE },
> security/security.c: [LOCKDOWN_NONE] = "none",
>
> $ git grep "\"none\"" crypto
> crypto/asymmetric_keys/public_key.c: hash_algo = "none";
> crypto/asymmetric_keys/public_key.c: hash_algo = "none";
> crypto/testmgr.h: * PKCS#1 RSA test vectors for hash algorithm "none"
>
> IMHO, this a bad practice.
What is a bad practice ?
$ git grep "\"sha256\"" security
security/apparmor/apparmorfs.c: dent =
aafs_create_file("sha256", S_IFREG | 0444, dir,
security/apparmor/apparmorfs.c: return rawdata_get_link_base(dentry,
inode, done, "sha256");
security/apparmor/apparmorfs.c: dent = create_profile_file(dir,
"sha256", profile,
security/integrity/ima/Kconfig: default "sha256" if IMA_DEFAULT_HASH_SHA256
security/ipe/audit.c:#define IPE_AUDIT_HASH_ALG "sha256" /* keep in sync
with audit_policy() */
$ git grep "\"sha256\"" crypto
crypto/asymmetric_keys/mscode_parser.c: ctx->digest_algo = "sha256";
crypto/asymmetric_keys/pkcs7_parser.c:
ctx->sinfo->sig->hash_algo = "sha256";
crypto/asymmetric_keys/public_key.c: strcmp(hash_algo,
"sha256") != 0 &&
crypto/asymmetric_keys/x509_cert_parser.c:
ctx->cert->sig->hash_algo = "sha256";
crypto/asymmetric_keys/x509_cert_parser.c:
ctx->cert->sig->hash_algo = "sha256";
crypto/drbg.c: .cra_name = "sha256",
crypto/drbg.c: .backend_cra_name = "sha256",
crypto/essiv.c: /* Synchronous hash, e.g., "sha256" */
crypto/krb5/rfc8009_aes2.c: .hash_name = "sha256",
crypto/sha256.c: .base.cra_name = "sha256",
crypto/sha256.c:MODULE_ALIAS_CRYPTO("sha256");
crypto/tcrypt.c: ret = min(ret, tcrypt_test("sha256"));
crypto/tcrypt.c: test_hash_speed("sha256", sec,
generic_hash_speed_template);
crypto/tcrypt.c: test_ahash_speed("sha256", sec,
generic_hash_speed_template);
crypto/testmgr.c: .alg = "sha256",
How is the handling of "none" different from other hash algorithms ?
Christophe
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v14 4/5] pkcs7, x509: Add ML-DSA support
2026-01-26 11:25 ` David Howells
@ 2026-01-26 13:56 ` James Bottomley
0 siblings, 0 replies; 15+ messages in thread
From: James Bottomley @ 2026-01-26 13:56 UTC (permalink / raw)
To: David Howells, Jarkko Sakkinen
Cc: Lukas Wunner, Ignat Korchagin, Herbert Xu, Eric Biggers,
Luis Chamberlain, Petr Pavlu, Daniel Gomez, Sami Tolvanen,
Jason A . Donenfeld, Ard Biesheuvel, Stephan Mueller,
linux-crypto, keyrings, linux-modules, linux-kernel
On Mon, 2026-01-26 at 11:25 +0000, David Howells wrote:
> Jarkko Sakkinen <jarkko@kernel.org> wrote:
>
> > Why don't we have a constant for "none"?
> >
> > $ git grep "\"none\"" security/
> > security/apparmor/audit.c: "none",
> > security/apparmor/lib.c: { "none", DEBUG_NONE },
> > security/security.c: [LOCKDOWN_NONE] = "none",
> >
> > $ git grep "\"none\"" crypto
> > crypto/asymmetric_keys/public_key.c:
> > hash_algo = "none";
> > crypto/asymmetric_keys/public_key.c:
> > hash_algo = "none";
> > crypto/testmgr.h: * PKCS#1 RSA test vectors for hash algorithm
> > "none"
> >
> > IMHO, this a bad practice.
>
> You'd think that the compiler and linker ought to be able to deal
> with read-only string sharing within compilation units.
They do ... it's -fmerge-string-constants, which has been enabled in
gcc for any optimization level above 0 for ages. The way its supposed
to work is that each string gets its own rodata section and the linker
eliminates duplicates.
> I don't particularly want to deal with combining every "none"
> string within the kernel into one within this patchset.
Agree: let's just rely on the tools and if they're not getting it right
someone can fix the tools.
Regards,
James
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-01-26 13:56 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-21 22:36 [PATCH v14 0/5] x509, pkcs7, crypto: Add ML-DSA and RSASSA-PSS signing David Howells
2026-01-21 22:36 ` [PATCH v14 1/5] crypto: Add ML-DSA crypto_sig support David Howells
2026-01-21 22:36 ` [PATCH v14 2/5] x509: Separately calculate sha256 for blacklist David Howells
2026-01-25 14:34 ` Jarkko Sakkinen
2026-01-21 22:36 ` [PATCH v14 3/5] pkcs7: Allow the signing algo to do whatever digestion it wants itself David Howells
2026-01-25 14:38 ` Jarkko Sakkinen
2026-01-26 11:11 ` David Howells
2026-01-21 22:36 ` [PATCH v14 4/5] pkcs7, x509: Add ML-DSA support David Howells
2026-01-25 14:42 ` Jarkko Sakkinen
2026-01-26 11:25 ` David Howells
2026-01-26 13:56 ` James Bottomley
2026-01-26 12:02 ` Christophe Leroy (CS GROUP)
2026-01-21 22:36 ` [PATCH v14 5/5] modsign: Enable ML-DSA module signing David Howells
2026-01-25 14:44 ` Jarkko Sakkinen
2026-01-23 11:13 ` [PATCH v14 0/5] x509, pkcs7, crypto: Add ML-DSA and RSASSA-PSS signing David Howells
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox