Skip to content

Add ML-KEM (FIPS 203) and ML-DSA (FIPS 204) support#361

Open
cconlon wants to merge 2 commits intowolfSSL:masterfrom
cconlon:pqc
Open

Add ML-KEM (FIPS 203) and ML-DSA (FIPS 204) support#361
cconlon wants to merge 2 commits intowolfSSL:masterfrom
cconlon:pqc

Conversation

@cconlon
Copy link
Copy Markdown
Member

@cconlon cconlon commented Apr 29, 2026

Summary

This PR adds TLS 1.3 post-quantum support to wolfssljni / wolfJSSE with ML-KEM (FIPS 203) named groups for key exchange, and ML-DSA (FIPS 204) X.509 certificate authentication. Available through standard JSSE APIs (SSLContext, SSLEngine, SSLSocket, KeyManagerFactory, TrustManagerFactory).

Feature additions

  • ML-KEM key exchange (FIPS 203) for TLS 1.3 - selectable through the standard JSSE configuration mechanisms, in this precedence order:

    1. SSLParameters.setNamedGroups() (JDK 20+, JEP 452) - per-engine, takes precedence when set.
    2. jdk.tls.namedGroups System property - JVM-wide. (JSSE convention is client-side only; wolfJSSE follows that.)
    3. wolfjsse.enabledSupportedCurves Security/system property - wolfJSSE-specific fallback that applies to both client and server, useful when running on a JDK older than 20 or when configuring server-side selection without per-engine code.

    Supported named groups:

    • Pure ML-KEM (FIPS 203 standalone, codepoints 0x0200–0x0202): ML-KEM-512, ML-KEM-768, ML-KEM-1024. Require native --enable-tls-mlkem-standalone.
    • IETF/IANA-registered PQ/T hybrids: SECP256R1MLKEM768 (0x11EB), X25519MLKEM768 (0x11EC), SECP384R1MLKEM1024 (0x11ED, CNSA 2.0 level).
    • OQS-assigned PQ/T hybrids: SECP256R1MLKEM512, SECP384R1MLKEM768, SECP521R1MLKEM1024, X25519MLKEM512, X448MLKEM768.
  • ML-DSA cert authentication (FIPS 204) - mutual auth across all three parameter sets:

    • ML-DSA-44 (Category 2)
    • ML-DSA-65 (Category 3)
    • ML-DSA-87 (Category 5, CNSA 2.0 mandated)

    Loaded through standard JCE keystores (JKS + PKCS12, JDK 24+ via JEP 497). Works on Java 8 too via a PEM-based KeyManager helper, since older JDKs have no ML-DSA KeyFactory. New loadKeyAndCertChain branch in WolfSSLEngineHelper passes the full PKCS#8 wrapper through for ML-DSA so native dispatches to the right parameter set.

Examples

The bundled ServerJSSE / ClientJSSE examples add a -pqc <named-group> flag and accept ML-DSA keystores via the existing -c (entity cert/key) and -A (trusted CA) flags.

  • examples/certs/gen-mldsa-certs.sh - produces ML-DSA-{44,65,87} root + server + client cert chains.
  • examples/provider/update-keystore-pqc.sh - produces server-, client-, and ca-mldsa<N>.{jks,p12} keystores (JDK 24+ keytool) for all three parameter sets. Server and client entity certs share one root per level so a single ca-mldsa<N> truststore validates both sides.

Native wolfSSL requirements

  • Build flags: --enable-mlkem --enable-mldsa. Add --enable-tls-mlkem-standalone for the pure ML-KEM-512/768/1024 groups (PQ/T hybrids work without it). For full coverage of every named group above, also include --enable-curve25519 (for X25519MLKEM*) and --enable-curve448 (for X448MLKEM768).
  • ML-DSA cert auth additionally requires native wolfSSL containing PR #10310 (ML-DSA SPKI / PKCS#8 in d2i_PUBKEY / d2i_PrivateKey). ML-KEM key exchange works on earlier wolfssl versions (ie: 5.9.1).

@cconlon cconlon self-assigned this Apr 29, 2026
Copilot AI review requested due to automatic review settings April 29, 2026 20:53
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds TLS 1.3 post-quantum support to wolfssljni/wolfJSSE by introducing ML-KEM named groups for key exchange and ML-DSA certificate authentication, surfaced via standard JSSE configuration paths and backed by JNI/native feature detection and key-share helpers.

