Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 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
c0e556f
Add pure ML-KEM TLS groups (MLKEM512/768/1024) with encoded-public-ke…
aidangarske Jun 5, 2026
9a91a59
Add hybrid PQC TLS groups (X25519MLKEM768, SecP256r1MLKEM768, SecP384…
aidangarske Jun 5, 2026
ef4602c
PQC interop: add TLS 1.3 handshake group interop (wolfProvider <-> de…
aidangarske Jun 5, 2026
8b190ba
Add ML-DSA encoder/decoder (PEM/DER SPKI+PKCS8) so ML-DSA keys and ce…
aidangarske Jun 5, 2026
f19fb30
Register ML-DSA TLS signature algorithms (mldsa44/65/87) so ML-DSA ce…
aidangarske Jun 5, 2026
f2c1a1f
Provide ML-DSA X509 signature AlgorithmIdentifier so wolfProvider can…
aidangarske Jun 5, 2026
2a43b3d
Register ML-DSA keymgmt OID/short-name aliases so ML-DSA CA certs val…
aidangarske Jun 5, 2026
fe77e16
Add nginx PQC OSP CI: ML-DSA cert auth + ML-KEM/hybrid KEX via nginx …
aidangarske Jun 5, 2026
6c9e411
nginx PQC OSP: run oqs-demos nginx in Docker with wolfProvider swappe…
aidangarske Jun 5, 2026
e4f1839
Add unit tests for ML-DSA encoder/decoder, ML-DSA X.509 signing, and …
aidangarske Jun 5, 2026
282e5ec
PQC interop: add ML-DSA certificate TLS authentication test (sign+ver…
aidangarske Jun 5, 2026
dc2cd4f
PQC security review fixes: zeroize seed/entropy/randomizer on free, a…
aidangarske Jun 5, 2026
31bd1df
PQC security review round 2: apply+validate keygen seed params at ini…
aidangarske Jun 5, 2026
368f26b
PQC security review round 3: use sigSize as authoritative capacity, d…
aidangarske Jun 5, 2026
0c27517
PQC security review round 4: fix ML-KEM KEM out-of-bounds scrub on un…
aidangarske Jun 5, 2026
49260ae
PQC security review round 5: copy IKME on KEM dupctx, advertise ML-DS…
aidangarske Jun 5, 2026
0f9d2a5
PQC security review round 6: reject NULL ML-DSA message update with n…
aidangarske Jun 5, 2026
32ff44d
nginx PQC test: lengthen HTTP response wait to avoid handshake-timing…
aidangarske Jun 5, 2026
009a00c
nginx PQC CI: skip pre-PQC-floor wolfSSL stable (master-only until v5…
aidangarske Jun 6, 2026
fd9083b
nginx PQC test: rename GROUPS->KEX_GROUPS (GROUPS is a bash special a…
aidangarske Jun 6, 2026
39fa5d2
nginx PQC: add replace-default/non-replace as a matrix axis (4 modes …
aidangarske Jun 6, 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
9 changes: 9 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Build artifacts from build-wolfprovider.sh are re-created inside the image;
# never ship them in the Docker build context (used by .github/nginx/Dockerfile).
openssl-source
openssl-install
wolfssl-source
wolfssl-install
wolfprov-install
**/*.bak
.git
93 changes: 93 additions & 0 deletions .github/nginx/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# oqs-demos nginx, but with wolfProvider in place of liboqs + oqs-provider.
#
# Mirrors https://github.com/open-quantum-safe/oqs-demos/tree/main/nginx: build
# a quantum-safe OpenSSL provider, build nginx against that OpenSSL, generate an
# ML-DSA CA + server certificate, and serve TLS 1.3 with PQC groups. The only
# swap is the crypto provider: wolfProvider (backed by wolfSSL) instead of
# oqs-provider, restricted to the FIPS 203 / FIPS 204 algorithms wolfProvider
# implements. The default groups and SIG_ALG below are the wolfProvider-
# supported subset of the oqs-demos defaults.
#
# Build context is the wolfProvider repository root:
# docker build -f .github/nginx/Dockerfile -t wolfprov-nginx .

ARG WOLFSSL_REF=master
ARG OPENSSL_REF=openssl-3.6.0
ARG NGINX_VERSION=1.28.0
ARG SIG_ALG=ML-DSA-65
ARG DEFAULT_GROUPS=MLKEM512:MLKEM768:MLKEM1024:X25519MLKEM768:SecP256r1MLKEM768:SecP384r1MLKEM1024
ARG REPLACE_DEFAULT_FLAG=--replace-default
ARG WP_OPENSSL_CONF=/opt/wolfProvider/openssl-install/ssl/openssl.cnf

FROM ubuntu:22.04 AS build
ARG WOLFSSL_REF
ARG OPENSSL_REF
ARG NGINX_VERSION
ARG SIG_ALG
ARG DEFAULT_GROUPS
ARG REPLACE_DEFAULT_FLAG
ARG WP_OPENSSL_CONF
ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential git cmake autoconf automake libtool pkg-config \
libpcre3-dev zlib1g-dev wget ca-certificates perl python3 \
&& rm -rf /var/lib/apt/lists/*

# Build OpenSSL 3.6+, wolfSSL, and wolfProvider. REPLACE_DEFAULT_FLAG is
# --replace-default (wolfProvider is the compile-time default) or empty
# (wolfProvider is a loadable module selected at runtime via provider.conf).
# The matrix passes both REPLACE_DEFAULT_FLAG and the matching WP_OPENSSL_CONF.
COPY . /opt/wolfProvider
WORKDIR /opt/wolfProvider
RUN OPENSSL_TAG=${OPENSSL_REF} WOLFSSL_TAG=${WOLFSSL_REF} \
./scripts/build-wolfprovider.sh --enable-pqc ${REPLACE_DEFAULT_FLAG}

ENV WOLFPROV_ROOT=/opt/wolfProvider
ENV O=/opt/wolfProvider/openssl-install

# Build nginx against the wolfProvider-backed OpenSSL. The download and build
# run BEFORE LD_LIBRARY_PATH points at the wolfProvider OpenSSL: otherwise
# wget's own HTTPS to nginx.org would route through wolfProvider and fail
# certificate verification. nginx finds the libs at runtime via -rpath.
WORKDIR /opt/wolfProvider
RUN wget -q "https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz" \
&& tar -zxf "nginx-${NGINX_VERSION}.tar.gz" && rm "nginx-${NGINX_VERSION}.tar.gz"
WORKDIR /opt/wolfProvider/nginx-${NGINX_VERSION}
RUN ./configure --prefix=/opt/nginx --with-http_ssl_module \
--with-cc-opt="-I${O}/include" \
--with-ld-opt="-L${O}/lib -L${O}/lib64 -Wl,-rpath,${O}/lib -Wl,-rpath,${O}/lib64 -Wl,-rpath,/opt/wolfProvider/wolfprov-install/lib -Wl,-rpath,/opt/wolfProvider/wolfssl-install/lib" \
&& make -j"$(nproc)" && make install

# From here on cert generation and the runtime server go through wolfProvider.
# In replace-default builds WP_OPENSSL_CONF is the stock openssl.cnf (a no-op);
# in non-replace builds it is provider.conf, which activates only wolfProvider
# so the PQC crypto is genuinely served by wolfSSL, not OpenSSL's native PQC.
ENV LD_LIBRARY_PATH=/opt/wolfProvider/wolfprov-install/lib:/opt/wolfProvider/wolfssl-install/lib:/opt/wolfProvider/openssl-install/lib:/opt/wolfProvider/openssl-install/lib64
ENV OPENSSL_CONF=${WP_OPENSSL_CONF}
ENV OPENSSL_MODULES=/opt/wolfProvider/wolfprov-install/lib

# Generate the ML-DSA CA + server certificate through wolfProvider (the oqs-demos
# SIG_ALG=mldsa65 arrangement) and lay down the PQC nginx config + test page.
WORKDIR /opt/nginx
COPY .github/nginx/nginx.conf /opt/nginx/conf/nginx.conf
RUN mkdir -p pki cacert logs html \
&& echo "wolfProvider quantum-safe nginx" > html/index.html \
&& "${O}/bin/openssl" req -x509 -new -newkey "${SIG_ALG}" -nodes \
-keyout cacert/CA.key -out cacert/CA.crt -subj "/CN=wolfProvider PQC CA" \
-days 365 -addext basicConstraints=critical,CA:TRUE \
-addext keyUsage=critical,keyCertSign \
&& "${O}/bin/openssl" genpkey -algorithm "${SIG_ALG}" -out pki/server.key \
&& "${O}/bin/openssl" req -new -key pki/server.key -subj "/CN=oqs-nginx" \
-addext subjectAltName=DNS:localhost -out server.csr \
&& "${O}/bin/openssl" x509 -req -in server.csr -CA cacert/CA.crt -CAkey cacert/CA.key \
-CAcreateserial -days 365 -copy_extensions copy -out pki/server.crt

COPY .github/nginx/test.sh /opt/nginx/test.sh
RUN chmod +x /opt/nginx/test.sh

ENV DEFAULT_GROUPS=${DEFAULT_GROUPS}
EXPOSE 4433

# Default: serve. The CI test step overrides this with test.sh.
CMD ["/opt/nginx/sbin/nginx", "-c", "/opt/nginx/conf/nginx.conf", "-g", "daemon off;"]
39 changes: 39 additions & 0 deletions .github/nginx/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
worker_processes auto;

events {
worker_connections 1024;
}

http {
# No mime.types include: the relative path is install-layout dependent and
# the test serves a single plain page, so default_type is enough.
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;

# Quantum-safe HTTPS server: ML-DSA certificate (FIPS 204) authentication
# and ML-KEM / hybrid groups (FIPS 203) for key exchange, served through
# wolfProvider. The group set is the wolfProvider-supported subset of the
# oqs-demos nginx DEFAULT_GROUPS.
server {
listen 0.0.0.0:4433 ssl;
server_name localhost;

access_log /opt/nginx/logs/access.log;
error_log /opt/nginx/logs/error.log;

ssl_certificate /opt/nginx/pki/server.crt;
ssl_certificate_key /opt/nginx/pki/server.key;

ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;

ssl_protocols TLSv1.3;
ssl_ecdh_curve MLKEM512:MLKEM768:MLKEM1024:X25519MLKEM768:SecP256r1MLKEM768:SecP384r1MLKEM1024;

location / {
root html;
index index.html index.htm;
}
}
}
52 changes: 52 additions & 0 deletions .github/nginx/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/bin/bash
#
# oqs-demos nginx connection test, wolfProvider edition. Starts the quantum-safe
# nginx and connects once per supported group (the s_client / HTTP GET
# equivalent of oqs-demos testrun.py's "curl --cacert CA.crt --curves <kem>"),
# asserting the negotiated PQC group, the ML-DSA peer signature, a verified
# chain, and that the page is served. Exits non-zero if any group fails; the CI
# step inverts that under WOLFPROV_FORCE_FAIL=1.

O=/opt/wolfProvider/openssl-install
CA=/opt/nginx/cacert/CA.crt
PORT=4433
# NB: not "GROUPS" -- that is a bash special array (the user's group IDs).
KEX_GROUPS="X25519MLKEM768 SecP256r1MLKEM768 SecP384r1MLKEM1024 MLKEM512 MLKEM768 MLKEM1024"

export LD_LIBRARY_PATH="/opt/wolfProvider/wolfprov-install/lib:/opt/wolfProvider/wolfssl-install/lib:${O}/lib:${O}/lib64${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"

echo "Quantum-safe groups under test: ${KEX_GROUPS}"
/opt/nginx/sbin/nginx -c /opt/nginx/conf/nginx.conf
sleep 2
if ! "${O}/bin/openssl" s_client -connect "localhost:${PORT}" </dev/null \
>/dev/null 2>&1; then
echo "WARNING: nginx did not answer on ${PORT}; recent error log:"
tail -n 5 /opt/nginx/logs/error.log 2>/dev/null
fi

fail=0
for g in ${KEX_GROUPS}; do
out=$( (printf 'GET / HTTP/1.0\r\nHost: localhost\r\n\r\n'; sleep 1) \
| "${O}/bin/openssl" s_client -connect "localhost:${PORT}" \
-groups "${g}" -CAfile "${CA}" -servername localhost 2>&1)

if echo "${out}" | grep -q "Negotiated TLS1.3 group: ${g}" \
&& echo "${out}" | grep -qi "Peer signature type: mldsa65" \
&& echo "${out}" | grep -q "Verify return code: 0 (ok)" \
&& echo "${out}" | grep -q "wolfProvider quantum-safe nginx"; then
echo "PASS: ${g} (ML-DSA auth + quantum-safe key exchange)"
else
echo "FAIL: ${g}"
echo "${out}" | grep -iE "group|signature|verify return|alert|error" | head -4
fail=1
fi
done

/opt/nginx/sbin/nginx -c /opt/nginx/conf/nginx.conf -s stop 2>/dev/null

if [ "${fail}" -eq 0 ]; then
echo "All quantum-safe groups served successfully."
else
echo "One or more quantum-safe groups failed."
fi
exit "${fail}"
135 changes: 135 additions & 0 deletions .github/workflows/nginx-pqc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
name: Nginx PQC Tests

# Runs the open-quantum-safe oqs-demos nginx demo with wolfProvider swapped in
# for liboqs + oqs-provider (.github/nginx/Dockerfile), then runs oqs-demos'
# own connection test (.github/nginx/test.sh): connect once per quantum-safe
# group and assert ML-DSA (FIPS 204) certificate authentication plus an
# ML-KEM / hybrid (FIPS 203) key exchange.
#
# Only the PQC path is exercised. Each wolfSSL ref runs in 4 modes:
# replace-default and non-replace (wolfProvider loaded via provider.conf), each
# in normal and force-fail. Versioning matches the other PQC workflows: wolfSSL
# latest -stable (PQC floor v5.9.2-stable) and master, against the latest
# OpenSSL 3.x release. The OSP source (nginx) is pinned to a fixed release in
# the Dockerfile -- upstream master is unstable and non-reproducible.

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/OpenSSL versions
runs-on: ubuntu-22.04
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
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 "openssl-tag=$OSSL" >> "$GITHUB_OUTPUT"
# PQC needs the wc_MlDsaKey_* seed/message API that lands after
# v5.9.1-stable, so the floor is v5.9.2-stable. master is always
# eligible; the latest -stable row is only added once it is past the
# floor (i.e. v5.9.2-stable+ is released). Until then only master
# runs -- a pre-floor -stable would just fail the --enable-pqc gate.
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 eligible wolfSSL ref expands to 4 rows: replace-default and
# non-replace, each in normal and force-fail anti-test mode. The
# matrix owns the replace-default and force-fail axes; the test step
# hands its raw result to check-workflow-result.sh which inverts the
# force-fail expectation.
MATRIX=$(jq -nc --arg latest "$LATEST" \
--argjson latest_pqc "$LATEST_PQC_ELIGIBLE" '
def rows($ref; $lbl):
[ {"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-ref":$ref,
"replace":.replace, "force_fail":.ff});
{ include: (
rows("master"; "master")
+ (if $latest_pqc
then rows($latest; ("latest stable (" + $latest + ")"))
else [] end)
) }')
echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT"

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

# Build the oqs-demos nginx image with wolfProvider in place of
# oqs-provider. nginx is pinned to release-1.28.0 in the Dockerfile.
- name: Build quantum-safe nginx image
run: |
docker build \
--build-arg WOLFSSL_REF=${{ matrix.wolfssl-ref }} \
--build-arg OPENSSL_REF=${{ needs.discover-versions.outputs.openssl-tag }} \
--build-arg REPLACE_DEFAULT_FLAG=${{ matrix.replace && '--replace-default' || '' }} \
--build-arg WP_OPENSSL_CONF=${{ matrix.replace && '/opt/wolfProvider/openssl-install/ssl/openssl.cnf' || '/opt/wolfProvider/provider.conf' }} \
-f .github/nginx/Dockerfile -t wolfprov-nginx .

# Run oqs-demos' connection test against the running server. In normal
# mode every quantum-safe group must negotiate with an ML-DSA verified
# chain; in force-fail mode wolfProvider fails every operation so the
# handshakes break and check-workflow-result.sh inverts that to a pass,
# proving wolfProvider genuinely served the PQC crypto.
- name: Run oqs-demos nginx PQC connection test
shell: bash
run: |
set +e
docker run --rm -e ${{ matrix.force_fail || 'WOLFPROV_NOOP=1' }} \
wolfprov-nginx /opt/nginx/test.sh 2>&1 | tee nginx-pqc.log
TEST_RESULT=${PIPESTATUS[0]}
$GITHUB_WORKSPACE/.github/scripts/check-workflow-result.sh \
$TEST_RESULT "${{ matrix.force_fail }}" nginx-pqc
Loading
Loading