From 0063c16e7ea48bd2a7a3d6b20c3a41f91e75f2fb Mon Sep 17 00:00:00 2001 From: aidan garske Date: Wed, 22 Jul 2026 11:37:40 -0700 Subject: [PATCH] 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 */