keys: Support for ML-DSA module signing

-----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEqG5UsNXhtOCrfGQP+7dXa6fLC2sFAmmFu8gACgkQ+7dXa6fL
 C2t5UA//Zz3G9/libuvGx3tVuhaub8WQS43GNBN1h5Js0xkbGfhyBfAvGcX1xwiL
 VCrjZZsQbIp1oijr0G7P0KsGB1aVyBOYN7phLEYLsdDvqZt7mVMNSePq0xELPjMw
 tF2Ca7TIWx/GOlReInl4gxnzyBlDrYAyvrBCCU1SfQyTqDWQCbVPdFQJtJY2mY6j
 l5q2qBZ0QB4G34D5sPjYhc23kcl8BdNLzQGe9IRjVqHfDyWa1cBqAI6eQLMX3kt4
 wJp8oWVrA/89nk2IwzTPJTIRJm16df4Cpa6Frr9o4CQi+5N8uPhxpN4iEc3G6EGn
 eZ8ohCoNhsG7k+nd2tSDvp/1kmqL261+rPXcw1MiHs49mTKp4a4r62O4Hdd2jMf4
 dR0p2/jBiqeAT2jYuc6iQxfEvzTq8D6K4u0ThlUvE5EpIb2H7Gk8HcWFn5kBbnx/
 VxGTPEkzwDn1jxg1VoPg59uT/7rYWVy1MjI54EyFuWmIz7W2J/5QsKFzSSpVn9nW
 eGuGZvL+EqMPS9GqQimfnwa27RNQZ4oJKr58OqJVEoyaNPoeQO2XlFT1kHWfK3tb
 RlncfRLqbZ27qpz50InOwHQvGoEW32cnf9SQPTKQpWDXaWe2Sb1wxLcmhsyhXFah
 erP33Ea3P76+JsXlw385Q33xa4dB/7IQT0kytr1i0kKm4lDlpho=
 =KaEy
 -----END PGP SIGNATURE-----

Merge tag 'keys-next-20260206' of git://git.kernel.org/pub/scm/linux/kernel/git/dhowells/linux-fs

Pull keys update from David Howells:
 "This adds support for ML-DSA signatures in X.509 certificates and
  PKCS#7/CMS messages, thereby allowing this algorithm to be used for
  signing modules, kexec'able binaries, wifi regulatory data, etc..

  This requires OpenSSL-3.5 at a minimum and preferably OpenSSL-4 (so
  that it can avoid the use of CMS signedAttrs - but that version is not
  cut yet). certs/Kconfig does a check to hide the signing options if
  OpenSSL does not list the algorithm as being available"

* tag 'keys-next-20260206' of git://git.kernel.org/pub/scm/linux/kernel/git/dhowells/linux-fs:
  pkcs7: Change a pr_warn() to pr_warn_once()
  pkcs7: Allow authenticatedAttributes for ML-DSA
  modsign: Enable ML-DSA module signing
  pkcs7, x509: Add ML-DSA support
  pkcs7: Allow the signing algo to do whatever digestion it wants itself
  pkcs7, x509: Rename ->digest to ->m
  x509: Separately calculate sha256 for blacklist
  crypto: Add ML-DSA crypto_sig support
This commit is contained in:
Linus Torvalds 2026-02-10 09:32:30 -08:00
commit b63c907203
20 changed files with 472 additions and 70 deletions

View file

@ -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.

View file

@ -39,6 +39,39 @@ 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
depends on OPENSSL_SUPPORTS_ML_DSA
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
depends on OPENSSL_SUPPORTS_ML_DSA
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
depends on OPENSSL_SUPPORTS_ML_DSA
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
@ -154,4 +187,11 @@ config SYSTEM_BLACKLIST_AUTH_UPDATE
keyring. The PKCS#7 signature of the description is set in the key
payload. Blacklist keys cannot be removed.
config OPENSSL_SUPPORTS_ML_DSA
def_bool $(success, openssl list -key-managers | grep -q ML-DSA-87)
help
Support for ML-DSA-44/65/87 was added in openssl-3.5, so as long
as older versions are supported, the key types may only be
set after testing the installed binary for support.
endmenu

View file

@ -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 \

View file

@ -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"

View file

@ -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

View file

