From 1976facacce546a8e60e58d794c819decb40a7a6 Mon Sep 17 00:00:00 2001 From: David Garske Date: Mon, 19 Oct 2026 10:15:47 -0700 Subject: [PATCH] Size RSA 4096 buffers and reject truncated public names --- .github/workflows/fwtpm-test.yml | 18 +++++ examples/bench/bench.c | 20 +++--- src/fwtpm/fwtpm_command.c | 17 +++-- src/fwtpm/fwtpm_crypto.c | 8 ++- tests/fwtpm_unit_tests.c | 113 +++++++++++++++++++++++++++++++ wolftpm/fwtpm/fwtpm.h | 4 ++ wolftpm/fwtpm/fwtpm_nv.h | 7 +- wolftpm/tpm2_param_enc.h | 4 +- 8 files changed, 175 insertions(+), 16 deletions(-) diff --git a/.github/workflows/fwtpm-test.yml b/.github/workflows/fwtpm-test.yml index 962163754..480c8fa10 100644 --- a/.github/workflows/fwtpm-test.yml +++ b/.github/workflows/fwtpm-test.yml @@ -75,6 +75,24 @@ jobs: wolfssl_config: --enable-wolftpm --enable-pkcallbacks --enable-keygen --disable-rsa build_only: true + # RSA 4096 at runtime. The chip macros raise MAX_RSA_KEY_BITS to + # 4096, which is where a public area outgrows the buffers sized + # for RSA 2048; every other entry here leaves it at 2048. + - name: fwtpm-rsa4096 + os: ubuntu-latest + wolftpm_config: --enable-fwtpm --enable-swtpm --enable-infineon=slb9672 + wolfssl_config: --enable-wolftpm --enable-pkcallbacks --enable-keygen + build_only: false + + # Build-only: the same chip macros with RSA disabled, so the RSA + # sizing branches must not fire and the buffers stay at their + # classical sizes. + - name: fwtpm-no-rsa-chip4096 + os: ubuntu-latest + wolftpm_config: --enable-fwtpm --enable-swtpm --enable-infineon=slb9672 + wolfssl_config: --enable-wolftpm --enable-pkcallbacks --enable-keygen --disable-rsa + build_only: true + # Build-only: fwTPM with ECC disabled - name: fwtpm-no-ecc os: ubuntu-latest diff --git a/examples/bench/bench.c b/examples/bench/bench.c index b67203491..b0c38efcc 100644 --- a/examples/bench/bench.c +++ b/examples/bench/bench.c @@ -447,6 +447,7 @@ int TPM2_Wrapper_BenchArgs(void* userCtx, int argc, char *argv[]) double start; int count; int sha1Supported; + int rsaBits = WOLFTPM2_WRAP_RSA_KEY_BITS; int skipDec = 0; /* skip a decrypt op whose paired encrypt was unsupported */ TPM_ALG_ID paramEncAlg = TPM_ALG_NULL; WOLFTPM2_SESSION tpmSession; @@ -621,10 +622,12 @@ int TPM2_Wrapper_BenchArgs(void* userCtx, int argc, char *argv[]) &publicTemplate, (byte*)gKeyAuth, sizeof(gKeyAuth)-1); if (rc != 0) goto exit; } while (bench_stats_check(start, &count, maxKeyGenDurSec)); - bench_stats_asym_finish("RSA", 2048, "key gen", count, start); + bench_stats_asym_finish("RSA", rsaBits, "key gen", count, start); /* Perform RSA encrypt / decrypt (no pad) */ - message.size = 256; /* test message 0x11,0x11,etc */ + /* No padding means the message is exactly the modulus width, which the + * chip defaults take to 512 bytes on an RSA 4096 part. */ + message.size = rsaBits / 8; XMEMSET(message.buffer, 0x11, message.size); bench_stats_start(&count, &start); @@ -635,11 +638,12 @@ int TPM2_Wrapper_BenchArgs(void* userCtx, int argc, char *argv[]) if (rc != 0) break; } while (bench_stats_check(start, &count, maxDuration)); skipDec = (rc != 0 && bench_unsupported(rc)); - rc = bench_asym_done("RSA", 2048, "Public", count, start, rc); + rc = bench_asym_done("RSA", rsaBits, "Public", count, start, rc); if (rc != 0) goto exit; if (skipDec) { - printf("%-6s %5d %-9s Skipped (not supported)\n", "RSA", 2048, "Private"); + printf("%-6s %5d %-9s Skipped (not supported)\n", "RSA", rsaBits, + "Private"); } else { bench_stats_start(&count, &start); @@ -649,7 +653,7 @@ int TPM2_Wrapper_BenchArgs(void* userCtx, int argc, char *argv[]) cipher.buffer, cipher.size, plain.buffer, &plain.size); if (rc != 0) break; } while (bench_stats_check(start, &count, maxDuration)); - rc = bench_asym_done("RSA", 2048, "Private", count, start, rc); + rc = bench_asym_done("RSA", rsaBits, "Private", count, start, rc); if (rc != 0) goto exit; } @@ -666,11 +670,11 @@ int TPM2_Wrapper_BenchArgs(void* userCtx, int argc, char *argv[]) if (rc != 0) break; } while (bench_stats_check(start, &count, maxDuration)); skipDec = (rc != 0 && bench_unsupported(rc)); - rc = bench_asym_done("RSA", 2048, "Pub OAEP", count, start, rc); + rc = bench_asym_done("RSA", rsaBits, "Pub OAEP", count, start, rc); if (rc != 0) goto exit; if (skipDec) { - printf("%-6s %5d %-9s Skipped (not supported)\n", "RSA", 2048, + printf("%-6s %5d %-9s Skipped (not supported)\n", "RSA", rsaBits, "Priv OAEP"); } else { @@ -681,7 +685,7 @@ int TPM2_Wrapper_BenchArgs(void* userCtx, int argc, char *argv[]) cipher.buffer, cipher.size, plain.buffer, &plain.size); if (rc != 0) break; } while (bench_stats_check(start, &count, maxDuration)); - rc = bench_asym_done("RSA", 2048, "Priv OAEP", count, start, rc); + rc = bench_asym_done("RSA", rsaBits, "Priv OAEP", count, start, rc); if (rc != 0) goto exit; } diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 112a2132a..e403179b9 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -315,7 +315,10 @@ static TPM_RC FwSpdmTpmKeyName(const byte* rawPub, word32 rawPubSz, pkt.buf = buf; pkt.size = (int)FWTPM_MAX_PUB_BUF; TPM2_Packet_AppendPublicArea(&pkt, pub); - rc = FwNameFromPublicArea(buf, (word32)pkt.pos, name); + if (pkt.overflow) + rc = TPM_RC_FAILURE; + else + rc = FwNameFromPublicArea(buf, (word32)pkt.pos, name); } FWTPM_FREE_BUF(buf); FWTPM_FREE_VAR(pub); @@ -7288,12 +7291,13 @@ static TPM_RC FwCmd_Create(FWTPM_CTX* ctx, TPM2_Packet* cmd, nameDigSz = TPM2_GetHashDigestSize(inPublic->publicArea.nameAlg); FWTPM_ALLOC_BUF(pubBuf2, FWTPM_MAX_PUB_BUF); if (rc == 0) { + XMEMSET(&tmpPkt2, 0, sizeof(tmpPkt2)); tmpPkt2.buf = pubBuf2; tmpPkt2.pos = 0; tmpPkt2.size = (int)FWTPM_MAX_PUB_BUF; TPM2_Packet_AppendPublicArea(&tmpPkt2, &inPublic->publicArea); FwStoreU16BE(objName, inPublic->publicArea.nameAlg); - if (nameDigSz > 0) { + if (nameDigSz > 0 && !tmpPkt2.overflow) { int hashRc = wc_Hash( FwGetWcHashType(inPublic->publicArea.nameAlg), pubBuf2, tmpPkt2.pos, objName + 2, nameDigSz); @@ -8147,15 +8151,18 @@ static TPM_RC FwCmd_Import(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } if (rc == 0) { + XMEMSET(&tmpPkt, 0, sizeof(tmpPkt)); tmpPkt.buf = pubAreaBuf; tmpPkt.pos = 0; tmpPkt.size = (int)FWTPM_MAX_PUB_BUF; TPM2_Packet_AppendPublicArea(&tmpPkt, &objectPublic->publicArea); pubAreaSz = tmpPkt.pos; FwStoreU16BE(nameBuf, objNameAlg); - rc = wc_Hash(objWcHash, pubAreaBuf, pubAreaSz, - nameBuf + 2, objDigestSz); - if (rc != 0) { + if (tmpPkt.overflow) { + rc = TPM_RC_FAILURE; + } + else if (wc_Hash(objWcHash, pubAreaBuf, pubAreaSz, + nameBuf + 2, objDigestSz) != 0) { rc = TPM_RC_FAILURE; } else { diff --git a/src/fwtpm/fwtpm_crypto.c b/src/fwtpm/fwtpm_crypto.c index 0f7c63a71..c0de62ee4 100644 --- a/src/fwtpm/fwtpm_crypto.c +++ b/src/fwtpm/fwtpm_crypto.c @@ -202,6 +202,7 @@ int FwComputePublicName(TPMT_PUBLIC* pub, TPM2B_NAME* name) if (rc == 0) { /* Marshal public area into temp buffer */ + XMEMSET(&tmpPkt, 0, sizeof(tmpPkt)); tmpPkt.buf = pubBuf; tmpPkt.pos = 0; tmpPkt.size = (int)FWTPM_MAX_PUB_BUF; @@ -210,7 +211,12 @@ int FwComputePublicName(TPMT_PUBLIC* pub, TPM2B_NAME* name) wcHash = FwGetWcHashType(pub->nameAlg); digestSz = TPM2_GetHashDigestSize(pub->nameAlg); - if (wcHash == WC_HASH_TYPE_NONE || digestSz == 0) { + if (tmpPkt.overflow) { + /* An append that does not fit is skipped, so a short buffer + * names a partial area: two keys could share one Name. */ + rc = TPM_RC_FAILURE; + } + else if (wcHash == WC_HASH_TYPE_NONE || digestSz == 0) { rc = TPM_RC_HASH; } } diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index dbf9060ed..92f208e47 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -18852,6 +18852,115 @@ static void test_fwtpm_custom_io_hal(void) } #endif +#ifndef NO_RSA +/* The largest valid RSA public area this build can produce, with the + * modulus filled so two keys can be told apart by their bytes. */ +static void fwtpm_rsa_max_pub(TPMT_PUBLIC* pub, byte fill) +{ + UINT16 modSz = (UINT16)(MAX_RSA_KEY_BITS / 8); + + XMEMSET(pub, 0, sizeof(*pub)); + pub->type = TPM_ALG_RSA; + pub->nameAlg = TPM_ALG_SHA256; + pub->objectAttributes = TPMA_OBJECT_fixedTPM | + TPMA_OBJECT_fixedParent | TPMA_OBJECT_sensitiveDataOrigin | + TPMA_OBJECT_userWithAuth | TPMA_OBJECT_restricted | + TPMA_OBJECT_decrypt; + pub->parameters.rsaDetail.symmetric.algorithm = TPM_ALG_AES; + pub->parameters.rsaDetail.symmetric.keyBits.aes = 128; + pub->parameters.rsaDetail.symmetric.mode.aes = TPM_ALG_CFB; + pub->parameters.rsaDetail.scheme.scheme = TPM_ALG_NULL; + pub->parameters.rsaDetail.keyBits = MAX_RSA_KEY_BITS; + pub->parameters.rsaDetail.exponent = 0x10001; + pub->unique.rsa.size = modSz; + XMEMSET(pub->unique.rsa.buffer, fill, modSz); +} + +/* A public area too large for the buffer, which stays in bounds because + * MAX_RSA_KEY_BYTES is twice the modulus width. */ +static void fwtpm_rsa_oversize_pub(TPMT_PUBLIC* pub, byte fill) +{ + fwtpm_rsa_max_pub(pub, fill); + pub->unique.rsa.size = (UINT16)MAX_RSA_KEY_BYTES; + XMEMSET(pub->unique.rsa.buffer, fill, MAX_RSA_KEY_BYTES); +} + +/* An oversized area must be refused, not named from the part that fit. */ +static void test_fwtpm_public_name_rsa_overflow(void) +{ + TPMT_PUBLIC pub; + TPM2B_NAME name; + TPM2_Packet pkt; + byte bufA[FWTPM_MAX_PUB_BUF]; + byte bufB[FWTPM_MAX_PUB_BUF]; + int posA; + + fwtpm_rsa_oversize_pub(&pub, 0xAA); + XMEMSET(&pkt, 0, sizeof(pkt)); + pkt.buf = bufA; + pkt.size = (int)sizeof(bufA); + TPM2_Packet_AppendPublicArea(&pkt, &pub); + if (!pkt.overflow) { + /* A PQC build sizes this buffer for ML-DSA, so no RSA public area + * the build can represent is large enough to overflow it. */ + printf("Test fwTPM: %-6s %-42s Skipped\n", "", + "Public name refuses an oversized area:"); + return; + } + posA = pkt.pos; + + /* The modulus does not fit, and an append that does not fit is skipped + * whole, so a second key differing only there marshals identically. */ + fwtpm_rsa_oversize_pub(&pub, 0xBB); + XMEMSET(&pkt, 0, sizeof(pkt)); + pkt.buf = bufB; + pkt.size = (int)sizeof(bufB); + TPM2_Packet_AppendPublicArea(&pkt, &pub); + AssertIntEQ(pkt.pos, posA); + AssertIntEQ(XMEMCMP(bufA, bufB, (size_t)posA), 0); + + /* Which is why naming it has to fail: those two keys would otherwise + * share one Name, and a Name is what binds authorization. */ + XMEMSET(&name, 0, sizeof(name)); + AssertIntEQ(FwComputePublicName(&pub, &name), TPM_RC_FAILURE); + AssertIntEQ(name.size, 0); + + fwtpm_pass("Public name refuses an oversized area:", 0); +} + +static void test_fwtpm_public_name_rsa_max(void) +{ + TPMT_PUBLIC pub; + TPM2B_NAME nameA; + TPM2B_NAME nameB; + TPM2_Packet pkt; + byte buf[FWTPM_MAX_PUB_BUF]; + UINT16 modSz = (UINT16)(MAX_RSA_KEY_BITS / 8); + + XMEMSET(&nameA, 0, sizeof(nameA)); + XMEMSET(&nameB, 0, sizeof(nameB)); + fwtpm_rsa_max_pub(&pub, 0xAA); + + /* A field that does not fit is skipped rather than truncated, so the + * buffer has to hold the largest public area whole. */ + XMEMSET(&pkt, 0, sizeof(pkt)); + pkt.buf = buf; + pkt.size = (int)sizeof(buf); + TPM2_Packet_AppendPublicArea(&pkt, &pub); + AssertIntEQ(pkt.overflow, 0); + + /* The modulus is all that tells these two apart: a skipped one gives + * them the same Name, and Names bind authorization. */ + AssertIntEQ(FwComputePublicName(&pub, &nameA), TPM_RC_SUCCESS); + XMEMSET(pub.unique.rsa.buffer, 0xBB, modSz); + AssertIntEQ(FwComputePublicName(&pub, &nameB), TPM_RC_SUCCESS); + AssertIntEQ(nameA.size, nameB.size); + AssertIntNE(XMEMCMP(nameA.name, nameB.name, nameA.size), 0); + + fwtpm_pass("Public name at max RSA size:", 0); +} +#endif /* !NO_RSA */ + /* ================================================================== */ /* main */ /* ================================================================== */ @@ -19377,6 +19486,10 @@ int fwtpm_unit_tests(int argc, char *argv[]) #endif /* Hierarchy & misc */ +#ifndef NO_RSA + test_fwtpm_public_name_rsa_max(); + test_fwtpm_public_name_rsa_overflow(); +#endif test_fwtpm_test_parms(); test_fwtpm_incremental_selftest(); test_fwtpm_incremental_selftest_truncated_returns_cmd_size(); diff --git a/wolftpm/fwtpm/fwtpm.h b/wolftpm/fwtpm/fwtpm.h index fed8c52f9..acc06293a 100644 --- a/wolftpm/fwtpm/fwtpm.h +++ b/wolftpm/fwtpm/fwtpm.h @@ -309,6 +309,10 @@ #endif #if FWTPM_MAX_PUB_BUF_RAW > 384 #define FWTPM_MAX_PUB_BUF (FWTPM_MAX_PUB_BUF_RAW + 128) + /* An RSA public area reaches 604 bytes at RSA 4096. The chip macros + * raise MAX_RSA_KEY_BITS whether or not RSA is built. */ + #elif !defined(NO_RSA) && ((MAX_RSA_KEY_BITS / 8) + 128) > 512 + #define FWTPM_MAX_PUB_BUF ((MAX_RSA_KEY_BITS / 8) + 128) #else #define FWTPM_MAX_PUB_BUF 512 /* classical RSA/ECC default */ #endif diff --git a/wolftpm/fwtpm/fwtpm_nv.h b/wolftpm/fwtpm/fwtpm_nv.h index 2acccb358..766d78191 100644 --- a/wolftpm/fwtpm/fwtpm_nv.h +++ b/wolftpm/fwtpm/fwtpm_nv.h @@ -50,7 +50,8 @@ #endif /* PUBAREA_EST: largest enabled PQC pub key + 128 B TPMT_PUBLIC header - * slack, or 600 classical floor — whichever is bigger. */ + * slack, or the 600 classical floor, or RSA's own 604 at 4096 bits. The + * chip macros raise MAX_RSA_KEY_BITS whether or not RSA is built. */ #ifdef WOLFTPM_V185 #if FWTPM_MAX_MLDSA_PUB_SIZE >= FWTPM_MAX_MLKEM_PUB_SIZE #define FWTPM_NV_PUBAREA_UNIQUE FWTPM_MAX_MLDSA_PUB_SIZE @@ -59,9 +60,13 @@ #endif #if FWTPM_NV_PUBAREA_UNIQUE > 472 /* 600 - 128 = classical floor */ #define FWTPM_NV_PUBAREA_EST (FWTPM_NV_PUBAREA_UNIQUE + 128) + #elif !defined(NO_RSA) && ((MAX_RSA_KEY_BITS / 8) + 128) > 600 + #define FWTPM_NV_PUBAREA_EST ((MAX_RSA_KEY_BITS / 8) + 128) #else #define FWTPM_NV_PUBAREA_EST 600 #endif +#elif !defined(NO_RSA) && ((MAX_RSA_KEY_BITS / 8) + 128) > 600 +#define FWTPM_NV_PUBAREA_EST ((MAX_RSA_KEY_BITS / 8) + 128) #else #define FWTPM_NV_PUBAREA_EST 600 #endif diff --git a/wolftpm/tpm2_param_enc.h b/wolftpm/tpm2_param_enc.h index 6f5c01f0a..8143eef0c 100644 --- a/wolftpm/tpm2_param_enc.h +++ b/wolftpm/tpm2_param_enc.h @@ -34,7 +34,9 @@ * exceed MAX_DIGEST_BUFFER (1024), and an fwTPM outPrivate carries a 32-byte * integrity value plus a 16-byte IV on top of the sensitive area. */ #ifndef TPM2_XOR_MASK_MAX -#define TPM2_XOR_MASK_MAX 1536 +/* The sensitive area, and so that blob, scales with the RSA key size. */ +#define TPM2_XOR_MASK_MAX \ + ((UINT32)(sizeof(TPM2B_PRIVATE) > 1536 ? sizeof(TPM2B_PRIVATE) : 1536)) #endif /* XOR parameter encryption/decryption (raw pointer interface).