Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
aad005d
Add PQC scaffolding: ML-KEM/ML-DSA macros, names, externs, build flag
aidangarske May 23, 2026
d203979
Add ML-KEM keymgmt and KEM dispatch for 512/768/1024
aidangarske May 23, 2026
be25627
Add ML-DSA keymgmt and signature dispatch for 44/65/87
aidangarske May 23, 2026
3df1a6f
Add ML-KEM and ML-DSA unit tests + dupctx buffer copy fix
aidangarske May 23, 2026
f78fdb8
Add PQC version-compat CI: pre-PQC, latest stable, master
aidangarske May 23, 2026
c1b7c10
Add FIPS 204 ctx mode for ML-DSA + three-way interop validator in CI
aidangarske May 23, 2026
60f2cd6
Add ML-KEM and ML-DSA raw key import/export roundtrip tests
aidangarske May 23, 2026
dae5cd6
Gate PQC macros on header availability via __has_include
aidangarske May 23, 2026
0aec54f
Address Copilot review + dynamic wolfSSL version matrix with PQC floor
aidangarske May 23, 2026
618ad0a
Document ML-KEM and ML-DSA support in README and integration guide
aidangarske May 23, 2026
39e677c
Address Skoll review: input validation, consistency checks, dup selec…
aidangarske May 23, 2026
ef9ac48
Run PQC version matrix on draft PRs too (match wolfTPM behavior)
aidangarske May 23, 2026
ed58142
Use wc_mlkem.h (mlkem.h removed on wolfssl master); drop absence check
aidangarske May 23, 2026
0b04e5a
CI: diagnose OpenSSL default provider PQC support
aidangarske May 23, 2026
371c4e6
interop: use global lib ctx for default provider side (CI lib ctx fix)
aidangarske May 23, 2026
f69c064
CI: include lib64 in LD_LIBRARY_PATH so Linux finds the local libcrypto
aidangarske May 23, 2026
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
141 changes: 141 additions & 0 deletions .github/workflows/wolfssl-versions-pqc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
name: wolfSSL Versions (PQC)

# Backward-compatibility matrix for ML-KEM and ML-DSA. Mirrors wolfTPM's
# wolfssl-versions-pqc.yml pattern: a discover-versions job dynamically
# resolves the latest -stable wolfSSL tag and decides if it is past the PQC
# floor, then the build job runs three rows -- pre-PQC floor, dynamically
# resolved latest -stable, and master.
#
# PQC_FLOOR is v5.9.1-stable: the wc_MlDsaKey_* / wc_dilithium_sign_ctx_msg
# API that wolfProvider's PQC code depends on lands post-v5.9.1-stable
# (wolfSSL PR #10436), so v5.9.2-stable+ is the first PQC-eligible release.
# Older wolfSSL versions skip the PQC code paths via settings.h gating and
# only verify the no-symbol path still builds.

on:
push:
branches: [ 'master', 'main', 'release/**' ]
pull_request:
branches: [ '*' ]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
discover-versions:
name: Resolve wolfSSL version matrix
runs-on: ubuntu-22.04
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
latest-stable: ${{ steps.set-matrix.outputs.latest-stable }}
steps:
- name: Resolve latest -stable wolfSSL tag
id: set-matrix
run: |
set -euo pipefail
LATEST=$(git ls-remote --tags --refs \
https://github.com/wolfSSL/wolfssl.git 'v*-stable' \
| awk -F/ '{print $NF}' | sort -V | tail -n 1)
if [ -z "${LATEST:-}" ]; then
echo "::error::Could not resolve latest wolfSSL -stable tag"
exit 1
fi
echo "Latest stable wolfSSL: $LATEST"
echo "latest-stable=$LATEST" >> "$GITHUB_OUTPUT"
# Enable PQC when $LATEST is strictly newer than v5.9.1-stable
# (i.e. v5.9.2-stable, v5.10+, v6+, ...). Anything at or before
# the floor lacks the wc_MlDsaKey_* / wc_dilithium_sign_ctx_msg
# API and stays on the no-symbol path.
PQC_FLOOR="v5.9.1-stable"
if [ "$(printf '%s\n%s\n' "$PQC_FLOOR" "$LATEST" \
| sort -V | tail -n1)" != "$PQC_FLOOR" ]; then
LATEST_PQC_ELIGIBLE=true
else
LATEST_PQC_ELIGIBLE=false
fi
echo "latest-stable PQC eligible: $LATEST_PQC_ELIGIBLE"
MATRIX=$(jq -nc \
--arg latest "$LATEST" \
--argjson latest_pqc "$LATEST_PQC_ELIGIBLE" '{
include: [
{"name":"pre-PQC (v5.8.0-stable, PQC disabled)",
"wolfssl-ref":"v5.8.0-stable","pqc":false},
{"name":("latest stable (" + $latest + ", PQC " +
(if $latest_pqc then "enabled" else "disabled" end) + ")"),
"wolfssl-ref":$latest,"pqc":$latest_pqc},
{"name":"master (PQC enabled)",
"wolfssl-ref":"master","pqc":true}
]
}')
echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT"

