diff --git a/.github/workflows/pqc-examples.yml b/.github/workflows/pqc-examples.yml index 789b7cd5..4759399f 100644 --- a/.github/workflows/pqc-examples.yml +++ b/.github/workflows/pqc-examples.yml @@ -172,3 +172,88 @@ 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) + permissions: + contents: read + 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 + with: + persist-credentials: false + + - 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" + + - 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 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 + fi + done + + - name: PQC TLS handshake E2E (ML-KEM x ML-DSA matrix) + run: | + set +e + 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="$GITHUB_WORKSPACE/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/fwtpm_tls.log + run.out + pqtls.out + config.log + tests/*.log + retention-days: 5 diff --git a/.gitignore b/.gitignore index 0e67d092..072105e4 100644 --- a/.gitignore +++ b/.gitignore @@ -78,6 +78,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 +119,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 bce99cdb..2e6e228b 100644 --- a/examples/pqc/README.md +++ b/examples/pqc/README.md @@ -14,11 +14,20 @@ the full fwTPM PQC reference. ``` ./configure --enable-wolftpm --enable-mldsa --enable-mlkem \ - --enable-harden --enable-keygen + --enable-tls-mlkem-standalone --enable-experimental \ + --enable-harden --enable-keygen --enable-certgen make sudo make install ``` +`--enable-tls-mlkem-standalone` is required for the standalone `ML_KEM_*` TLS +groups; without it wolfSSL only offers the hybrid groups and +`wolfSSL_UseKeyShare` rejects the client default. + +`--enable-certgen` is needed by the TLS `gen_pqc_certs` tool below; +`--enable-wolftpm` provides the crypto callback and private-key-id support the +TLS server uses. + **wolfTPM**: ``` @@ -172,3 +181,58 @@ 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 a wolfSSL that routes `wc_MlDsaKey_SignCtx` to the crypto callback for +device keys (private key in the TPM). That landed upstream, so master or any +later release works. No shipping TPM implements TCG v1.85 PQC yet, so this runs +against the in-tree fwTPM. + +Demo scope: the identity key is an unauthenticated deterministic TPM primary +(empty auth), reproducible by both `gen_pqc_certs` and the server from the owner +hierarchy. A production deployment should protect the identity key with a +non-empty auth value or policy so it cannot be recreated from the public cert. +The client validates the server chain against the demo CA but does not bind the +certificate to the host name, so the demo connects to the default localhost and +does not pass `-h=`. Supplying `-h=` turns on strict verification including +`wolfSSL_check_domain_name`, which this leaf cannot satisfy; a production +deployment should issue the leaf with a matching subjectAltName. + +Three programs: +- `examples/pqc/gen_pqc_certs` — makes a software ML-DSA CA and a device leaf + cert whose subject key is the TPM ML-DSA key. +- `examples/tls/tls_server -mldsa` — recreates that TPM key and serves TLS 1.3. +- `examples/tls/tls_client -mldsa` — connects, ML-KEM key exchange, verifies the CA. + +``` +./src/fwtpm/fwtpm_server --clear & + +# 1. certificate chain bound to the TPM key (-mldsa must match the server) +./examples/pqc/gen_pqc_certs -mldsa=65 + +# 2. server (same -mldsa as gen_pqc_certs) +./examples/tls/tls_server -p=11111 -mldsa=65 & + +# 3. client (choose the ML-KEM group) +./examples/tls/tls_client -p=11111 -mldsa -group=ML_KEM_768 +``` + +Options: +- `gen_pqc_certs -mldsa=44/65/87` — ML-DSA parameter set. +- `tls_server -p= -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). + +The one-shot end-to-end test drives all three and asserts the ML-KEM group, +TPM-signed ML-DSA authentication, CA verification, and app data: + +``` +ENABLE_PQC_TLS=1 ./examples/run_examples.sh # includes the PQC TLS matrix +``` diff --git a/examples/pqc/gen_pqc_certs.c b/examples/pqc/gen_pqc_certs.c new file mode 100644 index 00000000..9bf2b215 --- /dev/null +++ b/examples/pqc/gen_pqc_certs.c @@ -0,0 +1,303 @@ +/* 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); + } + } + +#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); + 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 bcd01959..47be3dd9 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/run_examples.sh b/examples/run_examples.sh index 840063c7..68ecc372 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/tls_client.c b/examples/tls/tls_client.c index 5b2f8e2d..925ea571 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_common.h b/examples/tls/tls_common.h index 925c7c14..26e8916d 100644 --- a/examples/tls/tls_common.h +++ b/examples/tls/tls_common.h @@ -381,6 +381,84 @@ 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; +} + +/* 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 /* WOLFTPM_TLS_PQC */ + 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 52d5f1f1..69992054 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/src/tpm2_cryptocb.c b/src/tpm2_cryptocb.c index 27b26212..ba967b32 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,56 @@ 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; + + /* 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 || + 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) { + return BAD_FUNC_ARG; + } + 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/unit_tests.c b/tests/unit_tests.c index ca40237d..6754039d 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,164 @@ 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); + + /* 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, BAD_FUNC_ARG); + info.pk.pqc_sign.preHashType = WC_HASH_TYPE_NONE; + + /* too big for one-shot */ + info.pk.pqc_sign.inlen = MAX_DIGEST_BUFFER + 1; + rc = wolfTPM2_CryptoDevCb(INVALID_DEVID, &info, &tpmCtx); + 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, BAD_FUNC_ARG); + 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 +7070,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 108b3093..4f40378b 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 */