Merge pull request #559 from aidangarske/mldsa-tls-cryptocb

Add TPM MLDSA authentication for post quantum TLS 1.3 with example and tests
pull/562/head
David Garske 2026-07-29 16:00:40 -07:00 committed by GitHub
commit fa87ed478b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 1092 additions and 19 deletions

View File

@ -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

2
.gitignore vendored
View File

@ -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

View File

@ -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=<port> -mldsa=44/65/87`.
- `tls_client -h=<host> -p=<port> -group=<name>` where `<name>` 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
```

View File

@ -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 <config.h>
#endif
#include <wolftpm/tpm2_wrap.h>
#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 <hal/tpm_io.h>
#include <examples/tpm_test.h>
#include <wolfssl/wolfcrypt/wc_mldsa.h>
#include <wolfssl/wolfcrypt/asn_public.h>
#include <wolfssl/wolfcrypt/random.h>
#include <stdio.h>
#include <string.h>
#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, &paramSet) != 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

View File

@ -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

View File

@ -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"

View File

@ -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 @@ exit:
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);

View File

@ -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:

View File

@ -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 @@ exit:
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

View File

@ -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) {

View File

@ -32,6 +32,9 @@
#include <wolftpm/tpm2_swtpm.h>
#include <wolftpm/tpm2_tis.h>
#include <wolftpm/tpm2_spdm.h>
#ifdef WOLFTPM_MLDSA_SIGN
#include <wolfssl/wolfcrypt/wc_mldsa.h>
#endif
#include <hal/tpm_io.h>
#include <examples/tpm_test.h>
@ -4917,6 +4920,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)
@ -7309,6 +7470,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_RsaUnpadPkcsv15();
test_TPM2_ASN_DecodeX509Cert_Valid();

View File

@ -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 */