pqc-build-test:
name: ${{ matrix.name }}
needs: discover-versions
runs-on: ubuntu-22.04
timeout-minutes: 30
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.discover-versions.outputs.matrix) }}
steps:
- name: Checkout wolfProvider
uses: actions/checkout@v4
with:
fetch-depth: 1

# OpenSSL is pinned to 3.5.4 on every row so the cross-provider interop
# test can verify against the default provider's native ML-KEM/ML-DSA.
# OpenSSL 3.5 is the first release with native PQC support; older 3.x
# versions can build wolfProvider but the interop step would have
# nothing to compare against on the default-provider side.
- name: Build wolfProvider (PQC=${{ matrix.pqc }})
run: |
if [ "${{ matrix.pqc }}" = "true" ]; then
OPENSSL_TAG=openssl-3.5.4 \
WOLFSSL_TAG=${{ matrix.wolfssl-ref }} \
./scripts/build-wolfprovider.sh --enable-pqc
else
OPENSSL_TAG=openssl-3.5.4 \
WOLFSSL_TAG=${{ matrix.wolfssl-ref }} \
./scripts/build-wolfprovider.sh
fi

# On PQC-enabled rows the PQC tests must be present. We do NOT assert
# absence on the no-PQC rows because v5.9.x's --enable-all-crypto now
# auto-enables MLKEM/DILITHIUM, so the "latest stable" row will pick up
# PQC at the wolfSSL level even without --enable-pqc. wolfProvider
# auto-detects and compiles in the PQC code in that case, which is fine.
- name: Confirm PQC tests present on PQC-enabled rows
if: matrix.pqc == true
run: |
./test/unit.test --list | grep -q 'test_mlkem_keygen' \
|| { echo 'ERROR: PQC tests missing in PQC-enabled build'; \
exit 1; }
./test/unit.test --list | grep -q 'test_mldsa_sign_verify' \
|| { echo 'ERROR: ML-DSA tests missing in PQC-enabled build'; \
exit 1; }

# Three-way interop: wolfProvider <-> OpenSSL default <-> wolfSSL direct.
# Only runs on PQC-enabled rows; OpenSSL 3.5+ has native ML-KEM/ML-DSA
# in the default provider, so this proves wolfProvider's bytes are
# FIPS 203/204 standards-compliant against two reference implementations.
# Linux x86_64 OpenSSL installs to lib64 by default; LD_LIBRARY_PATH
# must include both lib and lib64 or the dynamic linker falls through
# to the system libcrypto/libssl (Ubuntu 22.04 ships 3.0.2, which has
# no ML-KEM/ML-DSA in the default provider).
- name: Three-way PQC interop validation
if: matrix.pqc == true
run: |
LD_LIBRARY_PATH="$(pwd)/wolfssl-install/lib:$(pwd)/openssl-install/lib:$(pwd)/openssl-install/lib64" \
./test/pqc_interop.test

- name: Print errors on failure
if: ${{ failure() }}
run: |
if [ -f test-suite.log ]; then
cat test-suite.log
fi
1 change: 1 addition & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ PR stands for Pull Request, and PR <NUMBER> references a GitHub pull request
number where the code change was added.

## New Feature Additions
* Add ML-KEM (FIPS 203) and ML-DSA (FIPS 204) post-quantum algorithm support via `--enable-pqc` (PR 399)
* Add OpenSSL FIPS baseline process implementation (PR 357)
* Add seed-src handling for wolfProvider (PR 350)
* Add EC public key auto derivation from private key (PR 338)
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ Information on how to configure, build, and test wolfProvider can be found here:
* X25519, X448 (key exchange)
* Ed25519, Ed448 (signatures)

### Post-Quantum (NIST FIPS 203 / 204)
Requires wolfSSL master (post-v5.9.1-stable) and OpenSSL 3.5+ for native
default-provider interop. Opt in with `./scripts/build-wolfprovider.sh --enable-pqc`.

