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
84 changes: 84 additions & 0 deletions .github/workflows/pqc-examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,87 @@ jobs:
config.log
run.out
retention-days: 5

# Full TPM-backed PQC TLS 1.3 handshake: ML-KEM key exchange + ML-DSA
# CertificateVerify signed on the TPM (device key never leaves the chip).
pqc-tls-examples:
name: PQC TLS 1.3 examples (ML-DSA auth + ML-KEM)
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
runs-on: ubuntu-latest
container:
image: ghcr.io/wolfssl/wolftpm-ci:v1.0
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
timeout-minutes: 30

steps:
- name: Checkout wolfTPM
uses: actions/checkout@v4

# TODO(revert-when-merged): build wolfSSL from the aidangarske/wolfssl
# mldsa-signctx-cryptocb-devkey branch because the wc_MlDsaKey_SignCtx
# crypto-callback fix (device-key ML-DSA signing) is not yet upstream.
# Once that wolfSSL PR merges, switch this back to a wolfSSL/wolfssl tag
# (>= the release carrying the fix) and delete this note.
- name: Build wolfSSL (PQC TLS + crypto-cb ML-DSA device-key fix)
run: |
cd ~
git clone --depth 1 \
--branch mldsa-signctx-cryptocb-devkey \
https://github.com/aidangarske/wolfssl.git
cd wolfssl
./autogen.sh
./configure --enable-wolftpm --enable-pkcallbacks --enable-keygen \
--enable-certgen --enable-dilithium --enable-mlkem \
--enable-experimental --enable-tls-mlkem-standalone --enable-harden \
CPPFLAGS="-DWOLFSSL_TLSX_PQC_MLKEM_STORE_OBJ -DWC_RSA_NO_PADDING" \
--prefix="$HOME/wolfssl-install"
make -j"$(nproc)"
make install

- name: wolfSSL version info
run: grep LIBWOLFSSL_VERSION_STRING "$HOME/wolfssl-install/include/wolfssl/version.h"

- name: Build wolfTPM with v1.85 + fwTPM + debug
run: |
./autogen.sh
CPPFLAGS="-I$HOME/wolfssl-install/include" \
LDFLAGS="-L$HOME/wolfssl-install/lib -Wl,-rpath,$HOME/wolfssl-install/lib" \
./configure --enable-v185 --enable-fwtpm --enable-debug=verbose
make -j"$(nproc)"

# A stub means wolfSSL was missing a required feature (cert-gen, ML-DSA
# keygen/sign, private-key-id, or TLS 1.3) — fail loudly rather than skip.
- name: Verify PQC TLS examples built (not stubs)
run: |
for b in examples/pqc/gen_pqc_certs \
examples/tls/tls_server_pq examples/tls/tls_client_pq; do
if ./$b -h 2>&1 | grep -q "Requires"; then
echo "FAIL: $b is a stub — wolfSSL missing a required feature"
exit 1
fi
done

- name: PQC TLS handshake E2E (ML-KEM x ML-DSA matrix)
run: |
export LD_LIBRARY_PATH="$HOME/wolfssl-install/lib"
rc=0
for combo in "ML_KEM_768 65" "ML_KEM_512 44" \
"ML_KEM_1024 87" "SECP256R1MLKEM768 65"; do
set -- $combo
echo "== group $1 / ML-DSA-$2 =="
if ! ./tests/tls_pq_e2e.sh "$1" "$2"; then rc=1; break; fi
done
exit $rc

