From 9f245b08df7aa5a60e50184d07c81f1e370f2510 Mon Sep 17 00:00:00 2001 From: David Garske Date: Mon, 2 Nov 2026 13:06:49 -0800 Subject: [PATCH 1/2] bench: cover every parameter set, curve and RSA size the build supports --- .github/workflows/fwtpm-test.yml | 12 ++ examples/bench/bench.c | 202 +++++++++++++++++++++++++++---- 2 files changed, 188 insertions(+), 26 deletions(-) diff --git a/.github/workflows/fwtpm-test.yml b/.github/workflows/fwtpm-test.yml index b134fde1..c3992c5d 100644 --- a/.github/workflows/fwtpm-test.yml +++ b/.github/workflows/fwtpm-test.yml @@ -78,6 +78,18 @@ jobs: wolfssl_config: --enable-wolftpm --enable-pkcallbacks --enable-keygen --enable-dilithium --enable-mlkem --enable-experimental --enable-harden --disable-tls13 --disable-hkdf build_only: false + # v1.85 PQC with one ML-DSA parameter set left out, so the server + # answers TPM_RC_PARMS for it. The benchmark walks every set, so it + # has to record that one as unsupported and carry on to the rest; + # run_examples.sh fails on a non-zero bench exit, which is what + # catches a regression here. + - name: fwtpm-v185-mldsa-trim + os: ubuntu-latest + wolftpm_config: --enable-fwtpm --enable-v185 + wolfssl_config: --enable-wolftpm --enable-pkcallbacks --enable-keygen --enable-dilithium --enable-mlkem --enable-experimental --enable-harden + build_only: false + extra_cflags: -DWOLFSSL_NO_ML_DSA_44 + # Build-only: fwTPM with RSA disabled - name: fwtpm-no-rsa os: ubuntu-latest diff --git a/examples/bench/bench.c b/examples/bench/bench.c index b0c38efc..03a67538 100644 --- a/examples/bench/bench.c +++ b/examples/bench/bench.c @@ -196,6 +196,53 @@ static int bench_asym_done(const char* algo, int strength, const char* desc, return rc; } +/* True if rc from a key creation means the part does not implement the + * requested parameter set, modulus or curve. Creation is where those are + * first checked, so it reports codes a finished operation never produces: + * TPM_RC_PARMS for an excluded ML-DSA/ML-KEM parameter set, TPM_RC_KEY_SIZE + * for a modulus the TPM does not offer, TPM_RC_CURVE for a curve it lacks. */ +static int bench_keygen_unsupported(int rc) +{ + return bench_unsupported(rc) || + ((rc & RC_MAX_FMT1) == TPM_RC_PARMS) || + ((rc & RC_MAX_FMT1) == TPM_RC_KEY_SIZE) || + ((rc & RC_MAX_FMT1) == TPM_RC_CURVE); +} + +/* bench_asym_done for a key creation, so an unsupported parameter set, size + * or curve records a skip and lets the caller move to the next one instead + * of ending the benchmark. */ +static int bench_keygen_done(const char* algo, int strength, const char* desc, + int count, double start, int rc) +{ + if (rc != 0 && bench_keygen_unsupported(rc)) { + printf("%-6s %5d %-9s Skipped (not supported)\n", algo, strength, desc); + return 0; + } + return bench_asym_done(algo, strength, desc, count, start, rc); +} + +/* The reported strength is the curve name, which is not the field size for + * every curve (P-521 carries 66-byte fields). */ +static int bench_ecc_bits(TPM_ECC_CURVE curve) +{ + int bits; + + if (curve == TPM_ECC_NIST_P256) { + bits = 256; + } + else if (curve == TPM_ECC_NIST_P384) { + bits = 384; + } + else if (curve == TPM_ECC_NIST_P521) { + bits = 521; + } + else { + bits = TPM2_GetCurveSize(curve) * 8; + } + return bits; +} + static int bench_sym_hash(WOLFTPM2_DEV* dev, const char* desc, int algo, const byte* in, word32 inSz, byte* digest, word32 digestSz, double maxDuration) { @@ -269,9 +316,10 @@ static int bench_sym_aes(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* storageKey, #ifdef WOLFTPM_MLDSA /* Benchmark Pure ML-DSA: key gen, sign and verify (sign/verify sequence). */ static int bench_pqc_mldsa(WOLFTPM2_DEV* dev, double maxDuration, - double maxKeyGenDurSec) + double maxKeyGenDurSec, int level) { int rc, count; + int strength; double start; WOLFTPM2_KEY mldsaKey; TPMT_PUBLIC publicTemplate; @@ -287,10 +335,14 @@ static int bench_pqc_mldsa(WOLFTPM2_DEV* dev, double maxDuration, XMEMSET(message, 0x11, sizeof(message)); XMEMSET(sig, 0, sizeof(sig)); + /* The reported strength is the parameter set name, not the enum value. */ + strength = (level == TPM_MLDSA_44) ? 44 : + (level == TPM_MLDSA_65) ? 65 : 87; + rc = wolfTPM2_GetKeyTemplate_MLDSA(&publicTemplate, TPMA_OBJECT_sign | TPMA_OBJECT_fixedTPM | TPMA_OBJECT_fixedParent | TPMA_OBJECT_sensitiveDataOrigin | TPMA_OBJECT_userWithAuth | - TPMA_OBJECT_noDA, TPM_MLDSA_65, 0); + TPMA_OBJECT_noDA, level, 0); if (rc != 0) return rc; bench_stats_start(&count, &start); @@ -301,7 +353,7 @@ static int bench_pqc_mldsa(WOLFTPM2_DEV* dev, double maxDuration, &publicTemplate, NULL, 0); if (rc != 0) break; } while (bench_stats_check(start, &count, maxKeyGenDurSec)); - rc = bench_asym_done("ML-DSA", 65, "key gen", count, start, rc); + rc = bench_keygen_done("ML-DSA", strength, "key gen", count, start, rc); if (rc != 0) return rc; if (count == 0) @@ -325,7 +377,7 @@ static int bench_pqc_mldsa(WOLFTPM2_DEV* dev, double maxDuration, } if (rc != 0) break; } while (bench_stats_check(start, &count, maxDuration)); - rc = bench_asym_done("ML-DSA", 65, "sign", count, start, rc); + rc = bench_asym_done("ML-DSA", strength, "sign", count, start, rc); if (rc != 0) goto exit; if (count == 0) goto exit; /* no signature produced; nothing to verify */ @@ -338,7 +390,7 @@ static int bench_pqc_mldsa(WOLFTPM2_DEV* dev, double maxDuration, message, (int)sizeof(message), sig, sigSz, &validation); if (rc != 0) break; } while (bench_stats_check(start, &count, maxDuration)); - rc = bench_asym_done("ML-DSA", 65, "verify", count, start, rc); + rc = bench_asym_done("ML-DSA", strength, "verify", count, start, rc); if (rc != 0) goto exit; exit: @@ -350,9 +402,10 @@ static int bench_pqc_mldsa(WOLFTPM2_DEV* dev, double maxDuration, #ifdef WOLFTPM_MLKEM /* Benchmark ML-KEM: key gen, encapsulate and decapsulate. */ static int bench_pqc_mlkem(WOLFTPM2_DEV* dev, double maxDuration, - double maxKeyGenDurSec) + double maxKeyGenDurSec, int level) { int rc, count; + int strength; double start; WOLFTPM2_KEY mlkemKey; TPMT_PUBLIC publicTemplate; @@ -364,10 +417,14 @@ static int bench_pqc_mlkem(WOLFTPM2_DEV* dev, double maxDuration, XMEMSET(&mlkemKey, 0, sizeof(mlkemKey)); XMEMSET(&publicTemplate, 0, sizeof(publicTemplate)); + /* The reported strength is the parameter set name, not the enum value. */ + strength = (level == TPM_MLKEM_512) ? 512 : + (level == TPM_MLKEM_768) ? 768 : 1024; + rc = wolfTPM2_GetKeyTemplate_MLKEM(&publicTemplate, TPMA_OBJECT_decrypt | TPMA_OBJECT_fixedTPM | TPMA_OBJECT_fixedParent | TPMA_OBJECT_sensitiveDataOrigin | TPMA_OBJECT_userWithAuth | - TPMA_OBJECT_noDA, TPM_MLKEM_768); + TPMA_OBJECT_noDA, level); if (rc != 0) return rc; bench_stats_start(&count, &start); @@ -378,7 +435,7 @@ static int bench_pqc_mlkem(WOLFTPM2_DEV* dev, double maxDuration, &publicTemplate, NULL, 0); if (rc != 0) break; } while (bench_stats_check(start, &count, maxKeyGenDurSec)); - rc = bench_asym_done("ML-KEM", 768, "key gen", count, start, rc); + rc = bench_keygen_done("ML-KEM", strength, "key gen", count, start, rc); if (rc != 0) return rc; if (count == 0) @@ -392,7 +449,7 @@ static int bench_pqc_mlkem(WOLFTPM2_DEV* dev, double maxDuration, secret, &secretSz); if (rc != 0) break; } while (bench_stats_check(start, &count, maxDuration)); - rc = bench_asym_done("ML-KEM", 768, "encap", count, start, rc); + rc = bench_asym_done("ML-KEM", strength, "encap", count, start, rc); if (rc != 0) goto exit; bench_stats_start(&count, &start); @@ -402,7 +459,7 @@ static int bench_pqc_mlkem(WOLFTPM2_DEV* dev, double maxDuration, secret, &secretSz); if (rc != 0) break; } while (bench_stats_check(start, &count, maxDuration)); - rc = bench_asym_done("ML-KEM", 768, "decap", count, start, rc); + rc = bench_asym_done("ML-KEM", strength, "decap", count, start, rc); if (rc != 0) goto exit; exit: @@ -448,11 +505,56 @@ int TPM2_Wrapper_BenchArgs(void* userCtx, int argc, char *argv[]) int count; int sha1Supported; int rsaBits = WOLFTPM2_WRAP_RSA_KEY_BITS; + int skipDec = 0; /* skip a decrypt op whose paired encrypt was unsupported */ + int keyFailed; /* the last key creation of a row failed */ TPM_ALG_ID paramEncAlg = TPM_ALG_NULL; WOLFTPM2_SESSION tpmSession; double maxDuration = TPM2_BENCH_DURATION_SEC; double maxKeyGenDurSec = TPM2_BENCH_DURATION_KEYGEN_SEC; + int j; + int k; + int eccBits; + /* Every entry is bounded by what the TPM structures can carry. The + * configured size is kept as a candidate when it sits below 2048, so a + * build that benchmarked it does not lose that row. */ + static const int rsaSizes[] = { +#if WOLFTPM2_WRAP_RSA_KEY_BITS < 2048 || MAX_RSA_KEY_BITS < 2048 + WOLFTPM2_WRAP_RSA_KEY_BITS, +#endif +#if MAX_RSA_KEY_BITS >= 2048 + 2048, +#endif +#if MAX_RSA_KEY_BITS >= 3072 + 3072, +#endif +#if MAX_RSA_KEY_BITS >= 4096 + 4096, +#endif + }; + TPM_ECC_CURVE eccCurve; + /* P-256 as before, plus the larger curves the host build enables, + * guarded as the ECC tests in tests/unit_tests.c are. */ + static const TPM_ECC_CURVE eccCurves[] = { + TPM_ECC_NIST_P256, +#if (defined(HAVE_ECC384) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 384 + TPM_ECC_NIST_P384, +#endif +#if (defined(HAVE_ECC521) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 521 + TPM_ECC_NIST_P521, +#endif + }; +#if defined(WOLFTPM_MLDSA) || defined(WOLFTPM_MLKEM) + int i; +#endif +#ifdef WOLFTPM_MLDSA + static const int mldsaLevels[] = { + TPM_MLDSA_44, TPM_MLDSA_65, TPM_MLDSA_87 }; +#endif +#ifdef WOLFTPM_MLKEM + static const int mlkemLevels[] = { + TPM_MLKEM_512, TPM_MLKEM_768, TPM_MLKEM_1024 }; +#endif /* Static, so a previous call with -iter must not leak into this one */ gBenchIter = 0; @@ -607,11 +709,18 @@ int TPM2_Wrapper_BenchArgs(void* userCtx, int argc, char *argv[]) if (rc != 0 && (rc & RC_MAX_FMT1) != TPM_RC_HASH) goto exit; + for (k = 0; k < (int)(sizeof(rsaSizes)/sizeof(rsaSizes[0])); k++) { + rsaBits = rsaSizes[k]; + /* Create RSA key for encrypt/decrypt */ rc = wolfTPM2_GetKeyTemplate_RSA(&publicTemplate, TPMA_OBJECT_sensitiveDataOrigin | TPMA_OBJECT_userWithAuth | TPMA_OBJECT_decrypt | TPMA_OBJECT_sign | TPMA_OBJECT_noDA); if (rc != 0) goto exit; + /* The template helper takes no size, so it comes from + * WOLFTPM2_WRAP_RSA_KEY_BITS. Override it to walk the sizes the TPM + * structures can carry. */ + publicTemplate.parameters.rsaDetail.keyBits = (TPMI_RSA_KEY_BITS)rsaBits; bench_stats_start(&count, &start); do { if (count > 0) { @@ -620,9 +729,17 @@ int TPM2_Wrapper_BenchArgs(void* userCtx, int argc, char *argv[]) } rc = wolfTPM2_CreateAndLoadKey(&dev, &rsaKey, &storageKey.handle, &publicTemplate, (byte*)gKeyAuth, sizeof(gKeyAuth)-1); - if (rc != 0) goto exit; + /* A part that does not implement this modulus reports itself here + * rather than ending the benchmark. */ + if (rc != 0) break; } while (bench_stats_check(start, &count, maxKeyGenDurSec)); - bench_stats_asym_finish("RSA", rsaBits, "key gen", count, start); + keyFailed = (rc != 0); + rc = bench_keygen_done("RSA", rsaBits, "key gen", count, start, rc); + if (rc != 0) goto exit; + if (count == 0 || keyFailed) { + XMEMSET(&rsaKey, 0, sizeof(rsaKey)); + continue; /* size unsupported; the rest needs the key */ + } /* Perform RSA encrypt / decrypt (no pad) */ /* No padding means the message is exactly the modulus width, which the @@ -691,14 +808,20 @@ int TPM2_Wrapper_BenchArgs(void* userCtx, int argc, char *argv[]) rc = wolfTPM2_UnloadHandle(&dev, &rsaKey.handle); if (rc != 0) goto exit; + } /* RSA sizes */ + + for (j = 0; j < (int)(sizeof(eccCurves)/sizeof(eccCurves[0])); j++) { + eccCurve = eccCurves[j]; /* Create an ECC key for ECDSA */ rc = wolfTPM2_GetKeyTemplate_ECC(&publicTemplate, TPMA_OBJECT_sensitiveDataOrigin | TPMA_OBJECT_userWithAuth | TPMA_OBJECT_sign | TPMA_OBJECT_noDA, - TPM_ECC_NIST_P256, TPM_ALG_ECDSA); + eccCurve, TPM_ALG_ECDSA); if (rc != 0) goto exit; + /* Report the curve the template carries, not the one requested */ + eccBits = bench_ecc_bits(publicTemplate.parameters.eccDetail.curveID); bench_stats_start(&count, &start); do { if (count > 0) { @@ -707,9 +830,17 @@ int TPM2_Wrapper_BenchArgs(void* userCtx, int argc, char *argv[]) } rc = wolfTPM2_CreateAndLoadKey(&dev, &eccKey, &storageKey.handle, &publicTemplate, (byte*)gKeyAuth, sizeof(gKeyAuth)-1); - if (rc != 0) goto exit; + /* A part that does not implement this curve reports itself here + * rather than ending the benchmark. */ + if (rc != 0) break; } while (bench_stats_check(start, &count, maxDuration)); - bench_stats_asym_finish("ECC", 256, "key gen", count, start); + keyFailed = (rc != 0); + rc = bench_keygen_done("ECC", eccBits, "key gen", count, start, rc); + if (rc != 0) goto exit; + if (count == 0 || keyFailed) { + XMEMSET(&eccKey, 0, sizeof(eccKey)); + continue; /* curve unsupported; the rest needs the key */ + } /* Perform sign / verify */ message.size = TPM_SHA256_DIGEST_SIZE; /* test message 0x11,0x11,etc */ @@ -722,16 +853,20 @@ int TPM2_Wrapper_BenchArgs(void* userCtx, int argc, char *argv[]) cipher.buffer, &cipher.size); if (rc != 0) break; } while (bench_stats_check(start, &count, maxDuration)); - rc = bench_asym_done("ECDSA", 256, "sign", count, start, rc); + rc = bench_asym_done("ECDSA", eccBits, "sign", count, start, rc); if (rc != 0) goto exit; bench_stats_start(&count, &start); do { - rc = wolfTPM2_VerifyHash(&dev, &eccKey, cipher.buffer, cipher.size, - message.buffer, message.size); + /* Verify under the key's scheme hash, which signed it, rather than + * the curve's default hash */ + rc = wolfTPM2_VerifyHash_ex(&dev, &eccKey, cipher.buffer, cipher.size, + message.buffer, message.size, + eccKey.pub.publicArea.parameters.eccDetail.scheme.details.ecdsa + .hashAlg); if (rc != 0) break; } while (bench_stats_check(start, &count, maxDuration)); - rc = bench_asym_done("ECDSA", 256, "verify", count, start, rc); + rc = bench_asym_done("ECDSA", eccBits, "verify", count, start, rc); if (rc != 0) goto exit; rc = wolfTPM2_UnloadHandle(&dev, &eccKey.handle); @@ -742,11 +877,17 @@ int TPM2_Wrapper_BenchArgs(void* userCtx, int argc, char *argv[]) rc = wolfTPM2_GetKeyTemplate_ECC(&publicTemplate, TPMA_OBJECT_sensitiveDataOrigin | TPMA_OBJECT_userWithAuth | TPMA_OBJECT_decrypt | TPMA_OBJECT_noDA, - TPM_ECC_NIST_P256, TPM_ALG_ECDH); + eccCurve, TPM_ALG_ECDH); if (rc != 0) goto exit; rc = wolfTPM2_CreateAndLoadKey(&dev, &eccKey, &storageKey.handle, &publicTemplate, (byte*)gKeyAuth, sizeof(gKeyAuth)-1); - if (rc != 0) goto exit; + if (rc != 0) { + /* A part can offer the curve for ECDSA but not for ECDH */ + rc = bench_keygen_done("ECDHE", eccBits, "key gen", 0, start, rc); + if (rc != 0) goto exit; + XMEMSET(&eccKey, 0, sizeof(eccKey)); + continue; + } /* Create ephemeral ECC key and generate a shared secret */ bench_stats_start(&count, &start); @@ -756,11 +897,12 @@ int TPM2_Wrapper_BenchArgs(void* userCtx, int argc, char *argv[]) cipher.buffer, &cipher.size); if (rc != 0) break; } while (bench_stats_check(start, &count, maxDuration)); - rc = bench_asym_done("ECDHE", 256, "agree", count, start, rc); + rc = bench_asym_done("ECDHE", eccBits, "agree", count, start, rc); if (rc != 0) goto exit; rc = wolfTPM2_UnloadHandle(&dev, &eccKey.handle); if (rc != 0) goto exit; + } /* ECC curves */ #if defined(WOLFTPM_MLDSA) || defined(WOLFTPM_MLKEM) /* Post-quantum (TPM 2.0 v1.85). Skipped under parameter encryption: the @@ -770,12 +912,20 @@ int TPM2_Wrapper_BenchArgs(void* userCtx, int argc, char *argv[]) if (paramEncAlg == TPM_ALG_NULL) { wolfTPM2_UnloadHandle(&dev, &storageKey.handle); #ifdef WOLFTPM_MLDSA - rc = bench_pqc_mldsa(&dev, maxDuration, maxKeyGenDurSec); - if (rc != 0) goto exit; + for (i = 0; i < (int)(sizeof(mldsaLevels)/sizeof(mldsaLevels[0])); + i++) { + rc = bench_pqc_mldsa(&dev, maxDuration, maxKeyGenDurSec, + mldsaLevels[i]); + if (rc != 0) goto exit; + } #endif #ifdef WOLFTPM_MLKEM - rc = bench_pqc_mlkem(&dev, maxDuration, maxKeyGenDurSec); - if (rc != 0) goto exit; + for (i = 0; i < (int)(sizeof(mlkemLevels)/sizeof(mlkemLevels[0])); + i++) { + rc = bench_pqc_mlkem(&dev, maxDuration, maxKeyGenDurSec, + mlkemLevels[i]); + if (rc != 0) goto exit; + } #endif } #endif From c9e769b4e00629c13ade193c4612a95df373e5b6 Mon Sep 17 00:00:00 2001 From: David Garske Date: Mon, 2 Nov 2026 14:15:13 -0800 Subject: [PATCH 2/2] Run only the benchmark in the ML-DSA-44 trim CI configuration --- .github/workflows/fwtpm-test.yml | 40 ++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/.github/workflows/fwtpm-test.yml b/.github/workflows/fwtpm-test.yml index c3992c5d..4a1744f0 100644 --- a/.github/workflows/fwtpm-test.yml +++ b/.github/workflows/fwtpm-test.yml @@ -80,14 +80,14 @@ jobs: # v1.85 PQC with one ML-DSA parameter set left out, so the server # answers TPM_RC_PARMS for it. The benchmark walks every set, so it - # has to record that one as unsupported and carry on to the rest; - # run_examples.sh fails on a non-zero bench exit, which is what - # catches a regression here. + # has to record that one as unsupported and carry on to the rest. + # The unit tests use ML-DSA-44 throughout, so only the benchmark + # runs, in its own step below. - name: fwtpm-v185-mldsa-trim os: ubuntu-latest - wolftpm_config: --enable-fwtpm --enable-v185 + wolftpm_config: --enable-fwtpm --enable-swtpm --enable-v185 wolfssl_config: --enable-wolftpm --enable-pkcallbacks --enable-keygen --enable-dilithium --enable-mlkem --enable-experimental --enable-harden - build_only: false + build_only: true extra_cflags: -DWOLFSSL_NO_ML_DSA_44 # Build-only: fwTPM with RSA disabled @@ -403,6 +403,36 @@ jobs: ./examples/bench/bench -iter=1 | tee /tmp/fwtpm_no_sha1_bench.log grep -E '^SHA1[[:space:]]+Skipped [(]not supported[)]$' /tmp/fwtpm_no_sha1_bench.log + - name: Run benchmark with ML-DSA-44 left out + if: matrix.name == 'fwtpm-v185-mldsa-trim' + run: | + set -euo pipefail + ./src/fwtpm/fwtpm_server --clear > /tmp/fwtpm_mldsa_trim.log 2>&1 & + server_pid=$! + trap 'kill "$server_pid" 2>/dev/null || true' EXIT + sleep 1 + kill -0 "$server_pid" + ./examples/bench/bench -iter=1 | tee /tmp/fwtpm_mldsa_trim_bench.log + grep -E '^ML-DSA +44 key gen +Skipped [(]not supported[)]$' \ + /tmp/fwtpm_mldsa_trim_bench.log + grep -E '^ML-DSA +65 key gen +[0-9]' /tmp/fwtpm_mldsa_trim_bench.log + grep -E '^ECDSA +384 verify +[0-9]' /tmp/fwtpm_mldsa_trim_bench.log + grep -E '^ECDSA +521 verify +[0-9]' /tmp/fwtpm_mldsa_trim_bench.log + + - name: Run benchmark across RSA sizes + if: matrix.name == 'fwtpm-rsa4096' + run: | + set -euo pipefail + ./src/fwtpm/fwtpm_server --clear > /tmp/fwtpm_rsa4096.log 2>&1 & + server_pid=$! + trap 'kill "$server_pid" 2>/dev/null || true' EXIT + sleep 1 + kill -0 "$server_pid" + ./examples/bench/bench -iter=1 | tee /tmp/fwtpm_rsa4096_bench.log + grep -E '^RSA +3072 Priv OAEP +[0-9]' /tmp/fwtpm_rsa4096_bench.log + grep -E '^RSA +4096 Priv OAEP +[0-9]' /tmp/fwtpm_rsa4096_bench.log + grep -E '^ECDSA +384 verify +[0-9]' /tmp/fwtpm_rsa4096_bench.log + - name: Print test-suite.log on failure if: ${{ failure() && !matrix.build_only }} run: |