* ML-KEM (FIPS 203) — ML-KEM-512, ML-KEM-768, ML-KEM-1024 (key encapsulation)
* ML-DSA (FIPS 204) — ML-DSA-44, ML-DSA-65, ML-DSA-87 (signatures, pure mode with empty context per FIPS 204 sec 5.2)


## Support

Expand Down
54 changes: 54 additions & 0 deletions docs/INTEGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ This retrieves dependencies (OpenSSL and wolfSSL) and compiles them as necessary
| `--openssl-dir=/path` | Use existing OpenSSL installation |
| `--replace-default` | Make wolfProvider the default provider |
| `--enable-replace-default-testing` | Enable unit testing with replace-default |
| `--enable-pqc` | Enable ML-KEM and ML-DSA post-quantum algorithms (adds `--enable-mlkem --enable-dilithium --enable-experimental` to wolfSSL). Requires wolfSSL post-v5.9.1-stable. |

**Examples:**

Expand Down Expand Up @@ -82,6 +83,7 @@ sudo make install
| `--enable-pwdbased` | PKCS#12 support |
| `--enable-hmac-copy` | Faster repeated HMAC with same key (wolfSSL 5.7.8+) |
| `--enable-sp=yes,asm --enable-sp-math-all` | SP Integer maths |
| `--enable-mlkem --enable-dilithium --enable-experimental` | ML-KEM and ML-DSA post-quantum algorithms (wolfSSL post-v5.9.1-stable). The `build-wolfprovider.sh --enable-pqc` flag sets these automatically. |

**Optional CPPFLAGS:**

Expand Down Expand Up @@ -151,6 +153,58 @@ This makes replace default mode useful for testing scenarios where you want to e

---

## Post-Quantum Cryptography (ML-KEM and ML-DSA)

wolfProvider supports NIST's post-quantum algorithms via the wolfSSL backend:

| Algorithm | Standard | Parameter Sets |
|-----------|----------|----------------|
| ML-KEM (key encapsulation) | FIPS 203 | ML-KEM-512, ML-KEM-768, ML-KEM-1024 |
| ML-DSA (digital signature) | FIPS 204 | ML-DSA-44, ML-DSA-65, ML-DSA-87 |

ML-DSA uses pure mode with an empty context string (FIPS 204 sec 5.2, Algorithm 22) — interoperable with OpenSSL 3.5+'s native ML-DSA.

### Requirements

- **wolfSSL**: post-v5.9.1-stable (i.e. v5.9.2-stable or master). Older releases lack the `wc_MlDsaKey_*` and `wc_dilithium_sign_ctx_msg` API surface that wolfProvider's PQC code uses.
- **OpenSSL**: any 3.x. OpenSSL 3.5+ is required only for cross-provider interop against its native ML-KEM/ML-DSA implementations.

### Building with PQC

```bash
./scripts/build-wolfprovider.sh --enable-pqc
```

This adds `--enable-mlkem --enable-dilithium --enable-experimental` to the wolfSSL configure step. wolfProvider auto-detects the resulting `WOLFSSL_HAVE_MLKEM` / `HAVE_DILITHIUM` macros via `include/wolfprovider/settings.h` (gated on `__has_include` of the corresponding wolfSSL headers) and registers the six PQC algorithms.

### Usage Example

```bash
# Generate an ML-DSA-65 key with wolfProvider
OPENSSL_CONF=provider.conf openssl genpkey -algorithm ML-DSA-65 -out key.pem

# Sign and verify with ML-DSA-65
OPENSSL_CONF=provider.conf openssl pkeyutl -sign -inkey key.pem -in msg.bin -out sig.bin
OPENSSL_CONF=provider.conf openssl pkeyutl -verify -pubin -inkey pub.pem -sigfile sig.bin -in msg.bin
```

The OpenSSL CLI can also enumerate available algorithms:

```bash
OPENSSL_CONF=provider.conf openssl list -kem-algorithms -provider libwolfprov
OPENSSL_CONF=provider.conf openssl list -signature-algorithms -provider libwolfprov
```

### Validation

A standalone three-way interop validator (`test/pqc_interop.test`) cross-checks every ML-KEM / ML-DSA combination against:
- OpenSSL 3.5+'s native default provider
- wolfSSL's `wc_*` APIs directly (no provider abstraction)

This proves wolfProvider's raw-key, ciphertext, and signature bytes are FIPS 203 / 204 standards-compliant. The CI workflow `.github/workflows/wolfssl-versions-pqc.yml` runs this validator on every PR, plus a backward-compatibility build against pre-PQC wolfSSL to verify the no-symbol path still builds cleanly.

---

## Testing