Changes:

  • Added ML-KEM named-group constants/parsing + TLS 1.3 key_share pre-generation (useKeyShare) and configuration via SSLParameters.setNamedGroups() / jdk.tls.namedGroups / wolfjsse.enabledSupportedCurves.
  • Added ML-DSA certificate authentication support paths (incl. PKCS#8 handling and supported signature algorithm advertising).
  • Added extensive integration/unit tests plus example scripts and generated PQC test artifacts.

Reviewed changes

Copilot reviewed 53 out of 80 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/test/com/wolfssl/test/WolfSSLTest.java Adds unit tests for PQC named-group constants, parsing, and feature-detect JNI calls.
src/test/com/wolfssl/test/WolfSSLSessionTest.java Adds tests for useKeyShare() behavior and useSupportedCurves() aggregate-failure semantics.
src/test/com/wolfssl/provider/jsse/test/WolfSSLTestFactory.java Adjusts test harness to handle larger PQC handshake flights and adds opt-in extra debug.
src/test/com/wolfssl/provider/jsse/test/WolfSSLPQCTestUtil.java New PEM-based KeyManager/TrustManager helpers to run ML-DSA tests on older JDKs.
src/test/com/wolfssl/provider/jsse/test/WolfSSLPQCKeyExchangeTest.java New end-to-end SSLEngine ML-KEM named-group negotiation tests.
src/test/com/wolfssl/provider/jsse/test/WolfSSLPQCAuthenticationTest.java New end-to-end ML-DSA authentication tests + signature scheme formatting test.
src/test/com/wolfssl/provider/jsse/test/WolfSSLPQCAuthKeyStoreTest.java New JDK 24+ keystore-based ML-DSA authentication tests (JKS/PKCS12).
src/test/com/wolfssl/provider/jsse/test/WolfSSLJSSETestSuite.java Adds PQC test classes to the main JSSE test suite.
src/test/com/wolfssl/provider/jsse/test/WolfSSLCNSA2Test.java New composite CNSA 2.0 compliance integration tests (kex/auth/cipher/protocol).
src/java/com/wolfssl/provider/jsse/WolfSSLUtil.java Adds ML-DSA signature-scheme mapping and jdk.tls.namedGroups parsing helper.
src/java/com/wolfssl/provider/jsse/WolfSSLParametersHelper.java Reflective get/set for SSLParameters.setNamedGroups() (JDK 20+) and propagation.
src/java/com/wolfssl/provider/jsse/WolfSSLParameters.java Copies named-groups across parameter cloning (reflection-based for Java 8 baseline).
src/java/com/wolfssl/provider/jsse/WolfSSLInternalVerifyCb.java Fixes authType detection for ML-DSA vs DSA substring collision; minor formatting.
src/java/com/wolfssl/provider/jsse/WolfSSLImplementSSLSession.java Advertises ML-DSA signature algorithms in local supported signature alg list.
src/java/com/wolfssl/provider/jsse/WolfSSLEngineHelper.java Applies supported_groups config sources, filters PQC groups without TLS 1.3, and pre-generates PQC key shares.
src/java/com/wolfssl/WolfSSLSession.java Adds useKeyShare() JNI binding and improves useSupportedCurves() error aggregation.
src/java/com/wolfssl/WolfSSL.java Adds ML-KEM named-group constants, PQC feature-detect JNI APIs, public named-group parsing, and PQC-group classification helper.
native/com_wolfssl_WolfSSLSession.h Declares JNI binding for WolfSSLSession.useKeyShare.
native/com_wolfssl_WolfSSLSession.c Implements JNI useKeyShare via wolfSSL_UseKeyShare() with TLS 1.3 gating.
native/com_wolfssl_WolfSSLCertificate.c Maps native ML-DSA signature types to JSSE-visible algorithm strings.
native/com_wolfssl_WolfSSL.h Declares JNI PQC feature-detect APIs and ML-KEM named-group constants.
native/com_wolfssl_WolfSSL.c Implements JNI PQC feature-detect APIs (PQCEnabled, MLKEMEnabled, MLDSAEnabled, MLKEMOldIdsEnabled).
examples/server.sh Documents PQC usage patterns for the non-JSSE server wrapper script.
examples/provider/update-keystore-pqc.sh New script to generate ML-DSA JKS/PKCS12 keystores with JDK 24+ keytool.
examples/provider/ServerJSSE.sh Documents PQC usage patterns for JSSE server wrapper script.
examples/provider/ServerJSSE.java Adds -pqc named-group restriction support for JSSE server example.
examples/provider/ClientJSSE.sh Documents PQC usage patterns for JSSE client wrapper script.
examples/provider/ClientJSSE.java Adds -pqc named-group restriction support for JSSE client example.
examples/client.sh Documents PQC usage patterns for the non-JSSE client wrapper script.
examples/certs/pqc/server-mldsa87-priv.pem Adds generated ML-DSA-87 server private key test artifact.
examples/certs/pqc/server-mldsa65-priv.pem Adds generated ML-DSA-65 server private key test artifact.
examples/certs/pqc/server-mldsa44.pem Adds generated ML-DSA-44 server certificate test artifact.
examples/certs/pqc/server-mldsa44-priv.pem Adds generated ML-DSA-44 server private key test artifact.
examples/certs/pqc/root-mldsa87-priv.pem Adds generated ML-DSA-87 root private key test artifact.
examples/certs/pqc/root-mldsa65-priv.pem Adds generated ML-DSA-65 root private key test artifact.
examples/certs/pqc/root-mldsa44.pem Adds generated ML-DSA-44 root certificate test artifact.
examples/certs/pqc/root-mldsa44-priv.pem Adds generated ML-DSA-44 root private key test artifact.
examples/certs/pqc/client-mldsa87-priv.pem Adds generated ML-DSA-87 client private key test artifact.
examples/certs/pqc/client-mldsa65-priv.pem Adds generated ML-DSA-65 client private key test artifact.
examples/certs/pqc/client-mldsa44.pem Adds generated ML-DSA-44 client certificate test artifact.
examples/certs/pqc/client-mldsa44-priv.pem Adds generated ML-DSA-44 client private key test artifact.
examples/certs/gen-mldsa-certs.sh New OpenSSL-based generator for ML-DSA cert chains used by tests/examples.
examples/Server.java Adds -pqc support and key_share pre-generation in the non-JSSE server example.
examples/README.md Documents how to run PQC TLS 1.3 examples and requirements/limitations.
examples/Client.java Adds -pqc support and key_share pre-generation in the non-JSSE client example.
README.md Updates property docs for PQC named groups, signature schemes, and jdk.tls.namedGroups.
Comments suppressed due to low confidence (11)

src/java/com/wolfssl/WolfSSL.java:1

  • With getNamedGroupFromString now public, it should be resilient to common inputs. As written, switch (curveName) will throw NPE on null and will not handle leading/trailing whitespace, which is easy to introduce via properties (e.g., jdk.tls.namedGroups). Consider adding a null/trim normalization at the top (and optionally case normalization where safe) and returning WOLFSSL_NAMED_GROUP_INVALID for null/unparseable values instead of throwing.
    native/com_wolfssl_WolfSSLSession.c:1
  • JNI useKeyShare casts the Java int group to word16 without validating range/sign. Negative values or values > 65535 will wrap and can select an unintended group ID, producing incorrect behavior. Mandatory: validate group in JNI (and/or in the Java wrapper) to ensure it is within [0, 65535] and return BAD_FUNC_ARG (or throw IllegalArgumentException on the Java side) when invalid.
    examples/provider/update-keystore-pqc.sh:1
  • The script uses predictable /tmp paths derived from $$ for probe keystores and the scratch working directory. This is vulnerable to symlink/hardlink attacks and can also collide in some environments. Prefer mktemp/mktemp -d for both the probe file and work, and add a trap to ensure cleanup on early exit.
    examples/provider/update-keystore-pqc.sh:1
  • The script uses predictable /tmp paths derived from $$ for probe keystores and the scratch working directory. This is vulnerable to symlink/hardlink attacks and can also collide in some environments. Prefer mktemp/mktemp -d for both the probe file and work, and add a trap to ensure cleanup on early exit.
    examples/provider/update-keystore-pqc.sh:1
  • The script uses predictable /tmp paths derived from $$ for probe keystores and the scratch working directory. This is vulnerable to symlink/hardlink attacks and can also collide in some environments. Prefer mktemp/mktemp -d for both the probe file and work, and add a trap to ensure cleanup on early exit.
    src/test/com/wolfssl/provider/jsse/test/WolfSSLPQCTestUtil.java:1
  • This utility does a lot of manual I/O boilerplate (explicit close(), manual read loop, charset by name). For more robust and concise test code, consider switching to try-with-resources and Files.readAllBytes(...), and prefer StandardCharsets.UTF_8. Also, the current read loop doesn’t verify that the file was fully read (off == all.length), which can silently produce truncated PEM parsing if a short read occurs.
    src/test/com/wolfssl/provider/jsse/test/WolfSSLPQCTestUtil.java:1
  • This utility does a lot of manual I/O boilerplate (explicit close(), manual read loop, charset by name). For more robust and concise test code, consider switching to try-with-resources and Files.readAllBytes(...), and prefer StandardCharsets.UTF_8. Also, the current read loop doesn’t verify that the file was fully read (off == all.length), which can silently produce truncated PEM parsing if a short read occurs.
    src/test/com/wolfssl/provider/jsse/test/WolfSSLPQCTestUtil.java:1
  • This utility does a lot of manual I/O boilerplate (explicit close(), manual read loop, charset by name). For more robust and concise test code, consider switching to try-with-resources and Files.readAllBytes(...), and prefer StandardCharsets.UTF_8. Also, the current read loop doesn’t verify that the file was fully read (off == all.length), which can silently produce truncated PEM parsing if a short read occurs.
    src/java/com/wolfssl/provider/jsse/WolfSSLUtil.java:1
  • jdk.tls.namedGroups parsing only removes the exact substring \", \" and doesn’t trim individual tokens. Inputs like \"X25519MLKEM768, secp256r1\" (double spaces) or \"X25519MLKEM768,\tsecp256r1\" will leave whitespace and likely cause group lookup failures downstream. Consider trimming each token (or splitting on a regex like \\s*,\\s*) to make property parsing robust.
    src/java/com/wolfssl/provider/jsse/WolfSSLUtil.java:1
  • jdk.tls.namedGroups parsing only removes the exact substring \", \" and doesn’t trim individual tokens. Inputs like \"X25519MLKEM768, secp256r1\" (double spaces) or \"X25519MLKEM768,\tsecp256r1\" will leave whitespace and likely cause group lookup failures downstream. Consider trimming each token (or splitting on a regex like \\s*,\\s*) to make property parsing robust.
    src/test/com/wolfssl/provider/jsse/test/WolfSSLPQCAuthenticationTest.java:1
  • The comment below the test references “JEP 527” elsewhere in this block (ML-DSA is JEP 497 per this PR description). Please correct the JEP reference to avoid confusing future maintainers about which JDK change introduced ML-DSA.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread examples/certs/pqc/server-mldsa87-priv.pem
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 53 out of 80 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/java/com/wolfssl/provider/jsse/WolfSSLEngineHelper.java
Comment thread src/java/com/wolfssl/provider/jsse/WolfSSLUtil.java Outdated
Comment thread README.md Outdated
Copy link
Copy Markdown

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Choose a reason for hiding this comment

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

Fenrir Automated Review — PR #361

Scan targets checked: wolfssljni-bugs, wolfssljni-src

Findings: 1
1 finding(s) posted as inline comments (see file-level comments below)

This review was generated automatically by Fenrir. Findings are non-blocking.

Comment thread src/java/com/wolfssl/WolfSSLSession.java
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 54 out of 81 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (5)

examples/certs/pqc/server-mldsa87-priv.pem:1

  • The PR adds multiple ML-DSA private keys to the repository under examples/certs/pqc/. Even if intended for testing, committing private key material is a security risk (accidental reuse, downstream consumption, and secret-scanning policy violations) and also increases repo blast radius. Prefer removing the *-priv.pem files from version control and generating them at test/CI time (e.g., run gen-mldsa-certs.sh and update-keystore-pqc.sh in CI before PQC tests), keeping only public certs (or generating everything on-demand and skipping when absent).
    src/java/com/wolfssl/provider/jsse/WolfSSLUtil.java:1
  • jdk.tls.namedGroups parsing is fragile: it only removes the exact sequence ", " and will leave other whitespace (leading/trailing spaces, tabs, multiple spaces) in tokens. That can cause group names like "secp256r1" to become " secp256r1" and fail resolution downstream. Consider splitting on a whitespace-tolerant delimiter (e.g., trim each token or split on "\\s*,\\s*") and dropping empty tokens.
    src/java/com/wolfssl/WolfSSLSession.java:1
  • This introduces a nested synchronized (sslLock) inside an outer synchronized (sslLock). Java locks are re-entrant so it works, but the inner block is redundant and obscures intent. Removing the inner synchronization (or restructuring so there is only one lock acquisition per call) will simplify the control flow and avoid confusion during future changes.
    src/test/com/wolfssl/provider/jsse/test/WolfSSLPQCTestUtil.java:1
  • Unused import in this new test utility (ByteArrayInputStream) should be removed to keep the file clean and avoid checkstyle/IDE warnings.
    src/test/com/wolfssl/provider/jsse/test/WolfSSLPQCTestUtil.java:1
  • This PEM reader assumes f.length() fits in an int and that reading fills the buffer fully; if a short read occurs, the remaining bytes stay as zeros and can corrupt the Base64 payload. It would be more robust to read using a streaming approach (e.g., Files.readAllBytes / ByteArrayOutputStream) and to use StandardCharsets.UTF_8 instead of the string charset name.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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