@ -53,6 +53,17 @@ config PKCS7_MESSAGE_PARSER
This option provides support for parsing PKCS#7 format messages for
signature data and provides the ability to verify the signature.
config PKCS7_WAIVE_AUTHATTRS_REJECTION_FOR_MLDSA
bool "Waive rejection of authenticatedAttributes for ML-DSA"
depends on PKCS7_MESSAGE_PARSER
depends on CRYPTO_MLDSA
help
Due to use of CMS_NOATTR with ML-DSA not being supported in
OpenSSL < 4.0 (and thus any released version), enabling this
allows authenticatedAttributes to be used with ML-DSA for
module signing. Use of authenticatedAttributes in this
context is normally rejected.
config PKCS7_TEST_KEY
tristate "PKCS#7 testing key type"
depends on SYSTEM_DATA_VERIFICATION

View file

@ -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,
};

View file

@ -92,14 +92,29 @@ static int pkcs7_check_authattrs(struct pkcs7_message *msg)
if (!sinfo)
goto inconsistent;
#ifdef CONFIG_PKCS7_WAIVE_AUTHATTRS_REJECTION_FOR_MLDSA
msg->authattrs_rej_waivable = true;
#endif
if (sinfo->authattrs) {
want = true;
msg->have_authattrs = true;
#ifdef CONFIG_PKCS7_WAIVE_AUTHATTRS_REJECTION_FOR_MLDSA
if (strncmp(sinfo->sig->pkey_algo, "mldsa", 5) != 0)
msg->authattrs_rej_waivable = false;
#endif
} 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 +312,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;
@ -599,8 +629,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;
}

View file

@ -55,6 +55,9 @@ struct pkcs7_message {
struct pkcs7_signed_info *signed_infos;
u8 version; /* Version of cert (1 -> PKCS#7 or CMS; 3 -> CMS) */
bool have_authattrs; /* T if have authattrs */
#ifdef CONFIG_PKCS7_WAIVE_AUTHATTRS_REJECTION_FOR_MLDSA
bool authattrs_rej_waivable; /* T if authatts rejection can be waived */
#endif
/* Content Data (or NULL) */
enum OID data_type; /* Type of Data */

View file

@ -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,26 @@ 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);
memcpy(sig->m, sinfo->authattrs, sinfo->authattrs_len);
sig->m[0] = ASN1_CONS_BIT | ASN1_SET;
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;
ret = 0;
} else {
ret = crypto_shash_digest(desc, sig->m,
sinfo->authattrs_len,
sig->m);
if (ret < 0)
goto error;
}
pr_devel("AADigest = [%*ph]\n", 8, sig->m);
}
error:
@ -137,9 +150,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);
@ -407,6 +425,12 @@ int pkcs7_verify(struct pkcs7_message *pkcs7,
return -EKEYREJECTED;
}
if (pkcs7->have_authattrs) {
#ifdef CONFIG_PKCS7_WAIVE_AUTHATTRS_REJECTION_FOR_MLDSA
if (pkcs7->authattrs_rej_waivable) {
pr_warn_once("Waived invalid module sig (has authattrs)\n");
break;
}
#endif
pr_warn("Invalid module sig (has authattrs)\n");
return -EKEYREJECTED;
}

View file

@ -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;
@ -425,8 +435,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);

View file

@ -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);
}
}

View file

@ -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 @@ ecdsa:
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;
}

View file

@ -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 */

View file

@ -31,12 +31,33 @@ 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;
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.
*/
@ -50,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)
@ -63,25 +85,15 @@ 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;
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:
crypto_free_shash(tfm);
out:
pr_devel("<==%s() = %d\n", __func__, ret);
return ret;
}

201
crypto/mldsa.c Normal file
View file

@ -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");

View file

@ -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;

View file

@ -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
};

View file

@ -27,7 +27,7 @@
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#if OPENSSL_VERSION_MAJOR >= 3
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
# define USE_PKCS11_PROVIDER
# include <openssl/provider.h>
# include <openssl/store.h>
@ -315,18 +315,39 @@ 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 |
#ifdef CMS_NO_SIGNING_TIME
CMS_NO_SIGNING_TIME |
#endif
use_keyid;
#if OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_VERSION_NUMBER < 0x40000000L
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")) {
/* ML-DSA + CMS_NOATTR is not supported in openssl-3.5
* and before.
*/
use_signed_attrs = 0;
}
#endif
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

View file

@ -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);