### Unit Tests
Expand Down
40 changes: 40 additions & 0 deletions include/wolfprovider/alg_funcs.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,16 @@ typedef void (*DFUNC)(void);
#define WP_NAMES_DH "DH"
#define WP_NAMES_DHX "DHX"

/* ML-KEM names (NIST FIPS 203). */
#define WP_NAMES_ML_KEM_512 "ML-KEM-512"
#define WP_NAMES_ML_KEM_768 "ML-KEM-768"
#define WP_NAMES_ML_KEM_1024 "ML-KEM-1024"

/* ML-DSA names (NIST FIPS 204). */
#define WP_NAMES_ML_DSA_44 "ML-DSA-44"
#define WP_NAMES_ML_DSA_65 "ML-DSA-65"
#define WP_NAMES_ML_DSA_87 "ML-DSA-87"

/* DRBG names. */
#define WP_NAMES_SEED_SRC "SEED-SRC"
#define WP_NAMES_CTR_DRBG "CTR-DRBG"
Expand Down Expand Up @@ -222,6 +232,28 @@ void wp_ecx_free(wp_Ecx* ecx);
void* wp_ecx_get_key(wp_Ecx* ecx);
wolfSSL_Mutex* wp_ecx_get_mutex(wp_Ecx* ecx);

/* Internal ML-KEM types and functions. */
typedef struct wp_MlKem wp_MlKem;
typedef struct wp_MlKemData wp_MlKemData;

int wp_mlkem_up_ref(wp_MlKem* mlkem);
void wp_mlkem_free(wp_MlKem* mlkem);
void* wp_mlkem_get_key(wp_MlKem* mlkem);
const wp_MlKemData* wp_mlkem_get_data(const wp_MlKem* mlkem);
word32 wp_mlkem_data_ct_size(const wp_MlKemData* data);
/* ML-KEM shared secret size is a FIPS 203 constant (32 bytes) independent
* of the parameter set. */
#define WP_MLKEM_SS_SIZE 32

/* Internal ML-DSA types and functions. */
typedef struct wp_MlDsa wp_MlDsa;

int wp_mldsa_up_ref(wp_MlDsa* mldsa);
void wp_mldsa_free(wp_MlDsa* mldsa);
void* wp_mldsa_get_key(wp_MlDsa* mldsa);
int wp_mldsa_get_level(const wp_MlDsa* mldsa);
int wp_mldsa_get_sig_size(const wp_MlDsa* mldsa);

/* Internal DH types and functions. */
typedef struct wp_Dh wp_Dh;

Expand Down Expand Up @@ -325,12 +357,14 @@ extern const OSSL_DISPATCH wp_ed25519_signature_functions[];
extern const OSSL_DISPATCH wp_ed448_signature_functions[];
extern const OSSL_DISPATCH wp_hmac_signature_functions[];
extern const OSSL_DISPATCH wp_cmac_signature_functions[];
extern const OSSL_DISPATCH wp_mldsa_signature_functions[];

/* Asymmetric cipher implementations. */
extern const OSSL_DISPATCH wp_rsa_asym_cipher_functions[];

/* KEM implementations. */
extern const OSSL_DISPATCH wp_rsa_asym_kem_functions[];
extern const OSSL_DISPATCH wp_mlkem_asym_kem_functions[];

/* Key Management implementations. */
extern const OSSL_DISPATCH wp_rsa_keymgmt_functions[];
Expand All @@ -344,6 +378,12 @@ extern const OSSL_DISPATCH wp_dh_keymgmt_functions[];
extern const OSSL_DISPATCH wp_hmac_keymgmt_functions[];
extern const OSSL_DISPATCH wp_cmac_keymgmt_functions[];
extern const OSSL_DISPATCH wp_kdf_keymgmt_functions[];
extern const OSSL_DISPATCH wp_mlkem512_keymgmt_functions[];
extern const OSSL_DISPATCH wp_mlkem768_keymgmt_functions[];
extern const OSSL_DISPATCH wp_mlkem1024_keymgmt_functions[];
extern const OSSL_DISPATCH wp_mldsa44_keymgmt_functions[];
extern const OSSL_DISPATCH wp_mldsa65_keymgmt_functions[];
extern const OSSL_DISPATCH wp_mldsa87_keymgmt_functions[];

