Add support for ML-DSA keys and signatures to the PKCS#7 and X.509
implementations.
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 | 15 ++++++++++++++
crypto/asymmetric_keys/public_key.c | 7 +++++++
crypto/asymmetric_keys/x509_cert_parser.c | 24 +++++++++++++++++++++++
include/linux/oid_registry.h | 5 +++++
4 files changed, 51 insertions(+)
diff --git a/crypto/asymmetric_keys/pkcs7_parser.c b/crypto/asymmetric_keys/pkcs7_parser.c
index 3cdbab3b9f50..90c36fe1b5ed 100644
--- a/crypto/asymmetric_keys/pkcs7_parser.c
+++ b/crypto/asymmetric_keys/pkcs7_parser.c
@@ -297,6 +297,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_does_hash = true;
+ break;
+ case OID_id_ml_dsa_65:
+ ctx->sinfo->sig->pkey_algo = "mldsa65";
+ ctx->sinfo->sig->encoding = "raw";
+ ctx->sinfo->sig->algo_does_hash = true;
+ break;
+ case OID_id_ml_dsa_87:
+ ctx->sinfo->sig->pkey_algo = "mldsa87";
+ ctx->sinfo->sig->encoding = "raw";
+ ctx->sinfo->sig->algo_does_hash = 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 e5b177c8e842..ed6b4b5ae4ef 100644
--- a/crypto/asymmetric_keys/public_key.c
+++ b/crypto/asymmetric_keys/public_key.c
@@ -142,6 +142,13 @@ 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;
} 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..5ab5b4e5f1b4 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_does_hash = true;
+ ctx->cert->sig->hash_algo = ctx->cert->sig->pkey_algo;
+ ctx->cert->sig->encoding = "raw";
+ ctx->sig_algo = ctx->last_oid;
+ return 0;
}
/*
@@ -524,6 +539,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..30821a6a4f72 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 (Dilithium) */
+ 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
};
On Mon, Jan 05, 2026 at 03:21:28PM +0000, David Howells wrote:
> Add support for ML-DSA keys and signatures to the PKCS#7 and X.509
> implementations.
>
> 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 | 15 ++++++++++++++
> crypto/asymmetric_keys/public_key.c | 7 +++++++
> crypto/asymmetric_keys/x509_cert_parser.c | 24 +++++++++++++++++++++++
> include/linux/oid_registry.h | 5 +++++
> 4 files changed, 51 insertions(+)
This "PKCS#7" (really CMS -- the kernel misleadingly uses the old name)
stuff is really hard to understand. I'm trying to understand what
message you're actually passing to mldsa_verify(). That's kind of the
whole point, after all.
The message comes from the byte array public_key_signature::digest
(which is misleadingly named, as it's not always a digest). In turn,
that comes from the following:
1.) If the CMS object doesn't include signed attributes, then it's a
digest of the real message the caller provided.
2.) If the CMS object includes signed attributes, then the message is
the signed attributes as a byte array. The signed attributes are
required to include a message digest attribute whose value matches a
digest of the real message the caller provided.
In either (1) or (2), the digest algorithm used comes from the CMS
object itself, from SignerInfo::digestAlgorithm. The kernel allows
SHA-1, SHA-224, SHA-256, SHA-384, SHA-512, SM3, Streebog-256,
Streebog-512, SHA3-256, SHA3-384, and SHA3-512.
So, a couple issues. First, case (1) isn't actually compatible with
RFC 9882 (https://datatracker.ietf.org/doc/rfc9882/) which is the
specification for ML-DSA support in CMS. RFC 9882 is clear that if
there are no signed attributes, then the ML-DSA signature is computed
directly over the signed-data, not over a digest of it.
That needs to either be implemented correctly, or not at all. (If only
(2) is actually needed, then "not at all" probably would be preferable.)
Second, because the digest algorithm comes from the untrusted signature
object and the kernel is allowing different many digest algorithms,
attackers are free to search for preimages across algorithms. For
example, if an attacker can find a Streebog-512 digest that matches a
particular SHA-512 digest that was used in a valid signature, they could
forge signatures. This would only require a weakness in Streebog-512.
While the root cause of this seems to be a flaw in CMS itself, it can be
mitigated by more strictly limiting the allowed digest algorithms. The
kernel already does this for the existing signature algorithms.
For simplicity and to avoid this issue entirely, I suggest just allowing
SHA-512 only. That's the only one that RFC 9882 says MUST be supported
with ML-DSA.
- Eric
Eric Biggers <ebiggers@kernel.org> wrote: > > 1.) If the CMS object doesn't include signed attributes, then it's a > digest of the real message the caller provided. Yeah - that needs fixing, but I need to be able to test it. openssl-4.0 (at least that's what appears to be on the master branch) will have a fix for ML-DSA CMS_NOATTR support (it was committed in November), but it's not available yet unless you want to build your own. sign-file would would normally use CMS_NOATTR, and this is worked round by patch 4 in this series by using signed attributes for ML_DSA. David
Eric Biggers <ebiggers@kernel.org> wrote: > This "PKCS#7" (really CMS -- the kernel misleadingly uses the old name) I implemented PKCS#7 first and then added CMS on top of that. > That needs to either be implemented correctly, or not at all. (If only > (2) is actually needed, then "not at all" probably would be preferable.) At the time of writing, openssl didn't fully support CMS with ML-DSA, and that limited things. David
On Tue, Jan 06, 2026 at 12:02:51AM -0800, Eric Biggers wrote: > For simplicity and to avoid this issue entirely, I suggest just allowing > SHA-512 only. That's the only one that RFC 9882 says MUST be supported > with ML-DSA. That being said, this is only applicable for the case where signed attributes are used. If you can get the other case working properly and just support that case, where the real user message is what is passed to ML-DSA, that would also avoid this issue and be much simpler. - Eric
© 2016 - 2026 Red Hat, Inc.