From 0063c16e7ea48bd2a7a3d6b20c3a41f91e75f2fb Mon Sep 17 00:00:00 2001 From: aidan garske Date: Wed, 22 Jul 2026 11:37:40 -0700 Subject: [PATCH 1/4] Add TPM backed ML-DSA authentication for TLS 1.3 with PQC example and tests --- .github/workflows/pqc-examples.yml | 84 ++++++++ .gitignore | 5 + examples/pqc/README.md | 59 +++++- examples/pqc/gen_pqc_certs.c | 299 +++++++++++++++++++++++++++++ examples/pqc/include.am | 5 + examples/tls/include.am | 20 +- examples/tls/tls_client_pq.c | 242 +++++++++++++++++++++++ examples/tls/tls_client_pq.h | 35 ++++ examples/tls/tls_server_pq.c | 274 ++++++++++++++++++++++++++ examples/tls/tls_server_pq.h | 35 ++++ src/tpm2_cryptocb.c | 47 ++++- tests/include.am | 7 + tests/tls_pq_e2e.sh | 221 +++++++++++++++++++++ tests/unit_tests.c | 158 +++++++++++++++ wolftpm/tpm2_wrap.h | 3 + 15 files changed, 1490 insertions(+), 4 deletions(-) create mode 100644 examples/pqc/gen_pqc_certs.c create mode 100644 examples/tls/tls_client_pq.c create mode 100644 examples/tls/tls_client_pq.h create mode 100644 examples/tls/tls_server_pq.c create mode 100644 examples/tls/tls_server_pq.h create mode 100755 tests/tls_pq_e2e.sh diff --git a/.github/workflows/pqc-examples.yml b/.github/workflows/pqc-examples.yml index 789b7cd5e..80235f445 100644 --- a/.github/workflows/pqc-examples.yml +++ b/.github/workflows/pqc-examples.yml @@ -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 diff --git a/.gitignore b/.gitignore index 0e67d092b..f0d5bbc12 100644 --- a/.gitignore +++ b/.gitignore @@ -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 @@ -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 @@ -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 diff --git a/examples/pqc/README.md b/examples/pqc/README.md index bce99cdb4..cf932332c 100644 --- a/examples/pqc/README.md +++ b/examples/pqc/README.md @@ -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**: ``` @@ -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= -mldsa=44/65/87`. +- `tls_client_pq -h= -p= -group=` where `` 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 +``` diff --git a/examples/pqc/gen_pqc_certs.c b/examples/pqc/gen_pqc_certs.c new file mode 100644 index 000000000..6c09101d1 --- /dev/null +++ b/examples/pqc/gen_pqc_certs.c @@ -0,0 +1,299 @@ +/* gen_pqc_certs.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* Generate a software ML-DSA CA and a device leaf certificate whose subject key + * is a TPM-resident ML-DSA key, for the TLS PQC examples. */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#if !defined(WOLFTPM2_NO_WRAPPER) && !defined(WOLFTPM2_NO_WOLFCRYPT) && \ + !defined(NO_FILESYSTEM) && \ + defined(WOLFTPM_MLDSA) && defined(WOLFSSL_CERT_GEN) && \ + !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && \ + !defined(WOLFSSL_DILITHIUM_NO_MAKE_KEY) && \ + !defined(WOLFSSL_MLDSA_NO_SIGN) && !defined(WOLFSSL_DILITHIUM_NO_SIGN) && \ + !defined(WOLFSSL_MLDSA_NO_ASN1) && !defined(WOLFSSL_DILITHIUM_NO_ASN1) + +#include +#include + +#include +#include +#include + +#include +#include + +#define CA_CERT_FILE "./certs/pq-ca-cert.der" +#define SERVER_CERT_FILE "./certs/pq-server-cert.der" + +/* generous for ML-DSA-87 cert (pub ~2592 + sig ~4627 + overhead) */ +#define PQC_CERT_BUF_SZ 10000 + +static void usage(void) +{ + printf("Expected usage:\n"); + printf("./examples/pqc/gen_pqc_certs [-mldsa=44/65/87]\n"); + printf("* -mldsa=44/65/87: ML-DSA parameter set (default 65)\n"); + printf("Outputs %s and %s\n", CA_CERT_FILE, SERVER_CERT_FILE); +} + +static int parseParamSet(const char* v, TPMI_MLDSA_PARAMETER_SET* ps) +{ + if (XSTRCMP(v, "44") == 0) { *ps = TPM_MLDSA_44; return 0; } + if (XSTRCMP(v, "65") == 0) { *ps = TPM_MLDSA_65; return 0; } + if (XSTRCMP(v, "87") == 0) { *ps = TPM_MLDSA_87; return 0; } + return BAD_FUNC_ARG; +} + +static int mldsaTypes(TPMI_MLDSA_PARAMETER_SET ps, int* keyType, int* sigType, + int* wcLevel) +{ + switch (ps) { + case TPM_MLDSA_44: + *keyType = ML_DSA_44_TYPE; *sigType = CTC_ML_DSA_44; + *wcLevel = WC_ML_DSA_44; return 0; + case TPM_MLDSA_65: + *keyType = ML_DSA_65_TYPE; *sigType = CTC_ML_DSA_65; + *wcLevel = WC_ML_DSA_65; return 0; + case TPM_MLDSA_87: + *keyType = ML_DSA_87_TYPE; *sigType = CTC_ML_DSA_87; + *wcLevel = WC_ML_DSA_87; return 0; + default: + return BAD_FUNC_ARG; + } +} + +static int writeDer(const char* file, const byte* der, int derSz) +{ + XFILE f = XFOPEN(file, "wb"); + if (f == XBADFILE) { + printf("Failed to open %s for write\n", file); + return -1; + } + if (XFWRITE(der, 1, (size_t)derSz, f) != (size_t)derSz) { + XFCLOSE(f); + return -1; + } + XFCLOSE(f); + return 0; +} + +static int TPM2_PQC_GenCerts(void* userCtx, int argc, char *argv[]) +{ + int rc = 0, sz = 0; + WOLFTPM2_DEV dev; + WOLFTPM2_KEY mldsaKey; + TPMT_PUBLIC pub; + TPMI_MLDSA_PARAMETER_SET paramSet = TPM_MLDSA_65; + int keyType = ML_DSA_65_TYPE, sigType = CTC_ML_DSA_65; + int wcLevel = WC_ML_DSA_65; + WC_RNG rng; + wc_MlDsaKey caKey; + wc_MlDsaKey tpmPubKey; + Cert caCert; + Cert leafCert; + byte* caDer = NULL; + byte* leafDer = NULL; + int caSz = 0, leafSz = 0; + int haveRng = 0, haveCaKey = 0, havePubKey = 0; + + XMEMSET(&dev, 0, sizeof(dev)); + XMEMSET(&mldsaKey, 0, sizeof(mldsaKey)); + XMEMSET(&pub, 0, sizeof(pub)); + XMEMSET(&caKey, 0, sizeof(caKey)); + XMEMSET(&tpmPubKey, 0, sizeof(tpmPubKey)); + XMEMSET(&caCert, 0, sizeof(caCert)); + XMEMSET(&leafCert, 0, sizeof(leafCert)); + + if (argc >= 2 && (XSTRCMP(argv[1], "-?") == 0 || + XSTRCMP(argv[1], "-h") == 0 || XSTRCMP(argv[1], "--help") == 0)) { + usage(); + return 0; + } + while (argc > 1) { + if (XSTRNCMP(argv[argc-1], "-mldsa=", 7) == 0) { + if (parseParamSet(argv[argc-1] + 7, ¶mSet) != 0) { + usage(); + return BAD_FUNC_ARG; + } + } + argc--; + } + (void)mldsaTypes(paramSet, &keyType, &sigType, &wcLevel); + + caDer = (byte*)XMALLOC(PQC_CERT_BUF_SZ, NULL, DYNAMIC_TYPE_TMP_BUFFER); + leafDer = (byte*)XMALLOC(PQC_CERT_BUF_SZ, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (caDer == NULL || leafDer == NULL) { + rc = MEMORY_E; + } + + if (rc == 0) { + rc = wolfTPM2_Init(&dev, TPM2_IoCb, userCtx); + if (rc != TPM_RC_SUCCESS) { + printf("wolfTPM2_Init failed 0x%x: %s\n", rc, + wolfTPM2_GetRCString(rc)); + } + } + + if (rc == 0) { + /* TPM ML-DSA device key (deterministic; the server recreates it) */ + rc = wolfTPM2_GetKeyTemplate_MLDSA(&pub, + TPMA_OBJECT_sign | TPMA_OBJECT_fixedTPM | TPMA_OBJECT_fixedParent | + TPMA_OBJECT_sensitiveDataOrigin | TPMA_OBJECT_userWithAuth | + TPMA_OBJECT_noDA, paramSet, 0); + if (rc == TPM_RC_SUCCESS) { + rc = wolfTPM2_CreatePrimaryKey(&dev, &mldsaKey, TPM_RH_OWNER, &pub, + NULL, 0); + } + if (rc != TPM_RC_SUCCESS) { + printf("Create TPM ML-DSA key failed 0x%x: %s\n", + rc, wolfTPM2_GetRCString(rc)); + } + else { + printf("TPM ML-DSA device key: handle 0x%08x, pub %u bytes\n", + (unsigned)mldsaKey.handle.hndl, + (unsigned)mldsaKey.pub.publicArea.unique.mldsa.size); + } + } + + if (rc == 0) { + rc = wc_InitRng(&rng); + if (rc == 0) haveRng = 1; + } + + if (rc == 0) { + /* software CA key */ + rc = wc_MlDsaKey_Init(&caKey, NULL, INVALID_DEVID); + if (rc == 0) haveCaKey = 1; + if (rc == 0) rc = wc_MlDsaKey_SetParams(&caKey, wcLevel); + if (rc == 0) rc = wc_MlDsaKey_MakeKey(&caKey, &rng); + if (rc != 0) printf("CA key gen failed %d\n", rc); + } + + if (rc == 0) { + /* self-signed CA cert */ + rc = wc_InitCert(&caCert); + } + if (rc == 0) { + caCert.daysValid = 365; + caCert.selfSigned = 1; + caCert.isCA = 1; + caCert.sigType = sigType; + XSTRNCPY(caCert.subject.country, "US", CTC_NAME_SIZE); + XSTRNCPY(caCert.subject.org, "wolfSSL", CTC_NAME_SIZE); + XSTRNCPY(caCert.subject.commonName, "wolfTPM PQC Demo CA", + CTC_NAME_SIZE); + XMEMCPY(&caCert.issuer, &caCert.subject, sizeof(CertName)); + sz = wc_MakeCert_ex(&caCert, caDer, PQC_CERT_BUF_SZ, keyType, &caKey, + &rng); + if (sz < 0) rc = sz; + } + if (rc == 0) { + sz = wc_SignCert_ex(caCert.bodySz, caCert.sigType, caDer, + PQC_CERT_BUF_SZ, keyType, &caKey, &rng); + if (sz < 0) rc = sz; + else caSz = sz; + } + + if (rc == 0) { + /* TPM public key for the leaf subject */ + rc = wc_MlDsaKey_Init(&tpmPubKey, NULL, INVALID_DEVID); + if (rc == 0) havePubKey = 1; + if (rc == 0) rc = wc_MlDsaKey_SetParams(&tpmPubKey, wcLevel); + if (rc == 0) rc = wc_MlDsaKey_ImportPubRaw(&tpmPubKey, + mldsaKey.pub.publicArea.unique.mldsa.buffer, + mldsaKey.pub.publicArea.unique.mldsa.size); + if (rc != 0) printf("import TPM pub failed %d\n", rc); + } + + if (rc == 0) { + /* device leaf cert: subject = TPM key, issuer = CA, CA-signed */ + rc = wc_InitCert(&leafCert); + } + if (rc == 0) { + leafCert.daysValid = 365; + leafCert.isCA = 0; + leafCert.sigType = sigType; + XSTRNCPY(leafCert.subject.country, "US", CTC_NAME_SIZE); + XSTRNCPY(leafCert.subject.org, "wolfSSL", CTC_NAME_SIZE); + XSTRNCPY(leafCert.subject.commonName, "wolfTPM ML-DSA Device", + CTC_NAME_SIZE); + rc = wc_SetIssuerBuffer(&leafCert, caDer, caSz); + } + if (rc == 0) { + sz = wc_MakeCert_ex(&leafCert, leafDer, PQC_CERT_BUF_SZ, keyType, + &tpmPubKey, &rng); + if (sz < 0) rc = sz; + } + if (rc == 0) { + sz = wc_SignCert_ex(leafCert.bodySz, leafCert.sigType, leafDer, + PQC_CERT_BUF_SZ, keyType, &caKey, &rng); + if (sz < 0) rc = sz; + else leafSz = sz; + } + + if (rc == 0) { + rc = writeDer(CA_CERT_FILE, caDer, caSz); + if (rc == 0) rc = writeDer(SERVER_CERT_FILE, leafDer, leafSz); + if (rc == 0) { + printf("Wrote %s (%d bytes) and %s (%d bytes)\n", + CA_CERT_FILE, caSz, SERVER_CERT_FILE, leafSz); + } + } + + if (havePubKey) wc_MlDsaKey_Free(&tpmPubKey); + if (haveCaKey) wc_MlDsaKey_Free(&caKey); + if (haveRng) wc_FreeRng(&rng); + if (mldsaKey.handle.hndl != 0) + wolfTPM2_UnloadHandle(&dev, &mldsaKey.handle); + wolfTPM2_Cleanup(&dev); + XFREE(caDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(leafDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return rc; +} + +#endif /* !WOLFTPM2_NO_WRAPPER && !WOLFTPM2_NO_WOLFCRYPT && WOLFTPM_MLDSA */ + +#ifndef NO_MAIN_DRIVER +int main(int argc, char *argv[]) +{ + int rc = -1; +#if !defined(WOLFTPM2_NO_WRAPPER) && !defined(WOLFTPM2_NO_WOLFCRYPT) && \ + !defined(NO_FILESYSTEM) && \ + defined(WOLFTPM_MLDSA) && defined(WOLFSSL_CERT_GEN) && \ + !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && \ + !defined(WOLFSSL_DILITHIUM_NO_MAKE_KEY) && \ + !defined(WOLFSSL_MLDSA_NO_SIGN) && !defined(WOLFSSL_DILITHIUM_NO_SIGN) && \ + !defined(WOLFSSL_MLDSA_NO_ASN1) && !defined(WOLFSSL_DILITHIUM_NO_ASN1) + rc = TPM2_PQC_GenCerts(NULL, argc, argv); +#else + (void)argc; + (void)argv; + printf("Requires --enable-pqc (v1.85 ML-DSA) and wolfSSL cert gen\n"); +#endif + return rc; +} +#endif diff --git a/examples/pqc/include.am b/examples/pqc/include.am index bcd01959b..47be3dd9a 100644 --- a/examples/pqc/include.am +++ b/examples/pqc/include.am @@ -29,6 +29,11 @@ examples_pqc_mlkem_decap_neg_SOURCES = examples/pqc/mlkem_decap_neg.c examples_pqc_mlkem_decap_neg_LDADD = src/libwolftpm.la $(LIB_STATIC_ADD) examples_pqc_mlkem_decap_neg_DEPENDENCIES = src/libwolftpm.la +noinst_PROGRAMS += examples/pqc/gen_pqc_certs +examples_pqc_gen_pqc_certs_SOURCES = examples/pqc/gen_pqc_certs.c +examples_pqc_gen_pqc_certs_LDADD = src/libwolftpm.la $(LIB_STATIC_ADD) +examples_pqc_gen_pqc_certs_DEPENDENCIES = src/libwolftpm.la + EXTRA_DIST += examples/pqc/README.md endif diff --git a/examples/tls/include.am b/examples/tls/include.am index 2d3fbc15b..4726d873f 100644 --- a/examples/tls/include.am +++ b/examples/tls/include.am @@ -26,12 +26,30 @@ examples_tls_tls_server_SOURCES = examples/tls/tls_server.c \ examples_tls_tls_server_LDADD = src/libwolftpm.la $(LIB_STATIC_ADD) examples_tls_tls_server_DEPENDENCIES = src/libwolftpm.la +if BUILD_PQC +noinst_PROGRAMS += examples/tls/tls_client_pq +noinst_HEADERS += examples/tls/tls_client_pq.h +examples_tls_tls_client_pq_SOURCES = examples/tls/tls_client_pq.c +examples_tls_tls_client_pq_LDADD = src/libwolftpm.la $(LIB_STATIC_ADD) +examples_tls_tls_client_pq_DEPENDENCIES = src/libwolftpm.la + +noinst_PROGRAMS += examples/tls/tls_server_pq +noinst_HEADERS += examples/tls/tls_server_pq.h +examples_tls_tls_server_pq_SOURCES = examples/tls/tls_server_pq.c +examples_tls_tls_server_pq_LDADD = src/libwolftpm.la $(LIB_STATIC_ADD) +examples_tls_tls_server_pq_DEPENDENCIES = src/libwolftpm.la +endif + endif example_tlsdir = $(exampledir)/tls dist_example_tls_DATA = \ examples/tls/tls_client.c \ - examples/tls/tls_server.c + examples/tls/tls_server.c \ + examples/tls/tls_client_pq.c \ + examples/tls/tls_server_pq.c DISTCLEANFILES+= examples/tls/.libs/tls_client DISTCLEANFILES+= examples/tls/.libs/tls_server +DISTCLEANFILES+= examples/tls/.libs/tls_client_pq +DISTCLEANFILES+= examples/tls/.libs/tls_server_pq diff --git a/examples/tls/tls_client_pq.c b/examples/tls/tls_client_pq.c new file mode 100644 index 000000000..df95d2d5c --- /dev/null +++ b/examples/tls/tls_client_pq.c @@ -0,0 +1,242 @@ +/* tls_client_pq.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* TLS 1.3 client for the PQC example: ML-KEM key exchange, validates the + * server's TPM ML-DSA certificate against the CA. Pair with tls_server_pq. */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#if !defined(WOLFTPM2_NO_WRAPPER) && defined(WOLFTPM_CRYPTOCB) && \ + !defined(WOLFTPM2_NO_WOLFCRYPT) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(WOLFCRYPT_ONLY) && defined(WOLFTPM_MLDSA_SIGN) && \ + defined(WOLFSSL_TLS13) && !defined(NO_FILESYSTEM) + +#ifndef _WIN32 +/* host-resolve declarations tls_common.h needs on POSIX */ +#include +#include +#endif + +#include +#include + +#include +#include + +#define PQ_CA_CERT "./certs/pq-ca-cert.der" +#define PQ_CERT_BUF_SZ 10000 + +static const char kHelloMsg[] = "hello wolfssl!\n"; + +static void usage(void) +{ + printf("Expected usage:\n"); + printf("./examples/tls/tls_client_pq [-h=host] [-p=port] [-group=name]\n"); + printf("* -group: ML_KEM_512/768/1024, or hybrid SECP256R1MLKEM768 /\n"); + printf(" X25519MLKEM768 (default ML_KEM_768)\n"); +} + +static int parseGroup(const char* v, int* group) +{ + if (XSTRCMP(v, "ML_KEM_512") == 0) { *group = WOLFSSL_ML_KEM_512; return 0; } + if (XSTRCMP(v, "ML_KEM_768") == 0) { *group = WOLFSSL_ML_KEM_768; return 0; } + if (XSTRCMP(v, "ML_KEM_1024") == 0) { + *group = WOLFSSL_ML_KEM_1024; return 0; + } + if (XSTRCMP(v, "SECP256R1MLKEM768") == 0) { + *group = WOLFSSL_SECP256R1MLKEM768; return 0; + } + if (XSTRCMP(v, "X25519MLKEM768") == 0) { + *group = WOLFSSL_X25519MLKEM768; return 0; + } + return BAD_FUNC_ARG; +} + +static int readDer(const char* file, byte* der, int* derSz) +{ + XFILE f = XFOPEN(file, "rb"); + long sz; + if (f == XBADFILE) { + printf("Cannot open %s (run gen_pqc_certs first)\n", file); + return -1; + } + XFSEEK(f, 0, XSEEK_END); sz = XFTELL(f); XREWIND(f); + if (sz <= 0 || sz > *derSz) { XFCLOSE(f); return -1; } + if (XFREAD(der, 1, (size_t)sz, f) != (size_t)sz) { XFCLOSE(f); return -1; } + XFCLOSE(f); + *derSz = (int)sz; + return 0; +} + +int TPM2_TLS_ClientPQArgs(void* userCtx, int argc, char *argv[]) +{ + int rc = 0; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + SockIoCbCtx sockIoCtx; + const char* host = TLS_HOST; + word32 port = TLS_PORT; + int group = WOLFSSL_ML_KEM_768; + byte* caDer = NULL; + int caSz = PQ_CERT_BUF_SZ; + char reply[MAX_MSG_SZ]; + int replySz; + + (void)userCtx; + XMEMSET(&sockIoCtx, 0, sizeof(sockIoCtx)); + sockIoCtx.fd = -1; + sockIoCtx.listenFd = -1; + + while (argc > 1) { + if (XSTRNCMP(argv[argc-1], "-h=", 3) == 0) { + host = argv[argc-1] + 3; + } + else if (XSTRNCMP(argv[argc-1], "-p=", 3) == 0) { + port = (word32)XATOI(argv[argc-1] + 3); + } + else if (XSTRNCMP(argv[argc-1], "-group=", 7) == 0) { + if (parseGroup(argv[argc-1] + 7, &group) != 0) { + usage(); + return BAD_FUNC_ARG; + } + } + else if (XSTRCMP(argv[argc-1], "-?") == 0 || + XSTRCMP(argv[argc-1], "-h") == 0) { + usage(); + return 0; + } + argc--; + } + + caDer = (byte*)XMALLOC(PQ_CERT_BUF_SZ, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (caDer == NULL) { + return MEMORY_E; + } + + ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); + if (ctx == NULL) { + rc = MEMORY_E; + } + if (rc == 0) { + wolfSSL_CTX_SetIORecv(ctx, SockIORecv); + wolfSSL_CTX_SetIOSend(ctx, SockIOSend); + /* require the server chain to validate against the software CA */ + wolfSSL_CTX_set_verify(ctx, WOLFSSL_VERIFY_PEER, NULL); + rc = readDer(PQ_CA_CERT, caDer, &caSz); + } + if (rc == 0) { + if (wolfSSL_CTX_load_verify_buffer(ctx, caDer, caSz, + WOLFSSL_FILETYPE_ASN1) != WOLFSSL_SUCCESS) { + printf("load_verify_buffer failed\n"); + rc = -1; + } + } + if (rc == 0) { + ssl = wolfSSL_new(ctx); + if (ssl == NULL) { + rc = MEMORY_E; + } + } + if (rc == 0) { + wolfSSL_SetIOReadCtx(ssl, &sockIoCtx); + wolfSSL_SetIOWriteCtx(ssl, &sockIoCtx); + if (wolfSSL_UseKeyShare(ssl, (word16)group) != WOLFSSL_SUCCESS) { + printf("UseKeyShare failed\n"); + rc = -1; + } + } + if (rc == 0) { + /* advertise only the chosen group; no silent downgrade */ + if (wolfSSL_set_groups(ssl, &group, 1) != WOLFSSL_SUCCESS) { + printf("set_groups failed\n"); + rc = -1; + } + } + if (rc == 0) { + rc = SetupSocketAndConnect(&sockIoCtx, host, port); + } + if (rc == 0) { + do { + rc = wolfSSL_connect(ssl); + } while (rc != WOLFSSL_SUCCESS && + (wolfSSL_want_read(ssl) || wolfSSL_want_write(ssl))); + if (rc != WOLFSSL_SUCCESS) { + printf("connect failed: %s\n", + wolfSSL_ERR_reason_error_string(wolfSSL_get_error(ssl, rc))); + } + else { + rc = 0; + } + } + if (rc == 0) { + printf("Handshake: %s, group %s\n", + wolfSSL_get_cipher(ssl), wolfSSL_get_curve_name(ssl)); + printf("Server ML-DSA identity verified against the CA\n"); + if (wolfSSL_write(ssl, kHelloMsg, (int)XSTRLEN(kHelloMsg)) <= 0) { + printf("write failed: %s\n", + wolfSSL_ERR_reason_error_string(wolfSSL_get_error(ssl, 0))); + rc = -1; + } + } + if (rc == 0) { + replySz = wolfSSL_read(ssl, reply, sizeof(reply) - 1); + if (replySz <= 0) { + printf("read failed: %s\n", + wolfSSL_ERR_reason_error_string( + wolfSSL_get_error(ssl, replySz))); + rc = -1; + } + else { + reply[replySz] = 0; + printf("Server: %s", reply); + } + } + + if (ssl != NULL) { wolfSSL_shutdown(ssl); wolfSSL_free(ssl); } + if (ctx != NULL) wolfSSL_CTX_free(ctx); + CloseAndCleanupSocket(&sockIoCtx); + XFREE(caDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return rc; +} + +#endif + +#ifndef NO_MAIN_DRIVER +int main(int argc, char *argv[]) +{ + int rc = -1; +#if !defined(WOLFTPM2_NO_WRAPPER) && defined(WOLFTPM_CRYPTOCB) && \ + !defined(WOLFTPM2_NO_WOLFCRYPT) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(WOLFCRYPT_ONLY) && defined(WOLFTPM_MLDSA_SIGN) && \ + defined(WOLFSSL_TLS13) && !defined(NO_FILESYSTEM) + rc = TPM2_TLS_ClientPQArgs(NULL, argc, argv); +#else + (void)argc; + (void)argv; + printf("Requires wolfTPM --enable-pqc and wolfSSL crypto callbacks\n"); +#endif + return rc; +} +#endif diff --git a/examples/tls/tls_client_pq.h b/examples/tls/tls_client_pq.h new file mode 100644 index 000000000..dcc41cb5b --- /dev/null +++ b/examples/tls/tls_client_pq.h @@ -0,0 +1,35 @@ +/* tls_client_pq.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifndef _TPM_TLS_CLIENT_PQ_H_ +#define _TPM_TLS_CLIENT_PQ_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +int TPM2_TLS_ClientPQArgs(void* userCtx, int argc, char *argv[]); + +#ifdef __cplusplus + } /* extern "C" */ +#endif + +#endif /* _TPM_TLS_CLIENT_PQ_H_ */ diff --git a/examples/tls/tls_server_pq.c b/examples/tls/tls_server_pq.c new file mode 100644 index 000000000..ee0405c54 --- /dev/null +++ b/examples/tls/tls_server_pq.c @@ -0,0 +1,274 @@ +/* tls_server_pq.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* TLS 1.3 server whose ML-DSA identity key lives in the TPM. The TPM signs the + * CertificateVerify via the wolfTPM crypto callback. Pair with tls_client_pq. */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#if !defined(WOLFTPM2_NO_WRAPPER) && defined(WOLFTPM_CRYPTOCB) && \ + !defined(WOLFTPM2_NO_WOLFCRYPT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(WOLFCRYPT_ONLY) && defined(WOLFTPM_MLDSA_SIGN) && \ + defined(WOLF_PRIVATE_KEY_ID) && defined(WOLFSSL_TLS13) && \ + !defined(NO_FILESYSTEM) + +#ifndef _WIN32 +/* host-resolve declarations tls_common.h needs on POSIX */ +#include +#include +#endif + +#include +#include +#include +#include + +#include +#include + +#define PQ_SERVER_CERT "./certs/pq-server-cert.der" +#define PQ_CERT_BUF_SZ 10000 + +static const char kReplyMsg[] = "I hear you fa shizzle!\n"; + +static void usage(void) +{ + printf("Expected usage:\n"); + printf("./examples/tls/tls_server_pq [-p=port] [-mldsa=44/65/87]\n"); + printf("* -mldsa=44/65/87: ML-DSA set (must match gen_pqc_certs, def 65)\n"); +} + +static int parseParamSet(const char* v, TPMI_MLDSA_PARAMETER_SET* ps) +{ + if (XSTRCMP(v, "44") == 0) { *ps = TPM_MLDSA_44; return 0; } + if (XSTRCMP(v, "65") == 0) { *ps = TPM_MLDSA_65; return 0; } + if (XSTRCMP(v, "87") == 0) { *ps = TPM_MLDSA_87; return 0; } + return BAD_FUNC_ARG; +} + +static int readDer(const char* file, byte* der, int* derSz) +{ + XFILE f = XFOPEN(file, "rb"); + long sz; + if (f == XBADFILE) { + printf("Cannot open %s (run gen_pqc_certs first)\n", file); + return -1; + } + XFSEEK(f, 0, XSEEK_END); sz = XFTELL(f); XREWIND(f); + if (sz <= 0 || sz > *derSz) { XFCLOSE(f); return -1; } + if (XFREAD(der, 1, (size_t)sz, f) != (size_t)sz) { XFCLOSE(f); return -1; } + XFCLOSE(f); + *derSz = (int)sz; + return 0; +} + +int TPM2_TLS_ServerPQArgs(void* userCtx, int argc, char *argv[]) +{ + int rc; + WOLFTPM2_DEV dev; + WOLFTPM2_KEY mldsaKey; + TPMT_PUBLIC pub; + TpmCryptoDevCtx tpmCtx; + int tpmDevId = INVALID_DEVID; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + SockIoCbCtx sockIoCtx; + word32 port = TLS_PORT; + TPMI_MLDSA_PARAMETER_SET paramSet = TPM_MLDSA_65; + const byte keyId[] = { 't','p','m','m','l','d','s','a' }; + byte* certDer = NULL; + int certSz = PQ_CERT_BUF_SZ; + char msg[MAX_MSG_SZ]; + int msgSz; + + XMEMSET(&dev, 0, sizeof(dev)); + XMEMSET(&mldsaKey, 0, sizeof(mldsaKey)); + XMEMSET(&pub, 0, sizeof(pub)); + XMEMSET(&tpmCtx, 0, sizeof(tpmCtx)); + XMEMSET(&sockIoCtx, 0, sizeof(sockIoCtx)); + sockIoCtx.fd = -1; + sockIoCtx.listenFd = -1; + + while (argc > 1) { + if (XSTRCMP(argv[argc-1], "-?") == 0 || + XSTRCMP(argv[argc-1], "-h") == 0) { + usage(); + return 0; + } + else if (XSTRNCMP(argv[argc-1], "-p=", 3) == 0) { + port = (word32)XATOI(argv[argc-1] + 3); + } + else if (XSTRNCMP(argv[argc-1], "-mldsa=", 7) == 0) { + if (parseParamSet(argv[argc-1] + 7, ¶mSet) != 0) { + usage(); + return BAD_FUNC_ARG; + } + } + argc--; + } + + certDer = (byte*)XMALLOC(PQ_CERT_BUF_SZ, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (certDer == NULL) { + return MEMORY_E; + } + + rc = wolfTPM2_Init(&dev, TPM2_IoCb, userCtx); + if (rc != TPM_RC_SUCCESS) { + printf("wolfTPM2_Init failed 0x%x: %s\n", rc, wolfTPM2_GetRCString(rc)); + } + + if (rc == 0) { + /* recreate the same TPM ML-DSA identity key gen_pqc_certs certified */ + rc = wolfTPM2_GetKeyTemplate_MLDSA(&pub, + TPMA_OBJECT_sign | TPMA_OBJECT_fixedTPM | TPMA_OBJECT_fixedParent | + TPMA_OBJECT_sensitiveDataOrigin | TPMA_OBJECT_userWithAuth | + TPMA_OBJECT_noDA, paramSet, 0); + if (rc == TPM_RC_SUCCESS) { + rc = wolfTPM2_CreatePrimaryKey(&dev, &mldsaKey, TPM_RH_OWNER, &pub, + NULL, 0); + } + if (rc != TPM_RC_SUCCESS) { + printf("Create TPM ML-DSA key failed 0x%x: %s\n", + rc, wolfTPM2_GetRCString(rc)); + } + } + + if (rc == 0) { + tpmCtx.dev = &dev; + tpmCtx.mldsaKey = &mldsaKey; + rc = wolfTPM2_SetCryptoDevCb(&dev, wolfTPM2_CryptoDevCb, &tpmCtx, + &tpmDevId); + if (rc != TPM_RC_SUCCESS) { + printf("SetCryptoDevCb failed 0x%x\n", rc); + } + } + + if (rc == 0) { + ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method()); + if (ctx == NULL) { + rc = MEMORY_E; + } + } + if (rc == 0) { + wolfSSL_CTX_SetDevId(ctx, tpmDevId); + wolfSSL_CTX_SetIORecv(ctx, SockIORecv); + wolfSSL_CTX_SetIOSend(ctx, SockIOSend); + rc = readDer(PQ_SERVER_CERT, certDer, &certSz); + } + if (rc == 0) { + if (wolfSSL_CTX_use_certificate_buffer(ctx, certDer, certSz, + WOLFSSL_FILETYPE_ASN1) != WOLFSSL_SUCCESS) { + printf("use_certificate_buffer failed\n"); + rc = -1; + } + } + if (rc == 0) { + /* private key stays in the TPM: reference it by id + devId */ + if (wolfSSL_CTX_use_PrivateKey_Id(ctx, keyId, sizeof(keyId), tpmDevId) + != WOLFSSL_SUCCESS) { + printf("use_PrivateKey_Id failed\n"); + rc = -1; + } + } + if (rc == 0) { + ssl = wolfSSL_new(ctx); + if (ssl == NULL) { + rc = MEMORY_E; + } + } + if (rc == 0) { + wolfSSL_SetIOReadCtx(ssl, &sockIoCtx); + wolfSSL_SetIOWriteCtx(ssl, &sockIoCtx); + rc = SetupSocketAndListen(&sockIoCtx, port); + } + if (rc == 0) { + printf("PQC TLS server listening on port %u\n", (unsigned)port); + rc = SocketWaitClient(&sockIoCtx); + } + if (rc == 0) { + do { + rc = wolfSSL_accept(ssl); + } while (rc != WOLFSSL_SUCCESS && + (wolfSSL_want_read(ssl) || wolfSSL_want_write(ssl))); + if (rc != WOLFSSL_SUCCESS) { + printf("accept failed: %s\n", + wolfSSL_ERR_reason_error_string(wolfSSL_get_error(ssl, rc))); + } + else { + rc = 0; + } + } + if (rc == 0) { + printf("Handshake: %s, group %s (ML-DSA identity signed on TPM)\n", + wolfSSL_get_cipher(ssl), wolfSSL_get_curve_name(ssl)); + msgSz = wolfSSL_read(ssl, msg, sizeof(msg) - 1); + if (msgSz <= 0) { + printf("read failed: %s\n", + wolfSSL_ERR_reason_error_string(wolfSSL_get_error(ssl, msgSz))); + rc = -1; + } + else { + msg[msgSz] = 0; + printf("Client: %s", msg); + } + } + if (rc == 0) { + if (wolfSSL_write(ssl, kReplyMsg, (int)XSTRLEN(kReplyMsg)) <= 0) { + printf("write failed: %s\n", + wolfSSL_ERR_reason_error_string(wolfSSL_get_error(ssl, 0))); + rc = -1; + } + } + + if (ssl != NULL) { wolfSSL_shutdown(ssl); wolfSSL_free(ssl); } + if (ctx != NULL) wolfSSL_CTX_free(ctx); + CloseAndCleanupSocket(&sockIoCtx); + if (mldsaKey.handle.hndl != 0) + wolfTPM2_UnloadHandle(&dev, &mldsaKey.handle); + wolfTPM2_Cleanup(&dev); + XFREE(certDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return rc; +} + +#endif + +#ifndef NO_MAIN_DRIVER +int main(int argc, char *argv[]) +{ + int rc = -1; +#if !defined(WOLFTPM2_NO_WRAPPER) && defined(WOLFTPM_CRYPTOCB) && \ + !defined(WOLFTPM2_NO_WOLFCRYPT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(WOLFCRYPT_ONLY) && defined(WOLFTPM_MLDSA_SIGN) && \ + defined(WOLF_PRIVATE_KEY_ID) && defined(WOLFSSL_TLS13) && \ + !defined(NO_FILESYSTEM) + rc = TPM2_TLS_ServerPQArgs(NULL, argc, argv); +#else + (void)argc; + (void)argv; + printf("Requires wolfTPM --enable-pqc and wolfSSL crypto callbacks\n"); +#endif + return rc; +} +#endif diff --git a/examples/tls/tls_server_pq.h b/examples/tls/tls_server_pq.h new file mode 100644 index 000000000..ee512309e --- /dev/null +++ b/examples/tls/tls_server_pq.h @@ -0,0 +1,35 @@ +/* tls_server_pq.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifndef _TPM_TLS_SERVER_PQ_H_ +#define _TPM_TLS_SERVER_PQ_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +int TPM2_TLS_ServerPQArgs(void* userCtx, int argc, char *argv[]); + +#ifdef __cplusplus + } /* extern "C" */ +#endif + +#endif /* _TPM_TLS_SERVER_PQ_H_ */ diff --git a/src/tpm2_cryptocb.c b/src/tpm2_cryptocb.c index 27b262121..bec4ec24c 100644 --- a/src/tpm2_cryptocb.c +++ b/src/tpm2_cryptocb.c @@ -82,7 +82,7 @@ int wolfTPM2_CryptoDevCb(int devId, wc_CryptoInfo* info, void* ctx) rc = wolfTPM2_GetRandom(tlsCtx->dev, info->seed.seed, info->seed.sz); #endif /* !WC_NO_RNG */ } -#if !defined(NO_RSA) || defined(HAVE_ECC) +#if !defined(NO_RSA) || defined(HAVE_ECC) || defined(WOLFTPM_MLDSA_SIGN) else if (info->algo_type == WC_ALGO_TYPE_PK) { #ifndef NO_RSA /* RSA */ @@ -425,8 +425,51 @@ int wolfTPM2_CryptoDevCb(int devId, wc_CryptoInfo* info, void* ctx) #endif /* !WOLFTPM2_USE_SW_ECDHE */ } #endif /* HAVE_ECC */ + #ifdef WOLFTPM_MLDSA_SIGN + if (info->pk.type == WC_PK_TYPE_PQC_SIG_SIGN) { + TPM_HANDLE seqHandle = 0; + WOLFTPM2_HANDLE seqHandleObj; + int sigSz; + + /* pure ML-DSA one-shot only; pre-hash, oversized, no-key, bad-arg + * fall back */ + if (info->pk.pqc_sign.type != WC_PQC_SIG_TYPE_MLDSA || + info->pk.pqc_sign.preHashType != WC_HASH_TYPE_NONE || + info->pk.pqc_sign.out == NULL || + info->pk.pqc_sign.outlen == NULL || + (info->pk.pqc_sign.in == NULL && + info->pk.pqc_sign.inlen > 0) || + info->pk.pqc_sign.inlen > MAX_DIGEST_BUFFER || + tlsCtx->mldsaKey == NULL) { + return exit_rc; + } + sigSz = (int)*info->pk.pqc_sign.outlen; + + rc = wolfTPM2_SignSequenceStart(tlsCtx->dev, tlsCtx->mldsaKey, + info->pk.pqc_sign.context, (int)info->pk.pqc_sign.contextLen, + &seqHandle); + if (rc == 0) { + rc = wolfTPM2_SignSequenceComplete(tlsCtx->dev, seqHandle, + tlsCtx->mldsaKey, info->pk.pqc_sign.in, + (int)info->pk.pqc_sign.inlen, info->pk.pqc_sign.out, &sigSz); + if (rc == 0) { + *info->pk.pqc_sign.outlen = (word32)sigSz; + } + } + if (rc != 0 && seqHandle != 0) { + /* free seq on failure */ + XMEMSET(&seqHandleObj, 0, sizeof(seqHandleObj)); + seqHandleObj.hndl = seqHandle; + wolfTPM2_UnloadHandle(tlsCtx->dev, &seqHandleObj); + } + if (rc == BUFFER_E) { + /* preserve caller size-error; don't mask it as WC_HW_E */ + return BUFFER_E; + } + } + #endif /* WOLFTPM_MLDSA_SIGN */ } -#endif /* !NO_RSA || HAVE_ECC */ +#endif /* !NO_RSA || HAVE_ECC || WOLFTPM_MLDSA_SIGN */ #ifndef NO_AES else if (info->algo_type == WC_ALGO_TYPE_CIPHER) { if (info->cipher.type != WC_CIPHER_AES_CBC) { diff --git a/tests/include.am b/tests/include.am index 383aa6c06..37a5f7b00 100644 --- a/tests/include.am +++ b/tests/include.am @@ -41,3 +41,10 @@ if BUILD_FWTPM dist_noinst_SCRIPTS += tests/fwtpm_check.sh \ tests/fwtpm_da_retry.sh endif + +# tls_pq_e2e.sh is intentionally NOT a `make check` test: it needs the wolfSSL +# crypto-cb ML-DSA device-key fix (not yet upstream) and a cert-gen build, and +# it manages its own fwTPM server + NV file (which would collide with the +# parallel fwtpm_check.sh). It is distributed and run standalone by the +# pqc-examples CI workflow instead. +EXTRA_DIST += tests/tls_pq_e2e.sh diff --git a/tests/tls_pq_e2e.sh b/tests/tls_pq_e2e.sh new file mode 100755 index 000000000..9fec05b02 --- /dev/null +++ b/tests/tls_pq_e2e.sh @@ -0,0 +1,221 @@ +#!/bin/bash +# tls_pq_e2e.sh +# +# Copyright (C) 2006-2026 wolfSSL Inc. +# +# This file is part of wolfTPM. +# +# wolfTPM is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# wolfTPM is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA +# + +# End-to-end harness for the post-quantum TLS example: spawn fwtpm_server, +# generate a TPM-keyed ML-DSA cert chain, run tls_server_pq and tls_client_pq +# over a real TCP socket, and assert the full PQC handshake (ML-KEM key +# exchange + TPM-signed ML-DSA authentication, CA-verified) plus app data. + +set -u + +# returns: 0 = in use, 1 = free, 2 = no probe tool +check_port_in_use() { + if command -v nc >/dev/null 2>&1; then + nc -z localhost "$1" 2>/dev/null; return $? + elif command -v ss >/dev/null 2>&1; then + ss -tln 2>/dev/null | grep -qE "[:.]$1 "; return $? + elif command -v netstat >/dev/null 2>&1; then + netstat -an 2>/dev/null | grep -qE "[:.]$1 .*LISTEN"; return $? + fi + return 2 +} + +pick_available_port() { + local port attempts=0 + while [ $attempts -lt 20 ]; do + if command -v shuf >/dev/null 2>&1; then port=$(shuf -i 10000-60000 -n 1) + else port=$(( (RANDOM % 50000) + 10000 )); fi + check_port_in_use "$port"; [ $? -ne 0 ] && { echo "$port"; return 0; } + attempts=$((attempts + 1)) + done + return 1 +} + +# pick a port whose consecutive platform port (port+1) is also free +pick_available_pair() { + local port attempts=0 + while [ $attempts -lt 20 ]; do + port=$(pick_available_port) || return 1 + check_port_in_use "$((port + 1))" || { echo "$port"; return 0; } + attempts=$((attempts + 1)) + done + return 1 +} + +# wait for $1 to reach LISTEN (ss/netstat, not nc -z which eats the accept slot) +wait_for_listen() { + local port="$1" pid="$2" elapsed=0 + if ! command -v ss >/dev/null 2>&1 && ! command -v netstat >/dev/null 2>&1; then + sleep 2; return 0 + fi + while [ $elapsed -lt 400 ]; do + kill -0 "$pid" 2>/dev/null || return 1 + if command -v ss >/dev/null 2>&1; then + ss -tln 2>/dev/null | grep -qE "[:.]${port} " && return 0 + elif netstat -an 2>/dev/null | grep -qE "[:.]${port} .*LISTEN"; then + return 0 + fi + sleep 0.02 + elapsed=$((elapsed + 1)) + done + return 1 +} + +# binaries and generated artifacts live in the build tree (VPATH-safe) +BUILD_DIR="$(pwd)" +cd "$BUILD_DIR" || exit 1 + +SERVER="$BUILD_DIR/src/fwtpm/fwtpm_server" +GEN="$BUILD_DIR/examples/pqc/gen_pqc_certs" +TLS_SRV="$BUILD_DIR/examples/tls/tls_server_pq" +TLS_CLI="$BUILD_DIR/examples/tls/tls_client_pq" +# fwTPM uses the compile-time FWTPM_NV_FILE (cwd-relative), not an env var +NV_FILE="$BUILD_DIR/fwtpm_nv.bin" +LOGP="/tmp/tls_pq_e2e_$$" +# examples honor TPM2_SWTPM_PORT only when built with getenv support +HAS_GETENV=1 +WOLFTPM_OPTS="$BUILD_DIR/wolftpm/options.h" +if [ -f "$WOLFTPM_OPTS" ] && grep -q "^#define NO_GETENV" "$WOLFTPM_OPTS"; then + HAS_GETENV=0 +fi +PORT="${TPM2_SWTPM_PORT:-2321}" +if check_port_in_use "$PORT" || check_port_in_use "$((PORT + 1))"; then + if [ "$HAS_GETENV" -eq 1 ]; then + PORT="$(pick_available_pair)" + else + echo "SKIP: TPM ports $PORT/$((PORT + 1)) busy (NO_GETENV build)" >&2 + exit 77 + fi +fi +PLATFORM_PORT=$((PORT + 1)) +TLS_PORT="$(pick_available_port)" +port_tries=0 +while { [ "$TLS_PORT" = "$PORT" ] || [ "$TLS_PORT" = "$PLATFORM_PORT" ]; } \ + && [ $port_tries -lt 10 ]; do + TLS_PORT="$(pick_available_port)" + port_tries=$((port_tries + 1)) +done +GROUP="${1:-ML_KEM_768}" +MLDSA="${2:-65}" +if [ -z "$PORT" ] || [ -z "$TLS_PORT" ]; then + echo "SKIP: could not allocate a free port" >&2; exit 77 +fi + +for bin in "$SERVER" "$GEN" "$TLS_SRV" "$TLS_CLI"; do + if [ ! -x "$bin" ]; then + echo "SKIP: $(basename "$bin") not built — configure --enable-fwtpm --enable-swtpm --enable-pqc" >&2 + exit 77 + fi +done + +# the examples compile executable stubs when wolfSSL cert-gen / private-key-id / +# TLS 1.3 support is missing; skip rather than fail in that build matrix +for bin in "$GEN" "$TLS_SRV" "$TLS_CLI"; do + if "$bin" -h 2>&1 | grep -q "Requires"; then + echo "SKIP: $(basename "$bin") built without required wolfSSL features" >&2 + exit 77 + fi +done + +export TPM2_SWTPM_PORT=$PORT + +rm -f "$NV_FILE" + +echo "== Starting fwtpm_server on port $PORT ==" +"$SERVER" \ + --port $PORT --platform-port $PLATFORM_PORT --clear \ + >"${LOGP}_fwtpm.log" 2>&1 & +SERVER_PID=$! + +( + for _ in 1 2 3 4 5 6 7 8 9 10; do + if exec 3<>/dev/tcp/127.0.0.1/$PORT; then exec 3>&-; exit 0; fi + sleep 0.5 + done + exit 1 +) 2>/dev/null +if [ $? -ne 0 ] || ! kill -0 "$SERVER_PID" 2>/dev/null; then + echo "FAIL: fwtpm_server not accepting connections on port $PORT" >&2 + kill "$SERVER_PID" 2>/dev/null; rm -f "$NV_FILE"; exit 1 +fi + +TLS_SRV_PID="" +cleanup() { + [ -n "$TLS_SRV_PID" ] && kill "$TLS_SRV_PID" 2>/dev/null + TLS_SRV_PID="" + [ -n "$SERVER_PID" ] && { kill "$SERVER_PID" 2>/dev/null; \ + wait "$SERVER_PID" 2>/dev/null; } + SERVER_PID="" + rm -f "$NV_FILE" "$BUILD_DIR/certs/pq-ca-cert.der" \ + "$BUILD_DIR/certs/pq-server-cert.der" +} +trap cleanup EXIT + +mkdir -p "$BUILD_DIR/certs" +echo "== Generating TPM ML-DSA-$MLDSA cert chain ==" +if ! "$GEN" -mldsa=$MLDSA >"${LOGP}_gen.log" 2>&1; then + echo "FAIL: gen_pqc_certs" >&2; cat "${LOGP}_gen.log" >&2; cleanup; exit 1 +fi + +echo "== Starting tls_server_pq on port $TLS_PORT ==" +"$TLS_SRV" -p=$TLS_PORT -mldsa=$MLDSA >"${LOGP}_server.log" 2>&1 & +TLS_SRV_PID=$! +if ! wait_for_listen "$TLS_PORT" "$TLS_SRV_PID"; then + echo "FAIL: tls_server_pq not listening on port $TLS_PORT" >&2 + cat "${LOGP}_server.log" >&2; cleanup; exit 1 +fi + +echo "== Running tls_client_pq (group $GROUP) ==" +"$TLS_CLI" -h=localhost -p=$TLS_PORT -group=$GROUP >"${LOGP}_client.log" 2>&1 +CLI_RC=$? + +# if the client connected, the server serves it and exits; wait to flush its +# log. Otherwise (e.g. unsupported group) kill it so we do not hang forever. +if [ $CLI_RC -eq 0 ]; then + wait "$TLS_SRV_PID" 2>/dev/null +else + kill "$TLS_SRV_PID" 2>/dev/null +fi +TLS_SRV_PID="" + +cleanup + +CLI=$(cat "${LOGP}_client.log") +SRV=$(cat "${LOGP}_server.log") +FAIL=0 +echo "$CLI" | grep -qi "group $GROUP" || { echo "FAIL: client did not negotiate $GROUP" >&2; FAIL=1; } +echo "$CLI" | grep -q "verified against the CA" || { echo "FAIL: client did not verify server cert" >&2; FAIL=1; } +echo "$CLI" | grep -q "I hear you fa shizzle" || { echo "FAIL: no app data from server" >&2; FAIL=1; } +echo "$SRV" | grep -q "signed on TPM" || { echo "FAIL: server did not sign on the TPM" >&2; FAIL=1; } + +if [ $CLI_RC -eq 0 ] && [ $FAIL -eq 0 ]; then + echo "OK: PQC TLS handshake (ML-KEM $GROUP + TPM ML-DSA-$MLDSA) passed" + exit 0 +fi +echo "FAIL: PQC TLS E2E (client rc=$CLI_RC)" >&2 +if ! echo "$SRV" | grep -q "signed on TPM"; then + echo "HINT: server never signed on the TPM — the linked wolfSSL may lack the" >&2 + echo " wc_MlDsaKey_SignCtx crypto-callback route (see examples/pqc/README.md)" >&2 +fi +echo "--- client ---" >&2; echo "$CLI" >&2 +echo "--- server ---" >&2; echo "$SRV" >&2 +exit 1 diff --git a/tests/unit_tests.c b/tests/unit_tests.c index ca40237de..04367df4f 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -32,6 +32,9 @@ #include #include #include +#ifdef WOLFTPM_MLDSA_SIGN +#include +#endif #include #include @@ -4667,6 +4670,160 @@ static void test_wolfTPM2_CryptoDevCb_EccVerifyOversizedRS(void) #endif } +static void test_wolfTPM2_CryptoDevCb_MlDsaSign(void) +{ +#if !defined(WOLFTPM2_NO_WRAPPER) && defined(WOLFTPM_CRYPTOCB) && \ + !defined(WOLFTPM2_NO_WOLFCRYPT) && defined(WOLFTPM_MLDSA_SIGN) + int rc; + WOLFTPM2_DEV dev; + WOLFTPM2_KEY dummyKey; + WOLFTPM2_KEY tpmKey; + TPMT_PUBLIC pub; + TpmCryptoDevCtx tpmCtx; + wc_CryptoInfo info; + byte msg[32]; + byte sig[64]; + byte signContext[4]; + word32 sigLen = (word32)sizeof(sig); + byte* bigSig = NULL; + word32 bigSigLen; +#ifdef WOLFTPM_MLDSA_VERIFY + byte otherContext[4]; + TPM_HANDLE vSeq; + TPMT_TK_VERIFIED vtk; +#endif + + XMEMSET(&tpmCtx, 0, sizeof(tpmCtx)); + XMEMSET(&dummyKey, 0, sizeof(dummyKey)); + XMEMSET(&tpmKey, 0, sizeof(tpmKey)); + XMEMSET(&pub, 0, sizeof(pub)); + XMEMSET(msg, 0x5A, sizeof(msg)); + + rc = wolfTPM2_Init(&dev, TPM2_IoCb, NULL); + AssertIntEQ(rc, 0); + tpmCtx.dev = &dev; + + XMEMSET(&info, 0, sizeof(info)); + info.algo_type = WC_ALGO_TYPE_PK; + info.pk.type = WC_PK_TYPE_PQC_SIG_SIGN; + info.pk.pqc_sign.type = WC_PQC_SIG_TYPE_MLDSA; + info.pk.pqc_sign.in = msg; + info.pk.pqc_sign.inlen = (word32)sizeof(msg); + info.pk.pqc_sign.out = sig; + info.pk.pqc_sign.outlen = &sigLen; + + /* no key: fall back */ + tpmCtx.mldsaKey = NULL; + rc = wolfTPM2_CryptoDevCb(INVALID_DEVID, &info, &tpmCtx); + AssertIntEQ(rc, CRYPTOCB_UNAVAILABLE); + + /* wrong PQC type: fall back */ + info.pk.pqc_sign.type = WC_PQC_SIG_TYPE_MLDSA + 1; + tpmCtx.mldsaKey = &dummyKey; + rc = wolfTPM2_CryptoDevCb(INVALID_DEVID, &info, &tpmCtx); + AssertIntEQ(rc, CRYPTOCB_UNAVAILABLE); + + /* pre-hash: fall back, not pure ML-DSA */ + info.pk.pqc_sign.type = WC_PQC_SIG_TYPE_MLDSA; + info.pk.pqc_sign.preHashType = WC_HASH_TYPE_SHA256; + rc = wolfTPM2_CryptoDevCb(INVALID_DEVID, &info, &tpmCtx); + AssertIntEQ(rc, CRYPTOCB_UNAVAILABLE); + info.pk.pqc_sign.preHashType = WC_HASH_TYPE_NONE; + + /* too big for one-shot: fall back */ + info.pk.pqc_sign.inlen = MAX_DIGEST_BUFFER + 1; + rc = wolfTPM2_CryptoDevCb(INVALID_DEVID, &info, &tpmCtx); + AssertIntEQ(rc, CRYPTOCB_UNAVAILABLE); + info.pk.pqc_sign.inlen = (word32)sizeof(msg); + + /* NULL outlen: reject before dereference */ + info.pk.pqc_sign.outlen = NULL; + rc = wolfTPM2_CryptoDevCb(INVALID_DEVID, &info, &tpmCtx); + AssertIntEQ(rc, CRYPTOCB_UNAVAILABLE); + info.pk.pqc_sign.outlen = &sigLen; + + /* happy path (needs v1.85 ML-DSA) */ + rc = wolfTPM2_GetKeyTemplate_MLDSA(&pub, + TPMA_OBJECT_sign | TPMA_OBJECT_fixedTPM | TPMA_OBJECT_fixedParent | + TPMA_OBJECT_sensitiveDataOrigin | TPMA_OBJECT_userWithAuth | + TPMA_OBJECT_noDA, TPM_MLDSA_65, 0); + if (rc == 0) { + rc = wolfTPM2_CreatePrimaryKey(&dev, &tpmKey, TPM_RH_OWNER, &pub, + NULL, 0); + } + if (rc == TPM_RC_VALUE || rc == TPM_RC_SCHEME || + rc == TPM_RC_COMMAND_CODE || rc == (int)(RC_VER1 + 0x043)) { + /* TPM lacks ML-DSA: skip */ + printf("Test TPM Wrapper: %-40s Skipped (not supported)\n", + "CryptoDevCb ML-DSA sign:"); + } + else { + AssertIntEQ(rc, 0); + bigSig = (byte*)XMALLOC(WC_MLDSA_65_SIG_SIZE, NULL, + DYNAMIC_TYPE_TMP_BUFFER); + AssertNotNull(bigSig); + tpmCtx.mldsaKey = &tpmKey; + info.pk.pqc_sign.type = WC_PQC_SIG_TYPE_MLDSA; + info.pk.pqc_sign.out = bigSig; + + /* full buffer: signs, sets len */ + bigSigLen = (word32)WC_MLDSA_65_SIG_SIZE; + info.pk.pqc_sign.outlen = &bigSigLen; + rc = wolfTPM2_CryptoDevCb(INVALID_DEVID, &info, &tpmCtx); + AssertIntEQ(rc, 0); + AssertIntEQ((int)bigSigLen, WC_MLDSA_65_SIG_SIZE); + + /* context passthrough: sig must verify under the same context */ + XMEMSET(signContext, 0x42, sizeof(signContext)); + bigSigLen = (word32)WC_MLDSA_65_SIG_SIZE; + info.pk.pqc_sign.outlen = &bigSigLen; + info.pk.pqc_sign.context = signContext; + info.pk.pqc_sign.contextLen = (byte)sizeof(signContext); + rc = wolfTPM2_CryptoDevCb(INVALID_DEVID, &info, &tpmCtx); + AssertIntEQ(rc, 0); + AssertIntEQ((int)bigSigLen, WC_MLDSA_65_SIG_SIZE); + info.pk.pqc_sign.context = NULL; + info.pk.pqc_sign.contextLen = 0; + +#ifdef WOLFTPM_MLDSA_VERIFY + rc = wolfTPM2_VerifySequenceStart(&dev, &tpmKey, signContext, + (int)sizeof(signContext), &vSeq); + AssertIntEQ(rc, 0); + rc = wolfTPM2_VerifySequenceUpdate(&dev, vSeq, msg, (int)sizeof(msg)); + AssertIntEQ(rc, 0); + XMEMSET(&vtk, 0, sizeof(vtk)); + rc = wolfTPM2_VerifySequenceComplete(&dev, vSeq, &tpmKey, + NULL, 0, bigSig, (int)bigSigLen, &vtk); + AssertIntEQ(rc, 0); + + /* a different context must not verify the same signature */ + XMEMSET(otherContext, 0x24, sizeof(otherContext)); + rc = wolfTPM2_VerifySequenceStart(&dev, &tpmKey, otherContext, + (int)sizeof(otherContext), &vSeq); + AssertIntEQ(rc, 0); + rc = wolfTPM2_VerifySequenceUpdate(&dev, vSeq, msg, (int)sizeof(msg)); + AssertIntEQ(rc, 0); + XMEMSET(&vtk, 0, sizeof(vtk)); + rc = wolfTPM2_VerifySequenceComplete(&dev, vSeq, &tpmKey, + NULL, 0, bigSig, (int)bigSigLen, &vtk); + AssertIntNE(rc, 0); +#endif /* WOLFTPM_MLDSA_VERIFY */ + + /* small buffer: preserves the size-error contract, not WC_HW_E */ + bigSigLen = 16; + info.pk.pqc_sign.outlen = &bigSigLen; + rc = wolfTPM2_CryptoDevCb(INVALID_DEVID, &info, &tpmCtx); + AssertIntEQ(rc, BUFFER_E); + + XFREE(bigSig, NULL, DYNAMIC_TYPE_TMP_BUFFER); + wolfTPM2_UnloadHandle(&dev, &tpmKey.handle); + printf("Test TPM Wrapper: %-40s Passed\n", "CryptoDevCb ML-DSA sign:"); + } + + wolfTPM2_Cleanup(&dev); +#endif +} + static void test_TPM2_ASN_DecodeX509Cert_Errors(void) { #if !defined(WOLFTPM2_NO_WRAPPER) && !defined(WOLFTPM2_NO_ASN) @@ -6909,6 +7066,7 @@ int unit_tests(int argc, char *argv[]) test_wolfTPM2_ReadPublicKey(); test_wolfTPM2_CSR(); test_wolfTPM2_CryptoDevCb_EccVerifyOversizedRS(); + test_wolfTPM2_CryptoDevCb_MlDsaSign(); test_TPM2_ASN_DecodeX509Cert_Errors(); test_TPM2_ASN_DecodeX509Cert_Valid(); test_TPM2_ASN_DecodeTag_Errors(); diff --git a/wolftpm/tpm2_wrap.h b/wolftpm/tpm2_wrap.h index 108b30934..4f40378b1 100644 --- a/wolftpm/tpm2_wrap.h +++ b/wolftpm/tpm2_wrap.h @@ -4339,6 +4339,9 @@ typedef struct TpmCryptoDevCtx { unsigned short useSymmetricOnTPM:1; /* if set indicates desire to use symmetric algorithms on TPM */ #endif unsigned short useFIPSMode:1; /* if set requires FIPS mode on TPM and no fallback to software algos */ +#ifdef WOLFTPM_MLDSA_SIGN + WOLFTPM2_KEY* mldsaKey; /* ML-DSA identity key; private key stays in TPM */ +#endif } TpmCryptoDevCtx; #endif /* WOLFTPM_CRYPTOCB || HAVE_PK_CALLBACKS */ From 43903b6f2e5c9cfbadc635b0a8643f12db5ae719 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 28 Jul 2026 15:27:48 -0700 Subject: [PATCH 2/4] Fold PQC TLS into tls_client, tls_server and run_examples.sh --- .github/workflows/pqc-examples.yml | 41 +++-- .gitignore | 3 - examples/pqc/README.md | 26 +-- examples/pqc/gen_pqc_certs.c | 4 + examples/run_examples.sh | 61 +++++++ examples/tls/include.am | 20 +-- examples/tls/tls_client.c | 149 +++++++++++++++- examples/tls/tls_client_pq.c | 242 ------------------------- examples/tls/tls_client_pq.h | 35 ---- examples/tls/tls_common.h | 80 +++++++++ examples/tls/tls_server.c | 145 ++++++++++++++- examples/tls/tls_server_pq.c | 274 ----------------------------- examples/tls/tls_server_pq.h | 35 ---- src/tpm2_cryptocb.c | 17 +- tests/include.am | 7 - tests/tls_pq_e2e.sh | 221 ----------------------- tests/unit_tests.c | 14 +- 17 files changed, 489 insertions(+), 885 deletions(-) delete mode 100644 examples/tls/tls_client_pq.c delete mode 100644 examples/tls/tls_client_pq.h delete mode 100644 examples/tls/tls_server_pq.c delete mode 100644 examples/tls/tls_server_pq.h delete mode 100755 tests/tls_pq_e2e.sh diff --git a/.github/workflows/pqc-examples.yml b/.github/workflows/pqc-examples.yml index 80235f445..e1f0a2158 100644 --- a/.github/workflows/pqc-examples.yml +++ b/.github/workflows/pqc-examples.yml @@ -177,6 +177,8 @@ jobs: # 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) + permissions: + contents: read if: github.event_name != 'pull_request' || github.event.pull_request.draft == false runs-on: ubuntu-latest container: @@ -189,6 +191,8 @@ jobs: steps: - name: Checkout wolfTPM uses: actions/checkout@v4 + with: + persist-credentials: false # TODO(revert-when-merged): build wolfSSL from the aidangarske/wolfssl # mldsa-signctx-cryptocb-devkey branch because the wc_MlDsaKey_SignCtx @@ -196,12 +200,17 @@ jobs: # 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) + env: + # aidangarske/wolfssl mldsa-signctx-cryptocb-devkey + WOLFSSL_PQC_SHA: 6b0c832284286dbaec8e5ab35581ff470e90826b run: | cd ~ - git clone --depth 1 \ - --branch mldsa-signctx-cryptocb-devkey \ - https://github.com/aidangarske/wolfssl.git + # pinned to an immutable commit: a mutable branch tip would let a + # force-push run unreviewed code inside this job + git clone --no-checkout https://github.com/aidangarske/wolfssl.git cd wolfssl + git fetch --depth 1 origin "$WOLFSSL_PQC_SHA" + git checkout "$WOLFSSL_PQC_SHA" ./autogen.sh ./configure --enable-wolftpm --enable-pkcallbacks --enable-keygen \ --enable-certgen --enable-dilithium --enable-mlkem \ @@ -227,7 +236,7 @@ jobs: - 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 + examples/tls/tls_server examples/tls/tls_client; do if ./$b -h 2>&1 | grep -q "Requires"; then echo "FAIL: $b is a stub — wolfSSL missing a required feature" exit 1 @@ -236,23 +245,31 @@ jobs: - name: PQC TLS handshake E2E (ML-KEM x ML-DSA matrix) run: | + set +e 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 + # run_examples.sh pairs the classical TLS tests with the wolfSSL + # example client/server, so point it at the clone built above + export WOLFSSL_PATH="$HOME/wolfssl" + ./src/fwtpm/fwtpm_server >/tmp/fwtpm_tls.log 2>&1 & + echo $! > /tmp/fwtpm_tls.pid + sleep 2 + ENABLE_PQC_TLS=1 ./examples/run_examples.sh + rc=$? exit $rc + - name: Stop fwtpm_server (PQC TLS) + if: always() + run: kill "$(cat /tmp/fwtpm_tls.pid)" 2>/dev/null || true + - name: Upload failure logs if: failure() uses: actions/upload-artifact@v4 with: name: pqc-tls-examples-logs path: | - /tmp/tls_pq_e2e_*.log + /tmp/fwtpm_tls.log + run.out + pqtls.out config.log tests/*.log retention-days: 5 diff --git a/.gitignore b/.gitignore index f0d5bbc12..072105e4b 100644 --- a/.gitignore +++ b/.gitignore @@ -64,14 +64,11 @@ 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 diff --git a/examples/pqc/README.md b/examples/pqc/README.md index cf932332c..0968bb677 100644 --- a/examples/pqc/README.md +++ b/examples/pqc/README.md @@ -14,11 +14,16 @@ the full fwTPM PQC reference. ``` ./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. @@ -193,14 +198,16 @@ Demo scope: the identity key is an unauthenticated deterministic TPM primary 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. +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_pq` — recreates that TPM key and serves TLS 1.3. -- `examples/tls/tls_client_pq` — connects, ML-KEM key exchange, verifies the CA. +- `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 & @@ -209,16 +216,16 @@ Three programs: ./examples/pqc/gen_pqc_certs -mldsa=65 # 2. server (same -mldsa as gen_pqc_certs) -./examples/tls/tls_server_pq -p=11111 -mldsa=65 & +./examples/tls/tls_server -p=11111 -mldsa=65 & # 3. client (choose the ML-KEM group) -./examples/tls/tls_client_pq -h=localhost -p=11111 -group=ML_KEM_768 +./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_pq -p= -mldsa=44/65/87`. -- `tls_client_pq -h= -p= -group=` where `` is +- `tls_server -p= -mldsa=44/65/87`. +- `tls_client -h= -p= -group=` where `` is `ML_KEM_512/768/1024` or a hybrid `SECP256R1MLKEM768` / `X25519MLKEM768` (hybrids need the matching classical curve enabled in wolfSSL). @@ -226,6 +233,5 @@ 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 +ENABLE_PQC_TLS=1 ./examples/run_examples.sh # includes the PQC TLS matrix ``` diff --git a/examples/pqc/gen_pqc_certs.c b/examples/pqc/gen_pqc_certs.c index 6c09101d1..9bf2b215a 100644 --- a/examples/pqc/gen_pqc_certs.c +++ b/examples/pqc/gen_pqc_certs.c @@ -264,6 +264,10 @@ static int TPM2_PQC_GenCerts(void* userCtx, int argc, char *argv[]) } } +#ifdef WOLFSSL_CERT_GEN_CACHE + /* wc_SetIssuerBuffer retains a decoded copy until this is called */ + wc_SetCert_Free(&leafCert); +#endif if (havePubKey) wc_MlDsaKey_Free(&tpmPubKey); if (haveCaKey) wc_MlDsaKey_Free(&caKey); if (haveRng) wc_FreeRng(&rng); diff --git a/examples/run_examples.sh b/examples/run_examples.sh index 840063c7f..68ecc372b 100755 --- a/examples/run_examples.sh +++ b/examples/run_examples.sh @@ -678,6 +678,49 @@ run_tpm_tls_server() { # Usage: run_tpm_tls_server [ecc/rsa] [tpmargs] [tlsversi popd >> $TPMPWD/run.out 2>&1 } + +run_tpm_tls_pq() { # Usage: run_tpm_tls_pq [ML-KEM group] [ML-DSA set] + echo -e "TLS test (TPM PQC) group $1 ML-DSA-$2" + generate_port + + # the TPM ML-DSA identity and its CA are regenerated per parameter set + echo -e "./examples/pqc/gen_pqc_certs -mldsa=$2" + ./examples/pqc/gen_pqc_certs -mldsa=$2 >> $TPMPWD/run.out 2>&1 + RESULT=$? + [ $RESULT -ne 0 ] && echo -e "gen_pqc_certs mldsa=$2 failed! $RESULT" && exit 1 + + echo -e "./examples/tls/tls_server -p=$port -mldsa=$2" + ./examples/tls/tls_server -p=$port -mldsa=$2 > $TPMPWD/pqtls.out 2>&1 & + SERVER_PID=$! + if ! wait_for_port "$port" 500; then + echo -e "TPM PQC TLS server failed to start on port $port" + kill $SERVER_PID 2>/dev/null + exit 1 + fi + + echo -e "./examples/tls/tls_client -p=$port -mldsa -group=$1" + ./examples/tls/tls_client -p=$port -mldsa -group=$1 >> $TPMPWD/run.out 2>&1 + RESULT=$? + if [ $RESULT -ne 0 ]; then + kill $SERVER_PID 2>/dev/null + wait $SERVER_PID 2>/dev/null + cat $TPMPWD/pqtls.out >> $TPMPWD/run.out + rm -f $TPMPWD/pqtls.out + echo -e "tpm pqc tls $1 mldsa=$2 failed! $RESULT" && exit 1 + fi + wait $SERVER_PID 2>/dev/null + SRV_RESULT=$? + cat $TPMPWD/pqtls.out >> $TPMPWD/run.out + [ $SRV_RESULT -ne 0 ] && rm -f $TPMPWD/pqtls.out && \ + echo -e "tpm pqc tls server $1 mldsa=$2 failed! $SRV_RESULT" && exit 1 + + # this run's server must have signed on the TPM, not in software + grep -q "signed on TPM" $TPMPWD/pqtls.out + RESULT=$? + rm -f $TPMPWD/pqtls.out + [ $RESULT -ne 0 ] && echo -e "tpm pqc tls $1 mldsa=$2 did not sign on TPM!" && exit 1 +} + if [ $WOLFCRYPT_ENABLE -eq 1 ] && [ $WOLFCRYPT_DEFAULT -eq 0 ] && [ $NO_FILESYSTEM -eq 0 ]; then if [ $WOLFCRYPT_RSA -eq 1 ]; then # TLS client/server RSA TLS v1.2 and v1.3 Crypto callbacks @@ -739,6 +782,24 @@ if [ $WOLFCRYPT_ENABLE -eq 1 ] && [ $WOLFCRYPT_DEFAULT -eq 0 ] && [ $NO_FILESYST fi fi +# TLS 1.3 with ML-KEM key exchange and a TPM held ML-DSA identity. Both ends +# are wolfTPM examples, so no wolfSSL example pairing is needed here. +# Requires a wolfSSL that routes wc_MlDsaKey_SignCtx to the crypto callback for +# device keys; set ENABLE_PQC_TLS=1 once linked against one. +if [ -z "$ENABLE_PQC_TLS" ]; then + ENABLE_PQC_TLS=0 +fi +if [ $ENABLE_V185 -eq 1 ] && [ $NO_FILESYSTEM -eq 0 ] && \ + [ $ENABLE_PQC_TLS -eq 1 ]; then + run_tpm_tls_pq "ML_KEM_768" "65" + run_tpm_tls_pq "ML_KEM_512" "44" + run_tpm_tls_pq "ML_KEM_1024" "87" + # hybrid groups need the classical curve half + if [ $WOLFCRYPT_ECC -eq 1 ]; then + run_tpm_tls_pq "SECP256R1MLKEM768" "65" + fi +fi + # Clock Tests echo -e "Clock tests" diff --git a/examples/tls/include.am b/examples/tls/include.am index 4726d873f..2d3fbc15b 100644 --- a/examples/tls/include.am +++ b/examples/tls/include.am @@ -26,30 +26,12 @@ examples_tls_tls_server_SOURCES = examples/tls/tls_server.c \ examples_tls_tls_server_LDADD = src/libwolftpm.la $(LIB_STATIC_ADD) examples_tls_tls_server_DEPENDENCIES = src/libwolftpm.la -if BUILD_PQC -noinst_PROGRAMS += examples/tls/tls_client_pq -noinst_HEADERS += examples/tls/tls_client_pq.h -examples_tls_tls_client_pq_SOURCES = examples/tls/tls_client_pq.c -examples_tls_tls_client_pq_LDADD = src/libwolftpm.la $(LIB_STATIC_ADD) -examples_tls_tls_client_pq_DEPENDENCIES = src/libwolftpm.la - -noinst_PROGRAMS += examples/tls/tls_server_pq -noinst_HEADERS += examples/tls/tls_server_pq.h -examples_tls_tls_server_pq_SOURCES = examples/tls/tls_server_pq.c -examples_tls_tls_server_pq_LDADD = src/libwolftpm.la $(LIB_STATIC_ADD) -examples_tls_tls_server_pq_DEPENDENCIES = src/libwolftpm.la -endif - endif example_tlsdir = $(exampledir)/tls dist_example_tls_DATA = \ examples/tls/tls_client.c \ - examples/tls/tls_server.c \ - examples/tls/tls_client_pq.c \ - examples/tls/tls_server_pq.c + examples/tls/tls_server.c DISTCLEANFILES+= examples/tls/.libs/tls_client DISTCLEANFILES+= examples/tls/.libs/tls_server -DISTCLEANFILES+= examples/tls/.libs/tls_client_pq -DISTCLEANFILES+= examples/tls/.libs/tls_server_pq diff --git a/examples/tls/tls_client.c b/examples/tls/tls_client.c index 5b2f8e2d2..925ea571e 100644 --- a/examples/tls/tls_client.c +++ b/examples/tls/tls_client.c @@ -89,10 +89,17 @@ static void usage(void) { printf("Expected usage:\n"); - printf("./examples/tls/tls_client [-ecc/rsa] [-aes/xor]\n"); + printf("./examples/tls/tls_client [-ecc/rsa/mldsa] [-aes/xor]\n"); printf("* -ecc: Use ECC key/cert\n"); printf("* -rsa: Use RSA key/cert\n"); +#ifdef WOLFTPM_TLS_PQC + printf("* -mldsa: Validate a server TPM ML-DSA cert against the PQC CA\n"); + printf(" from gen_pqc_certs (TLS 1.3 only)\n"); + printf("* -group=NAME: ML_KEM_512/768/1024, SECP256R1MLKEM768 or\n"); + printf(" X25519MLKEM768 (default ML_KEM_768, implies -mldsa)\n"); +#endif printf("* -aes/xor: Use Parameter Encryption\n"); + printf("* -h=host: Server hostname (default %s)\n", TLS_HOST); printf("* -p=port: Supply a custom port number (default %d)\n", TLS_PORT); #if defined(WOLFTPM_CRYPTOCB) && defined(HAVE_PK_CALLBACKS) printf("* -pk: Use PK callbacks, not crypto callbacks\n"); @@ -133,8 +140,16 @@ int TPM2_TLS_ClientArgs(void* userCtx, int argc, char *argv[]) int total_size; int i; #endif + const char* host = TLS_HOST; + int hostGiven = 0; int useECC = 0; int usePK = 0; +#ifdef WOLFTPM_TLS_PQC + int useMLDSA = 0; + int kemGroup = WOLFSSL_ML_KEM_768; + byte* pqCa = NULL; + int pqCaSz = TLS_PQ_CERT_BUF_SZ; +#endif TPM_ALG_ID paramEncAlg = TPM_ALG_NULL; WOLFTPM2_SESSION tpmSession; TPMT_PUBLIC publicTemplate; @@ -172,7 +187,21 @@ int TPM2_TLS_ClientArgs(void* userCtx, int argc, char *argv[]) } } while (argc > 1) { +#ifdef WOLFTPM_TLS_PQC + if (XSTRCMP(argv[argc-1], "-mldsa") == 0) { + useMLDSA = 1; + } + else if (XSTRNCMP(argv[argc-1], "-group=", 7) == 0) { + useMLDSA = 1; + if (TlsParseKemGroup(argv[argc-1] + 7, &kemGroup) != 0) { + usage(); + return BAD_FUNC_ARG; + } + } + else if (XSTRCMP(argv[argc-1], "-ecc") == 0) { +#else if (XSTRCMP(argv[argc-1], "-ecc") == 0) { +#endif useECC = 1; } else if (XSTRCMP(argv[argc-1], "-rsa") == 0) { @@ -189,6 +218,10 @@ int TPM2_TLS_ClientArgs(void* userCtx, int argc, char *argv[]) usePK = 1; } #endif + else if (XSTRNCMP(argv[argc-1], "-h=", XSTRLEN("-h=")) == 0) { + host = argv[argc-1] + XSTRLEN("-h="); + hostGiven = 1; + } else if (XSTRNCMP(argv[argc-1], "-p=", XSTRLEN("-p=")) == 0) { const char* portStr = argv[argc-1] + XSTRLEN("-p="); port = (word32)XATOI(portStr); @@ -199,6 +232,19 @@ int TPM2_TLS_ClientArgs(void* userCtx, int argc, char *argv[]) argc--; } +#ifdef WOLFTPM_TLS_PQC + if (useMLDSA && usePK) { + printf("-mldsa requires crypto callbacks; -pk has no ML-DSA route\n"); + return BAD_FUNC_ARG; + } + if (useMLDSA && paramEncAlg != TPM_ALG_NULL) { + /* the ML-DSA path uses no auth session, so -aes/-xor would be a + * silently ignored claim rather than real parameter encryption */ + printf("-mldsa does not use a parameter encryption session\n"); + return BAD_FUNC_ARG; + } +#endif + printf("TPM2 TLS Client Example\n"); printf("\tUse %s keys\n", useECC ? "ECC" : "RSA"); printf("\tUse Parameter Encryption: %s\n", TPM2_GetAlgName(paramEncAlg)); @@ -212,7 +258,11 @@ int TPM2_TLS_ClientArgs(void* userCtx, int argc, char *argv[]) } #endif #ifdef NO_RSA - if (!useECC) { + if (!useECC + #ifdef WOLFTPM_TLS_PQC + && !useMLDSA + #endif + ) { printf("RSA not compiled in!\n"); return 0; /* don't report error */ } @@ -232,6 +282,10 @@ int TPM2_TLS_ClientArgs(void* userCtx, int argc, char *argv[]) #endif #ifdef HAVE_ECC tpmCtx.eccKey = &eccKey; + #ifndef WOLFTPM2_USE_SW_ECDHE + /* Ephemeral key, also used for the ECDHE half of a hybrid PQC group */ + tpmCtx.ecdhKey = &ecdhKey; + #endif #endif tpmCtx.storageKey = &storageKey; #ifdef WOLFTPM_USE_SYMMETRIC @@ -245,6 +299,14 @@ int TPM2_TLS_ClientArgs(void* userCtx, int argc, char *argv[]) } #endif +#ifdef WOLFTPM_TLS_PQC + if (useMLDSA) { + /* The client only validates the server ML-DSA chain here, so no TPM + * key of its own is needed. */ + goto tls_setup; + } +#endif + /* See if primary storage key already exists */ rc = getPrimaryStoragekey(&dev, &storageKey, useECC ? TPM_ALG_ECC : TPM_ALG_RSA); @@ -326,16 +388,23 @@ int TPM2_TLS_ClientArgs(void* userCtx, int argc, char *argv[]) if (rc != 0) goto exit; } - #ifndef WOLFTPM2_USE_SW_ECDHE - /* Ephemeral Key */ - tpmCtx.ecdhKey = &ecdhKey; - #endif #endif /* HAVE_ECC */ /* Setup the WOLFSSL context (factory) * Use highest version, allow downgrade */ - if ((ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())) == NULL) { +#ifdef WOLFTPM_TLS_PQC +tls_setup: +#endif +#ifdef WOLFTPM_TLS_PQC + if (useMLDSA) { + /* ML-DSA certificates are TLS 1.3 only */ + ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); + } + else +#endif + ctx = wolfSSL_CTX_new(wolfSSLv23_client_method()); + if (ctx == NULL) { rc = MEMORY_E; goto exit; } @@ -353,9 +422,35 @@ int TPM2_TLS_ClientArgs(void* userCtx, int argc, char *argv[]) } #endif +#ifdef WOLFTPM_TLS_PQC + if (useMLDSA) { + /* Require the server chain to validate against the PQC CA */ + wolfSSL_CTX_set_verify(ctx, WOLFSSL_VERIFY_PEER, NULL); + + pqCa = (byte*)XMALLOC(TLS_PQ_CERT_BUF_SZ, NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (pqCa == NULL) { + rc = MEMORY_E; goto exit; + } + rc = ReadDerFile(TLS_PQ_CA_CERT, pqCa, &pqCaSz); + if (rc != 0) { + printf("Run examples/pqc/gen_pqc_certs first\n"); + goto exit; + } + if (wolfSSL_CTX_load_verify_buffer(ctx, pqCa, pqCaSz, + WOLFSSL_FILETYPE_ASN1) != WOLFSSL_SUCCESS) { + printf("load_verify_buffer failed\n"); + rc = -1; goto exit; + } + } + else { +#endif /* Server certificate validation */ /* Note: Can use "WOLFSSL_VERIFY_NONE" to skip peer cert validation */ - wolfSSL_CTX_set_verify(ctx, WOLFSSL_VERIFY_PEER, myVerify); + /* myVerify overrides chain failures for local testing; do not apply that + * override to a peer the user pointed us at explicitly. */ + wolfSSL_CTX_set_verify(ctx, WOLFSSL_VERIFY_PEER, + hostGiven ? NULL : myVerify); #ifdef NO_FILESYSTEM /* Load CA Certificates from Buffer */ if (!useECC) { @@ -516,6 +611,9 @@ int TPM2_TLS_ClientArgs(void* userCtx, int argc, char *argv[]) } #endif /* !NO_TLS_MUTUAL_AUTH */ +#ifdef WOLFTPM_TLS_PQC + } +#endif #ifdef TLS_CIPHER_SUITE /* Optionally choose the cipher suite */ rc = wolfSSL_CTX_set_cipher_list(ctx, TLS_CIPHER_SUITE); @@ -537,8 +635,31 @@ int TPM2_TLS_ClientArgs(void* userCtx, int argc, char *argv[]) } #endif +#ifdef WOLFTPM_TLS_PQC + if (useMLDSA) { + if (wolfSSL_UseKeyShare(ssl, (word16)kemGroup) != WOLFSSL_SUCCESS) { + printf("UseKeyShare failed\n"); + rc = -1; goto exit; + } + /* advertise only the chosen group; no silent downgrade */ + if (wolfSSL_set_groups(ssl, &kemGroup, 1) != WOLFSSL_SUCCESS) { + printf("set_groups failed\n"); + rc = -1; goto exit; + } + } +#endif + + /* An explicitly supplied host must match the certificate, or chaining to + * a trusted CA alone would accept any peer that CA ever issued. */ + if (hostGiven) { + if (wolfSSL_check_domain_name(ssl, host) != WOLFSSL_SUCCESS) { + printf("check_domain_name failed for %s\n", host); + rc = -1; goto exit; + } + } + /* Setup socket and connection */ - rc = SetupSocketAndConnect(&sockIoCtx, TLS_HOST, port); + rc = SetupSocketAndConnect(&sockIoCtx, host, port); if (rc != 0) goto exit; /* Setup read/write callback contexts */ @@ -558,6 +679,13 @@ int TPM2_TLS_ClientArgs(void* userCtx, int argc, char *argv[]) if (rc != WOLFSSL_SUCCESS) { goto exit; } +#ifdef WOLFTPM_TLS_PQC + if (useMLDSA) { + printf("Handshake: %s, group %s\n", + wolfSSL_get_cipher(ssl), wolfSSL_get_curve_name(ssl)); + printf("Server ML-DSA identity verified against the CA\n"); + } +#endif #ifdef TLS_BENCH_MODE benchStart = gettime_secs(0) - benchStart; printf("Connect: %9.3f sec (%9.3f CPS)\n", benchStart, 1/benchStart); @@ -658,6 +786,9 @@ int TPM2_TLS_ClientArgs(void* userCtx, int argc, char *argv[]) CloseAndCleanupSocket(&sockIoCtx); +#ifdef WOLFTPM_TLS_PQC + XFREE(pqCa, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif wolfTPM2_UnloadHandle(&dev, &storageKey.handle); #ifndef NO_RSA wolfTPM2_UnloadHandle(&dev, &rsaKey.handle); diff --git a/examples/tls/tls_client_pq.c b/examples/tls/tls_client_pq.c deleted file mode 100644 index df95d2d5c..000000000 --- a/examples/tls/tls_client_pq.c +++ /dev/null @@ -1,242 +0,0 @@ -/* tls_client_pq.c - * - * Copyright (C) 2006-2026 wolfSSL Inc. - * - * This file is part of wolfTPM. - * - * wolfTPM is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * wolfTPM is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA - */ - -/* TLS 1.3 client for the PQC example: ML-KEM key exchange, validates the - * server's TPM ML-DSA certificate against the CA. Pair with tls_server_pq. */ - -#ifdef HAVE_CONFIG_H - #include -#endif - -#include - -#if !defined(WOLFTPM2_NO_WRAPPER) && defined(WOLFTPM_CRYPTOCB) && \ - !defined(WOLFTPM2_NO_WOLFCRYPT) && !defined(NO_WOLFSSL_CLIENT) && \ - !defined(WOLFCRYPT_ONLY) && defined(WOLFTPM_MLDSA_SIGN) && \ - defined(WOLFSSL_TLS13) && !defined(NO_FILESYSTEM) - -#ifndef _WIN32 -/* host-resolve declarations tls_common.h needs on POSIX */ -#include -#include -#endif - -#include -#include - -#include -#include - -#define PQ_CA_CERT "./certs/pq-ca-cert.der" -#define PQ_CERT_BUF_SZ 10000 - -static const char kHelloMsg[] = "hello wolfssl!\n"; - -static void usage(void) -{ - printf("Expected usage:\n"); - printf("./examples/tls/tls_client_pq [-h=host] [-p=port] [-group=name]\n"); - printf("* -group: ML_KEM_512/768/1024, or hybrid SECP256R1MLKEM768 /\n"); - printf(" X25519MLKEM768 (default ML_KEM_768)\n"); -} - -static int parseGroup(const char* v, int* group) -{ - if (XSTRCMP(v, "ML_KEM_512") == 0) { *group = WOLFSSL_ML_KEM_512; return 0; } - if (XSTRCMP(v, "ML_KEM_768") == 0) { *group = WOLFSSL_ML_KEM_768; return 0; } - if (XSTRCMP(v, "ML_KEM_1024") == 0) { - *group = WOLFSSL_ML_KEM_1024; return 0; - } - if (XSTRCMP(v, "SECP256R1MLKEM768") == 0) { - *group = WOLFSSL_SECP256R1MLKEM768; return 0; - } - if (XSTRCMP(v, "X25519MLKEM768") == 0) { - *group = WOLFSSL_X25519MLKEM768; return 0; - } - return BAD_FUNC_ARG; -} - -static int readDer(const char* file, byte* der, int* derSz) -{ - XFILE f = XFOPEN(file, "rb"); - long sz; - if (f == XBADFILE) { - printf("Cannot open %s (run gen_pqc_certs first)\n", file); - return -1; - } - XFSEEK(f, 0, XSEEK_END); sz = XFTELL(f); XREWIND(f); - if (sz <= 0 || sz > *derSz) { XFCLOSE(f); return -1; } - if (XFREAD(der, 1, (size_t)sz, f) != (size_t)sz) { XFCLOSE(f); return -1; } - XFCLOSE(f); - *derSz = (int)sz; - return 0; -} - -int TPM2_TLS_ClientPQArgs(void* userCtx, int argc, char *argv[]) -{ - int rc = 0; - WOLFSSL_CTX* ctx = NULL; - WOLFSSL* ssl = NULL; - SockIoCbCtx sockIoCtx; - const char* host = TLS_HOST; - word32 port = TLS_PORT; - int group = WOLFSSL_ML_KEM_768; - byte* caDer = NULL; - int caSz = PQ_CERT_BUF_SZ; - char reply[MAX_MSG_SZ]; - int replySz; - - (void)userCtx; - XMEMSET(&sockIoCtx, 0, sizeof(sockIoCtx)); - sockIoCtx.fd = -1; - sockIoCtx.listenFd = -1; - - while (argc > 1) { - if (XSTRNCMP(argv[argc-1], "-h=", 3) == 0) { - host = argv[argc-1] + 3; - } - else if (XSTRNCMP(argv[argc-1], "-p=", 3) == 0) { - port = (word32)XATOI(argv[argc-1] + 3); - } - else if (XSTRNCMP(argv[argc-1], "-group=", 7) == 0) { - if (parseGroup(argv[argc-1] + 7, &group) != 0) { - usage(); - return BAD_FUNC_ARG; - } - } - else if (XSTRCMP(argv[argc-1], "-?") == 0 || - XSTRCMP(argv[argc-1], "-h") == 0) { - usage(); - return 0; - } - argc--; - } - - caDer = (byte*)XMALLOC(PQ_CERT_BUF_SZ, NULL, DYNAMIC_TYPE_TMP_BUFFER); - if (caDer == NULL) { - return MEMORY_E; - } - - ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); - if (ctx == NULL) { - rc = MEMORY_E; - } - if (rc == 0) { - wolfSSL_CTX_SetIORecv(ctx, SockIORecv); - wolfSSL_CTX_SetIOSend(ctx, SockIOSend); - /* require the server chain to validate against the software CA */ - wolfSSL_CTX_set_verify(ctx, WOLFSSL_VERIFY_PEER, NULL); - rc = readDer(PQ_CA_CERT, caDer, &caSz); - } - if (rc == 0) { - if (wolfSSL_CTX_load_verify_buffer(ctx, caDer, caSz, - WOLFSSL_FILETYPE_ASN1) != WOLFSSL_SUCCESS) { - printf("load_verify_buffer failed\n"); - rc = -1; - } - } - if (rc == 0) { - ssl = wolfSSL_new(ctx); - if (ssl == NULL) { - rc = MEMORY_E; - } - } - if (rc == 0) { - wolfSSL_SetIOReadCtx(ssl, &sockIoCtx); - wolfSSL_SetIOWriteCtx(ssl, &sockIoCtx); - if (wolfSSL_UseKeyShare(ssl, (word16)group) != WOLFSSL_SUCCESS) { - printf("UseKeyShare failed\n"); - rc = -1; - } - } - if (rc == 0) { - /* advertise only the chosen group; no silent downgrade */ - if (wolfSSL_set_groups(ssl, &group, 1) != WOLFSSL_SUCCESS) { - printf("set_groups failed\n"); - rc = -1; - } - } - if (rc == 0) { - rc = SetupSocketAndConnect(&sockIoCtx, host, port); - } - if (rc == 0) { - do { - rc = wolfSSL_connect(ssl); - } while (rc != WOLFSSL_SUCCESS && - (wolfSSL_want_read(ssl) || wolfSSL_want_write(ssl))); - if (rc != WOLFSSL_SUCCESS) { - printf("connect failed: %s\n", - wolfSSL_ERR_reason_error_string(wolfSSL_get_error(ssl, rc))); - } - else { - rc = 0; - } - } - if (rc == 0) { - printf("Handshake: %s, group %s\n", - wolfSSL_get_cipher(ssl), wolfSSL_get_curve_name(ssl)); - printf("Server ML-DSA identity verified against the CA\n"); - if (wolfSSL_write(ssl, kHelloMsg, (int)XSTRLEN(kHelloMsg)) <= 0) { - printf("write failed: %s\n", - wolfSSL_ERR_reason_error_string(wolfSSL_get_error(ssl, 0))); - rc = -1; - } - } - if (rc == 0) { - replySz = wolfSSL_read(ssl, reply, sizeof(reply) - 1); - if (replySz <= 0) { - printf("read failed: %s\n", - wolfSSL_ERR_reason_error_string( - wolfSSL_get_error(ssl, replySz))); - rc = -1; - } - else { - reply[replySz] = 0; - printf("Server: %s", reply); - } - } - - if (ssl != NULL) { wolfSSL_shutdown(ssl); wolfSSL_free(ssl); } - if (ctx != NULL) wolfSSL_CTX_free(ctx); - CloseAndCleanupSocket(&sockIoCtx); - XFREE(caDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); - return rc; -} - -#endif - -#ifndef NO_MAIN_DRIVER -int main(int argc, char *argv[]) -{ - int rc = -1; -#if !defined(WOLFTPM2_NO_WRAPPER) && defined(WOLFTPM_CRYPTOCB) && \ - !defined(WOLFTPM2_NO_WOLFCRYPT) && !defined(NO_WOLFSSL_CLIENT) && \ - !defined(WOLFCRYPT_ONLY) && defined(WOLFTPM_MLDSA_SIGN) && \ - defined(WOLFSSL_TLS13) && !defined(NO_FILESYSTEM) - rc = TPM2_TLS_ClientPQArgs(NULL, argc, argv); -#else - (void)argc; - (void)argv; - printf("Requires wolfTPM --enable-pqc and wolfSSL crypto callbacks\n"); -#endif - return rc; -} -#endif diff --git a/examples/tls/tls_client_pq.h b/examples/tls/tls_client_pq.h deleted file mode 100644 index dcc41cb5b..000000000 --- a/examples/tls/tls_client_pq.h +++ /dev/null @@ -1,35 +0,0 @@ -/* tls_client_pq.h - * - * Copyright (C) 2006-2026 wolfSSL Inc. - * - * This file is part of wolfTPM. - * - * wolfTPM is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * wolfTPM is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA - */ - -#ifndef _TPM_TLS_CLIENT_PQ_H_ -#define _TPM_TLS_CLIENT_PQ_H_ - -#ifdef __cplusplus - extern "C" { -#endif - -int TPM2_TLS_ClientPQArgs(void* userCtx, int argc, char *argv[]); - -#ifdef __cplusplus - } /* extern "C" */ -#endif - -#endif /* _TPM_TLS_CLIENT_PQ_H_ */ diff --git a/examples/tls/tls_common.h b/examples/tls/tls_common.h index 925c7c148..89bd7364a 100644 --- a/examples/tls/tls_common.h +++ b/examples/tls/tls_common.h @@ -381,6 +381,86 @@ static inline void CloseAndCleanupSocket(SockIoCbCtx* sockIoCtx) /* --- BEGIN Supporting TLS functions --- */ /******************************************************************************/ +/* The PQC TLS mode needs more than TPM ML-DSA support: TLS 1.3 for the + * certificate type, a filesystem for the DER identity, the crypto callback + * that routes signing to the TPM, and private-key-id to reference it. */ +#if defined(WOLFTPM_MLDSA_SIGN) && defined(WOLFSSL_TLS13) && \ + defined(WOLFTPM_CRYPTOCB) && defined(WOLF_PRIVATE_KEY_ID) && \ + !defined(NO_FILESYSTEM) && \ + defined(WOLFSSL_HAVE_MLDSA) && !defined(WOLFSSL_MLDSA_NO_SIGN) && \ + !defined(WOLFSSL_MLDSA_NO_VERIFY) && \ + defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_MAKE_KEY) && \ + !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) && \ + !defined(WOLFSSL_MLKEM_NO_DECAPSULATE) + #define WOLFTPM_TLS_PQC +#endif + +#ifdef WOLFTPM_TLS_PQC +/* PQC identity produced by examples/pqc/gen_pqc_certs */ +#define TLS_PQ_CA_CERT "./certs/pq-ca-cert.der" +#define TLS_PQ_SERVER_CERT "./certs/pq-server-cert.der" +#define TLS_PQ_CERT_BUF_SZ 10000 + +static inline int TlsParseMldsaSet(const char* v, + TPMI_MLDSA_PARAMETER_SET* ps) +{ + if (XSTRCMP(v, "44") == 0) { *ps = TPM_MLDSA_44; return 0; } + if (XSTRCMP(v, "65") == 0) { *ps = TPM_MLDSA_65; return 0; } + if (XSTRCMP(v, "87") == 0) { *ps = TPM_MLDSA_87; return 0; } + return BAD_FUNC_ARG; +} + +/* ML-KEM (and hybrid) key share groups for the TLS 1.3 PQC examples */ +static inline int TlsParseKemGroup(const char* v, int* group) +{ + if (XSTRCMP(v, "ML_KEM_512") == 0) { + *group = WOLFSSL_ML_KEM_512; return 0; + } + if (XSTRCMP(v, "ML_KEM_768") == 0) { + *group = WOLFSSL_ML_KEM_768; return 0; + } + if (XSTRCMP(v, "ML_KEM_1024") == 0) { + *group = WOLFSSL_ML_KEM_1024; return 0; + } + if (XSTRCMP(v, "SECP256R1MLKEM768") == 0) { + *group = WOLFSSL_SECP256R1MLKEM768; return 0; + } + if (XSTRCMP(v, "X25519MLKEM768") == 0) { + *group = WOLFSSL_X25519MLKEM768; return 0; + } + return BAD_FUNC_ARG; +} +#endif /* WOLFTPM_TLS_PQC */ + +#ifndef NO_FILESYSTEM +/* Read a DER file into der, updating derSz with the bytes read. */ +static inline int ReadDerFile(const char* file, byte* der, int* derSz) +{ + XFILE f; + long sz; + + f = XFOPEN(file, "rb"); + if (f == XBADFILE) { + printf("Cannot open %s\n", file); + return -1; + } + XFSEEK(f, 0, XSEEK_END); + sz = XFTELL(f); + XREWIND(f); + if (sz <= 0 || sz > *derSz) { + XFCLOSE(f); + return -1; + } + if (XFREAD(der, 1, (size_t)sz, f) != (size_t)sz) { + XFCLOSE(f); + return -1; + } + XFCLOSE(f); + *derSz = (int)sz; + return 0; +} +#endif /* !NO_FILESYSTEM */ + static inline int myVerify(int preverify, WOLFSSL_X509_STORE_CTX* store) { /* Verify Callback Arguments: diff --git a/examples/tls/tls_server.c b/examples/tls/tls_server.c index 52d5f1f16..69992054a 100644 --- a/examples/tls/tls_server.c +++ b/examples/tls/tls_server.c @@ -113,9 +113,13 @@ static int mStop = 0; static void usage(void) { printf("Expected usage:\n"); - printf("./examples/tls/tls_server [-ecc/rsa] [-aes/xor]\n"); + printf("./examples/tls/tls_server [-ecc/rsa/mldsa] [-aes/xor]\n"); printf("* -ecc: Use ECC key/cert\n"); printf("* -rsa: Use RSA key/cert\n"); +#ifdef WOLFTPM_TLS_PQC + printf("* -mldsa[=44/65/87]: Use TPM ML-DSA key with the PQC cert from\n"); + printf(" gen_pqc_certs (TLS 1.3 only, default 65)\n"); +#endif printf("* -aes/xor: Use Parameter Encryption\n"); printf("* -p=port: Supply a custom port number (default %d)\n", TLS_PORT); #if defined(WOLFTPM_CRYPTOCB) && defined(HAVE_PK_CALLBACKS) @@ -173,6 +177,14 @@ int TPM2_TLS_ServerArgs(void* userCtx, int argc, char *argv[]) int usePK = 0; int runLoop = 0; int useSelfSign = 0; +#ifdef WOLFTPM_TLS_PQC + int useMLDSA = 0; + TPMI_MLDSA_PARAMETER_SET mldsaSet = TPM_MLDSA_65; + WOLFTPM2_KEY mldsaKey; + byte* pqCert = NULL; + int pqCertSz = TLS_PQ_CERT_BUF_SZ; + static const byte mldsaKeyId[] = { 't','p','m','m','l','d','s','a' }; +#endif TPM_ALG_ID paramEncAlg = TPM_ALG_NULL; WOLFTPM2_SESSION tpmSession; TPMT_PUBLIC publicTemplate; @@ -198,6 +210,9 @@ int TPM2_TLS_ServerArgs(void* userCtx, int argc, char *argv[]) #endif #endif XMEMSET(&tpmSession, 0, sizeof(tpmSession)); +#ifdef WOLFTPM_TLS_PQC + XMEMSET(&mldsaKey, 0, sizeof(mldsaKey)); +#endif if (argc >= 2) { if (XSTRCMP(argv[1], "-?") == 0 || @@ -214,6 +229,18 @@ int TPM2_TLS_ServerArgs(void* userCtx, int argc, char *argv[]) else if (XSTRCMP(argv[argc-1], "-rsa") == 0) { useECC = 0; } +#ifdef WOLFTPM_TLS_PQC + else if (XSTRCMP(argv[argc-1], "-mldsa") == 0) { + useMLDSA = 1; + } + else if (XSTRNCMP(argv[argc-1], "-mldsa=", 7) == 0) { + useMLDSA = 1; + if (TlsParseMldsaSet(argv[argc-1] + 7, &mldsaSet) != 0) { + usage(); + return BAD_FUNC_ARG; + } + } +#endif else if (XSTRCMP(argv[argc-1], "-aes") == 0) { paramEncAlg = TPM_ALG_CFB; } @@ -241,7 +268,27 @@ int TPM2_TLS_ServerArgs(void* userCtx, int argc, char *argv[]) argc--; } +#ifdef WOLFTPM_TLS_PQC + if (useMLDSA && usePK) { + printf("-mldsa requires crypto callbacks; -pk has no ML-DSA route\n"); + return BAD_FUNC_ARG; + } + if (useMLDSA && paramEncAlg != TPM_ALG_NULL) { + /* the ML-DSA path uses no auth session, so -aes/-xor would be a + * silently ignored claim rather than real parameter encryption */ + printf("-mldsa does not use a parameter encryption session\n"); + return BAD_FUNC_ARG; + } +#endif + printf("TPM2 TLS Server Example\n"); +#ifdef WOLFTPM_TLS_PQC + if (useMLDSA) { + printf("\tUse ML-DSA-%d keys\n", + mldsaSet == TPM_MLDSA_44 ? 44 : (mldsaSet == TPM_MLDSA_87 ? 87 : 65)); + } + else +#endif printf("\tUse %s keys\n", useECC ? "ECC" : "RSA"); printf("\tUse Parameter Encryption: %s\n", TPM2_GetAlgName(paramEncAlg)); printf("\tUsing Port: %d\n", port); @@ -254,7 +301,11 @@ int TPM2_TLS_ServerArgs(void* userCtx, int argc, char *argv[]) } #endif #ifdef NO_RSA - if (!useECC) { + if (!useECC + #ifdef WOLFTPM_TLS_PQC + && !useMLDSA + #endif + ) { printf("RSA not compiled in!\n"); return 0; /* don't report error */ } @@ -274,8 +325,17 @@ int TPM2_TLS_ServerArgs(void* userCtx, int argc, char *argv[]) #endif #ifdef HAVE_ECC tpmCtx.eccKey = &eccKey; + #ifndef WOLFTPM2_USE_SW_ECDHE + /* Ephemeral key, also used for the ECDHE half of a hybrid PQC group */ + tpmCtx.ecdhKey = &ecdhKey; + #endif #endif tpmCtx.storageKey = &storageKey; +#ifdef WOLFTPM_TLS_PQC + if (useMLDSA) { + tpmCtx.mldsaKey = &mldsaKey; + } +#endif #ifdef WOLFTPM_USE_SYMMETRIC tpmCtx.useSymmetricOnTPM = 1; #endif @@ -287,6 +347,27 @@ int TPM2_TLS_ServerArgs(void* userCtx, int argc, char *argv[]) } #endif +#ifdef WOLFTPM_TLS_PQC + if (useMLDSA) { + /* Recreate the same TPM ML-DSA identity key gen_pqc_certs certified. + * It is a primary key, so no storage parent is involved. */ + rc = wolfTPM2_GetKeyTemplate_MLDSA(&publicTemplate, + TPMA_OBJECT_sign | TPMA_OBJECT_fixedTPM | TPMA_OBJECT_fixedParent | + TPMA_OBJECT_sensitiveDataOrigin | TPMA_OBJECT_userWithAuth | + TPMA_OBJECT_noDA, mldsaSet, 0); + if (rc == 0) { + rc = wolfTPM2_CreatePrimaryKey(&dev, &mldsaKey, TPM_RH_OWNER, + &publicTemplate, NULL, 0); + } + if (rc != 0) { + printf("Create TPM ML-DSA key failed 0x%x: %s\n", + rc, wolfTPM2_GetRCString(rc)); + goto exit; + } + goto tls_setup; + } +#endif + /* See if primary storage key already exists */ rc = getPrimaryStoragekey(&dev, &storageKey, useECC ? TPM_ALG_ECC : TPM_ALG_RSA); @@ -368,16 +449,23 @@ int TPM2_TLS_ServerArgs(void* userCtx, int argc, char *argv[]) if (rc != 0) goto exit; } - #ifndef WOLFTPM2_USE_SW_ECDHE - /* Ephemeral Key */ - tpmCtx.ecdhKey = &ecdhKey; - #endif #endif /* HAVE_ECC */ +#ifdef WOLFTPM_TLS_PQC +tls_setup: +#endif /* Setup the WOLFSSL context (factory) * Use highest version, allow downgrade */ - if ((ctx = wolfSSL_CTX_new(wolfSSLv23_server_method())) == NULL) { +#ifdef WOLFTPM_TLS_PQC + if (useMLDSA) { + /* ML-DSA certificates are TLS 1.3 only */ + ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method()); + } + else +#endif + ctx = wolfSSL_CTX_new(wolfSSLv23_server_method()); + if (ctx == NULL) { rc = MEMORY_E; goto exit; } @@ -394,6 +482,36 @@ int TPM2_TLS_ServerArgs(void* userCtx, int argc, char *argv[]) wolfTPM_PK_SetCb(ctx); } #endif +#ifdef WOLFTPM_TLS_PQC + if (useMLDSA) { + /* The PQC example does not do client auth; the client validates the + * server ML-DSA chain against the CA from gen_pqc_certs. */ + wolfSSL_CTX_set_verify(ctx, WOLFSSL_VERIFY_NONE, NULL); + + pqCert = (byte*)XMALLOC(TLS_PQ_CERT_BUF_SZ, NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (pqCert == NULL) { + rc = MEMORY_E; goto exit; + } + rc = ReadDerFile(TLS_PQ_SERVER_CERT, pqCert, &pqCertSz); + if (rc != 0) { + printf("Run examples/pqc/gen_pqc_certs first\n"); + goto exit; + } + if (wolfSSL_CTX_use_certificate_buffer(ctx, pqCert, pqCertSz, + WOLFSSL_FILETYPE_ASN1) != WOLFSSL_SUCCESS) { + printf("use_certificate_buffer failed\n"); + rc = -1; goto exit; + } + /* private key stays in the TPM: reference it by id + devId */ + if (wolfSSL_CTX_use_PrivateKey_Id(ctx, mldsaKeyId, + sizeof(mldsaKeyId), tpmDevId) != WOLFSSL_SUCCESS) { + printf("use_PrivateKey_Id failed\n"); + rc = -1; goto exit; + } + } + else { +#endif /* Server certificate validation */ /* Note: Can use "WOLFSSL_VERIFY_NONE" to skip peer cert validation */ @@ -547,6 +665,9 @@ int TPM2_TLS_ServerArgs(void* userCtx, int argc, char *argv[]) goto exit; #endif /* HAVE_ECC */ } +#ifdef WOLFTPM_TLS_PQC + } +#endif #ifdef TLS_CIPHER_SUITE @@ -604,6 +725,12 @@ int TPM2_TLS_ServerArgs(void* userCtx, int argc, char *argv[]) if (rc != WOLFSSL_SUCCESS) { goto exit; } + #ifdef WOLFTPM_TLS_PQC + if (useMLDSA) { + printf("Handshake: %s, group %s (ML-DSA identity signed on TPM)\n", + wolfSSL_get_cipher(ssl), wolfSSL_get_curve_name(ssl)); + } + #endif #ifdef TLS_BENCH_MODE benchStart = gettime_secs(0) - benchStart; printf("Accept: %9.3f sec (%9.3f CPS)\n", benchStart, 1/benchStart); @@ -705,6 +832,10 @@ int TPM2_TLS_ServerArgs(void* userCtx, int argc, char *argv[]) CloseAndCleanupSocket(&sockIoCtx); wolfTPM2_UnloadHandle(&dev, &storageKey.handle); +#ifdef WOLFTPM_TLS_PQC + wolfTPM2_UnloadHandle(&dev, &mldsaKey.handle); + XFREE(pqCert, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif #ifndef NO_RSA wolfTPM2_UnloadHandle(&dev, &rsaKey.handle); #endif diff --git a/examples/tls/tls_server_pq.c b/examples/tls/tls_server_pq.c deleted file mode 100644 index ee0405c54..000000000 --- a/examples/tls/tls_server_pq.c +++ /dev/null @@ -1,274 +0,0 @@ -/* tls_server_pq.c - * - * Copyright (C) 2006-2026 wolfSSL Inc. - * - * This file is part of wolfTPM. - * - * wolfTPM is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * wolfTPM is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA - */ - -/* TLS 1.3 server whose ML-DSA identity key lives in the TPM. The TPM signs the - * CertificateVerify via the wolfTPM crypto callback. Pair with tls_client_pq. */ - -#ifdef HAVE_CONFIG_H - #include -#endif - -#include - -#if !defined(WOLFTPM2_NO_WRAPPER) && defined(WOLFTPM_CRYPTOCB) && \ - !defined(WOLFTPM2_NO_WOLFCRYPT) && !defined(NO_WOLFSSL_SERVER) && \ - !defined(WOLFCRYPT_ONLY) && defined(WOLFTPM_MLDSA_SIGN) && \ - defined(WOLF_PRIVATE_KEY_ID) && defined(WOLFSSL_TLS13) && \ - !defined(NO_FILESYSTEM) - -#ifndef _WIN32 -/* host-resolve declarations tls_common.h needs on POSIX */ -#include -#include -#endif - -#include -#include -#include -#include - -#include -#include - -#define PQ_SERVER_CERT "./certs/pq-server-cert.der" -#define PQ_CERT_BUF_SZ 10000 - -static const char kReplyMsg[] = "I hear you fa shizzle!\n"; - -static void usage(void) -{ - printf("Expected usage:\n"); - printf("./examples/tls/tls_server_pq [-p=port] [-mldsa=44/65/87]\n"); - printf("* -mldsa=44/65/87: ML-DSA set (must match gen_pqc_certs, def 65)\n"); -} - -static int parseParamSet(const char* v, TPMI_MLDSA_PARAMETER_SET* ps) -{ - if (XSTRCMP(v, "44") == 0) { *ps = TPM_MLDSA_44; return 0; } - if (XSTRCMP(v, "65") == 0) { *ps = TPM_MLDSA_65; return 0; } - if (XSTRCMP(v, "87") == 0) { *ps = TPM_MLDSA_87; return 0; } - return BAD_FUNC_ARG; -} - -static int readDer(const char* file, byte* der, int* derSz) -{ - XFILE f = XFOPEN(file, "rb"); - long sz; - if (f == XBADFILE) { - printf("Cannot open %s (run gen_pqc_certs first)\n", file); - return -1; - } - XFSEEK(f, 0, XSEEK_END); sz = XFTELL(f); XREWIND(f); - if (sz <= 0 || sz > *derSz) { XFCLOSE(f); return -1; } - if (XFREAD(der, 1, (size_t)sz, f) != (size_t)sz) { XFCLOSE(f); return -1; } - XFCLOSE(f); - *derSz = (int)sz; - return 0; -} - -int TPM2_TLS_ServerPQArgs(void* userCtx, int argc, char *argv[]) -{ - int rc; - WOLFTPM2_DEV dev; - WOLFTPM2_KEY mldsaKey; - TPMT_PUBLIC pub; - TpmCryptoDevCtx tpmCtx; - int tpmDevId = INVALID_DEVID; - WOLFSSL_CTX* ctx = NULL; - WOLFSSL* ssl = NULL; - SockIoCbCtx sockIoCtx; - word32 port = TLS_PORT; - TPMI_MLDSA_PARAMETER_SET paramSet = TPM_MLDSA_65; - const byte keyId[] = { 't','p','m','m','l','d','s','a' }; - byte* certDer = NULL; - int certSz = PQ_CERT_BUF_SZ; - char msg[MAX_MSG_SZ]; - int msgSz; - - XMEMSET(&dev, 0, sizeof(dev)); - XMEMSET(&mldsaKey, 0, sizeof(mldsaKey)); - XMEMSET(&pub, 0, sizeof(pub)); - XMEMSET(&tpmCtx, 0, sizeof(tpmCtx)); - XMEMSET(&sockIoCtx, 0, sizeof(sockIoCtx)); - sockIoCtx.fd = -1; - sockIoCtx.listenFd = -1; - - while (argc > 1) { - if (XSTRCMP(argv[argc-1], "-?") == 0 || - XSTRCMP(argv[argc-1], "-h") == 0) { - usage(); - return 0; - } - else if (XSTRNCMP(argv[argc-1], "-p=", 3) == 0) { - port = (word32)XATOI(argv[argc-1] + 3); - } - else if (XSTRNCMP(argv[argc-1], "-mldsa=", 7) == 0) { - if (parseParamSet(argv[argc-1] + 7, ¶mSet) != 0) { - usage(); - return BAD_FUNC_ARG; - } - } - argc--; - } - - certDer = (byte*)XMALLOC(PQ_CERT_BUF_SZ, NULL, DYNAMIC_TYPE_TMP_BUFFER); - if (certDer == NULL) { - return MEMORY_E; - } - - rc = wolfTPM2_Init(&dev, TPM2_IoCb, userCtx); - if (rc != TPM_RC_SUCCESS) { - printf("wolfTPM2_Init failed 0x%x: %s\n", rc, wolfTPM2_GetRCString(rc)); - } - - if (rc == 0) { - /* recreate the same TPM ML-DSA identity key gen_pqc_certs certified */ - rc = wolfTPM2_GetKeyTemplate_MLDSA(&pub, - TPMA_OBJECT_sign | TPMA_OBJECT_fixedTPM | TPMA_OBJECT_fixedParent | - TPMA_OBJECT_sensitiveDataOrigin | TPMA_OBJECT_userWithAuth | - TPMA_OBJECT_noDA, paramSet, 0); - if (rc == TPM_RC_SUCCESS) { - rc = wolfTPM2_CreatePrimaryKey(&dev, &mldsaKey, TPM_RH_OWNER, &pub, - NULL, 0); - } - if (rc != TPM_RC_SUCCESS) { - printf("Create TPM ML-DSA key failed 0x%x: %s\n", - rc, wolfTPM2_GetRCString(rc)); - } - } - - if (rc == 0) { - tpmCtx.dev = &dev; - tpmCtx.mldsaKey = &mldsaKey; - rc = wolfTPM2_SetCryptoDevCb(&dev, wolfTPM2_CryptoDevCb, &tpmCtx, - &tpmDevId); - if (rc != TPM_RC_SUCCESS) { - printf("SetCryptoDevCb failed 0x%x\n", rc); - } - } - - if (rc == 0) { - ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method()); - if (ctx == NULL) { - rc = MEMORY_E; - } - } - if (rc == 0) { - wolfSSL_CTX_SetDevId(ctx, tpmDevId); - wolfSSL_CTX_SetIORecv(ctx, SockIORecv); - wolfSSL_CTX_SetIOSend(ctx, SockIOSend); - rc = readDer(PQ_SERVER_CERT, certDer, &certSz); - } - if (rc == 0) { - if (wolfSSL_CTX_use_certificate_buffer(ctx, certDer, certSz, - WOLFSSL_FILETYPE_ASN1) != WOLFSSL_SUCCESS) { - printf("use_certificate_buffer failed\n"); - rc = -1; - } - } - if (rc == 0) { - /* private key stays in the TPM: reference it by id + devId */ - if (wolfSSL_CTX_use_PrivateKey_Id(ctx, keyId, sizeof(keyId), tpmDevId) - != WOLFSSL_SUCCESS) { - printf("use_PrivateKey_Id failed\n"); - rc = -1; - } - } - if (rc == 0) { - ssl = wolfSSL_new(ctx); - if (ssl == NULL) { - rc = MEMORY_E; - } - } - if (rc == 0) { - wolfSSL_SetIOReadCtx(ssl, &sockIoCtx); - wolfSSL_SetIOWriteCtx(ssl, &sockIoCtx); - rc = SetupSocketAndListen(&sockIoCtx, port); - } - if (rc == 0) { - printf("PQC TLS server listening on port %u\n", (unsigned)port); - rc = SocketWaitClient(&sockIoCtx); - } - if (rc == 0) { - do { - rc = wolfSSL_accept(ssl); - } while (rc != WOLFSSL_SUCCESS && - (wolfSSL_want_read(ssl) || wolfSSL_want_write(ssl))); - if (rc != WOLFSSL_SUCCESS) { - printf("accept failed: %s\n", - wolfSSL_ERR_reason_error_string(wolfSSL_get_error(ssl, rc))); - } - else { - rc = 0; - } - } - if (rc == 0) { - printf("Handshake: %s, group %s (ML-DSA identity signed on TPM)\n", - wolfSSL_get_cipher(ssl), wolfSSL_get_curve_name(ssl)); - msgSz = wolfSSL_read(ssl, msg, sizeof(msg) - 1); - if (msgSz <= 0) { - printf("read failed: %s\n", - wolfSSL_ERR_reason_error_string(wolfSSL_get_error(ssl, msgSz))); - rc = -1; - } - else { - msg[msgSz] = 0; - printf("Client: %s", msg); - } - } - if (rc == 0) { - if (wolfSSL_write(ssl, kReplyMsg, (int)XSTRLEN(kReplyMsg)) <= 0) { - printf("write failed: %s\n", - wolfSSL_ERR_reason_error_string(wolfSSL_get_error(ssl, 0))); - rc = -1; - } - } - - if (ssl != NULL) { wolfSSL_shutdown(ssl); wolfSSL_free(ssl); } - if (ctx != NULL) wolfSSL_CTX_free(ctx); - CloseAndCleanupSocket(&sockIoCtx); - if (mldsaKey.handle.hndl != 0) - wolfTPM2_UnloadHandle(&dev, &mldsaKey.handle); - wolfTPM2_Cleanup(&dev); - XFREE(certDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); - return rc; -} - -#endif - -#ifndef NO_MAIN_DRIVER -int main(int argc, char *argv[]) -{ - int rc = -1; -#if !defined(WOLFTPM2_NO_WRAPPER) && defined(WOLFTPM_CRYPTOCB) && \ - !defined(WOLFTPM2_NO_WOLFCRYPT) && !defined(NO_WOLFSSL_SERVER) && \ - !defined(WOLFCRYPT_ONLY) && defined(WOLFTPM_MLDSA_SIGN) && \ - defined(WOLF_PRIVATE_KEY_ID) && defined(WOLFSSL_TLS13) && \ - !defined(NO_FILESYSTEM) - rc = TPM2_TLS_ServerPQArgs(NULL, argc, argv); -#else - (void)argc; - (void)argv; - printf("Requires wolfTPM --enable-pqc and wolfSSL crypto callbacks\n"); -#endif - return rc; -} -#endif diff --git a/examples/tls/tls_server_pq.h b/examples/tls/tls_server_pq.h deleted file mode 100644 index ee512309e..000000000 --- a/examples/tls/tls_server_pq.h +++ /dev/null @@ -1,35 +0,0 @@ -/* tls_server_pq.h - * - * Copyright (C) 2006-2026 wolfSSL Inc. - * - * This file is part of wolfTPM. - * - * wolfTPM is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * wolfTPM is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA - */ - -#ifndef _TPM_TLS_SERVER_PQ_H_ -#define _TPM_TLS_SERVER_PQ_H_ - -#ifdef __cplusplus - extern "C" { -#endif - -int TPM2_TLS_ServerPQArgs(void* userCtx, int argc, char *argv[]); - -#ifdef __cplusplus - } /* extern "C" */ -#endif - -#endif /* _TPM_TLS_SERVER_PQ_H_ */ diff --git a/src/tpm2_cryptocb.c b/src/tpm2_cryptocb.c index bec4ec24c..ba967b32d 100644 --- a/src/tpm2_cryptocb.c +++ b/src/tpm2_cryptocb.c @@ -431,17 +431,22 @@ int wolfTPM2_CryptoDevCb(int devId, wc_CryptoInfo* info, void* ctx) WOLFTPM2_HANDLE seqHandleObj; int sigSz; - /* pure ML-DSA one-shot only; pre-hash, oversized, no-key, bad-arg - * fall back */ + /* Not an ML-DSA request, or no TPM key configured: software can + * legitimately take it. */ if (info->pk.pqc_sign.type != WC_PQC_SIG_TYPE_MLDSA || - info->pk.pqc_sign.preHashType != WC_HASH_TYPE_NONE || + tlsCtx->mldsaKey == NULL) { + return exit_rc; + } + /* The key lives in the TPM, so a software fallback would sign with + * absent private material. Fail instead of yielding. Only the pure + * one-shot form is supported here. */ + if (info->pk.pqc_sign.preHashType != WC_HASH_TYPE_NONE || info->pk.pqc_sign.out == NULL || info->pk.pqc_sign.outlen == NULL || (info->pk.pqc_sign.in == NULL && info->pk.pqc_sign.inlen > 0) || - info->pk.pqc_sign.inlen > MAX_DIGEST_BUFFER || - tlsCtx->mldsaKey == NULL) { - return exit_rc; + info->pk.pqc_sign.inlen > MAX_DIGEST_BUFFER) { + return BAD_FUNC_ARG; } sigSz = (int)*info->pk.pqc_sign.outlen; diff --git a/tests/include.am b/tests/include.am index 37a5f7b00..383aa6c06 100644 --- a/tests/include.am +++ b/tests/include.am @@ -41,10 +41,3 @@ if BUILD_FWTPM dist_noinst_SCRIPTS += tests/fwtpm_check.sh \ tests/fwtpm_da_retry.sh endif - -# tls_pq_e2e.sh is intentionally NOT a `make check` test: it needs the wolfSSL -# crypto-cb ML-DSA device-key fix (not yet upstream) and a cert-gen build, and -# it manages its own fwTPM server + NV file (which would collide with the -# parallel fwtpm_check.sh). It is distributed and run standalone by the -# pqc-examples CI workflow instead. -EXTRA_DIST += tests/tls_pq_e2e.sh diff --git a/tests/tls_pq_e2e.sh b/tests/tls_pq_e2e.sh deleted file mode 100755 index 9fec05b02..000000000 --- a/tests/tls_pq_e2e.sh +++ /dev/null @@ -1,221 +0,0 @@ -#!/bin/bash -# tls_pq_e2e.sh -# -# Copyright (C) 2006-2026 wolfSSL Inc. -# -# This file is part of wolfTPM. -# -# wolfTPM is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# wolfTPM is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA -# - -# End-to-end harness for the post-quantum TLS example: spawn fwtpm_server, -# generate a TPM-keyed ML-DSA cert chain, run tls_server_pq and tls_client_pq -# over a real TCP socket, and assert the full PQC handshake (ML-KEM key -# exchange + TPM-signed ML-DSA authentication, CA-verified) plus app data. - -set -u - -# returns: 0 = in use, 1 = free, 2 = no probe tool -check_port_in_use() { - if command -v nc >/dev/null 2>&1; then - nc -z localhost "$1" 2>/dev/null; return $? - elif command -v ss >/dev/null 2>&1; then - ss -tln 2>/dev/null | grep -qE "[:.]$1 "; return $? - elif command -v netstat >/dev/null 2>&1; then - netstat -an 2>/dev/null | grep -qE "[:.]$1 .*LISTEN"; return $? - fi - return 2 -} - -pick_available_port() { - local port attempts=0 - while [ $attempts -lt 20 ]; do - if command -v shuf >/dev/null 2>&1; then port=$(shuf -i 10000-60000 -n 1) - else port=$(( (RANDOM % 50000) + 10000 )); fi - check_port_in_use "$port"; [ $? -ne 0 ] && { echo "$port"; return 0; } - attempts=$((attempts + 1)) - done - return 1 -} - -# pick a port whose consecutive platform port (port+1) is also free -pick_available_pair() { - local port attempts=0 - while [ $attempts -lt 20 ]; do - port=$(pick_available_port) || return 1 - check_port_in_use "$((port + 1))" || { echo "$port"; return 0; } - attempts=$((attempts + 1)) - done - return 1 -} - -# wait for $1 to reach LISTEN (ss/netstat, not nc -z which eats the accept slot) -wait_for_listen() { - local port="$1" pid="$2" elapsed=0 - if ! command -v ss >/dev/null 2>&1 && ! command -v netstat >/dev/null 2>&1; then - sleep 2; return 0 - fi - while [ $elapsed -lt 400 ]; do - kill -0 "$pid" 2>/dev/null || return 1 - if command -v ss >/dev/null 2>&1; then - ss -tln 2>/dev/null | grep -qE "[:.]${port} " && return 0 - elif netstat -an 2>/dev/null | grep -qE "[:.]${port} .*LISTEN"; then - return 0 - fi - sleep 0.02 - elapsed=$((elapsed + 1)) - done - return 1 -} - -# binaries and generated artifacts live in the build tree (VPATH-safe) -BUILD_DIR="$(pwd)" -cd "$BUILD_DIR" || exit 1 - -SERVER="$BUILD_DIR/src/fwtpm/fwtpm_server" -GEN="$BUILD_DIR/examples/pqc/gen_pqc_certs" -TLS_SRV="$BUILD_DIR/examples/tls/tls_server_pq" -TLS_CLI="$BUILD_DIR/examples/tls/tls_client_pq" -# fwTPM uses the compile-time FWTPM_NV_FILE (cwd-relative), not an env var -NV_FILE="$BUILD_DIR/fwtpm_nv.bin" -LOGP="/tmp/tls_pq_e2e_$$" -# examples honor TPM2_SWTPM_PORT only when built with getenv support -HAS_GETENV=1 -WOLFTPM_OPTS="$BUILD_DIR/wolftpm/options.h" -if [ -f "$WOLFTPM_OPTS" ] && grep -q "^#define NO_GETENV" "$WOLFTPM_OPTS"; then - HAS_GETENV=0 -fi -PORT="${TPM2_SWTPM_PORT:-2321}" -if check_port_in_use "$PORT" || check_port_in_use "$((PORT + 1))"; then - if [ "$HAS_GETENV" -eq 1 ]; then - PORT="$(pick_available_pair)" - else - echo "SKIP: TPM ports $PORT/$((PORT + 1)) busy (NO_GETENV build)" >&2 - exit 77 - fi -fi -PLATFORM_PORT=$((PORT + 1)) -TLS_PORT="$(pick_available_port)" -port_tries=0 -while { [ "$TLS_PORT" = "$PORT" ] || [ "$TLS_PORT" = "$PLATFORM_PORT" ]; } \ - && [ $port_tries -lt 10 ]; do - TLS_PORT="$(pick_available_port)" - port_tries=$((port_tries + 1)) -done -GROUP="${1:-ML_KEM_768}" -MLDSA="${2:-65}" -if [ -z "$PORT" ] || [ -z "$TLS_PORT" ]; then - echo "SKIP: could not allocate a free port" >&2; exit 77 -fi - -for bin in "$SERVER" "$GEN" "$TLS_SRV" "$TLS_CLI"; do - if [ ! -x "$bin" ]; then - echo "SKIP: $(basename "$bin") not built — configure --enable-fwtpm --enable-swtpm --enable-pqc" >&2 - exit 77 - fi -done - -# the examples compile executable stubs when wolfSSL cert-gen / private-key-id / -# TLS 1.3 support is missing; skip rather than fail in that build matrix -for bin in "$GEN" "$TLS_SRV" "$TLS_CLI"; do - if "$bin" -h 2>&1 | grep -q "Requires"; then - echo "SKIP: $(basename "$bin") built without required wolfSSL features" >&2 - exit 77 - fi -done - -export TPM2_SWTPM_PORT=$PORT - -rm -f "$NV_FILE" - -echo "== Starting fwtpm_server on port $PORT ==" -"$SERVER" \ - --port $PORT --platform-port $PLATFORM_PORT --clear \ - >"${LOGP}_fwtpm.log" 2>&1 & -SERVER_PID=$! - -( - for _ in 1 2 3 4 5 6 7 8 9 10; do - if exec 3<>/dev/tcp/127.0.0.1/$PORT; then exec 3>&-; exit 0; fi - sleep 0.5 - done - exit 1 -) 2>/dev/null -if [ $? -ne 0 ] || ! kill -0 "$SERVER_PID" 2>/dev/null; then - echo "FAIL: fwtpm_server not accepting connections on port $PORT" >&2 - kill "$SERVER_PID" 2>/dev/null; rm -f "$NV_FILE"; exit 1 -fi - -TLS_SRV_PID="" -cleanup() { - [ -n "$TLS_SRV_PID" ] && kill "$TLS_SRV_PID" 2>/dev/null - TLS_SRV_PID="" - [ -n "$SERVER_PID" ] && { kill "$SERVER_PID" 2>/dev/null; \ - wait "$SERVER_PID" 2>/dev/null; } - SERVER_PID="" - rm -f "$NV_FILE" "$BUILD_DIR/certs/pq-ca-cert.der" \ - "$BUILD_DIR/certs/pq-server-cert.der" -} -trap cleanup EXIT - -mkdir -p "$BUILD_DIR/certs" -echo "== Generating TPM ML-DSA-$MLDSA cert chain ==" -if ! "$GEN" -mldsa=$MLDSA >"${LOGP}_gen.log" 2>&1; then - echo "FAIL: gen_pqc_certs" >&2; cat "${LOGP}_gen.log" >&2; cleanup; exit 1 -fi - -echo "== Starting tls_server_pq on port $TLS_PORT ==" -"$TLS_SRV" -p=$TLS_PORT -mldsa=$MLDSA >"${LOGP}_server.log" 2>&1 & -TLS_SRV_PID=$! -if ! wait_for_listen "$TLS_PORT" "$TLS_SRV_PID"; then - echo "FAIL: tls_server_pq not listening on port $TLS_PORT" >&2 - cat "${LOGP}_server.log" >&2; cleanup; exit 1 -fi - -echo "== Running tls_client_pq (group $GROUP) ==" -"$TLS_CLI" -h=localhost -p=$TLS_PORT -group=$GROUP >"${LOGP}_client.log" 2>&1 -CLI_RC=$? - -# if the client connected, the server serves it and exits; wait to flush its -# log. Otherwise (e.g. unsupported group) kill it so we do not hang forever. -if [ $CLI_RC -eq 0 ]; then - wait "$TLS_SRV_PID" 2>/dev/null -else - kill "$TLS_SRV_PID" 2>/dev/null -fi -TLS_SRV_PID="" - -cleanup - -CLI=$(cat "${LOGP}_client.log") -SRV=$(cat "${LOGP}_server.log") -FAIL=0 -echo "$CLI" | grep -qi "group $GROUP" || { echo "FAIL: client did not negotiate $GROUP" >&2; FAIL=1; } -echo "$CLI" | grep -q "verified against the CA" || { echo "FAIL: client did not verify server cert" >&2; FAIL=1; } -echo "$CLI" | grep -q "I hear you fa shizzle" || { echo "FAIL: no app data from server" >&2; FAIL=1; } -echo "$SRV" | grep -q "signed on TPM" || { echo "FAIL: server did not sign on the TPM" >&2; FAIL=1; } - -if [ $CLI_RC -eq 0 ] && [ $FAIL -eq 0 ]; then - echo "OK: PQC TLS handshake (ML-KEM $GROUP + TPM ML-DSA-$MLDSA) passed" - exit 0 -fi -echo "FAIL: PQC TLS E2E (client rc=$CLI_RC)" >&2 -if ! echo "$SRV" | grep -q "signed on TPM"; then - echo "HINT: server never signed on the TPM — the linked wolfSSL may lack the" >&2 - echo " wc_MlDsaKey_SignCtx crypto-callback route (see examples/pqc/README.md)" >&2 -fi -echo "--- client ---" >&2; echo "$CLI" >&2 -echo "--- server ---" >&2; echo "$SRV" >&2 -exit 1 diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 04367df4f..6754039d3 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -4723,23 +4723,27 @@ static void test_wolfTPM2_CryptoDevCb_MlDsaSign(void) rc = wolfTPM2_CryptoDevCb(INVALID_DEVID, &info, &tpmCtx); AssertIntEQ(rc, CRYPTOCB_UNAVAILABLE); - /* pre-hash: fall back, not pure ML-DSA */ + /* With a TPM key set the private material is on-chip, so an unsupported + * request must fail rather than yield to a software signer that has no + * private key to use. */ + + /* pre-hash: not pure ML-DSA */ info.pk.pqc_sign.type = WC_PQC_SIG_TYPE_MLDSA; info.pk.pqc_sign.preHashType = WC_HASH_TYPE_SHA256; rc = wolfTPM2_CryptoDevCb(INVALID_DEVID, &info, &tpmCtx); - AssertIntEQ(rc, CRYPTOCB_UNAVAILABLE); + AssertIntEQ(rc, BAD_FUNC_ARG); info.pk.pqc_sign.preHashType = WC_HASH_TYPE_NONE; - /* too big for one-shot: fall back */ + /* too big for one-shot */ info.pk.pqc_sign.inlen = MAX_DIGEST_BUFFER + 1; rc = wolfTPM2_CryptoDevCb(INVALID_DEVID, &info, &tpmCtx); - AssertIntEQ(rc, CRYPTOCB_UNAVAILABLE); + AssertIntEQ(rc, BAD_FUNC_ARG); info.pk.pqc_sign.inlen = (word32)sizeof(msg); /* NULL outlen: reject before dereference */ info.pk.pqc_sign.outlen = NULL; rc = wolfTPM2_CryptoDevCb(INVALID_DEVID, &info, &tpmCtx); - AssertIntEQ(rc, CRYPTOCB_UNAVAILABLE); + AssertIntEQ(rc, BAD_FUNC_ARG); info.pk.pqc_sign.outlen = &sigLen; /* happy path (needs v1.85 ML-DSA) */ From 74d9d63742b209d59c599210f5a9242d4c74096b Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 28 Jul 2026 15:38:27 -0700 Subject: [PATCH 3/4] Scope PQC DER reader to the PQC guard for MSVC builds --- examples/tls/tls_common.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/tls/tls_common.h b/examples/tls/tls_common.h index 89bd7364a..26e8916de 100644 --- a/examples/tls/tls_common.h +++ b/examples/tls/tls_common.h @@ -430,9 +430,7 @@ static inline int TlsParseKemGroup(const char* v, int* group) } return BAD_FUNC_ARG; } -#endif /* WOLFTPM_TLS_PQC */ -#ifndef NO_FILESYSTEM /* Read a DER file into der, updating derSz with the bytes read. */ static inline int ReadDerFile(const char* file, byte* der, int* derSz) { @@ -459,7 +457,7 @@ static inline int ReadDerFile(const char* file, byte* der, int* derSz) *derSz = (int)sz; return 0; } -#endif /* !NO_FILESYSTEM */ +#endif /* WOLFTPM_TLS_PQC */ static inline int myVerify(int preverify, WOLFSSL_X509_STORE_CTX* store) { From 558ed1588950a4feae5951e671fcadd3c5fcaab5 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 29 Jul 2026 12:41:35 -0700 Subject: [PATCH 4/4] Build PQC TLS job against upstream wolfSSL --- .github/workflows/pqc-examples.yml | 36 +++++++++--------------------- examples/pqc/README.md | 7 +++--- 2 files changed, 14 insertions(+), 29 deletions(-) diff --git a/.github/workflows/pqc-examples.yml b/.github/workflows/pqc-examples.yml index e1f0a2158..4759399f5 100644 --- a/.github/workflows/pqc-examples.yml +++ b/.github/workflows/pqc-examples.yml @@ -194,31 +194,15 @@ jobs: with: persist-credentials: false - # 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) - env: - # aidangarske/wolfssl mldsa-signctx-cryptocb-devkey - WOLFSSL_PQC_SHA: 6b0c832284286dbaec8e5ab35581ff470e90826b - run: | - cd ~ - # pinned to an immutable commit: a mutable branch tip would let a - # force-push run unreviewed code inside this job - git clone --no-checkout https://github.com/aidangarske/wolfssl.git - cd wolfssl - git fetch --depth 1 origin "$WOLFSSL_PQC_SHA" - git checkout "$WOLFSSL_PQC_SHA" - ./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: Setup wolfSSL with PQC TLS + uses: ./.github/actions/setup-wolfssl + with: + configure-flags: >- + --enable-wolftpm --enable-pkcallbacks --enable-keygen + --enable-certgen --enable-dilithium --enable-mlkem + --enable-experimental --enable-tls-mlkem-standalone --enable-harden + cflags: -DWOLFSSL_TLSX_PQC_MLKEM_STORE_OBJ -DWC_RSA_NO_PADDING + prefix: $HOME/wolfssl-install - name: wolfSSL version info run: grep LIBWOLFSSL_VERSION_STRING "$HOME/wolfssl-install/include/wolfssl/version.h" @@ -249,7 +233,7 @@ jobs: export LD_LIBRARY_PATH="$HOME/wolfssl-install/lib" # run_examples.sh pairs the classical TLS tests with the wolfSSL # example client/server, so point it at the clone built above - export WOLFSSL_PATH="$HOME/wolfssl" + export WOLFSSL_PATH="$GITHUB_WORKSPACE/wolfssl" ./src/fwtpm/fwtpm_server >/tmp/fwtpm_tls.log 2>&1 & echo $! > /tmp/fwtpm_tls.pid sleep 2 diff --git a/examples/pqc/README.md b/examples/pqc/README.md index 0968bb677..2e6e228b4 100644 --- a/examples/pqc/README.md +++ b/examples/pqc/README.md @@ -189,9 +189,10 @@ 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. +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