Skip to content

Add support for ML-DSA PQC keys#19

Closed
stefanberger wants to merge 8 commits intolinux-integrity:next-testingfrom
stefanberger:mldsa
Closed

Add support for ML-DSA PQC keys#19
stefanberger wants to merge 8 commits intolinux-integrity:next-testingfrom
stefanberger:mldsa

Conversation

@stefanberger
Copy link
Copy Markdown
Contributor

This series adds support for ML-DSA PQC key support to the library and evmctl and adds test cases for signing and verifying to the sign_verfiy.test. It requires availability of OpenSSL 3.5.

@stefanberger stefanberger changed the base branch from next to next-testing June 27, 2025 15:12
@stefanberger stefanberger force-pushed the mldsa branch 7 times, most recently from 1a1ad73 to b04726b Compare June 30, 2025 19:51
@stefanberger stefanberger marked this pull request as draft July 1, 2025 21:19
@stefanberger stefanberger force-pushed the mldsa branch 8 times, most recently from 80459bf to 37929f9 Compare July 2, 2025 23:56
@stefanberger stefanberger force-pushed the mldsa branch 4 times, most recently from 372ffa0 to 22c8a2b Compare February 25, 2026 22:30
@coiby
Copy link
Copy Markdown

coiby commented Feb 26, 2026

Hi @stefanberger ,

If I understand the PR correctly, it's actually implementing Pre-Hash ML-DSA. Unfortunately, Currently CNSA 2.0 doesn't allow Pre-Hash ML-DSA,

Q: Is HashML-DSA, aka the pre-hash mode of ML-DSA, allowed in CNSA 2.0?

A: Not at the present time. HashML-DSA is a variant on ML-DSA in FIPS 204, which
describes a method of compressing a message before signing while intentionally
breaking interoperability with ML-DSA to prevent a vulnerability in the case of key re-
use. Because HashML-DSA does not offer any functionality not already offered by the
CNSA hash functions combined in a standard way with ML-DSA-87, and because
standard ML-DSA-87 is expected to be widely supported, NSA anticipates there will be
no need for HashML-DSA in NSS. Hence, at this time, HashML-DSA is not allowed. If at
a later date protocol usage demands it, NSA will provide specific guidance on its limited
usage at that time.

@stefanberger
Copy link
Copy Markdown
Contributor Author

stefanberger commented Feb 26, 2026

Hi @stefanberger ,

If I understand the PR correctly, it's actually implementing Pre-Hash ML-DSA. Unfortunately, Currently CNSA 2.0 doesn't allow Pre-Hash ML-DSA,

Yes, I am actually aware of exactly this and was wondering whether CNSA 2.0 was either incomplete/incorrect, this had to do something with timing of publication versus timing of HashML-DSA addition to standard, or this means that HashML-DSA is optional and ML-DSA can be used also for hashes. So basically this CNSA 2.0 means "forget about HashML-DSA"?? But then CNSA 2.0 is from NSA and HashML-DSA is from NIST. Left hand / right hand problem?

Q: Is HashML-DSA, aka the pre-hash mode of ML-DSA, allowed in CNSA 2.0?
A: Not at the present time. HashML-DSA is a variant on ML-DSA in FIPS 204, which
describes a method of compressing a message before signing while intentionally
breaking interoperability with ML-DSA to prevent a vulnerability in the case of key re-
use. Because HashML-DSA does not offer any functionality not already offered by the
CNSA hash functions combined in a standard way with ML-DSA-87, and because
standard ML-DSA-87 is expected to be widely supported, NSA anticipates there will be
no need for HashML-DSA in NSS. Hence, at this time, HashML-DSA is not allowed. If at
a later date protocol usage demands it, NSA will provide specific guidance on its limited
usage at that time.

@stefanberger
Copy link
Copy Markdown
Contributor Author

stefanberger commented Feb 26, 2026

"A: Not at the present time..."

... Also CNSA 2.1 could come out requiring "HashML-DSA" for products created in 202X and later -- if you can maintain backwards compatibility somehow.

@stefanberger stefanberger force-pushed the mldsa branch 2 times, most recently from a91c745 to fe98da5 Compare February 26, 2026 20:49
@stefanberger
Copy link
Copy Markdown
Contributor Author

Latest code push now uses pure-mode ML-DSA.

@stefanberger stefanberger force-pushed the mldsa branch 2 times, most recently from 6d327ef to 261c24b Compare March 4, 2026 16:10
@mimizohar
Copy link
Copy Markdown
Collaborator

Patch: "Support signing with ML-DSA keys when OpenSSL >=3.5 is available"

  • Attempts to assign -1 to siglen, instead of slen.

@mimizohar
Copy link
Copy Markdown
Collaborator

Patch: examples: Implement script to create ML-DSA-65 CA and signing keys

AI comment:

min=$(openssl version | awk '{print $2}' | cut -d. -f2)
if [ "${maj}" -lt 3 ] || [ "${min}" -lt 5 ]; then