/* Key exchange implementations. */
extern const OSSL_DISPATCH wp_ecdh_keyexch_functions[];
Expand Down
36 changes: 36 additions & 0 deletions include/wolfprovider/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,42 @@
#ifdef HAVE_ED448
#define WP_HAVE_ED448
#endif
/* PQC: gate on both wolfSSL feature macro AND header availability. On wolfSSL
* master with --enable-all-crypto (no --enable-experimental), the feature
* macros can be defined in options.h while the mlkem.h / dilithium.h headers
* are not installed, so probe the headers too. */
#ifdef WOLFSSL_HAVE_MLKEM
#if defined(__has_include)
/* wc_mlkem.h is present in both v5.9.1-stable (alongside mlkem.h)
* and on master (where mlkem.h was removed). Probe wc_mlkem.h only. */
#if __has_include(<wolfssl/wolfcrypt/wc_mlkem.h>)
#define WP_HAVE_MLKEM
#define WP_HAVE_ML_KEM_512
#define WP_HAVE_ML_KEM_768
#define WP_HAVE_ML_KEM_1024
#endif
#else
#define WP_HAVE_MLKEM
#define WP_HAVE_ML_KEM_512
#define WP_HAVE_ML_KEM_768
#define WP_HAVE_ML_KEM_1024
#endif
#endif
#ifdef HAVE_DILITHIUM
#if defined(__has_include)
#if __has_include(<wolfssl/wolfcrypt/dilithium.h>)
#define WP_HAVE_MLDSA
#define WP_HAVE_ML_DSA_44
#define WP_HAVE_ML_DSA_65
#define WP_HAVE_ML_DSA_87
#endif
#else
#define WP_HAVE_MLDSA
#define WP_HAVE_ML_DSA_44
#define WP_HAVE_ML_DSA_65
#define WP_HAVE_ML_DSA_87
#endif
#endif
#if !defined(NO_AES_CBC) && (defined(WP_HAVE_HMAC) || defined(WP_HAVE_CMAC))
#define WP_HAVE_KBKDF
#endif
Expand Down
6 changes: 6 additions & 0 deletions scripts/build-wolfprovider.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ show_help() {
echo " --debug-silent Debug logging compiled in but silent by default. Use WOLFPROV_LOG_LEVEL and WOLFPROV_LOG_COMPONENTS env vars to enable at runtime. Requires --debug."
echo " --enable-seed-src Enable SEED-SRC entropy source with /dev/urandom caching for fork-safe entropy."
echo " Note: This also enables WC_RNG_SEED_CB in wolfSSL."
echo " --enable-pqc Build wolfSSL with ML-KEM and ML-DSA post-quantum algorithms enabled."
echo " Adds --enable-mlkem --enable-dilithium --enable-experimental to wolfSSL configure."
echo ""
echo "Environment Variables:"
echo " OPENSSL_TAG OpenSSL tag to use (e.g., openssl-3.5.0)"
Expand All @@ -51,6 +53,7 @@ show_help() {
echo " WOLFPROV_FIPS_BASELINE If set to 1, applies FIPS baseline patch to OpenSSL (mutually exclusive with WOLFPROV_REPLACE_DEFAULT)"
echo " WOLFPROV_LEAVE_SILENT If set to 1, suppress logging of return 0 in functions where return 0 is expected behavior sometimes."
echo " WOLFPROV_SEED_SRC If set to 1, enables SEED-SRC with /dev/urandom caching (also enables WC_RNG_SEED_CB in wolfSSL)"
echo " WOLFPROV_PQC If set to 1, enables ML-KEM and ML-DSA post-quantum algorithms in wolfSSL"
echo ""
}

Expand Down Expand Up @@ -146,6 +149,9 @@ for arg in "$@"; do
--enable-seed-src)
WOLFPROV_SEED_SRC=1
;;
--enable-pqc)
WOLFPROV_PQC=1
;;
*)
args_wrong+="$arg, "
;;
Expand Down
5 changes: 5 additions & 0 deletions scripts/utils-wolfssl.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ if [ "$WOLFPROV_SEED_SRC" = "1" ]; then
WOLFSSL_FIPS_CONFIG_CFLAGS="${WOLFSSL_FIPS_CONFIG_CFLAGS} -DWC_RNG_SEED_CB"
fi

# Enable ML-KEM and ML-DSA in wolfSSL when --enable-pqc is requested
if [ "$WOLFPROV_PQC" = "1" ]; then
WOLFSSL_CONFIG_OPTS="${WOLFSSL_CONFIG_OPTS} --enable-mlkem --enable-dilithium --enable-experimental"
fi

WOLFSSL_DEBUG_ASN_TEMPLATE=${DWOLFSSL_DEBUG_ASN_TEMPLATE:-0}
WOLFPROV_DISABLE_ERR_TRACE=${WOLFPROV_DISABLE_ERR_TRACE:-0}
WOLFPROV_DEBUG=${WOLFPROV_DEBUG:-0}
Expand Down
Loading
Loading