Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,32 @@ jobs:
jdk_version: ${{ matrix.jdk_version }}
wolfssl_configure: ${{ matrix.wolfssl_configure }}

# --------------- PQC (ML-KEM + ML-DSA) sanity check -----------------
# Builds native wolfSSL master with the full PQC option set so that
# ML-KEM standalone groups, ML-KEM PQ/T hybrids (incl. X25519MLKEM* and
# X448MLKEM768), and ML-DSA cert auth all reach the gated tests instead
# of skipping. JDK 24 is required for JEP 497 ML-DSA KeyFactory and
# exercises the standard JCE keystore path. JDK 11 also runs to cover
# the JDK-8-compatible PEM-based PQC code path. The --enable-all
# variant additionally layers wolfSSL's broad feature set on top of
# the PQC flags to catch interaction regressions.
linux-zulu-pqc:
strategy:
matrix:
os: [ 'ubuntu-latest' ]
jdk_version: [ '11', '24' ]
wolfssl_configure: [
'--enable-jni --enable-mlkem --enable-tls-mlkem-standalone --enable-mldsa --enable-curve25519 --enable-curve448 --enable-ed25519 --enable-ed448',
'--enable-jni --enable-all --enable-mlkem --enable-tls-mlkem-standalone --enable-mldsa --enable-curve25519 --enable-curve448 --enable-ed25519 --enable-ed448',
]
name: ${{ matrix.os }} (Zulu JDK ${{ matrix.jdk_version }}, PQC full)
uses: ./.github/workflows/linux-common.yml
with:
os: ${{ matrix.os }}
jdk_distro: "zulu"
jdk_version: ${{ matrix.jdk_version }}
wolfssl_configure: ${{ matrix.wolfssl_configure }}

# -------------------- WOLFJNI_USE_IO_SELECT sanity check --------------------
# Only check one Linux and Mac JDK version as a sanity check.
# Using Zulu, but this can be expanded if needed.
Expand Down
36 changes: 32 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,7 @@ and used by wolfSSL JNI/JSSE.
| wolfssl.readWriteByteBufferPool.size | 16 | Integer | Sets the read/write per-thread ByteBuffer pool size |
| wolfssl.readWriteByteBufferPool.bufferSize | 17408 | String | Sets the read/write per-thread ByteBuffer size |
| wolfjsse.enabledCipherSuites | | String | Restricts enabled cipher suites |
| wolfjsse.enabledSupportedCurves | | String | Restricts enabled ECC curves |
| wolfjsse.enabledSupportedCurves | | String | Restricts enabled named groups (ECC curves and PQC/hybrid groups) |
| wolfjsse.enabledSignatureAlgorithms | | String | Restricts enabled signature algorithms |
| wolfjsse.keystore.type.required | | String | Restricts KeyStore type |
| wolfjsse.clientSessionCache.disabled | | "true" | Disables client session cache |
Expand Down Expand Up @@ -617,16 +617,24 @@ changing this property. This should be a comma-delimited String. Example use:
wolfjsse.enabledCipherSuites=TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
```

**wolfjsse.enabledSupportedCurves (String)** - Allows setting of specific ECC
curves to be enabled for SSL/TLS connections. This propogates down to the native
**wolfjsse.enabledSupportedCurves (String)** - Allows setting of specific
named groups (ECC curves and post-quantum / hybrid groups such as ML-KEM)
to be enabled for SSL/TLS connections. This propagates down to the native
wolfSSL API `wolfSSL_UseSupportedCurve()`. If invalid/bad values are found
when processing this property, connection establishment will fail with an
SSLException. This should be a comma-delimited String. Example use:
SSLException. This should be a comma-delimited String. Both compact
(`MLKEM768`, `X25519MLKEM768`) and IETF (`ML-KEM-768`, `SecP256r1MLKEM768`)
spellings are recognized for PQC and hybrid groups when native wolfSSL was
built with `--enable-mlkem`. Example use:

```
wolfjsse.enabledSupportedCurves=secp256r1, secp521r1
```

```
wolfjsse.enabledSupportedCurves=X25519MLKEM768, SecP256r1MLKEM768, ML-KEM-768
```

**wolfjsse.enabledSignatureAlgorithms (String)** - Allows restriction of the
signature algorithms sent in the TLS ClientHello Signature Algorithms
Extension. By using/setting this property, native wolfSSL will not populate
Expand All @@ -638,6 +646,14 @@ String of signature algorithm + MAC combinations. Example use:
wolfjsse.enabledSignatureAlgorithms=RSA+SHA256:ECDSA+SHA256
```

