|
|
||
|---|---|---|
| .. | ||
| README.md | ||
| gen_pqc_certs.c | ||
| include.am | ||
| mldsa_sign.c | ||
| mldsa_verify_neg.c | ||
| mlkem_decap_neg.c | ||
| mlkem_encap.c | ||
| pqc_ctrl.c | ||
| pqc_ctrl.sh | ||
| pqc_mssim_e2e.c | ||
README.md
wolfTPM TPM 2.0 v1.85 Post-Quantum Examples
Examples exercising the ML-DSA / ML-KEM post-quantum additions from TCG
TPM 2.0 Library Specification v1.85, wrapped by wolfTPM2_* API calls.
These examples run on the SealSQ QVault TPM — the first shipping TPM 2.0 with
v1.85 post-quantum (ML-DSA / ML-KEM) algorithms in silicon — over SPI, and on
the in-tree fwTPM server for CI or when no hardware is present. Build for the
SealSQ part with --enable-sealsq --enable-pqc; see
docs/FWTPM.md for the
fwTPM PQC reference.
Building
wolfSSL (ML-DSA and ML-KEM in wolfCrypt):
./configure --enable-wolftpm --enable-mldsa --enable-mlkem \
--enable-tls-mlkem-standalone --enable-experimental \
--enable-harden --enable-keygen --enable-certgen
make
sudo make install
--enable-tls-mlkem-standalone is required for the standalone ML_KEM_* TLS
groups; without it wolfSSL only offers the hybrid groups and
wolfSSL_UseKeyShare rejects the client default.
--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:
./configure --enable-fwtpm --enable-pqc
make
--enable-pqc is an alias for --enable-v185. If you omit both but
--enable-fwtpm is set and wolfCrypt has ML-DSA + ML-KEM,
configure auto-enables PQC.
Run the test suite
make check
Runs the full suite, including all PQC coverage:
tests/fwtpm_unit.test— 30+ in-process PQC handler teststests/unit.test— PQC wrapper tests over the mssim socket (ML-DSA Sign/Verify Sequence, ML-KEM Encap/Decap, EncryptSecret MLKEM, etc.)tests/pqc_mssim_e2e.sh— dedicated PQC end-to-end round-trip
The individual scripts make check invokes are also runnable directly
for faster targeted iteration:
./tests/fwtpm_check.sh # fwtpm_unit.test + unit.test + tpm2_tools suite
./tests/pqc_mssim_e2e.sh # PQC E2E only (fastest PQC-focused check)
Individual examples
All examples expect a running fwtpm_server on 127.0.0.1:2321:
./src/fwtpm/fwtpm_server --clear &
pqc_ctrl — PQC control center
One CLI to drive and validate a PQC TPM (SealSQ QVault TPM, or the fwTPM),
modeled on examples/spdm/spdm_ctrl: each command runs an operation and
controls the board. Every key operation flushes the transient object table
first, so a TPM with a small object memory (e.g. SealSQ QVault TPM) does not
hit TPM_RC_OBJECT_MEMORY when commands are chained.
./examples/pqc/pqc_ctrl # --all (default)
./examples/pqc/pqc_ctrl --caps --algs # identify + list supported algorithms
./examples/pqc/pqc_ctrl --mldsa=87 # ML-DSA-87 sign/verify
./examples/pqc/pqc_ctrl --mlkem=1024 # ML-KEM-1024 encap/decap
./examples/pqc/pqc_ctrl --selftest --getrandom=32 --pcrread=0
| Command | Description |
|---|---|
--caps |
Manufacturer, vendor string, firmware, FIPS mode |
--algs |
List the algorithms the TPM reports as supported |
--selftest |
TPM2_SelfTest |
--getrandom[=N] |
N random bytes (default 16) |
--pcrread[=idx] |
Read a PCR (SHA-256 bank, falling back to SHA-384) |
--pcrextend=idx |
Extend a PCR with a test digest (explicit index required) |
--flush |
Flush transient objects (board reset between ops) |
--clear |
TPM2_Clear — wipes the owner hierarchy |
--mldsa[=44/65/87] |
Pure ML-DSA sign/verify (default 65) |
--hash-mldsa[=44/65/87] |
Hash-ML-DSA (SHA-256 pre-hash) sign/verify |
--mlkem[=512/768/1024] |
ML-KEM encapsulate/decapsulate |
--all |
caps + algs + selftest + getrandom + pcrread + every PQC set |
Commands run left-to-right, so they can be chained. Requires --enable-v185
(or --enable-pqc). Point it at the SealSQ part with --enable-sealsq, or at
the fwTPM with --enable-fwtpm --enable-swtpm.
Run the whole command set as a pass/fail suite (mirrors
examples/spdm/spdm_test.sh). The destructive --clear is opt-in via
PQC_CTRL_CLEAR=1 so the suite never wipes a TPM unexpectedly:
./examples/pqc/pqc_ctrl.sh
PQC_CTRL_CLEAR=1 ./examples/pqc/pqc_ctrl.sh # also exercise TPM2_Clear
Benchmarks on SealSQ QVault TPM silicon
Measured with examples/bench/bench on a Raspberry Pi 5 driving the QVault TPM
over SPI (the first post-quantum TPM benchmarks on shipping-class silicon):
| Operation | Avg latency | Throughput |
|---|---|---|
| ML-DSA-65 key gen | 2044.7 ms | 0.49 ops/s |
| ML-DSA-65 sign | 581.0 ms | 1.72 ops/s |
| ML-DSA-65 verify | 163.1 ms | 6.13 ops/s |
| ML-KEM-768 key gen | 800.8 ms | 1.25 ops/s |
| ML-KEM-768 encapsulate | 211.8 ms | 4.72 ops/s |
| ML-KEM-768 decapsulate | 425.5 ms | 2.35 ops/s |
Verification is fast (comparable to ECDSA); key generation is a one-off
provisioning cost. See the top-level README.md TPM2 Benchmarks section for the
full classical + PQC run.
pqc_mssim_e2e
End-to-end client test over the mssim socket. Two round-trips:
- MLKEM-768
CreatePrimary+Encapsulate+Decapsulate. Asserts ciphertext is 1088 bytes and the two shared secrets are byte-identical. - HashMLDSA-65 (SHA-256)
CreatePrimary+SignDigest+VerifyDigestSignature. Asserts the signature is 3309 bytes and the validation ticket tag isTPM_ST_DIGEST_VERIFIED.
./examples/pqc/pqc_mssim_e2e
mlkem_encap
ML-KEM encapsulation round-trip. Creates a primary ML-KEM key, runs
Encapsulate, then Decapsulates the produced ciphertext and confirms
the shared secrets match.
./examples/pqc/mlkem_encap # default: MLKEM-768
./examples/pqc/mlkem_encap -mlkem=512
./examples/pqc/mlkem_encap -mlkem=1024
mldsa_sign
Pure ML-DSA sign+verify round-trip. Creates a primary ML-DSA key, signs
a fixed message via SignSequenceStart + SignSequenceComplete (Pure
ML-DSA is one-shot per Part 3 Sec.17.5, so the message rides on the
Complete buffer), then verifies via VerifySequenceStart +
VerifySequenceUpdate + VerifySequenceComplete (Sec.20.3 allows Update
on verify sequences). Asserts the returned validation ticket tag is
TPM_ST_MESSAGE_VERIFIED.
./examples/pqc/mldsa_sign # default: MLDSA-65
./examples/pqc/mldsa_sign -mldsa=44
./examples/pqc/mldsa_sign -mldsa=87
PQC keys via keygen / keyload
examples/keygen/keygen accepts v1.85 PQC options alongside -rsa,
-ecc, -sym, and -keyedhash:
./examples/keygen/keygen keyblob.bin -mldsa=65 # Pure ML-DSA
./examples/keygen/keygen keyblob.bin -hash_mldsa=65 # SHA-256 pre-hash
./examples/keygen/keygen keyblob.bin -mlkem=768 # ML-KEM
Parameter sets:
-mldsa=44|65|87(default 65)-hash_mldsa=44|65|87(default 65, SHA-256 pre-hash)-mlkem=512|768|1024(default 768)
Verify the produced blob round-trips through TPM2_Create + TPM2_Load
by loading it back:
./examples/keygen/keyload keyblob.bin
A successful load prints Loaded key to 0x80000000. The full 18-way
matrix (three variants x three parameter sets) is exercised by
examples/run_examples.sh when v1.85 is detected in config.h.
PQC keys for parameter encryption
A post-quantum primary can key a TPM 2.0 parameter-encryption session: ML-KEM (decrypt capable) is used as the session salt key and ML-DSA (sign only) as the session bind key. The session protects the command's first sized parameter the same way an RSA/ECC salted session does. Any RSA/ECC storage key the example needs (for example the parent of a created child) is unchanged.
Note on ML-DSA confidentiality: parameter-encryption confidentiality comes from the session key, which a bound session derives from the bind entity's authValue (TPM 2.0 Library Part 1, Salted Session). A sign-only ML-DSA key cannot exchange a salt, and the example's bind authValue is a public constant, so an ML-DSA bind alone provides session binding but no confidentiality against a bus observer. To keep the advertised encryption real, the helper additionally creates a transient SRK and uses it as the asymmetric salt for the ML-DSA session: confidentiality comes from the encrypted salt while the ML-DSA key supplies the binding. A real deployment that relies on a bare bound session for confidentiality must use a bind entity whose authValue is secret and was not sent in cleartext.
wrap_test, pcr/quote, and nvram/store and nvram/counter take
-mlkem[=512|768|1024] and -mldsa[=44|65|87]. keygen uses
-paramkey=mlkem[=...] / -paramkey=mldsa[=...] because its -mlkem /
-mldsa already select the child key algorithm.
./examples/wrap/wrap_test -aes -mlkem=768
./examples/pcr/quote 16 quote.blob -ecc -xor -mldsa=65
./examples/nvram/counter -aes -mldsa=65
./examples/keygen/keygen keyblob.bin -ecc -aes -paramkey=mlkem=768
ML-KEM is a restricted decryption (salt) key, which requires a symmetric
definition; the example helper sets AES-128-CFB on it (a TPM rejects a
restricted key with no symmetric algorithm via TPM_RC_SYMMETRIC).
create_primary ML-DSA primary
examples/keygen/create_primary can create an ML-DSA primary key:
./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 a wolfSSL that routes wc_MlDsaKey_SignCtx to the crypto callback for
device keys (private key in the TPM). That landed upstream, so master or any
later release works. 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, so the demo connects to the default localhost and
does not pass -h=. Supplying -h= turns on strict verification including
wolfSSL_check_domain_name, which this leaf cannot satisfy; a production
deployment should issue the leaf with a matching subjectAltName.
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 -mldsa— recreates that TPM key and serves TLS 1.3.examples/tls/tls_client -mldsa— 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 -p=11111 -mldsa=65 &
# 3. client (choose the ML-KEM group)
./examples/tls/tls_client -p=11111 -mldsa -group=ML_KEM_768
Options:
gen_pqc_certs -mldsa=44/65/87— ML-DSA parameter set.tls_server -p=<port> -mldsa=44/65/87.tls_client -h=<host> -p=<port> -group=<name>where<name>isML_KEM_512/768/1024or a hybridSECP256R1MLKEM768/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:
ENABLE_PQC_TLS=1 ./examples/run_examples.sh # includes the PQC TLS matrix