Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 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
c2bd794
PQC: canonical ML-KEM/ML-DSA APIs + address review feedback
aidangarske May 29, 2026
ec3e26d
PQC: address skoll review (empty msg, NULL-key reinit, KEM mixed-NULL…
aidangarske May 30, 2026
65953c2
PQC: address review nits (drop experimental mentions, early-return in…
aidangarske Jun 4, 2026
8f4e35c
Add PQC OpenSSL-vector KAT CI, ML-DSA message API, seed/ikme keygen p…
aidangarske Jun 4, 2026
343d15c
Make ML-KEM/ML-DSA opt-in (--enable-pqc/--enable-mlkem/--enable-mldsa…
aidangarske Jun 5, 2026
164174c
KAT CI: add --enable-openssl-test so replace-default builds OpenSSL e…
aidangarske Jun 5, 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
164 changes: 164 additions & 0 deletions .github/workflows/wolfssl-pqc-kat.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
name: wolfSSL PQC KAT (OpenSSL vectors)

# Runs OpenSSL's own ML-KEM (FIPS 203) and ML-DSA (FIPS 204) EVP KAT vectors
# (NIST ACVP + Wycheproof, 2602 sub-tests) through wolfProvider using
# OpenSSL's own evp_test harness, unmodified. Closely mirrors the version
# matrix of wolfssl-versions-pqc.yml: a discover job resolves the latest
# wolfSSL -stable tag and the latest OpenSSL release, then the test job runs
# the full KAT under four configurations per PQC-eligible wolfSSL version:
# replace-default (wolfProvider is the compile-time default)
# non-replace (wolfProvider loaded via provider.conf)
# each in normal mode (every file must pass) and force-fail anti-test mode
# (every file must fail, proving wolfProvider truly served the crypto). The
# force_fail axis lives in the matrix; the test step reports a raw result and
# check-workflow-result.sh inverts the expectation, matching the OSP workflows.

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

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

permissions:
contents: read

# TODO(revert before merge): the malformed-key vectors in OpenSSL's PQC KAT
# set only pass with the wolfcrypt FIPS 203/204 decode-validation fix, which
# currently lives on aidangarske/wolfssl branch pqc-decode-validation. The PQC
# rows therefore build wolfSSL from that fork/branch (matrix wolfssl-git).
# Once the fix merges to wolfSSL master, point the PQC rows back at the
# upstream repo and the dynamic latest-stable / master refs (see discover job).

jobs:
discover-versions:
name: Resolve wolfSSL/OpenSSL matrix
runs-on: ubuntu-22.04
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
openssl-tag: ${{ steps.set-matrix.outputs.openssl-tag }}
steps:
- name: Resolve latest wolfSSL -stable and latest OpenSSL release
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
# Latest stable OpenSSL release (exclude pre-releases). The PQC KAT
# vectors require OpenSSL 3.5+ (first release with native ML-KEM /
# ML-DSA and the 30-test_evp_data PQC files).
OSSL=$(git ls-remote --tags --refs \
https://github.com/openssl/openssl.git 'openssl-3.*' \
| awk -F/ '{print $NF}' | grep -E '^openssl-3\.[0-9.]+$' \
| sort -V | tail -n 1)
if [ -z "${OSSL:-}" ]; then
echo "::error::Could not resolve latest OpenSSL release tag"
exit 1
fi
echo "Latest stable wolfSSL: $LATEST"
echo "Latest OpenSSL: $OSSL"
echo "openssl-tag=$OSSL" >> "$GITHUB_OUTPUT"
PQC_FLOOR="v5.9.1-stable"
if [ "$(printf '%s\n%s\n' "$PQC_FLOOR" "$LATEST" \
| sort -V | tail -n1)" != "$PQC_FLOOR" ]; then
LATEST_PQC=true
else
LATEST_PQC=false
fi
# Each PQC version expands to replace-default x non-replace, each in
# normal and force-fail anti-test mode (the matrix owns the force-fail
# axis; the test step hands its raw result to check-workflow-result.sh
# which inverts the expectation). The pre-PQC row builds only.
#
# TODO(revert before merge): the PQC rows build the fork branch
# pqc-decode-validation. Restore upstream and the latest-stable /
# master refs once the wolfcrypt fix is on wolfSSL master.
UPSTREAM="https://github.com/wolfSSL/wolfssl.git"
FORK="https://github.com/aidangarske/wolfssl.git"
MATRIX=$(jq -nc \
--arg latest "$LATEST" \
--arg upstream "$UPSTREAM" \
--arg fork "$FORK" \
--argjson latest_pqc "$LATEST_PQC" '
def rows($git; $ref; $pqc; $lbl):
if $pqc then
[ {"replace":true,"ff":"WOLFPROV_FORCE_FAIL=1",
"sfx":" [replace-default] [force-fail]"},
{"replace":true,"ff":"","sfx":" [replace-default]"},
{"replace":false,"ff":"WOLFPROV_FORCE_FAIL=1",
"sfx":" [non-replace] [force-fail]"},
{"replace":false,"ff":"","sfx":" [non-replace]"} ]
| map({"name":($lbl+.sfx),"wolfssl-git":$git,
"wolfssl-ref":$ref,"pqc":true,"replace":.replace,
"force_fail":.ff})
else
[ {"name":($lbl+" [build-only]"),"wolfssl-git":$git,
"wolfssl-ref":$ref,"pqc":false,"replace":false,
"force_fail":""} ]
end;
{ include:
( rows($upstream; "v5.8.0-stable"; false; "pre-PQC v5.8.0-stable")
+ rows($fork; "pqc-decode-validation"; $latest_pqc;
("latest stable " + $latest))
+ rows($fork; "pqc-decode-validation"; true; "master") )
}')
echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT"

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

- name: Build wolfProvider (PQC=${{ matrix.pqc }}, replace=${{ matrix.replace }})
run: |
ARGS="--enable-pqc"
if [ "${{ matrix.replace }}" = "true" ]; then
ARGS="$ARGS --replace-default"
fi
if [ "${{ matrix.pqc }}" != "true" ]; then
ARGS=""
fi
# The KAT runs OpenSSL's evp_test; replace-default builds omit it
# ('no-tests') unless we ask for it here.
if [ "${{ matrix.pqc }}" = "true" ]; then
ARGS="$ARGS --enable-openssl-test"
fi
WOLFSSL_GIT=${{ matrix.wolfssl-git }} \
OPENSSL_TAG=${{ needs.discover-versions.outputs.openssl-tag }} \
WOLFSSL_TAG=${{ matrix.wolfssl-ref }} \
./scripts/build-wolfprovider.sh $ARGS

# Runs all 2602 OpenSSL ML-KEM/ML-DSA vectors. In normal mode every file
# must pass and the full count must run; in force-fail mode the run fails
# and check-workflow-result.sh inverts that to a pass, proving wolfProvider
# genuinely served the crypto with no silent OpenSSL fallback.
- name: PQC KAT (all 2602 OpenSSL vectors)
if: matrix.pqc == true
shell: bash
run: |
set +e
# The build step already built the provider with --enable-pqc; the KAT
# only runs against it (no rebuild). Force-fail is a runtime env var.
export ${{ matrix.force_fail }}
./scripts/test-pqc-kat.sh
TEST_RESULT=$?
$GITHUB_WORKSPACE/.github/scripts/check-workflow-result.sh \
$TEST_RESULT "${{ matrix.force_fail }}" pqc-kat
158 changes: 158 additions & 0 deletions .github/workflows/wolfssl-versions-pqc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
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 is opt-in (--enable-pqc). PQC_FLOOR is v5.9.1-stable: the wc_MlDsaKey_*
# seed/message API 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.
# PQC rows build with --enable-pqc against the latest OpenSSL (>= 3.6 required);
# older/no-flag rows build without it and verify PQC is absent (opt-in).

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 }}
openssl-tag: ${{ steps.set-matrix.outputs.openssl-tag }}
steps:
- name: Resolve latest -stable wolfSSL tag and latest OpenSSL release
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
# PQC needs OpenSSL 3.6+, so always build against the latest release.
OSSL=$(git ls-remote --tags --refs \
https://github.com/openssl/openssl.git 'openssl-3.*' \
| awk -F/ '{print $NF}' | grep -E '^openssl-3\.[0-9.]+$' \
| sort -V | tail -n 1)
if [ -z "${OSSL:-}" ]; then
echo "::error::Could not resolve latest OpenSSL release tag"
exit 1
fi
echo "Latest stable wolfSSL: $LATEST"
echo "Latest OpenSSL: $OSSL"
echo "latest-stable=$LATEST" >> "$GITHUB_OUTPUT"
echo "openssl-tag=$OSSL" >> "$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"
# Each row carries the build flag (enable) and which PQC test families
# must result (expect: both | mlkem | mldsa | none). This exercises the
# combined, per-algorithm, and opt-in-absent paths.
MATRIX=$(jq -nc \
--arg latest "$LATEST" \
--argjson latest_pqc "$LATEST_PQC_ELIGIBLE" '{
include: [
{"name":"pre-PQC (v5.8.0-stable)",
"wolfssl-ref":"v5.8.0-stable","enable":"","expect":"none"},
{"name":("latest stable (" + $latest + ")"),"wolfssl-ref":$latest,
"enable":(if $latest_pqc then "--enable-pqc" else "" end),
"expect":(if $latest_pqc then "both" else "none" end)},
{"name":"master (--enable-pqc)",
"wolfssl-ref":"master","enable":"--enable-pqc","expect":"both"},
{"name":"master (no flag, opt-in check)",
"wolfssl-ref":"master","enable":"","expect":"none"},
{"name":"master (--enable-mlkem only)",
"wolfssl-ref":"master","enable":"--enable-mlkem","expect":"mlkem"},
{"name":"master (--enable-mldsa only)",
"wolfssl-ref":"master","enable":"--enable-mldsa","expect":"mldsa"}
]
}')
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

# Every row builds against the latest OpenSSL release. PQC needs OpenSSL
# 3.6+ (and the enable flag enforces that floor); the interop step then
# compares wolfProvider against that release's native ML-KEM/ML-DSA.
- name: Build wolfProvider (${{ matrix.enable || 'no PQC flag' }})
run: |
OPENSSL_TAG=${{ needs.discover-versions.outputs.openssl-tag }} \
WOLFSSL_TAG=${{ matrix.wolfssl-ref }} \
./scripts/build-wolfprovider.sh ${{ matrix.enable }}

# Opt-in is per-algorithm: assert exactly the expected PQC test families
# are present (both / mlkem / mldsa / none). This catches a leaked
# algorithm, a missing one, or PQC dragged in without a flag.
- name: Verify PQC test presence matches opt-in (${{ matrix.expect }})
run: |
tests=$(./test/unit.test --list) || exit 1
kem=0; dsa=0
printf '%s\n' "$tests" | grep -q 'test_mlkem_keygen' && kem=1
printf '%s\n' "$tests" | grep -q 'test_mldsa_sign_verify' && dsa=1
echo "expect=${{ matrix.expect }} mlkem=$kem mldsa=$dsa"
case "${{ matrix.expect }}" in
both) [ "$kem" = 1 ] && [ "$dsa" = 1 ] ;;
mlkem) [ "$kem" = 1 ] && [ "$dsa" = 0 ] ;;
mldsa) [ "$kem" = 0 ] && [ "$dsa" = 1 ] ;;
none) [ "$kem" = 0 ] && [ "$dsa" = 0 ] ;;
*) false ;;
esac || { echo "ERROR: PQC test families do not match expect=${{ matrix.expect }}"; exit 1; }

# Three-way interop: wolfProvider <-> OpenSSL default <-> wolfSSL direct.
# Only runs on PQC-enabled rows; the latest OpenSSL (3.6+, the PQC floor)
# 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.expect == 'both'
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
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,20 @@ 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)
PQC is opt-in and requires wolfSSL master/v5.9.2-stable+ and OpenSSL 3.6+.

* With the script: `./scripts/build-wolfprovider.sh --enable-pqc`
(or `--enable-mlkem` / `--enable-mldsa` for one only)
* Building wolfProvider directly: `./configure --enable-pqc`
(or `--enable-mlkem` / `--enable-mldsa`); build wolfSSL with the matching
`--enable-mlkem` / `--enable-mldsa` and link an OpenSSL 3.6+

Without an enable flag no PQC code is compiled, regardless of what wolfSSL enables.

* 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
Loading
Loading