Standalone scheme tokens (no MAC component) are also accepted, including
ED25519, ED448, and the FIPS 204 ML-DSA names (`ML-DSA-44`, `ML-DSA-65`,
`ML-DSA-87`) when native wolfSSL was built with `--enable-mldsa`:

```
wolfjsse.enabledSignatureAlgorithms=ML-DSA-87:ECDSA+SHA384
```

**wolfjsse.keystore.type.required (String)** - Can be used to specify a KeyStore
type that is required to be used. If this is set, wolfJSSE will not allow use
of any KeyStore instances that are not of this type. One use of this option
Expand Down Expand Up @@ -724,6 +740,18 @@ advertised and used by the server if set.
**jdk.tls.client.SignatureSchemes (String)** - Controls which signature algorithms are
advertised and used by the client if set.

**jdk.tls.namedGroups (String)** - Comma-delimited list of TLS named groups
to advertise. Honored alongside `SSLParameters.setNamedGroups()` API (added in
JDK 20, JDK-8281236) and the `wolfjsse.enabledSupportedCurves` Security
property. Recognizes both classical curves (`secp256r1`, `secp384r1`) and
PQC / hybrid groups (`X25519MLKEM768`, `SecP256r1MLKEM768`, `ML-KEM-768`) when
native wolfSSL was built with `--enable-mlkem`. `SSLParameters.setNamedGroups()`
takes precedence over this property when both are set.

```
java -Djdk.tls.namedGroups=X25519MLKEM768,secp256r1 ...
```

**jdk.tls.useExtendedMasterSecret (boolean)** - Can be used to enable or
disable the use of the Extended Master Secret (EMS) extension. This extension
is enabled by default, unless explicitly disabled by setting this property to
Expand Down
55 changes: 55 additions & 0 deletions examples/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ public void run(String[] args) {
boolean useSecretCallback = false; /* enable TLS 1.3 secret cb */
String keyLogFile = "sslkeylog.log"; /* output keylog file */

String pqcAlg = null; /* PQC named group, -pqc */

long session = 0; /* pointer to WOLFSSL_SESSION */
boolean resumeSession = false; /* try one session resumption */

Expand Down Expand Up @@ -219,11 +221,24 @@ public void run(String[] args) {
} else if (arg.equals("-cid")) {
useDtlsCid = 1;

} else if (arg.equals("-pqc")) {
if (args.length < i+2) {
printUsage();
}
pqcAlg = args[++i];

} else {
printUsage();
}
}

/* PQC named groups are TLS 1.3 only */
if (pqcAlg != null && sslVersion != 4) {
System.out.println("-pqc requires -v 4 (TLS 1.3); " +
"PQC named groups are TLS 1.3 only");
System.exit(1);
}