This numeric comparison works correctly for OpenSSL 3.5.x but will fail for OpenSSL 3.10 or later — [ "10" -lt 5 ] evaluates to false correctly as a numeric comparison in POSIX sh, so this is actually safe. However the maj check has a latent issue: if maj is 4 or later, the [ "${maj}" -lt 3 ] check passes (correctly) but the [ "${min}" -lt 5 ] check may incorrectly reject a future OpenSSL 4.x release where the minor version happens to be less than 5. The two conditions should be combined more carefully:

if [ "${maj}" -lt 3 ] || { [ "${maj}" -eq 3 ] && [ "${min}" -lt 5 ]; }; then

As written, OpenSSL 4.0 would pass the major check but then fail on [ "0" -lt 5 ] — incorrectly rejecting a valid future version.

@mimizohar
Copy link
Copy Markdown
Collaborator

AI reported:

-sha256 Passed to openssl req for ML-DSA Key Generation:

shlog openssl req -verbose -new -nodes -utf8 -sha256 -days 10000 -batch -x509 \
    -newkey "$mldsa" \

As noted in patch 04's review, -sha256 is not meaningful for ML-DSA keys and should be removed. 

Notify the user which signature version will be created when first
--v3 (--v2) is passed and then switched to --v2 (--v3).

Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
@stefanberger stefanberger force-pushed the mldsa branch 3 times, most recently from 03bb46b to 4b507e2 Compare April 28, 2026 21:40
@stefanberger
Copy link
Copy Markdown
Contributor Author

Both fixed.

@stefanberger stefanberger marked this pull request as draft April 28, 2026 21:41
Comment thread src/libimaevm.c Outdated

if (*sig) {
if (siglen < (1 + sizeof(*hdr) + slen)) {
siglen = -1;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this where the comment "Attempts to assign -1 to siglen, instead of slen." belongs?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Printing an error here now but siglen = -1 is okay IMO.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

size_t is unsigned, while ssize_t is signed. siglen is defined as size_t, so it can't be -1.

Comment thread src/libimaevm.c Outdated
} else {
*sig = malloc(1 + sizeof(*hdr) + slen);
if (!*sig) {
siglen = -1;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Presumably also here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Printing an error here now but siglen = -1 is okay IMO.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as above: size_t is unsigned, while ssize_t is signed. siglen is defined as size_t, so it can't be -1.

Copy link
Copy Markdown
Collaborator

@mimizohar mimizohar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: uppress -> suppress in "Add IGNORE_EMBEDDED_FUNCTION name to checkpatch ignore list to uppress the"

Copy link
Copy Markdown
Collaborator

@mimizohar mimizohar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From AI: Missing '[' before "${min}"
error: if [ "${maj}" -lt 3 ] || ( [ "${maj}" -eq 3 ] && "${min}" -lt 5 ] ); then
corrected: if [ "${maj}" -lt 3 ] || ( [ "${maj}" -eq 3 ] && [ "${min}" -lt 5 ] ); then

Rename the suffix of temporary file from $FILE.tmp to $FILE.ima_file_id to
encode in its name that it holds and ima_file_id structure.

Fix an error message.

Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
Add IGNORE_EMBEDDED_FUNCTION name to checkpatch ignore list to suppress the
following type of warning:

WARNING: Prefer using '"%s...", __func__' to using 'create_sigv3_mldsa', \
   this function's name, in a string

Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
Set the size of an xattr_value that can be read to MAX_SIGNATURE_SIZE
so that ML-DSA keys can also be read once enabled (and MAX_SIGNATURE_SIZE
gets a larger value).

Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
Fix a memory leak by freeing pkey. Have it not report an error message
again since an error was already reported.

Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
OpenSSL >= v3.5.0 supports signing with ML-DSA-44/65/87. Add support for
it to the imaevm_create_sigv3 library function. Since the ML-DSA signatures
require a lot more space for the signature now, increase the size of the
array where the signatures are stored. The following are the sizes of
ML-DSA signatures by key type:

- ML-DSA-44: 2420
- ML-DSA-65: 3309
- ML-DSA-87: 4627

Prevent signature V2 from being created with any other key types than
'RSA', 'EC', 'GOST' (ECRDSA), or 'SM2'.

In the functions that created a v2 signature, only RSA, ECDSA, and ECRDSA
signatures are created and they can easily work with the old buffer size of
less than 1024 bytes.

The size available for extended attributes may be smaller than what is
required by the ML-DSA signature size, and therefore may not be possible
to store for example ML-DSA-87 signatures (depends on type of filesystem).
Nevertheless, extend the MAX_SIGNATURE_SIZE to the required size of
ML-DSA-87 and display an error if writing the signature of a size larger
than 4k did not work.

Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
ima-gen-local-ca-mldsa65.sh creates a CA with an ML-DSA-65 key and
ima-genkey-mldsa65.sh creates an ML-DSA-65 IMA file signing key along with
its certificate.

Also add a scripts for creating a ML-DSA-44 and ML-DSA-87 IMA file signing
keys. The latter key is good for local testing with the largest possible
signature.

Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
Create ML-DSA-44 & ML-DSA-65 keys if ML-DSA-44 can be created with the
installed version of OpenSSL. Add test cases for signing and verifying with
these types of keys.

Do not test with ML-DSA-87 keys since the signatures they create may be
too large for some filesystems' xattrs. On Btrfs for example it would be
possible to store the large signatures.

Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants