Skip to content

Commit fbbf6fe

Browse files
authored
Merge pull request #445 from aidangarske/v185-pq-support
Add TPM 2.0 v1.85 PQC (ML-DSA and ML-KEM) and fwTPM PQC Support
2 parents d095536 + d518bff commit fbbf6fe

41 files changed

Lines changed: 15549 additions & 513 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/fuzz.yml

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,34 @@ jobs:
1515
fail-fast: false
1616
matrix:
1717
include:
18-
# Full fuzz run (weekly/manual) - 10 minutes
18+
# Classical (v1.38) full fuzz run (weekly/manual) - 10 minutes
1919
- name: fuzz-full
2020
fuzz_time: 600
2121
smoke_only: false
22-
# Quick smoke test (PR) - 60 seconds
22+
wolfssl_extra_flags: ""
23+
wolftpm_extra_flags: ""
24+
max_len: 4096
25+
# Classical (v1.38) quick smoke test (PR) - 60 seconds
2326
- name: fuzz-smoke
2427
fuzz_time: 60
2528
smoke_only: true
29+
wolfssl_extra_flags: ""
30+
wolftpm_extra_flags: ""
31+
max_len: 4096
32+
# v1.85 PQC full fuzz run (weekly/manual) - 10 minutes
33+
- name: fuzz-full-pqc
34+
fuzz_time: 600
35+
smoke_only: false
36+
wolfssl_extra_flags: "--enable-dilithium --enable-mlkem --enable-experimental --enable-harden"
37+
wolftpm_extra_flags: "--enable-v185"
38+
max_len: 8192
39+
# v1.85 PQC quick smoke test (PR) - 60 seconds
40+
- name: fuzz-smoke-pqc
41+
fuzz_time: 60
42+
smoke_only: true
43+
wolfssl_extra_flags: "--enable-dilithium --enable-mlkem --enable-experimental --enable-harden"
44+
wolftpm_extra_flags: "--enable-v185"
45+
max_len: 8192
2646

2747
steps:
2848
- name: Checkout wolfTPM
@@ -42,6 +62,7 @@ jobs:
4262
run: |
4363
./autogen.sh
4464
CC=clang ./configure --enable-wolftpm --enable-pkcallbacks --enable-keygen \
65+
${{ matrix.wolfssl_extra_flags }} \
4566
CFLAGS="-fsanitize=fuzzer-no-link,address -fno-omit-frame-pointer -g -O1 -DWC_RSA_NO_PADDING" \
4667
LDFLAGS="-fsanitize=address"
4768
make -j$(nproc)
@@ -52,13 +73,40 @@ jobs:
5273
run: |
5374
./autogen.sh
5475
CC=clang ./configure --enable-fwtpm --enable-fuzz \
76+
${{ matrix.wolftpm_extra_flags }} \
5577
CFLAGS="-fsanitize=fuzzer-no-link,address -fno-omit-frame-pointer -g -O1" \
5678
LDFLAGS="-fsanitize=address"
5779
make -j$(nproc)
5880
5981
- name: Generate seed corpus
6082
run: python3 tests/fuzz/gen_corpus.py
6183

84+
- name: Verify v1.85 PQC opcode coverage in corpus
85+
if: contains(matrix.name, 'pqc')
86+
run: |
87+
# Without this guard, fuzz-*-pqc could spend 10 minutes fuzzing
88+
# classical paths with -DWOLFTPM_V185 set and never exercise an
89+
# Encapsulate or SignSequenceComplete opcode. Fail fast if any of
90+
# the 8 new v1.85 command codes is absent from the seed corpus.
91+
# Seeds are binary; opcodes appear as raw 4-byte big-endian
92+
# sequences. Hex-dump the concatenated corpus and grep the
93+
# resulting hex stream for each opcode's bytes.
94+
CORPUS_HEX=$(cat tests/fuzz/corpus/*.bin | xxd -p | tr -d '\n')
95+
MISSING=()
96+
for cc in 000001a3 000001a4 000001a5 000001a6 \
97+
000001a7 000001a8 000001a9 000001aa; do
98+
if ! echo "$CORPUS_HEX" | grep -q "$cc"; then
99+
MISSING+=("0x${cc^^}")
100+
fi
101+
done
102+
if [ ${#MISSING[@]} -gt 0 ]; then
103+
echo "ERROR: PQC seed corpus missing the following command codes:"
104+
printf ' %s\n' "${MISSING[@]}"
105+
echo "Update tests/fuzz/gen_corpus.py to emit a seed for each."
106+
exit 1
107+
fi
108+
echo "All 8 v1.85 PQC opcodes (0x1A3-0x1AA) present in seed corpus."
109+
62110
- name: Run fuzzer
63111
env:
64112
ASAN_OPTIONS: "detect_leaks=1:abort_on_error=1:symbolize=1"
@@ -68,7 +116,7 @@ jobs:
68116
./tests/fuzz/fwtpm_fuzz \
69117
tests/fuzz/corpus/ \
70118
-dict=tests/fuzz/tpm2.dict \
71-
-max_len=4096 \
119+
-max_len=${{ matrix.max_len }} \
72120
-timeout=30 \
73121
-rss_limit_mb=2048 \
74122
-print_final_stats=1 \

.github/workflows/fwtpm-test.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,29 @@ jobs:
3030
wolfssl_config: --enable-wolftpm --enable-pkcallbacks --enable-keygen
3131
build_only: false
3232

33+
# v1.85 PQC: full make check + pqc_mssim_e2e.sh + tpm2-tools
34+
# Highest-leverage entry — exercises wrapper unit tests
35+
# (tests/unit_tests.c v1.85 cases) + handler unit tests
36+
# (tests/fwtpm_unit_tests.c v1.85 cases) + the new mssim E2E
37+
# harness against fwtpm_server in one shot. --enable-swtpm omitted
38+
# because configure.ac:287 enables it by default on Linux.
39+
- name: fwtpm-v185
40+
os: ubuntu-latest
41+
wolftpm_config: --enable-fwtpm --enable-v185
42+
wolfssl_config: --enable-wolftpm --enable-pkcallbacks --enable-keygen --enable-dilithium --enable-mlkem --enable-experimental --enable-harden
43+
build_only: false
44+
45+
# v1.85 PQC: build-only safety net with DEBUG_WOLFTPM so any
46+
# printf-format-string drift in v1.85-guarded debug paths breaks
47+
# the build instead of silently corrupting log output. No
48+
# --enable-swtpm because build-only never invokes the socket client.
49+
- name: fwtpm-v185-build-only
50+
os: ubuntu-latest
51+
wolftpm_config: --enable-fwtpm --enable-v185
52+
wolfssl_config: --enable-wolftpm --enable-pkcallbacks --enable-keygen --enable-dilithium --enable-mlkem --enable-experimental --enable-harden
53+
build_only: true
54+
extra_cflags: -DDEBUG_WOLFTPM
55+
3356
# Build-only: fwTPM with RSA disabled
3457
- name: fwtpm-no-rsa
3558
os: ubuntu-latest

.github/workflows/make-test-swtpm.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,24 @@ jobs:
303303
test_command: "make check && WOLFSSL_PATH=./wolfssl NO_PUBASPRIV=1 ./examples/run_examples.sh"
304304
needs_install: true
305305

306+
# v1.85 PQC: swtpm-backed wrapper coverage. Triggers run_examples.sh
307+
# Build-only: --enable-v185 against PQC+pkcallbacks wolfSSL. swtpm
308+
# has no PQC, so runtime PQC tests live in fwtpm-v185.
309+
- name: v185-pqc-swtpm-build
310+
wolfssl_config: "--enable-wolftpm --enable-pkcallbacks --enable-keygen --enable-dilithium --enable-mlkem --enable-experimental --enable-harden"
311+
wolftpm_config: "--enable-v185"
312+
test_command: "make"
313+
314+
# Regression: build the v185-pq-support branch WITHOUT --enable-v185
315+
# to catch #ifdef WOLFTPM_V185 drift in tpm2_packet.c / tpm2_wrap.c
316+
# (a PQC-only declaration leaking out of its guard breaks classical
317+
# builds and would otherwise be invisible in CI — every other
318+
# classical entry runs against master, never against this branch).
319+
- name: v138-regression-after-v185
320+
wolfssl_config: "--enable-wolftpm --enable-pkcallbacks --enable-keygen"
321+
wolftpm_config: "--enable-fwtpm"
322+
test_command: "make check && WOLFSSL_PATH=./wolfssl ./examples/run_examples.sh"
323+
306324
steps:
307325
- name: Checkout wolfTPM
308326
uses: actions/checkout@master

.github/workflows/pqc-examples.yml

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
name: PQC Examples (v1.85)
2+
3+
on:
4+
push:
5+
branches: [ 'master', 'main', 'release/**' ]
6+
pull_request:
7+
branches: [ '*' ]
8+
9+
jobs:
10+
pqc-examples:
11+
runs-on: ubuntu-latest
12+
timeout-minutes: 30
13+
14+
steps:
15+
- name: Checkout wolfTPM
16+
uses: actions/checkout@v4
17+
18+
- name: Checkout wolfSSL
19+
uses: actions/checkout@v4
20+
with:
21+
repository: wolfssl/wolfssl
22+
path: wolfssl
23+
ref: master
24+
25+
- name: Install build deps + tpm2-tools
26+
run: |
27+
sudo apt-get update
28+
sudo apt-get install -y tpm2-tools libtss2-tcti-mssim0
29+
30+
- name: Build wolfSSL with PQC
31+
working-directory: ./wolfssl
32+
run: |
33+
./autogen.sh
34+
./configure --enable-wolftpm --enable-pkcallbacks --enable-keygen \
35+
--enable-dilithium --enable-mlkem --enable-experimental \
36+
--enable-harden \
37+
CFLAGS="-DWC_RSA_NO_PADDING"
38+
make
39+
sudo make install
40+
sudo ldconfig
41+
42+
- name: Build wolfTPM with v1.85 + fwTPM + debug
43+
run: |
44+
./autogen.sh
45+
# --enable-swtpm omitted: it's the Linux configure default
46+
# (configure.ac:287). Passing it explicitly was redundant.
47+
# --enable-debug=verbose: full client + fwTPM dispatch logs so
48+
# CI failures (e.g. keyload integrity) come with TPM-side trace.
49+
./configure --enable-v185 --enable-fwtpm --enable-debug=verbose
50+
make
51+
52+
# ----- Tier 1: make check -----
53+
# Runs unit.test (wrapper) + fwtpm_unit.test (handler) + tpm2-tools
54+
# compatibility + tests/pqc_mssim_e2e.sh in one shot via fwtpm_check.sh.
55+
- name: make check (unit + fwtpm_unit + tpm2-tools + pqc_mssim_e2e.sh)
56+
env:
57+
WOLFSSL_PATH: ${{ github.workspace }}/wolfssl
58+
run: |
59+
FWTPM_USE_FIXED_PORT=1 \
60+
sudo -E unshare --net /bin/bash -c '
61+
set -e
62+
ip link set lo up
63+
make check
64+
'
65+
# make check runs as root via sudo -E unshare; restore ownership of
66+
# any files left in the workspace so later steps (running as the
67+
# unprivileged runner) can rewrite them — otherwise stale root-owned
68+
# blobs (e.g. eccblob.bin) silently break run_examples.sh later.
69+
sudo chown -R "$(id -u):$(id -g)" .
70+
71+
# ----- Tier 2: per-example standalone runs -----
72+
# Each example gets its own GitHub Actions check so a regression
73+
# surfaces with a clear failure signal — not buried inside make check.
74+
- name: Start fwtpm_server for standalone example runs
75+
run: |
76+
rm -f fwtpm_nv.bin
77+
./src/fwtpm/fwtpm_server > /tmp/fwtpm_server.log 2>&1 &
78+
echo $! > /tmp/fwtpm_server.pid
79+
sleep 1
80+
kill -0 $(cat /tmp/fwtpm_server.pid)
81+
82+
- name: PQC keygen — every parameter set
83+
run: |
84+
for ps in 44 65 87; do
85+
./examples/keygen/keygen mldsa_sk.bin -mldsa=$ps || exit 1
86+
./examples/keygen/keygen hmldsa_sk.bin -hash_mldsa=$ps || exit 1
87+
done
88+
for ps in 512 768 1024; do
89+
./examples/keygen/keygen mlkem_sk.bin -mlkem=$ps || exit 1
90+
done
91+
92+
- name: ML-DSA sign + verify example (standalone)
93+
run: ./examples/pqc/mldsa_sign
94+
95+
- name: ML-KEM encap + decap example (standalone)
96+
run: ./examples/pqc/mlkem_encap
97+
98+
- name: Stop Tier 2 fwtpm_server (free port 2321 for E2E)
99+
run: |
100+
if [ -f /tmp/fwtpm_server.pid ]; then
101+
kill "$(cat /tmp/fwtpm_server.pid)" 2>/dev/null || true
102+
rm -f /tmp/fwtpm_server.pid
103+
fi
104+
# Defensive: kill any other default-port server lingering.
105+
pkill -f "fwtpm_server$" 2>/dev/null || true
106+
sleep 1
107+
108+
- name: PQC mssim E2E (MLKEM-768 + HashMLDSA-65 round-trips)
109+
run: ./tests/pqc_mssim_e2e.sh
110+
111+
- name: Restart fwtpm_server for Tier 5 (run_examples.sh)
112+
run: |
113+
# pqc_mssim_e2e.sh started + stopped its own server; Tier 5 needs
114+
# one again. Reuse the same default-port launch as Tier 2.
115+
pkill -f "fwtpm_server" 2>/dev/null || true
116+
sleep 1
117+
rm -f fwtpm_nv.bin
118+
./src/fwtpm/fwtpm_server > /tmp/fwtpm_server.log 2>&1 &
119+
echo $! > /tmp/fwtpm_server.pid
120+
sleep 1
121+
kill -0 "$(cat /tmp/fwtpm_server.pid)"
122+
123+
- name: Doc constants parity check
124+
run: |
125+
./tests/check_doc_constants.sh
126+
rc=$?
127+
if [ $rc -eq 77 ]; then
128+
echo "Step skipped (exit 77 — header or doc missing)"
129+
exit 0
130+
fi
131+
exit $rc
132+
133+
# ----- Tier 5: full run_examples.sh sweep -----
134+
# run_examples.sh does not start its own TPM — it expects one already
135+
# listening. Reuse the fwtpm_server started in Tier 2. Trace each
136+
# command (set -x) so the failing call line is in the CI log; on
137+
# failure, dump run.out (where the script redirects example stdout).
138+
- name: run_examples.sh full pass (auto-detects v1.85, runs 18-way matrix)
139+
env:
140+
WOLFSSL_PATH: ${{ github.workspace }}/wolfssl
141+
run: |
142+
set +e
143+
bash -x ./examples/run_examples.sh
144+
rc=$?
145+
set -e
146+
if [ $rc -ne 0 ]; then
147+
echo "=== run.out (last 200 lines) ==="
148+
tail -200 run.out
149+
echo "=== fwtpm_server.log (last 100 lines) ==="
150+
tail -100 /tmp/fwtpm_server.log
151+
fi
152+
exit $rc
153+
154+
- name: Stop fwtpm_server
155+
if: always()
156+
run: |
157+
if [ -f /tmp/fwtpm_server.pid ]; then
158+
kill $(cat /tmp/fwtpm_server.pid) 2>/dev/null || true
159+
rm -f /tmp/fwtpm_server.pid
160+
fi
161+
162+
- name: Upload failure logs
163+
if: failure()
164+
uses: actions/upload-artifact@v4
165+
with:
166+
name: pqc-examples-logs
167+
path: |
168+
/tmp/fwtpm_server.log
169+
/tmp/fwtpm_check_*.log
170+
test-suite.log
171+
tests/*.log
172+
config.log
173+
run.out
174+
retention-days: 5

.github/workflows/sanitizer.yml

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,40 @@ jobs:
3131
cflags: "-fsanitize=leak -fno-omit-frame-pointer -g"
3232
ldflags: "-fsanitize=leak"
3333

34+
# v1.85 PQC sanitizer coverage — three entries because each catches
35+
# a different bug class. SWTPM transport is the Linux configure
36+
# default (configure.ac:287); explicit flag omitted everywhere.
37+
# ASan: heap-buffer-overflow / use-after-scope on the new sequence-
38+
# handle objects + PQC marshaling paths.
39+
- name: "ASan-v185"
40+
cflags: "-fsanitize=address -O1 -fno-omit-frame-pointer -g"
41+
ldflags: "-fsanitize=address"
42+
asan_options: "detect_leaks=0"
43+
wolftpm_extra_config: "--enable-v185"
44+
wolfssl_extra_config: "--enable-dilithium --enable-mlkem --enable-experimental --enable-harden"
45+
46+
# UBSan-v185: enables undefined-behavior checks but disables
47+
# `alignment` (wolfSSL dilithium internal sword32 reads from
48+
# byte buffers) and the integer overflow/shift checks (wolfSSL
49+
# Hash_df 440<<24) — both pre-existing wolfSSL UB.
50+
- name: "UBSan-v185"
51+
cc: clang
52+
cflags: "-fsanitize=undefined -fno-sanitize=alignment,signed-integer-overflow,shift -fno-sanitize-recover=all -fno-omit-frame-pointer -g"
53+
ldflags: "-fsanitize=undefined"
54+
ubsan_options: "halt_on_error=1:print_stacktrace=1"
55+
wolftpm_extra_config: "--enable-v185"
56+
wolfssl_extra_config: "--enable-dilithium --enable-mlkem --enable-experimental --enable-harden"
57+
58+
# MSan-v185: Pure ML-DSA one-shot signing and streaming Hash-ML-DSA
59+
# both allocate sequence-handle state incrementally — partial-init
60+
# reads on those buffers are MSan territory, not ASan.
61+
- name: "MSan-v185"
62+
cc: clang
63+
cflags: "-fsanitize=memory -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer -O1 -g"
64+
ldflags: "-fsanitize=memory"
65+
wolftpm_extra_config: "--enable-v185"
66+
wolfssl_extra_config: "--enable-dilithium --enable-mlkem --enable-experimental --enable-harden"
67+
3468
steps:
3569
- name: Workaround high-entropy ASLR
3670
run: sudo sysctl vm.mmap_rnd_bits=28
@@ -53,7 +87,8 @@ jobs:
5387
working-directory: ./wolfssl
5488
run: |
5589
./autogen.sh
56-
./configure --enable-wolftpm --enable-pkcallbacks --enable-keygen \
90+
CC=${{ matrix.cc || 'gcc' }} ./configure --enable-wolftpm --enable-pkcallbacks --enable-keygen \
91+
${{ matrix.wolfssl_extra_config }} \
5792
--prefix=/tmp/wolfssl-install \
5893
CFLAGS="-DWC_RSA_NO_PADDING ${{ matrix.cflags }}" \
5994
LDFLAGS="${{ matrix.ldflags }}"
@@ -63,7 +98,8 @@ jobs:
6398
- name: Build wolfTPM with fwTPM + ${{ matrix.name }}
6499
run: |
65100
./autogen.sh
66-
./configure --enable-fwtpm --enable-swtpm --enable-debug \
101+
CC=${{ matrix.cc || 'gcc' }} ./configure --enable-fwtpm --enable-swtpm --enable-debug \
102+
${{ matrix.wolftpm_extra_config }} \
67103
--with-wolfcrypt=/tmp/wolfssl-install \
68104
CFLAGS="${{ matrix.cflags }}" \
69105
LDFLAGS="${{ matrix.ldflags }}"

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ examples/keygen/keyload
7070
examples/keygen/keygen
7171
examples/keygen/keyimport
7272
examples/keygen/external_import
73+
examples/pqc/mldsa_sign
74+
examples/pqc/mlkem_encap
75+
examples/pqc/pqc_mssim_e2e
7376
examples/nvram/extend
7477
examples/nvram/store
7578
examples/nvram/read

0 commit comments

Comments
 (0)