/* sort out DTLS versus TLS versions */
if (doDTLS == 1) {
if (sslVersion == 4) {
Expand Down Expand Up @@ -280,6 +295,27 @@ else if (sslVersion == 3) {
/* create context */
WolfSSLContext sslCtx = new WolfSSLContext(method);

/* Restrict TLS supported_groups extension to the single post
* quantum group passed via -pqc */
if (pqcAlg != null) {
int pqcGid = WolfSSL.getNamedGroupFromString(pqcAlg);
if (pqcGid == WolfSSL.WOLFSSL_NAMED_GROUP_INVALID) {
System.out.println("Unknown -pqc group: " + pqcAlg);
System.exit(1);
}
System.out.println("ML-KEM enabled: " + WolfSSL.MLKEMEnabled());
System.out.println("ML-DSA enabled: " + WolfSSL.MLDSAEnabled());
System.out.println("ML-KEM legacy IDs accepted: " +
WolfSSL.MLKEMOldIdsEnabled());

ret = sslCtx.useSupportedCurves(new String[] { pqcAlg });
if (ret != WolfSSL.SSL_SUCCESS) {
System.out.println("Failed to set -pqc group " + pqcAlg +
", ret = " + ret);
System.exit(1);
}
}

/* set up PSK, if being used */
if (usePsk == 1) {

Expand Down Expand Up @@ -452,6 +488,21 @@ else if (sslVersion == 3) {
/* create SSL object */
ssl = new WolfSSLSession(sslCtx);

/* Pre-send TLS 1.3 key share for the -pqc group to avoid a
* HelloRetryRequest if the server picks it. */
if (pqcAlg != null) {
int gid = WolfSSL.getNamedGroupFromString(pqcAlg);
if (WolfSSL.isPQCNamedGroup(gid)) {
ret = ssl.useKeyShare(gid);
if (ret != WolfSSL.SSL_SUCCESS &&
ret != WolfSSL.NOT_COMPILED_IN) {
System.out.println("useKeyShare failed for " + pqcAlg +
", ret = " + ret);
System.exit(1);
}
}
}

/* enable/load CRL functionality */
if (WolfSSL.isEnabledCRL() == 1) {
ret = ssl.enableCRL(WolfSSL.WOLFSSL_CRL_CHECKALL);
Expand Down Expand Up @@ -906,6 +957,10 @@ void printUsage() {
System.out.println("-P\t\tPublic Key Callbacks");
if (WolfSSL.secretCallbackEnabled())
System.out.println("-tls13secretcb\tEnable TLS 1.3 secret callback");
if (WolfSSL.MLKEMEnabled()) {
System.out.println("-pqc <alg>\tKey Share with specified " +
"post-quantum algorithm only, e.g. X25519MLKEM768");
}
System.exit(1);
}

Expand Down
110 changes: 110 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,116 @@ This will write out generated CSRs to the following directory:
examples/certs/generated/
```

## Post Quantum (ML-KEM, ML-DSA) with ServerJSSE / ClientJSSE

wolfJSSE supports TLS 1.3 post-quantum key exchange (ML-KEM / FIPS 203) and
post quantum certificate authentication (ML-DSA / FIPS 204) when native wolfSSL
has been built with `--enable-mlkem` and `--enable-mldsa`. To additionally
enable the pure (non-hybrid) `ML-KEM-512` / `ML-KEM-768` / `ML-KEM-1024` named
groups, native wolfSSL also needs `--enable-tls-mlkem-standalone`; without it
the PQ/T hybrid groups (e.g. `X25519MLKEM768`, `SECP384R1MLKEM1024`) still
work but the standalone groups are rejected at the native layer.

**Native wolfSSL version requirement (ML-DSA cert auth):** the ML-DSA cert
authentication path requires native wolfSSL containing PR
[#10310](https://github.com/wolfSSL/wolfssl/pull/10310), which added ML-DSA
SPKI / PKCS#8 DER support to `d2i_PUBKEY` / `d2i_PrivateKey`. That PR landed
after the wolfSSL 5.9.1 release tag, so a post-5.9.1 stable release is required.
The ML-KEM key-exchange path works fine on 5.9.1, only ML-DSA cert auth is
gated. On older native wolfSSL the handshake will fail with an
`SSLHandshakeException` (typically `error code: -125` on the verifier side and
`-313` on the peer).

The `ServerJSSE.sh` and `ClientJSSE.sh` examples can use the `-pqc <alg>`
option to specify a PQC named group for TLS 1.3 handshakes.

A PQC TLS 1.3 handshake can mix and match two independent pieces:

1. **Key exchange** -- pass `-pqc <named-group>` to use a post-quantum or
PQ/T-hybrid named group instead of the classical default.
2. **Certificate authentication** -- pass `-c` and/or `-A` to load ML-DSA
entity keys and roots instead of the classical defaults
(`server.jks` / `client.jks` / `ca-server.jks` / `ca-client.jks`).

The three subsections below show each common combination. All three
require `-v 4` (TLS 1.3).

### PQ-Hybrid Key Exchange with Classical Certs

To use classical RSA/ECDSA certs and only switch the key exchange to a PQ/T
hybrid group:

```
$ cd <wolfssljni_root>
$ ./examples/provider/ServerJSSE.sh -v 4 -pqc X25519MLKEM768
$ ./examples/provider/ClientJSSE.sh -h 127.0.0.1 -v 4 -pqc X25519MLKEM768
```

Other supported `-pqc` named groups (build flag dependent) include:

- **Pure ML-KEM (standalone, requires `--enable-tls-mlkem-standalone`):**
`ML-KEM-512`, `ML-KEM-768`, `ML-KEM-1024`.
- **PQ/T hybrids (default with `--enable-mlkem`):** `SECP256R1MLKEM768`,
`SECP384R1MLKEM1024` (CNSA 2.0 level), plus the OQS-assigned hybrids
`SECP256R1MLKEM512`, `SECP384R1MLKEM768`, `SECP521R1MLKEM1024`,
`X25519MLKEM512`, `X448MLKEM768`.

See `./examples/provider/ServerJSSE.sh -?` for the full list at runtime.

### ML-DSA Server Cert Authentication

To replace the classical server cert with an ML-DSA cert, pass an ML-DSA
keystore via `-c` (entity cert/key) on the server, and the matching truststore
via `-A` (trusted CA) on the client. The client still uses the default
classical `client.jks` for client auth:

```
$ ./examples/provider/ServerJSSE.sh -v 4 \
-pqc SECP384R1MLKEM1024 -l TLS_AES_256_GCM_SHA384 \
-c "../provider/server-mldsa87.jks:wolfSSL test"
$ ./examples/provider/ClientJSSE.sh -h 127.0.0.1 -v 4 \
-pqc SECP384R1MLKEM1024 -l TLS_AES_256_GCM_SHA384 \
-A "../provider/ca-mldsa87.jks:wolfSSL test"
```

### Mutual ML-DSA Authentication

For mutual authentication with ML-DSA certs, ServerJSSE verifies the client
by default, so just add `-c client-mldsa<N>.jks` on the client and the shared
truststore (`-A ca-mldsa<N>.jks`) on both sides:

```
$ ./examples/provider/ServerJSSE.sh -v 4 \
-pqc SECP384R1MLKEM1024 -l TLS_AES_256_GCM_SHA384 \
-c "../provider/server-mldsa87.jks:wolfSSL test" \
-A "../provider/ca-mldsa87.jks:wolfSSL test"
$ ./examples/provider/ClientJSSE.sh -h 127.0.0.1 -v 4 \
-pqc SECP384R1MLKEM1024 -l TLS_AES_256_GCM_SHA384 \
-c "../provider/client-mldsa87.jks:wolfSSL test" \
-A "../provider/ca-mldsa87.jks:wolfSSL test"
```

### Example Keystores

Example ML-DSA keystores can be found under `examples/provider/`:
`server-mldsa{44,65,87}.jks`, `client-mldsa{44,65,87}.jks`, and
`ca-mldsa{44,65,87}.jks` (password `wolfSSL test`). The server and client
entity certs at each level are signed by the same root, so a single
`ca-mldsa<N>.jks` truststore validates both sides.

JKS paths in the examples above are relative to the wrapper's working
directory (`examples/build/`). Loading the ML-DSA private keys from a JKS
requires JDK 24 or newer (JEP 497). To regenerate the keystores, run
`./examples/provider/update-keystore-pqc.sh` (also requires JDK 24+).

### CNSA 2.0 Compliance

CNSA 2.0 (NSA Commercial National Security Algorithm Suite 2.0) mandates
TLS 1.3 + ML-KEM-1024 (or a hybrid containing it) + AES-256-GCM + ML-DSA-87
cert auth. The "Mutual ML-DSA Authentication" example above (level **87**,
`SECP384R1MLKEM1024`, `TLS_AES_256_GCM_SHA384`) is the full CNSA 2.0
recipe.

## Support

Please contact the wolfSSL support team at support@wolfssl.com with any
Expand Down
Loading
Loading