- name: Upload failure logs
if: failure()
uses: actions/upload-artifact@v4
with:
name: pqc-tls-examples-logs
path: |
/tmp/tls_pq_e2e_*.log
config.log
tests/*.log
retention-days: 5
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,14 @@ pkcs7tpmsigned.p7s
pkcs7tpmsignedex.p7s
examples/tls/tls_server
examples/tls/tls_client_notpm
examples/tls/tls_client_pq
examples/tls/tls_server_pq
tests/unit.test
tests/unit.log
tests/fwtpm_unit.log
tests/fwtpm_unit.test
tests/fwtpm_check.sh.log
tests/tls_pq_e2e.sh.log
examples/keygen/create_primary
examples/keygen/keyload
examples/keygen/keygen
Expand All @@ -78,6 +81,7 @@ examples/keygen/ecdh
examples/pqc/mldsa_sign
examples/pqc/mlkem_encap
examples/pqc/pqc_mssim_e2e
examples/pqc/gen_pqc_certs
examples/nvram/extend
examples/nvram/store
examples/nvram/read
Expand Down Expand Up @@ -118,6 +122,7 @@ certs/server-*.der
certs/server-*.pem
certs/client-*.der
certs/client-*.pem
certs/pq-*.der
certs/serial.old
certs/0*.pem
certs/1*.pem
Expand Down
59 changes: 58 additions & 1 deletion examples/pqc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@ the full fwTPM PQC reference.

```
./configure --enable-wolftpm --enable-mldsa --enable-mlkem \
--enable-harden --enable-keygen
--enable-harden --enable-keygen --enable-certgen
make
sudo make install
```

`--enable-certgen` is needed by the TLS `gen_pqc_certs` tool below;
`--enable-wolftpm` provides the crypto callback and private-key-id support the
TLS server uses.

**wolfTPM**:

```
Expand Down Expand Up @@ -172,3 +176,56 @@ restricted key with no symmetric algorithm via `TPM_RC_SYMMETRIC`).
./examples/keygen/create_primary -mldsa # default MLDSA-65
./examples/keygen/create_primary -mldsa=87 -oh
```

## Post-Quantum TLS 1.3 (ML-KEM + TPM ML-DSA)

A full TLS 1.3 handshake where the server's ML-DSA identity key lives in the
TPM. The server signs the CertificateVerify on-chip via the wolfTPM crypto
callback; the client performs an ML-KEM key exchange and validates the server
against a software CA.

Requires wolfSSL with a fix that routes `wc_MlDsaKey_SignCtx` to the crypto
callback for device keys (private key in the TPM). No shipping TPM implements
TCG v1.85 PQC yet, so this runs against the in-tree fwTPM.

Demo scope: the identity key is an unauthenticated deterministic TPM primary
(empty auth), reproducible by both `gen_pqc_certs` and the server from the owner
hierarchy. A production deployment should protect the identity key with a
non-empty auth value or policy so it cannot be recreated from the public cert.
The client validates the server chain against the demo CA but does not bind the
certificate to the host name; a production client should also issue the leaf with
a matching subjectAltName and call `wolfSSL_check_domain_name` before connecting.

Three programs:
- `examples/pqc/gen_pqc_certs` — makes a software ML-DSA CA and a device leaf
cert whose subject key is the TPM ML-DSA key.
- `examples/tls/tls_server_pq` — recreates that TPM key and serves TLS 1.3.
- `examples/tls/tls_client_pq` — connects, ML-KEM key exchange, verifies the CA.

```
./src/fwtpm/fwtpm_server --clear &

# 1. certificate chain bound to the TPM key (-mldsa must match the server)
./examples/pqc/gen_pqc_certs -mldsa=65

# 2. server (same -mldsa as gen_pqc_certs)
./examples/tls/tls_server_pq -p=11111 -mldsa=65 &

# 3. client (choose the ML-KEM group)
./examples/tls/tls_client_pq -h=localhost -p=11111 -group=ML_KEM_768
```

Options:
- `gen_pqc_certs -mldsa=44/65/87` — ML-DSA parameter set.
- `tls_server_pq -p=<port> -mldsa=44/65/87`.
- `tls_client_pq -h=<host> -p=<port> -group=<name>` where `<name>` is
`ML_KEM_512/768/1024` or a hybrid `SECP256R1MLKEM768` / `X25519MLKEM768`
(hybrids need the matching classical curve enabled in wolfSSL).

The one-shot end-to-end test drives all three and asserts the ML-KEM group,
TPM-signed ML-DSA authentication, CA verification, and app data:

```
./tests/tls_pq_e2e.sh # ML_KEM_768 + ML-DSA-65
./tests/tls_pq_e2e.sh SECP256R1MLKEM768 87
```
Loading
Loading