From c63386bf93c99a3468d1fe41935ad8ece9b96386 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Mon, 13 Jul 2026 11:27:46 -0700 Subject: [PATCH 01/27] F-6847 - Bound ECDSA verify R/S to key size in crypto callback --- src/tpm2_cryptocb.c | 10 ++++++- tests/unit_tests.c | 64 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/tpm2_cryptocb.c b/src/tpm2_cryptocb.c index 3047e531..27b26212 100644 --- a/src/tpm2_cryptocb.c +++ b/src/tpm2_cryptocb.c @@ -352,6 +352,7 @@ int wolfTPM2_CryptoDevCb(int devId, wc_CryptoInfo* info, void* ctx) byte sigRS[MAX_ECC_BYTES*2]; byte *r = sigRS, *s = &sigRS[MAX_ECC_BYTES]; word32 rLen = MAX_ECC_BYTES, sLen = MAX_ECC_BYTES; + word32 keySz = 0; XMEMSET(&eccPub, 0, sizeof(eccPub)); XMEMSET(sigRS, 0, sizeof(sigRS)); @@ -359,13 +360,20 @@ int wolfTPM2_CryptoDevCb(int devId, wc_CryptoInfo* info, void* ctx) /* Decode ECDSA Header */ rc = wc_ecc_sig_to_rs(info->pk.eccverify.sig, info->pk.eccverify.siglen, r, &rLen, s, &sLen); + if (rc == 0) { + /* R/S larger than key size underflows the pad offset */ + keySz = wc_ecc_size(info->pk.eccverify.key); + if (keySz == 0 || keySz > MAX_ECC_BYTES || + rLen > keySz || sLen > keySz) { + rc = exit_rc; + } + } if (rc == 0) { /* load public key into TPM */ rc = wolfTPM2_EccKey_WolfToTpm(tlsCtx->dev, info->pk.eccverify.key, &eccPub); if (rc == 0) { /* combine R and S at key size (zero pad leading) */ - word32 keySz = wc_ecc_size(info->pk.eccverify.key); XMEMMOVE(&sigRS[keySz-rLen], r, rLen); XMEMSET(&sigRS[0], 0, keySz-rLen); XMEMMOVE(&sigRS[keySz + (keySz-sLen)], s, sLen); diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 75483603..4ac6b701 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -4028,6 +4028,69 @@ static void test_wolfTPM2_CSR(void) #endif } +static void test_wolfTPM2_CryptoDevCb_EccVerifyOversizedRS(void) +{ +#if !defined(WOLFTPM2_NO_WRAPPER) && defined(WOLFTPM_CRYPTOCB) && \ + !defined(WOLFTPM2_NO_WOLFCRYPT) && defined(HAVE_ECC) && \ + defined(HAVE_ECC_VERIFY) && !defined(WC_NO_RNG) + int rc; + int i; + int verifyRes = 0; + WOLFTPM2_DEV dev; + TpmCryptoDevCtx tpmCtx; + wc_CryptoInfo info; + ecc_key key; + byte digest[32]; + byte sig[128]; + word32 sigSz = 0; + + /* DER ECDSA signature with a 40-byte R and 32-byte S: R exceeds the + * P-256 key size to drive the keySz - rLen offset underflow path */ + sig[sigSz++] = 0x30; + sig[sigSz++] = 0x4C; + sig[sigSz++] = 0x02; + sig[sigSz++] = 0x28; + for (i = 0; i < 40; i++) + sig[sigSz++] = 0x11; + sig[sigSz++] = 0x02; + sig[sigSz++] = 0x20; + for (i = 0; i < 32; i++) + sig[sigSz++] = 0x22; + + XMEMSET(digest, 0x33, sizeof(digest)); + XMEMSET(&tpmCtx, 0, sizeof(tpmCtx)); + + rc = wolfTPM2_Init(&dev, TPM2_IoCb, NULL); + AssertIntEQ(rc, 0); + tpmCtx.dev = &dev; + + rc = wc_ecc_init(&key); + AssertIntEQ(rc, 0); + rc = wc_ecc_make_key_ex(wolfTPM2_GetRng(&dev), 32, &key, ECC_SECP256R1); + AssertIntEQ(rc, 0); + + XMEMSET(&info, 0, sizeof(info)); + info.algo_type = WC_ALGO_TYPE_PK; + info.pk.type = WC_PK_TYPE_ECDSA_VERIFY; + info.pk.eccverify.sig = sig; + info.pk.eccverify.siglen = sigSz; + info.pk.eccverify.hash = digest; + info.pk.eccverify.hashlen = (word32)sizeof(digest); + info.pk.eccverify.res = &verifyRes; + info.pk.eccverify.key = &key; + + /* oversized R must be caught before loading the key into the TPM */ + rc = wolfTPM2_CryptoDevCb(INVALID_DEVID, &info, &tpmCtx); + AssertIntEQ(rc, CRYPTOCB_UNAVAILABLE); + + wc_ecc_free(&key); + wolfTPM2_Cleanup(&dev); + + printf("Test TPM Wrapper: %-40s %s\n", "CryptoDevCb ECC oversized R/S:", + rc == CRYPTOCB_UNAVAILABLE ? "Passed" : "Failed"); +#endif +} + #if !defined(WOLFTPM2_NO_WOLFCRYPT) && defined(HAVE_ECC) && \ !defined(WOLFTPM2_NO_ASN) #define FLAGS_USE_WOLFCRYPT (1 << 0) @@ -6162,6 +6225,7 @@ int unit_tests(int argc, char *argv[]) test_GetAlgId(); test_wolfTPM2_ReadPublicKey(); test_wolfTPM2_CSR(); + test_wolfTPM2_CryptoDevCb_EccVerifyOversizedRS(); #if !defined(WOLFTPM2_NO_WOLFCRYPT) && defined(WOLFTPM2_PEM_DECODE) && \ !defined(NO_RSA) test_wolfTPM_ImportPublicKey(); From 71d0b3d3f1e526e1776e599d987322a55adbec4f Mon Sep 17 00:00:00 2001 From: aidan garske Date: Mon, 13 Jul 2026 11:40:50 -0700 Subject: [PATCH 02/27] F-6839 - Fix dead error handling in TPM2_ASN_DecodeX509Cert --- src/tpm2_asn.c | 38 +++++++++++++++++++------------------- tests/unit_tests.c | 26 ++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/src/tpm2_asn.c b/src/tpm2_asn.c index 8faaa8de..3ba44228 100644 --- a/src/tpm2_asn.c +++ b/src/tpm2_asn.c @@ -93,7 +93,7 @@ int TPM2_ASN_GetLength(const uint8_t* input, word32* inOutIdx, int* len, \param inOutIdx Current position in buffer, updated to new position \param len Decoded length value \param maxIdx Maximum allowed index in buffer - \return Length on success, TPM_RC_VALUE on tag mismatch, TPM_RC_INSUFFICIENT on buffer error + \return 0 on success, TPM_RC_VALUE on tag mismatch, TPM_RC_INSUFFICIENT on buffer error */ static int TPM2_ASN_GetHeader(const uint8_t* input, byte tag, word32* inOutIdx, int* len, word32 maxIdx) @@ -114,7 +114,7 @@ static int TPM2_ASN_GetHeader(const uint8_t* input, byte tag, word32* inOutIdx, *len = length; *inOutIdx = idx; - return length; + return 0; } int TPM2_ASN_DecodeTag(const uint8_t* input, int inputSz, @@ -171,7 +171,7 @@ int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, } /* Store certificate location */ - if (rc >= 0) { + if (rc == 0) { x509->certBegin = idx; x509->cert = &input[idx]; @@ -180,7 +180,7 @@ int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, &idx, &cert_len, inputSz); } - if (rc >= 0) { + if (rc == 0) { x509->certSz = cert_len + (idx - x509->certBegin); /* Decode version */ @@ -188,27 +188,27 @@ int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, &idx, &len, inputSz); } - if (rc >= 0) { + if (rc == 0) { if (len <= 0 || idx >= (word32)inputSz) { rc = TPM_RC_VALUE; } } - if (rc >= 0) { + if (rc == 0) { /* check version tag is INTEGER */ if (input[idx] != TPM2_ASN_INTEGER) { rc = TPM_RC_VALUE; } } - if (rc >= 0) { + if (rc == 0) { idx += len; /* skip version */ /* Skip serial number */ rc = TPM2_ASN_GetHeader(input, TPM2_ASN_INTEGER, &idx, &len, inputSz); } - if (rc >= 0) { + if (rc == 0) { idx += len; /* skip serial */ /* Skip algorithm identifier */ @@ -216,7 +216,7 @@ int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, &idx, &len, inputSz); } - if (rc >= 0) { + if (rc == 0) { idx += len; /* skip signature oid */ /* Skip issuer */ @@ -224,7 +224,7 @@ int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, &idx, &len, inputSz); } - if (rc >= 0) { + if (rc == 0) { idx += len; /* skip issuer */ /* Skip validity */ @@ -232,7 +232,7 @@ int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, &idx, &len, inputSz); } - if (rc >= 0) { + if (rc == 0) { idx += len; /* skip validity */ /* Skip subject */ @@ -240,24 +240,24 @@ int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, &idx, &len, inputSz); } - if (rc >= 0) { + if (rc == 0) { idx += len; /* skip subject */ /* subject public key info */ rc = TPM2_ASN_GetHeader(input, TPM2_ASN_SEQUENCE | TPM2_ASN_CONSTRUCTED, &idx, &len, inputSz); } - if (rc >= 0) { + if (rc == 0) { /* cert - subject public key alg oid */ rc = TPM2_ASN_GetHeader(input, TPM2_ASN_SEQUENCE | TPM2_ASN_CONSTRUCTED, &idx, &len, inputSz); } - if (rc >= 0) { + if (rc == 0) { idx += len; /* skip alg oid */ /* Get public key */ rc = TPM2_ASN_GetHeader(input, TPM2_ASN_BIT_STRING, &idx, &pubkey_len, inputSz); } - if (rc >= 0) { + if (rc == 0) { /* skip leading zero for bit string */ if (pubkey_len > 0 && input[idx] == 0x00) { idx++; @@ -272,26 +272,26 @@ int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, &idx, &len, inputSz); } - if (rc >= 0) { + if (rc == 0) { /* signature oid */ rc = TPM2_ASN_GetHeader(input, TPM2_ASN_OBJECT_ID, &idx, &len, inputSz); } - if (rc >= 0) { + if (rc == 0) { idx += len; /* skip oid */ /* Skip signature algorithm parameters */ rc = TPM2_ASN_GetHeader(input, TPM2_ASN_TAG_NULL, &idx, &len, inputSz); } - if (rc >= 0) { + if (rc == 0) { idx += len; /* skip tag */ /* Get signature */ rc = TPM2_ASN_GetHeader(input, TPM2_ASN_BIT_STRING, &idx, &sig_len, inputSz); } - if (rc >= 0) { + if (rc == 0) { /* skip leading zero for bit string */ if (sig_len > 0 && input[idx] == 0x00) { idx++; diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 4ac6b701..809d3e58 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -4091,6 +4091,31 @@ static void test_wolfTPM2_CryptoDevCb_EccVerifyOversizedRS(void) #endif } +static void test_TPM2_ASN_DecodeX509Cert_Errors(void) +{ +#if !defined(WOLFTPM2_NO_WRAPPER) && !defined(WOLFTPM2_NO_ASN) + int rc; + DecodedX509 x509; + byte garbage[16]; + + XMEMSET(&x509, 0, sizeof(x509)); + XMEMSET(garbage, 0xFF, sizeof(garbage)); + + /* NULL arguments must be rejected, not dereferenced */ + rc = TPM2_ASN_DecodeX509Cert(NULL, 0, &x509); + AssertIntNE(rc, 0); + rc = TPM2_ASN_DecodeX509Cert(garbage, (int)sizeof(garbage), NULL); + AssertIntNE(rc, 0); + + /* malformed input must not report success */ + rc = TPM2_ASN_DecodeX509Cert(garbage, (int)sizeof(garbage), &x509); + AssertIntNE(rc, 0); + + printf("Test TPM Wrapper: %-40s %s\n", "ASN DecodeX509Cert errors:", + rc != 0 ? "Passed" : "Failed"); +#endif +} + #if !defined(WOLFTPM2_NO_WOLFCRYPT) && defined(HAVE_ECC) && \ !defined(WOLFTPM2_NO_ASN) #define FLAGS_USE_WOLFCRYPT (1 << 0) @@ -6226,6 +6251,7 @@ int unit_tests(int argc, char *argv[]) test_wolfTPM2_ReadPublicKey(); test_wolfTPM2_CSR(); test_wolfTPM2_CryptoDevCb_EccVerifyOversizedRS(); + test_TPM2_ASN_DecodeX509Cert_Errors(); #if !defined(WOLFTPM2_NO_WOLFCRYPT) && defined(WOLFTPM2_PEM_DECODE) && \ !defined(NO_RSA) test_wolfTPM_ImportPublicKey(); From 46c906ed2a2bf9b271ad87b11a4d46f04977f26c Mon Sep 17 00:00:00 2001 From: aidan garske Date: Mon, 13 Jul 2026 11:41:31 -0700 Subject: [PATCH 03/27] F-6840 - Report tag mismatch from TPM2_ASN_DecodeTag --- src/tpm2_asn.c | 3 +-- tests/unit_tests.c | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/tpm2_asn.c b/src/tpm2_asn.c index 3ba44228..de08915c 100644 --- a/src/tpm2_asn.c +++ b/src/tpm2_asn.c @@ -122,9 +122,8 @@ int TPM2_ASN_DecodeTag(const uint8_t* input, int inputSz, { word32 idx = *inOutIdx; int rc = TPM2_ASN_GetHeader(input, tag, &idx, tag_len, inputSz); - if (rc >= 0) { + if (rc == 0) { *inOutIdx = idx; - rc = 0; } return rc; } diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 809d3e58..791bf409 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -4116,6 +4116,28 @@ static void test_TPM2_ASN_DecodeX509Cert_Errors(void) #endif } +static void test_TPM2_ASN_DecodeTag_Errors(void) +{ +#if !defined(WOLFTPM2_NO_WRAPPER) && !defined(WOLFTPM2_NO_ASN) + int rc, idx, tagLen; + byte buf[4]; + + buf[0] = 0x30; buf[1] = 0x02; buf[2] = 0x00; buf[3] = 0x00; + + idx = 0; + rc = TPM2_ASN_DecodeTag(buf, (int)sizeof(buf), &idx, &tagLen, 0x30); + AssertIntEQ(rc, 0); + + /* wrong expected tag must be reported, not accepted as success */ + idx = 0; + rc = TPM2_ASN_DecodeTag(buf, (int)sizeof(buf), &idx, &tagLen, 0x02); + AssertIntNE(rc, 0); + + printf("Test TPM Wrapper: %-40s %s\n", "ASN DecodeTag tag mismatch:", + rc != 0 ? "Passed" : "Failed"); +#endif +} + #if !defined(WOLFTPM2_NO_WOLFCRYPT) && defined(HAVE_ECC) && \ !defined(WOLFTPM2_NO_ASN) #define FLAGS_USE_WOLFCRYPT (1 << 0) @@ -6252,6 +6274,7 @@ int unit_tests(int argc, char *argv[]) test_wolfTPM2_CSR(); test_wolfTPM2_CryptoDevCb_EccVerifyOversizedRS(); test_TPM2_ASN_DecodeX509Cert_Errors(); + test_TPM2_ASN_DecodeTag_Errors(); #if !defined(WOLFTPM2_NO_WOLFCRYPT) && defined(WOLFTPM2_PEM_DECODE) && \ !defined(NO_RSA) test_wolfTPM_ImportPublicKey(); From 7a6519ba1598c74dfb3da1012ee232055913f933 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Mon, 13 Jul 2026 11:42:21 -0700 Subject: [PATCH 04/27] F-6848 - Scrub hash context in TPM2_CalcCpHash and TPM2_CalcRpHash --- src/tpm2_param_enc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tpm2_param_enc.c b/src/tpm2_param_enc.c index 8be69d77..72d8d43d 100644 --- a/src/tpm2_param_enc.c +++ b/src/tpm2_param_enc.c @@ -389,6 +389,7 @@ int TPM2_CalcCpHash(TPMI_ALG_HASH authHash, TPM_CC cmdCode, rc = wc_HashFinal(&hash_ctx, hashType, hash->buffer); wc_HashFree(&hash_ctx, hashType); + TPM2_ForceZero(&hash_ctx, sizeof(hash_ctx)); } #ifdef WOLFTPM_DEBUG_VERBOSE @@ -435,6 +436,7 @@ int TPM2_CalcRpHash(TPMI_ALG_HASH authHash, rc = wc_HashFinal(&hash_ctx, hashType, hash->buffer); wc_HashFree(&hash_ctx, hashType); + TPM2_ForceZero(&hash_ctx, sizeof(hash_ctx)); } #ifdef WOLFTPM_DEBUG_VERBOSE From 378c181283bdaa6b9ee6a6b602482aa5e70a2624 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Mon, 13 Jul 2026 11:44:34 -0700 Subject: [PATCH 05/27] F-6844 - Clamp TPM2B sizes in TPM2_Packet_AppendPublicArea --- src/tpm2_packet.c | 22 ++++++++++++++++++++++ tests/unit_tests.c | 26 ++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/tpm2_packet.c b/src/tpm2_packet.c index e4809fb3..9bee4897 100644 --- a/src/tpm2_packet.c +++ b/src/tpm2_packet.c @@ -1157,6 +1157,8 @@ void TPM2_Packet_AppendPublicArea(TPM2_Packet* packet, TPMT_PUBLIC* publicArea) TPM2_Packet_AppendU16(packet, publicArea->type); TPM2_Packet_AppendU16(packet, publicArea->nameAlg); TPM2_Packet_AppendU32(packet, publicArea->objectAttributes); + if (publicArea->authPolicy.size > sizeof(publicArea->authPolicy.buffer)) + publicArea->authPolicy.size = sizeof(publicArea->authPolicy.buffer); TPM2_Packet_AppendU16(packet, publicArea->authPolicy.size); TPM2_Packet_AppendBytes(packet, publicArea->authPolicy.buffer, publicArea->authPolicy.size); @@ -1166,16 +1168,28 @@ void TPM2_Packet_AppendPublicArea(TPM2_Packet* packet, TPMT_PUBLIC* publicArea) switch (publicArea->type) { case TPM_ALG_KEYEDHASH: + if (publicArea->unique.keyedHash.size > + sizeof(publicArea->unique.keyedHash.buffer)) + publicArea->unique.keyedHash.size = + sizeof(publicArea->unique.keyedHash.buffer); TPM2_Packet_AppendU16(packet, publicArea->unique.keyedHash.size); TPM2_Packet_AppendBytes(packet, publicArea->unique.keyedHash.buffer, publicArea->unique.keyedHash.size); break; case TPM_ALG_SYMCIPHER: + if (publicArea->unique.sym.size > + sizeof(publicArea->unique.sym.buffer)) + publicArea->unique.sym.size = + sizeof(publicArea->unique.sym.buffer); TPM2_Packet_AppendU16(packet, publicArea->unique.sym.size); TPM2_Packet_AppendBytes(packet, publicArea->unique.sym.buffer, publicArea->unique.sym.size); break; case TPM_ALG_RSA: + if (publicArea->unique.rsa.size > + sizeof(publicArea->unique.rsa.buffer)) + publicArea->unique.rsa.size = + sizeof(publicArea->unique.rsa.buffer); TPM2_Packet_AppendU16(packet, publicArea->unique.rsa.size); TPM2_Packet_AppendBytes(packet, publicArea->unique.rsa.buffer, publicArea->unique.rsa.size); @@ -1186,6 +1200,10 @@ void TPM2_Packet_AppendPublicArea(TPM2_Packet* packet, TPMT_PUBLIC* publicArea) #ifdef WOLFTPM_MLDSA case TPM_ALG_MLDSA: case TPM_ALG_HASH_MLDSA: + if (publicArea->unique.mldsa.size > + sizeof(publicArea->unique.mldsa.buffer)) + publicArea->unique.mldsa.size = + sizeof(publicArea->unique.mldsa.buffer); TPM2_Packet_AppendU16(packet, publicArea->unique.mldsa.size); TPM2_Packet_AppendBytes(packet, publicArea->unique.mldsa.buffer, publicArea->unique.mldsa.size); @@ -1193,6 +1211,10 @@ void TPM2_Packet_AppendPublicArea(TPM2_Packet* packet, TPMT_PUBLIC* publicArea) #endif /* WOLFTPM_MLDSA */ #ifdef WOLFTPM_MLKEM case TPM_ALG_MLKEM: + if (publicArea->unique.mlkem.size > + sizeof(publicArea->unique.mlkem.buffer)) + publicArea->unique.mlkem.size = + sizeof(publicArea->unique.mlkem.buffer); TPM2_Packet_AppendU16(packet, publicArea->unique.mlkem.size); TPM2_Packet_AppendBytes(packet, publicArea->unique.mlkem.buffer, publicArea->unique.mlkem.size); diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 791bf409..58c14093 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -3873,6 +3873,31 @@ static void test_TPM2_AppendSensitive_Clamp(void) printf("Test TPM2: %-40s Passed\n", "AppendSensitive clamp:"); } +static void test_TPM2_AppendPublic_Clamp(void) +{ + TPM2_Packet packet; + byte buf[1024]; + TPM2B_PUBLIC pub; + word16 policyCap, rsaCap; + + policyCap = (word16)sizeof(pub.publicArea.authPolicy.buffer); + rsaCap = (word16)sizeof(pub.publicArea.unique.rsa.buffer); + + XMEMSET(&pub, 0, sizeof(pub)); + pub.publicArea.type = TPM_ALG_RSA; + pub.publicArea.nameAlg = TPM_ALG_SHA256; + pub.publicArea.authPolicy.size = policyCap + 100; + pub.publicArea.unique.rsa.size = rsaCap + 100; + XMEMSET(&packet, 0, sizeof(packet)); + packet.buf = buf; + packet.size = sizeof(buf); + TPM2_Packet_AppendPublic(&packet, &pub); + AssertIntEQ(pub.publicArea.authPolicy.size, policyCap); + AssertIntEQ(pub.publicArea.unique.rsa.size, rsaCap); + + printf("Test TPM2: %-40s Passed\n", "AppendPublic clamp:"); +} + /* Roundtrip a maximum-size inner payload (size == buffer capacity) so the * parse-side ParseU16Buf clamp branch is exercised with valid data. */ static void test_TPM2_Sensitive_MaxRoundtrip(void) @@ -6266,6 +6291,7 @@ int unit_tests(int argc, char *argv[]) test_TPM2_TIS_ValidateRspSz(); test_TPM2_ParsePublic_EmptyClears(); test_TPM2_AppendSensitive_Clamp(); + test_TPM2_AppendPublic_Clamp(); test_TPM2_Sensitive_MaxRoundtrip(); test_KeySealTemplate(); test_SealAndKeyedHash_Boundaries(); From 2b68fa99bbadb7b49caa7f0f77bbc931ed9690eb Mon Sep 17 00:00:00 2001 From: aidan garske Date: Mon, 13 Jul 2026 11:46:51 -0700 Subject: [PATCH 06/27] F-6842 - Add XOR param-enc mask-size boundary test --- tests/unit_tests.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 58c14093..6421ee33 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -1923,6 +1923,44 @@ static void test_TPM2_ParamEnc_XOR_Vector(void) #endif } +#ifndef TPM2_XOR_MASK_MAX +#define TPM2_XOR_MASK_MAX 1280 +#endif +static void test_TPM2_ParamEnc_XOR_MaskBoundary(void) +{ +#ifndef WOLFTPM2_NO_WOLFCRYPT + int rc; + TPMI_ALG_HASH authHash = TPM_ALG_SHA256; + TPM2B_AUTH sessKey; + TPM2B_NONCE nonceCaller, nonceTPM; + byte data[TPM2_XOR_MASK_MAX + 1]; + + sessKey.size = TPM_SHA256_DIGEST_SIZE; + XMEMSET(sessKey.buffer, 0xCC, sessKey.size); + nonceCaller.size = TPM_SHA256_DIGEST_SIZE; + XMEMSET(nonceCaller.buffer, 0x11, nonceCaller.size); + nonceTPM.size = TPM_SHA256_DIGEST_SIZE; + XMEMSET(nonceTPM.buffer, 0x22, nonceTPM.size); + XMEMSET(data, 0, sizeof(data)); + + /* exactly at capacity must succeed */ + rc = TPM2_ParamEnc_XOR(authHash, sessKey.buffer, sessKey.size, + nonceCaller.buffer, nonceCaller.size, + nonceTPM.buffer, nonceTPM.size, + data, TPM2_XOR_MASK_MAX); + AssertIntEQ(TPM_RC_SUCCESS, rc); + + /* one byte past capacity must be rejected */ + rc = TPM2_ParamEnc_XOR(authHash, sessKey.buffer, sessKey.size, + nonceCaller.buffer, nonceCaller.size, + nonceTPM.buffer, nonceTPM.size, + data, TPM2_XOR_MASK_MAX + 1); + AssertIntEQ(BUFFER_E, rc); + + printf("Test TPM Wrapper: %-40s Passed\n", "ParamEnc_XOR mask boundary:"); +#endif +} + static void test_TPM2_ParamEnc_AESCFB_Vector(void) { #if !defined(WOLFTPM2_NO_WOLFCRYPT) && defined(WOLFSSL_AES_CFB) @@ -6251,6 +6289,7 @@ int unit_tests(int argc, char *argv[]) test_TPM2_ResponseHmacVerification(); test_TPM2_CalcHmac(); test_TPM2_ParamEnc_XOR_Vector(); + test_TPM2_ParamEnc_XOR_MaskBoundary(); test_TPM2_ParamEnc_AESCFB_Vector(); test_TPM2_ParamEnc_AESCFB_KAT(); test_TPM2_ParamDec_XOR_Roundtrip(); From c9ec8031b97452d5ddd5e039ab302ab1c1bf578b Mon Sep 17 00:00:00 2001 From: aidan garske Date: Mon, 13 Jul 2026 11:47:15 -0700 Subject: [PATCH 07/27] F-6843 - Add AES-CFB param-enc key-size boundary test --- tests/unit_tests.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 6421ee33..cd5ae1a3 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -2008,6 +2008,35 @@ static void test_TPM2_ParamEnc_AESCFB_Vector(void) #endif } +static void test_TPM2_ParamEnc_AESCFB_KeyBoundary(void) +{ +#if !defined(WOLFTPM2_NO_WOLFCRYPT) && defined(WOLFSSL_AES_CFB) + int rc; + TPMI_ALG_HASH authHash = TPM_ALG_SHA256; + TPM2B_AUTH sessKey; + TPM2B_NONCE nonceCaller, nonceTPM; + byte data[32]; + + sessKey.size = TPM_SHA256_DIGEST_SIZE; + XMEMSET(sessKey.buffer, 0xDD, sessKey.size); + nonceCaller.size = TPM_SHA256_DIGEST_SIZE; + XMEMSET(nonceCaller.buffer, 0x33, nonceCaller.size); + nonceTPM.size = TPM_SHA256_DIGEST_SIZE; + XMEMSET(nonceTPM.buffer, 0x44, nonceTPM.size); + XMEMSET(data, 0, sizeof(data)); + + /* keyBits above 256 (symKeySz > 32) must be rejected, not overflow symKey */ + rc = TPM2_ParamEnc_AESCFB(authHash, 512, + sessKey.buffer, sessKey.size, + nonceCaller.buffer, nonceCaller.size, + nonceTPM.buffer, nonceTPM.size, + data, sizeof(data), 1); + AssertIntEQ(BUFFER_E, rc); + + printf("Test TPM Wrapper: %-40s Passed\n", "ParamEnc_AESCFB key boundary:"); +#endif +} + /* Known-answer test cross-checking TPM2_ParamEnc_AESCFB against an * independent KDFa + AES-CFB reference built from wolfCrypt primitives. * The pure round-trip test above cannot detect mutations that affect @@ -6291,6 +6320,7 @@ int unit_tests(int argc, char *argv[]) test_TPM2_ParamEnc_XOR_Vector(); test_TPM2_ParamEnc_XOR_MaskBoundary(); test_TPM2_ParamEnc_AESCFB_Vector(); + test_TPM2_ParamEnc_AESCFB_KeyBoundary(); test_TPM2_ParamEnc_AESCFB_KAT(); test_TPM2_ParamDec_XOR_Roundtrip(); test_TPM2_ParamDec_AESCFB_Roundtrip(); From a78f99f7b0a33c224a8e49bef0f99bb4cb9c0d3b Mon Sep 17 00:00:00 2001 From: aidan garske Date: Mon, 13 Jul 2026 11:48:23 -0700 Subject: [PATCH 08/27] F-6849 - Document 32-byte ML-KEM K in fwTPM seed KDF --- src/fwtpm/fwtpm_crypto.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/fwtpm/fwtpm_crypto.c b/src/fwtpm/fwtpm_crypto.c index 1ba56fde..eef70431 100644 --- a/src/fwtpm/fwtpm_crypto.c +++ b/src/fwtpm/fwtpm_crypto.c @@ -2592,6 +2592,7 @@ TPM_RC FwDecryptSeed(FWTPM_CTX* ctx, encSeedBuf, encSeedSz, &sharedK); } if (rc == 0) { + /* K is 32-byte ML-KEM secret per FIPS 203 (Part 1 47.4 errata) */ int kdfRc = TPM2_KDFa_ex(nameAlg, sharedK.buffer, sharedK.size, kdfLabel, encSeedBuf, (UINT32)encSeedSz, @@ -2845,6 +2846,7 @@ TPM_RC FwEncryptSeed(FWTPM_CTX* ctx, rc = TPM_RC_SIZE; } if (rc == 0) { + /* K is 32-byte ML-KEM secret per FIPS 203 (Part 1 47.4 errata) */ int kdfRc = TPM2_KDFa_ex(nameAlg, sharedK.buffer, sharedK.size, kdfLabel, ciphertext->buffer, (UINT32)ciphertext->size, From e770f3b99acfee431f889f4369d75dd37aa09702 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Mon, 13 Jul 2026 11:51:17 -0700 Subject: [PATCH 09/27] F-6845 - Add field-level roundtrip test for RSA and ECC TPMT_PUBLIC --- tests/unit_tests.c | 106 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/tests/unit_tests.c b/tests/unit_tests.c index cd5ae1a3..05d53ad8 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -3531,6 +3531,111 @@ static void test_TPM2_Signature_PQC_Serialize(void) * Hash-ML-DSA share the unique.mldsa arm (Part 2 Table 225 note); * ML-KEM has its own unique.mlkem arm. Verifies every round-tripped * field for the three key types. */ +static void test_TPM2_Public_RsaEcc_Roundtrip(void) +{ +#if !defined(WOLFTPM2_NO_WOLFCRYPT) + int rc, sz; + byte buf[sizeof(TPM2B_PUBLIC)]; + TPM2B_PUBLIC pubIn, pubOut; + const byte uniqueBytes[8] = { + 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22 + }; + + /* RSA with AES-128-CFB symmetric wrapper (exercises the AES mode field) + * and an RSASSA-SHA256 scheme */ + XMEMSET(&pubIn, 0, sizeof(pubIn)); + pubIn.publicArea.type = TPM_ALG_RSA; + pubIn.publicArea.nameAlg = TPM_ALG_SHA256; + pubIn.publicArea.objectAttributes = TPMA_OBJECT_sign; + pubIn.publicArea.parameters.rsaDetail.symmetric.algorithm = TPM_ALG_AES; + pubIn.publicArea.parameters.rsaDetail.symmetric.keyBits.aes = 128; + pubIn.publicArea.parameters.rsaDetail.symmetric.mode.aes = TPM_ALG_CFB; + pubIn.publicArea.parameters.rsaDetail.scheme.scheme = TPM_ALG_RSASSA; + pubIn.publicArea.parameters.rsaDetail.scheme.details.rsassa.hashAlg = + TPM_ALG_SHA256; + pubIn.publicArea.parameters.rsaDetail.keyBits = 2048; + pubIn.publicArea.parameters.rsaDetail.exponent = 0x10001; + pubIn.publicArea.unique.rsa.size = sizeof(uniqueBytes); + XMEMCPY(pubIn.publicArea.unique.rsa.buffer, uniqueBytes, + sizeof(uniqueBytes)); + + XMEMSET(buf, 0, sizeof(buf)); + sz = 0; + rc = TPM2_AppendPublic(buf, (word32)sizeof(buf), &sz, &pubIn); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntGT(sz, 0); + + XMEMSET(&pubOut, 0, sizeof(pubOut)); + rc = TPM2_ParsePublic(&pubOut, buf, (word32)sz, &sz); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(pubOut.publicArea.type, TPM_ALG_RSA); + AssertIntEQ(pubOut.publicArea.nameAlg, TPM_ALG_SHA256); + AssertIntEQ(pubOut.publicArea.parameters.rsaDetail.symmetric.algorithm, + TPM_ALG_AES); + AssertIntEQ(pubOut.publicArea.parameters.rsaDetail.symmetric.keyBits.aes, + 128); + AssertIntEQ(pubOut.publicArea.parameters.rsaDetail.symmetric.mode.aes, + TPM_ALG_CFB); + AssertIntEQ(pubOut.publicArea.parameters.rsaDetail.scheme.scheme, + TPM_ALG_RSASSA); + AssertIntEQ( + pubOut.publicArea.parameters.rsaDetail.scheme.details.rsassa.hashAlg, + TPM_ALG_SHA256); + AssertIntEQ(pubOut.publicArea.parameters.rsaDetail.keyBits, 2048); + AssertIntEQ((int)pubOut.publicArea.parameters.rsaDetail.exponent, 0x10001); + AssertIntEQ(pubOut.publicArea.unique.rsa.size, sizeof(uniqueBytes)); + AssertIntEQ(XMEMCMP(pubOut.publicArea.unique.rsa.buffer, uniqueBytes, + sizeof(uniqueBytes)), 0); + + /* ECC P-256 with ECDSA-SHA256 scheme and NULL symmetric/kdf */ + XMEMSET(&pubIn, 0, sizeof(pubIn)); + pubIn.publicArea.type = TPM_ALG_ECC; + pubIn.publicArea.nameAlg = TPM_ALG_SHA256; + pubIn.publicArea.objectAttributes = TPMA_OBJECT_sign; + pubIn.publicArea.parameters.eccDetail.symmetric.algorithm = TPM_ALG_NULL; + pubIn.publicArea.parameters.eccDetail.scheme.scheme = TPM_ALG_ECDSA; + pubIn.publicArea.parameters.eccDetail.scheme.details.ecdsa.hashAlg = + TPM_ALG_SHA256; + pubIn.publicArea.parameters.eccDetail.curveID = TPM_ECC_NIST_P256; + pubIn.publicArea.parameters.eccDetail.kdf.scheme = TPM_ALG_NULL; + pubIn.publicArea.unique.ecc.x.size = sizeof(uniqueBytes); + XMEMCPY(pubIn.publicArea.unique.ecc.x.buffer, uniqueBytes, + sizeof(uniqueBytes)); + pubIn.publicArea.unique.ecc.y.size = sizeof(uniqueBytes); + XMEMCPY(pubIn.publicArea.unique.ecc.y.buffer, uniqueBytes, + sizeof(uniqueBytes)); + + XMEMSET(buf, 0, sizeof(buf)); + sz = 0; + rc = TPM2_AppendPublic(buf, (word32)sizeof(buf), &sz, &pubIn); + AssertIntEQ(rc, TPM_RC_SUCCESS); + + XMEMSET(&pubOut, 0, sizeof(pubOut)); + rc = TPM2_ParsePublic(&pubOut, buf, (word32)sz, &sz); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(pubOut.publicArea.type, TPM_ALG_ECC); + AssertIntEQ(pubOut.publicArea.parameters.eccDetail.symmetric.algorithm, + TPM_ALG_NULL); + AssertIntEQ(pubOut.publicArea.parameters.eccDetail.scheme.scheme, + TPM_ALG_ECDSA); + AssertIntEQ( + pubOut.publicArea.parameters.eccDetail.scheme.details.ecdsa.hashAlg, + TPM_ALG_SHA256); + AssertIntEQ(pubOut.publicArea.parameters.eccDetail.curveID, + TPM_ECC_NIST_P256); + AssertIntEQ(pubOut.publicArea.parameters.eccDetail.kdf.scheme, + TPM_ALG_NULL); + AssertIntEQ(pubOut.publicArea.unique.ecc.x.size, sizeof(uniqueBytes)); + AssertIntEQ(XMEMCMP(pubOut.publicArea.unique.ecc.x.buffer, uniqueBytes, + sizeof(uniqueBytes)), 0); + AssertIntEQ(pubOut.publicArea.unique.ecc.y.size, sizeof(uniqueBytes)); + AssertIntEQ(XMEMCMP(pubOut.publicArea.unique.ecc.y.buffer, uniqueBytes, + sizeof(uniqueBytes)), 0); + + printf("Test TPM Wrapper: %-40s Passed\n", "Public RSA/ECC roundtrip:"); +#endif +} + static void test_TPM2_Public_PQC_Roundtrip(void) { int rc, sz; @@ -6354,6 +6459,7 @@ int unit_tests(int argc, char *argv[]) test_TPM2_Signature_EcSchnorrSm2Serialize(); #ifdef WOLFTPM_PQC test_TPM2_Signature_PQC_Serialize(); + test_TPM2_Public_RsaEcc_Roundtrip(); test_TPM2_Public_PQC_Roundtrip(); #endif test_TPM2_Sensitive_Roundtrip(); From 212f425783cdc75a2adf15a0d043cc73262e3a57 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Mon, 13 Jul 2026 12:00:31 -0700 Subject: [PATCH 10/27] F-6841 - Add response HMAC verification test for TPM2_ResponseProcess --- tests/unit_tests.c | 70 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 05d53ad8..c815e8d8 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -3014,6 +3014,75 @@ static void test_TPM2_ResponseProcess_ParamSizeOverflow(void) printf("Test TPM Wrapper:\tResponseProcess paramSize overflow:\tPassed\n"); } +static void test_TPM2_ResponseProcess_HmacVerify(void) +{ +#if !defined(WOLFTPM2_NO_WOLFCRYPT) && !defined(NO_HMAC) + TPM2_CTX ctx; + TPM2_AUTH_SESSION session[1]; + TPM2_Packet packet; + CmdInfo_t info; + TPM2B_DIGEST rpHash; + TPM2B_AUTH expHmac; + byte buf[128]; + int rc; + TPM_CC cmdCode = 0x17F; + UINT16 hmacSz = 32; + UINT32 paramSz = 4; + UINT32 pos, hmacOff, respSz; + byte attr = 0x01; + + XMEMSET(&ctx, 0, sizeof(ctx)); + XMEMSET(session, 0, sizeof(session)); + XMEMSET(&info, 0, sizeof(info)); + XMEMSET(buf, 0, sizeof(buf)); + + /* HMAC session with a known auth value and nonces */ + session[0].sessionHandle = HMAC_SESSION_FIRST; + session[0].authHash = TPM_ALG_SHA256; + session[0].auth.size = 4; + XMEMSET(session[0].auth.buffer, 0xA5, 4); + session[0].nonceCaller.size = 32; + XMEMSET(session[0].nonceCaller.buffer, 0x5C, 32); + session[0].nonceTPM.size = 32; + XMEMSET(session[0].nonceTPM.buffer, 0xC5, 32); + ctx.session = session; + info.authCnt = 1; + + /* header + paramSize(U32) + params + auth area (nonce, attr, hmac) */ + pos = TPM2_HEADER_SIZE; + buf[pos++] = 0; buf[pos++] = 0; buf[pos++] = 0; buf[pos++] = (byte)paramSz; + buf[pos++] = 0xDE; buf[pos++] = 0xAD; buf[pos++] = 0xBE; buf[pos++] = 0xEF; + buf[pos++] = 0; buf[pos++] = 0; /* nonce.size = 0 */ + buf[pos++] = attr; /* sessionAttributes */ + buf[pos++] = (byte)(hmacSz >> 8); buf[pos++] = (byte)(hmacSz & 0xFF); + hmacOff = pos; + pos += hmacSz; + respSz = pos; + + rc = TPM2_CalcRpHash(TPM_ALG_SHA256, cmdCode, &buf[TPM2_HEADER_SIZE + 4], + paramSz, &rpHash); + AssertIntEQ(rc, TPM_RC_SUCCESS); + XMEMSET(&expHmac, 0, sizeof(expHmac)); + rc = TPM2_CalcHmac(TPM_ALG_SHA256, &session[0].auth, &rpHash, + &session[0].nonceTPM, &session[0].nonceCaller, attr, &expHmac); + AssertIntEQ(rc, TPM_RC_SUCCESS); + XMEMCPY(&buf[hmacOff], expHmac.buffer, hmacSz); + + /* untampered response HMAC must verify */ + packet.buf = buf; packet.pos = 0; packet.size = (int)respSz; + rc = TPM2_ResponseProcess(&ctx, &packet, &info, cmdCode, respSz); + AssertIntEQ(rc, TPM_RC_SUCCESS); + + /* flipping one HMAC byte must be detected */ + buf[hmacOff] ^= 0xFF; + packet.buf = buf; packet.pos = 0; packet.size = (int)respSz; + rc = TPM2_ResponseProcess(&ctx, &packet, &info, cmdCode, respSz); + AssertIntEQ(rc, TPM_RC_HMAC); + + printf("Test TPM Wrapper:\tResponseProcess HMAC verify:\tPassed\n"); +#endif +} + /* wolfTPM2_NVCreateAuthPolicy must derive nameAlg from authPolicySz so * the policy digest hash matches the index's nameAlg. Bug-mode hardcoded * SHA-256 nameAlg, which made SHA-384/SHA-512 policies unsatisfiable. @@ -6452,6 +6521,7 @@ int unit_tests(int argc, char *argv[]) test_TPM2_Packet_RetryRestore(); #endif test_TPM2_ResponseProcess_ParamSizeOverflow(); + test_TPM2_ResponseProcess_HmacVerify(); test_wolfTPM2_NVCreateAuthPolicy_NameAlg(); test_wolfTPM2_GetKeyTemplate_KeyedHash_Scheme(); test_wolfTPM2_LoadEccPublicKey_Ex(); From 2bdbf6613a7e0b7332d6953941b5cdcc9a3723de Mon Sep 17 00:00:00 2001 From: aidan garske Date: Mon, 13 Jul 2026 12:16:00 -0700 Subject: [PATCH 11/27] F-6841 - Expose TPM2_CalcRpHash to tests via WOLFTPM_TEST_API --- wolftpm/tpm2_param_enc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wolftpm/tpm2_param_enc.h b/wolftpm/tpm2_param_enc.h index 6e0e303f..95619344 100644 --- a/wolftpm/tpm2_param_enc.h +++ b/wolftpm/tpm2_param_enc.h @@ -54,7 +54,7 @@ WOLFTPM_TEST_API int TPM2_CalcHmac(TPMI_ALG_HASH authHash, TPM2B_AUTH* auth, const TPM2B_DIGEST* hash, const TPM2B_NONCE* nonceNew, const TPM2B_NONCE* nonceOld, TPMA_SESSION sessionAttributes, TPM2B_AUTH* hmac); -WOLFTPM_LOCAL int TPM2_CalcRpHash(TPMI_ALG_HASH authHash, +WOLFTPM_TEST_API int TPM2_CalcRpHash(TPMI_ALG_HASH authHash, TPM_CC cmdCode, BYTE* param, UINT32 paramSz, TPM2B_DIGEST* hash); WOLFTPM_LOCAL int TPM2_CalcCpHash(TPMI_ALG_HASH authHash, TPM_CC cmdCode, TPM2B_NAME* name1, TPM2B_NAME* name2, TPM2B_NAME* name3, From 5aa84dda84cf762f3c9df998636049a8f699a07b Mon Sep 17 00:00:00 2001 From: aidan garske Date: Mon, 13 Jul 2026 12:28:50 -0700 Subject: [PATCH 12/27] F-6841 - Exercise response nonceTPM update in HMAC verify test --- tests/unit_tests.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/tests/unit_tests.c b/tests/unit_tests.c index c815e8d8..9f05276a 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -3023,10 +3023,11 @@ static void test_TPM2_ResponseProcess_HmacVerify(void) CmdInfo_t info; TPM2B_DIGEST rpHash; TPM2B_AUTH expHmac; + TPM2B_NONCE nonceTPM; byte buf[128]; - int rc; + int rc, i; TPM_CC cmdCode = 0x17F; - UINT16 hmacSz = 32; + UINT16 hmacSz = 32, nonceSz = 32; UINT32 paramSz = 4; UINT32 pos, hmacOff, respSz; byte attr = 0x01; @@ -3052,26 +3053,32 @@ static void test_TPM2_ResponseProcess_HmacVerify(void) pos = TPM2_HEADER_SIZE; buf[pos++] = 0; buf[pos++] = 0; buf[pos++] = 0; buf[pos++] = (byte)paramSz; buf[pos++] = 0xDE; buf[pos++] = 0xAD; buf[pos++] = 0xBE; buf[pos++] = 0xEF; - buf[pos++] = 0; buf[pos++] = 0; /* nonce.size = 0 */ + buf[pos++] = (byte)(nonceSz >> 8); buf[pos++] = (byte)(nonceSz & 0xFF); + for (i = 0; i < nonceSz; i++) + buf[pos++] = 0x99; /* response nonceTPM */ buf[pos++] = attr; /* sessionAttributes */ buf[pos++] = (byte)(hmacSz >> 8); buf[pos++] = (byte)(hmacSz & 0xFF); hmacOff = pos; pos += hmacSz; respSz = pos; + /* expected HMAC uses the response nonce as nonceTPM */ + nonceTPM.size = nonceSz; + XMEMSET(nonceTPM.buffer, 0x99, nonceSz); rc = TPM2_CalcRpHash(TPM_ALG_SHA256, cmdCode, &buf[TPM2_HEADER_SIZE + 4], paramSz, &rpHash); AssertIntEQ(rc, TPM_RC_SUCCESS); XMEMSET(&expHmac, 0, sizeof(expHmac)); rc = TPM2_CalcHmac(TPM_ALG_SHA256, &session[0].auth, &rpHash, - &session[0].nonceTPM, &session[0].nonceCaller, attr, &expHmac); + &nonceTPM, &session[0].nonceCaller, attr, &expHmac); AssertIntEQ(rc, TPM_RC_SUCCESS); XMEMCPY(&buf[hmacOff], expHmac.buffer, hmacSz); - /* untampered response HMAC must verify */ + /* untampered response HMAC must verify and update nonceTPM */ packet.buf = buf; packet.pos = 0; packet.size = (int)respSz; rc = TPM2_ResponseProcess(&ctx, &packet, &info, cmdCode, respSz); AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(session[0].nonceTPM.buffer[0], 0x99); /* flipping one HMAC byte must be detected */ buf[hmacOff] ^= 0xFF; From 3964e7fd48182328cd487d5713dcb68a57816f7a Mon Sep 17 00:00:00 2001 From: aidan garske Date: Mon, 13 Jul 2026 12:29:08 -0700 Subject: [PATCH 13/27] F-6844 - Clamp ECC point sizes in TPM2_Packet_AppendEccPoint --- src/tpm2_packet.c | 4 ++++ tests/unit_tests.c | 16 +++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/tpm2_packet.c b/src/tpm2_packet.c index 9bee4897..737ba81a 100644 --- a/src/tpm2_packet.c +++ b/src/tpm2_packet.c @@ -740,8 +740,12 @@ void TPM2_Packet_ParseAsymScheme(TPM2_Packet* packet, TPMT_ASYM_SCHEME* scheme) void TPM2_Packet_AppendEccPoint(TPM2_Packet* packet, TPMS_ECC_POINT* point) { + if (point->x.size > sizeof(point->x.buffer)) + point->x.size = sizeof(point->x.buffer); TPM2_Packet_AppendU16(packet, point->x.size); TPM2_Packet_AppendBytes(packet, point->x.buffer, point->x.size); + if (point->y.size > sizeof(point->y.buffer)) + point->y.size = sizeof(point->y.buffer); TPM2_Packet_AppendU16(packet, point->y.size); TPM2_Packet_AppendBytes(packet, point->y.buffer, point->y.size); } diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 9f05276a..d9c1f93c 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -4126,10 +4126,11 @@ static void test_TPM2_AppendPublic_Clamp(void) TPM2_Packet packet; byte buf[1024]; TPM2B_PUBLIC pub; - word16 policyCap, rsaCap; + word16 policyCap, rsaCap, eccCap; policyCap = (word16)sizeof(pub.publicArea.authPolicy.buffer); rsaCap = (word16)sizeof(pub.publicArea.unique.rsa.buffer); + eccCap = (word16)sizeof(pub.publicArea.unique.ecc.x.buffer); XMEMSET(&pub, 0, sizeof(pub)); pub.publicArea.type = TPM_ALG_RSA; @@ -4143,6 +4144,19 @@ static void test_TPM2_AppendPublic_Clamp(void) AssertIntEQ(pub.publicArea.authPolicy.size, policyCap); AssertIntEQ(pub.publicArea.unique.rsa.size, rsaCap); + /* ECC point x/y sizes must clamp on append too */ + XMEMSET(&pub, 0, sizeof(pub)); + pub.publicArea.type = TPM_ALG_ECC; + pub.publicArea.nameAlg = TPM_ALG_SHA256; + pub.publicArea.unique.ecc.x.size = eccCap + 100; + pub.publicArea.unique.ecc.y.size = eccCap + 100; + XMEMSET(&packet, 0, sizeof(packet)); + packet.buf = buf; + packet.size = sizeof(buf); + TPM2_Packet_AppendPublic(&packet, &pub); + AssertIntEQ(pub.publicArea.unique.ecc.x.size, eccCap); + AssertIntEQ(pub.publicArea.unique.ecc.y.size, eccCap); + printf("Test TPM2: %-40s Passed\n", "AppendPublic clamp:"); } From 9188fcd1aa8929e074738cfd2099b125db930a05 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Mon, 13 Jul 2026 12:29:16 -0700 Subject: [PATCH 14/27] F-6845 - Run RSA/ECC public roundtrip test in non-PQC builds --- tests/unit_tests.c | 142 ++++++++++++++++++++++----------------------- 1 file changed, 71 insertions(+), 71 deletions(-) diff --git a/tests/unit_tests.c b/tests/unit_tests.c index d9c1f93c..375e946f 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -3537,76 +3537,6 @@ static void test_TPM2_Signature_EcSchnorrSm2Serialize(void) "Signature ECSCHNORR/SM2 serialize:"); } -#ifdef WOLFTPM_PQC -/* Round-trip the v1.85 PQC arms of TPMT_SIGNATURE through the packet - * marshaler. Pure ML-DSA (Table 217 mldsa arm) is bare TPM2B + bytes — - * no hash field. Hash-ML-DSA prefixes a hashAlg before the TPM2B. The - * tests pin the on-wire byte counts to catch any future drift. */ -static void test_TPM2_Signature_PQC_Serialize(void) -{ - TPM2_Packet packet; - byte buf[256]; - TPMT_SIGNATURE sigIn, sigOut; - const byte sigBytes[16] = { - 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, - 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F - }; - - /* Pure ML-DSA: sigAlg(2) + sigSz(2) + sig(16) = 20 bytes. */ - XMEMSET(&sigIn, 0, sizeof(sigIn)); - sigIn.sigAlg = TPM_ALG_MLDSA; - sigIn.signature.mldsa.size = sizeof(sigBytes); - XMEMCPY(sigIn.signature.mldsa.buffer, sigBytes, sizeof(sigBytes)); - - XMEMSET(buf, 0, sizeof(buf)); - XMEMSET(&packet, 0, sizeof(packet)); - packet.buf = buf; - packet.size = sizeof(buf); - - TPM2_Packet_AppendSignature(&packet, &sigIn); - AssertIntEQ(packet.pos, 2 + 2 + (int)sizeof(sigBytes)); - - packet.pos = 0; - XMEMSET(&sigOut, 0, sizeof(sigOut)); - TPM2_Packet_ParseSignature(&packet, &sigOut); - AssertIntEQ(sigOut.sigAlg, TPM_ALG_MLDSA); - AssertIntEQ(sigOut.signature.mldsa.size, sizeof(sigBytes)); - AssertIntEQ(XMEMCMP(sigOut.signature.mldsa.buffer, - sigBytes, sizeof(sigBytes)), 0); - - /* Hash-ML-DSA: sigAlg(2) + hash(2) + sigSz(2) + sig(16) = 22 bytes. */ - XMEMSET(&sigIn, 0, sizeof(sigIn)); - sigIn.sigAlg = TPM_ALG_HASH_MLDSA; - sigIn.signature.hash_mldsa.hash = TPM_ALG_SHA256; - sigIn.signature.hash_mldsa.signature.size = sizeof(sigBytes); - XMEMCPY(sigIn.signature.hash_mldsa.signature.buffer, - sigBytes, sizeof(sigBytes)); - - XMEMSET(buf, 0, sizeof(buf)); - XMEMSET(&packet, 0, sizeof(packet)); - packet.buf = buf; - packet.size = sizeof(buf); - - TPM2_Packet_AppendSignature(&packet, &sigIn); - AssertIntEQ(packet.pos, 2 + 2 + 2 + (int)sizeof(sigBytes)); - - packet.pos = 0; - XMEMSET(&sigOut, 0, sizeof(sigOut)); - TPM2_Packet_ParseSignature(&packet, &sigOut); - AssertIntEQ(sigOut.sigAlg, TPM_ALG_HASH_MLDSA); - AssertIntEQ(sigOut.signature.hash_mldsa.hash, TPM_ALG_SHA256); - AssertIntEQ(sigOut.signature.hash_mldsa.signature.size, sizeof(sigBytes)); - AssertIntEQ(XMEMCMP(sigOut.signature.hash_mldsa.signature.buffer, - sigBytes, sizeof(sigBytes)), 0); - - printf("Test TPM Wrapper: %-40s Passed\n", "Signature PQC serialize:"); -} - -/* Round-trip the v1.85 PQC arms of TPM2B_PUBLIC through the - * TPM2_AppendPublic / TPM2_ParsePublic public marshalers. ML-DSA + - * Hash-ML-DSA share the unique.mldsa arm (Part 2 Table 225 note); - * ML-KEM has its own unique.mlkem arm. Verifies every round-tripped - * field for the three key types. */ static void test_TPM2_Public_RsaEcc_Roundtrip(void) { #if !defined(WOLFTPM2_NO_WOLFCRYPT) @@ -3712,6 +3642,76 @@ static void test_TPM2_Public_RsaEcc_Roundtrip(void) #endif } +#ifdef WOLFTPM_PQC +/* Round-trip the v1.85 PQC arms of TPMT_SIGNATURE through the packet + * marshaler. Pure ML-DSA (Table 217 mldsa arm) is bare TPM2B + bytes — + * no hash field. Hash-ML-DSA prefixes a hashAlg before the TPM2B. The + * tests pin the on-wire byte counts to catch any future drift. */ +static void test_TPM2_Signature_PQC_Serialize(void) +{ + TPM2_Packet packet; + byte buf[256]; + TPMT_SIGNATURE sigIn, sigOut; + const byte sigBytes[16] = { + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F + }; + + /* Pure ML-DSA: sigAlg(2) + sigSz(2) + sig(16) = 20 bytes. */ + XMEMSET(&sigIn, 0, sizeof(sigIn)); + sigIn.sigAlg = TPM_ALG_MLDSA; + sigIn.signature.mldsa.size = sizeof(sigBytes); + XMEMCPY(sigIn.signature.mldsa.buffer, sigBytes, sizeof(sigBytes)); + + XMEMSET(buf, 0, sizeof(buf)); + XMEMSET(&packet, 0, sizeof(packet)); + packet.buf = buf; + packet.size = sizeof(buf); + + TPM2_Packet_AppendSignature(&packet, &sigIn); + AssertIntEQ(packet.pos, 2 + 2 + (int)sizeof(sigBytes)); + + packet.pos = 0; + XMEMSET(&sigOut, 0, sizeof(sigOut)); + TPM2_Packet_ParseSignature(&packet, &sigOut); + AssertIntEQ(sigOut.sigAlg, TPM_ALG_MLDSA); + AssertIntEQ(sigOut.signature.mldsa.size, sizeof(sigBytes)); + AssertIntEQ(XMEMCMP(sigOut.signature.mldsa.buffer, + sigBytes, sizeof(sigBytes)), 0); + + /* Hash-ML-DSA: sigAlg(2) + hash(2) + sigSz(2) + sig(16) = 22 bytes. */ + XMEMSET(&sigIn, 0, sizeof(sigIn)); + sigIn.sigAlg = TPM_ALG_HASH_MLDSA; + sigIn.signature.hash_mldsa.hash = TPM_ALG_SHA256; + sigIn.signature.hash_mldsa.signature.size = sizeof(sigBytes); + XMEMCPY(sigIn.signature.hash_mldsa.signature.buffer, + sigBytes, sizeof(sigBytes)); + + XMEMSET(buf, 0, sizeof(buf)); + XMEMSET(&packet, 0, sizeof(packet)); + packet.buf = buf; + packet.size = sizeof(buf); + + TPM2_Packet_AppendSignature(&packet, &sigIn); + AssertIntEQ(packet.pos, 2 + 2 + 2 + (int)sizeof(sigBytes)); + + packet.pos = 0; + XMEMSET(&sigOut, 0, sizeof(sigOut)); + TPM2_Packet_ParseSignature(&packet, &sigOut); + AssertIntEQ(sigOut.sigAlg, TPM_ALG_HASH_MLDSA); + AssertIntEQ(sigOut.signature.hash_mldsa.hash, TPM_ALG_SHA256); + AssertIntEQ(sigOut.signature.hash_mldsa.signature.size, sizeof(sigBytes)); + AssertIntEQ(XMEMCMP(sigOut.signature.hash_mldsa.signature.buffer, + sigBytes, sizeof(sigBytes)), 0); + + printf("Test TPM Wrapper: %-40s Passed\n", "Signature PQC serialize:"); +} + +/* Round-trip the v1.85 PQC arms of TPM2B_PUBLIC through the + * TPM2_AppendPublic / TPM2_ParsePublic public marshalers. ML-DSA + + * Hash-ML-DSA share the unique.mldsa arm (Part 2 Table 225 note); + * ML-KEM has its own unique.mlkem arm. Verifies every round-tripped + * field for the three key types. */ static void test_TPM2_Public_PQC_Roundtrip(void) { int rc, sz; @@ -6548,9 +6548,9 @@ int unit_tests(int argc, char *argv[]) test_wolfTPM2_LoadEccPublicKey_Ex(); test_TPM2_KeyedHashScheme_XorSerialize(); test_TPM2_Signature_EcSchnorrSm2Serialize(); + test_TPM2_Public_RsaEcc_Roundtrip(); #ifdef WOLFTPM_PQC test_TPM2_Signature_PQC_Serialize(); - test_TPM2_Public_RsaEcc_Roundtrip(); test_TPM2_Public_PQC_Roundtrip(); #endif test_TPM2_Sensitive_Roundtrip(); From b7241a71a279a5e723fca19cb1a3acbaca6046b1 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Mon, 13 Jul 2026 12:45:54 -0700 Subject: [PATCH 15/27] F-6844 - Cover keyedHash/sym/mldsa/mlkem clamp arms in append test --- tests/unit_tests.c | 59 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 375e946f..7247426d 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -4124,13 +4124,21 @@ static void test_TPM2_AppendSensitive_Clamp(void) static void test_TPM2_AppendPublic_Clamp(void) { TPM2_Packet packet; - byte buf[1024]; + byte buf[sizeof(TPM2B_PUBLIC)]; TPM2B_PUBLIC pub; - word16 policyCap, rsaCap, eccCap; + word16 policyCap, rsaCap, eccCap, khCap, symCap; +#ifdef WOLFTPM_MLDSA + word16 mldsaCap; +#endif +#ifdef WOLFTPM_MLKEM + word16 mlkemCap; +#endif policyCap = (word16)sizeof(pub.publicArea.authPolicy.buffer); rsaCap = (word16)sizeof(pub.publicArea.unique.rsa.buffer); eccCap = (word16)sizeof(pub.publicArea.unique.ecc.x.buffer); + khCap = (word16)sizeof(pub.publicArea.unique.keyedHash.buffer); + symCap = (word16)sizeof(pub.publicArea.unique.sym.buffer); XMEMSET(&pub, 0, sizeof(pub)); pub.publicArea.type = TPM_ALG_RSA; @@ -4157,6 +4165,53 @@ static void test_TPM2_AppendPublic_Clamp(void) AssertIntEQ(pub.publicArea.unique.ecc.x.size, eccCap); AssertIntEQ(pub.publicArea.unique.ecc.y.size, eccCap); + /* keyedHash unique size must clamp */ + XMEMSET(&pub, 0, sizeof(pub)); + pub.publicArea.type = TPM_ALG_KEYEDHASH; + pub.publicArea.nameAlg = TPM_ALG_SHA256; + pub.publicArea.unique.keyedHash.size = khCap + 100; + XMEMSET(&packet, 0, sizeof(packet)); + packet.buf = buf; + packet.size = sizeof(buf); + TPM2_Packet_AppendPublic(&packet, &pub); + AssertIntEQ(pub.publicArea.unique.keyedHash.size, khCap); + + /* symcipher unique size must clamp */ + XMEMSET(&pub, 0, sizeof(pub)); + pub.publicArea.type = TPM_ALG_SYMCIPHER; + pub.publicArea.nameAlg = TPM_ALG_SHA256; + pub.publicArea.unique.sym.size = symCap + 100; + XMEMSET(&packet, 0, sizeof(packet)); + packet.buf = buf; + packet.size = sizeof(buf); + TPM2_Packet_AppendPublic(&packet, &pub); + AssertIntEQ(pub.publicArea.unique.sym.size, symCap); + +#ifdef WOLFTPM_MLDSA + mldsaCap = (word16)sizeof(pub.publicArea.unique.mldsa.buffer); + XMEMSET(&pub, 0, sizeof(pub)); + pub.publicArea.type = TPM_ALG_MLDSA; + pub.publicArea.nameAlg = TPM_ALG_SHA256; + pub.publicArea.unique.mldsa.size = mldsaCap + 100; + XMEMSET(&packet, 0, sizeof(packet)); + packet.buf = buf; + packet.size = sizeof(buf); + TPM2_Packet_AppendPublic(&packet, &pub); + AssertIntEQ(pub.publicArea.unique.mldsa.size, mldsaCap); +#endif +#ifdef WOLFTPM_MLKEM + mlkemCap = (word16)sizeof(pub.publicArea.unique.mlkem.buffer); + XMEMSET(&pub, 0, sizeof(pub)); + pub.publicArea.type = TPM_ALG_MLKEM; + pub.publicArea.nameAlg = TPM_ALG_SHA256; + pub.publicArea.unique.mlkem.size = mlkemCap + 100; + XMEMSET(&packet, 0, sizeof(packet)); + packet.buf = buf; + packet.size = sizeof(buf); + TPM2_Packet_AppendPublic(&packet, &pub); + AssertIntEQ(pub.publicArea.unique.mlkem.size, mlkemCap); +#endif + printf("Test TPM2: %-40s Passed\n", "AppendPublic clamp:"); } From 45369f5d5ea9fa290bf4747aeda1466a069eb10a Mon Sep 17 00:00:00 2001 From: aidan garske Date: Mon, 13 Jul 2026 12:46:03 -0700 Subject: [PATCH 16/27] F-6847 - Guard ECC verify test on curve size and add oversized-S case --- tests/unit_tests.c | 65 +++++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 7247426d..1c976c36 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -4374,9 +4374,10 @@ static void test_wolfTPM2_CryptoDevCb_EccVerifyOversizedRS(void) { #if !defined(WOLFTPM2_NO_WRAPPER) && defined(WOLFTPM_CRYPTOCB) && \ !defined(WOLFTPM2_NO_WOLFCRYPT) && defined(HAVE_ECC) && \ - defined(HAVE_ECC_VERIFY) && !defined(WC_NO_RNG) + defined(HAVE_ECC_VERIFY) && !defined(WC_NO_RNG) && (MAX_ECC_BYTES > 32) int rc; int i; + int c, rLen, sLen; int verifyRes = 0; WOLFTPM2_DEV dev; TpmCryptoDevCtx tpmCtx; @@ -4384,20 +4385,7 @@ static void test_wolfTPM2_CryptoDevCb_EccVerifyOversizedRS(void) ecc_key key; byte digest[32]; byte sig[128]; - word32 sigSz = 0; - - /* DER ECDSA signature with a 40-byte R and 32-byte S: R exceeds the - * P-256 key size to drive the keySz - rLen offset underflow path */ - sig[sigSz++] = 0x30; - sig[sigSz++] = 0x4C; - sig[sigSz++] = 0x02; - sig[sigSz++] = 0x28; - for (i = 0; i < 40; i++) - sig[sigSz++] = 0x11; - sig[sigSz++] = 0x02; - sig[sigSz++] = 0x20; - for (i = 0; i < 32; i++) - sig[sigSz++] = 0x22; + word32 sigSz; XMEMSET(digest, 0x33, sizeof(digest)); XMEMSET(&tpmCtx, 0, sizeof(tpmCtx)); @@ -4411,25 +4399,42 @@ static void test_wolfTPM2_CryptoDevCb_EccVerifyOversizedRS(void) rc = wc_ecc_make_key_ex(wolfTPM2_GetRng(&dev), 32, &key, ECC_SECP256R1); AssertIntEQ(rc, 0); - XMEMSET(&info, 0, sizeof(info)); - info.algo_type = WC_ALGO_TYPE_PK; - info.pk.type = WC_PK_TYPE_ECDSA_VERIFY; - info.pk.eccverify.sig = sig; - info.pk.eccverify.siglen = sigSz; - info.pk.eccverify.hash = digest; - info.pk.eccverify.hashlen = (word32)sizeof(digest); - info.pk.eccverify.res = &verifyRes; - info.pk.eccverify.key = &key; - - /* oversized R must be caught before loading the key into the TPM */ - rc = wolfTPM2_CryptoDevCb(INVALID_DEVID, &info, &tpmCtx); - AssertIntEQ(rc, CRYPTOCB_UNAVAILABLE); + /* c==0 drives the oversized-R guard, c==1 the oversized-S guard; both + * exceed the P-256 key size and must fall back before the TPM key load */ + for (c = 0; c < 2; c++) { + rLen = (c == 0) ? 40 : 32; + sLen = (c == 0) ? 32 : 40; + + sigSz = 0; + sig[sigSz++] = 0x30; + sig[sigSz++] = (byte)(2 + rLen + 2 + sLen); + sig[sigSz++] = 0x02; + sig[sigSz++] = (byte)rLen; + for (i = 0; i < rLen; i++) + sig[sigSz++] = 0x11; + sig[sigSz++] = 0x02; + sig[sigSz++] = (byte)sLen; + for (i = 0; i < sLen; i++) + sig[sigSz++] = 0x22; + + XMEMSET(&info, 0, sizeof(info)); + info.algo_type = WC_ALGO_TYPE_PK; + info.pk.type = WC_PK_TYPE_ECDSA_VERIFY; + info.pk.eccverify.sig = sig; + info.pk.eccverify.siglen = sigSz; + info.pk.eccverify.hash = digest; + info.pk.eccverify.hashlen = (word32)sizeof(digest); + info.pk.eccverify.res = &verifyRes; + info.pk.eccverify.key = &key; + + rc = wolfTPM2_CryptoDevCb(INVALID_DEVID, &info, &tpmCtx); + AssertIntEQ(rc, CRYPTOCB_UNAVAILABLE); + } wc_ecc_free(&key); wolfTPM2_Cleanup(&dev); - printf("Test TPM Wrapper: %-40s %s\n", "CryptoDevCb ECC oversized R/S:", - rc == CRYPTOCB_UNAVAILABLE ? "Passed" : "Failed"); + printf("Test TPM Wrapper: %-40s Passed\n", "CryptoDevCb ECC oversized R/S:"); #endif } From ce96373632052d0c0bf59052dd1401c59cfb863b Mon Sep 17 00:00:00 2001 From: aidan garske Date: Mon, 13 Jul 2026 12:46:16 -0700 Subject: [PATCH 17/27] F-6839 - Add valid-certificate happy-path test for DecodeX509Cert --- tests/unit_tests.c | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 1c976c36..fc4cc4c8 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -4458,8 +4458,31 @@ static void test_TPM2_ASN_DecodeX509Cert_Errors(void) rc = TPM2_ASN_DecodeX509Cert(garbage, (int)sizeof(garbage), &x509); AssertIntNE(rc, 0); - printf("Test TPM Wrapper: %-40s %s\n", "ASN DecodeX509Cert errors:", - rc != 0 ? "Passed" : "Failed"); + printf("Test TPM Wrapper: %-40s Passed\n", "ASN DecodeX509Cert errors:"); +#endif +} + +#if !defined(WOLFTPM2_NO_WRAPPER) && !defined(WOLFTPM2_NO_ASN) +#include +#endif +static void test_TPM2_ASN_DecodeX509Cert_Valid(void) +{ +#if !defined(WOLFTPM2_NO_WRAPPER) && !defined(WOLFTPM2_NO_ASN) + int rc; + DecodedX509 x509; + + /* a well-formed DER certificate must decode and populate the fields */ + XMEMSET(&x509, 0, sizeof(x509)); + rc = TPM2_ASN_DecodeX509Cert((uint8_t*)kSTSAFEIntCa20, + (int)sizeof(kSTSAFEIntCa20), &x509); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntGT(x509.certSz, 0); + AssertNotNull(x509.publicKey); + AssertIntGT(x509.pubKeySz, 0); + AssertNotNull(x509.signature); + AssertIntGT(x509.sigSz, 0); + + printf("Test TPM Wrapper: %-40s Passed\n", "ASN DecodeX509Cert valid:"); #endif } @@ -6626,6 +6649,7 @@ int unit_tests(int argc, char *argv[]) test_wolfTPM2_CSR(); test_wolfTPM2_CryptoDevCb_EccVerifyOversizedRS(); test_TPM2_ASN_DecodeX509Cert_Errors(); + test_TPM2_ASN_DecodeX509Cert_Valid(); test_TPM2_ASN_DecodeTag_Errors(); #if !defined(WOLFTPM2_NO_WOLFCRYPT) && defined(WOLFTPM2_PEM_DECODE) && \ !defined(NO_RSA) From 2ef42a01517c6b63a55fac5ddf2d18ef656e024e Mon Sep 17 00:00:00 2001 From: aidan garske Date: Mon, 13 Jul 2026 12:46:16 -0700 Subject: [PATCH 18/27] F-6840 - Use fixed pass print in DecodeTag error test --- tests/unit_tests.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/unit_tests.c b/tests/unit_tests.c index fc4cc4c8..797dfced 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -4503,8 +4503,7 @@ static void test_TPM2_ASN_DecodeTag_Errors(void) rc = TPM2_ASN_DecodeTag(buf, (int)sizeof(buf), &idx, &tagLen, 0x02); AssertIntNE(rc, 0); - printf("Test TPM Wrapper: %-40s %s\n", "ASN DecodeTag tag mismatch:", - rc != 0 ? "Passed" : "Failed"); + printf("Test TPM Wrapper: %-40s Passed\n", "ASN DecodeTag tag mismatch:"); #endif } From 14d02a32b2a2a999bf482f9ebc80fb16f6449364 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Mon, 13 Jul 2026 12:54:21 -0700 Subject: [PATCH 19/27] F-6842 - Share TPM2_XOR_MASK_MAX via header so test matches impl --- src/tpm2_param_enc.c | 7 ------- tests/unit_tests.c | 3 --- wolftpm/tpm2_param_enc.h | 6 ++++++ 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/tpm2_param_enc.c b/src/tpm2_param_enc.c index 72d8d43d..be593b9e 100644 --- a/src/tpm2_param_enc.c +++ b/src/tpm2_param_enc.c @@ -57,13 +57,6 @@ /* --- Param Enc/Dec Functions -- */ /******************************************************************************/ -/* Maximum XOR mask size. RSA-2048 inSensitive parameter blobs on Create can - * exceed MAX_DIGEST_BUFFER (1024), so leave headroom to ~1250 bytes. Keep - * stack usage bounded by switching to heap under WOLFTPM_SMALL_STACK. */ -#ifndef TPM2_XOR_MASK_MAX -#define TPM2_XOR_MASK_MAX 1280 -#endif - /* XOR parameter encryption/decryption (shared by client and fwTPM). * XOR is symmetric so encrypt and decrypt are the same operation. * nonceA/nonceB order determines direction (caller/TPM or TPM/caller). */ diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 797dfced..45954737 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -1923,9 +1923,6 @@ static void test_TPM2_ParamEnc_XOR_Vector(void) #endif } -#ifndef TPM2_XOR_MASK_MAX -#define TPM2_XOR_MASK_MAX 1280 -#endif static void test_TPM2_ParamEnc_XOR_MaskBoundary(void) { #ifndef WOLFTPM2_NO_WOLFCRYPT diff --git a/wolftpm/tpm2_param_enc.h b/wolftpm/tpm2_param_enc.h index 95619344..331df6cc 100644 --- a/wolftpm/tpm2_param_enc.h +++ b/wolftpm/tpm2_param_enc.h @@ -30,6 +30,12 @@ extern "C" { #endif +/* Maximum XOR mask size. RSA-2048 inSensitive parameter blobs on Create can + * exceed MAX_DIGEST_BUFFER (1024), so leave headroom to ~1250 bytes. */ +#ifndef TPM2_XOR_MASK_MAX +#define TPM2_XOR_MASK_MAX 1280 +#endif + /* XOR parameter encryption/decryption (raw pointer interface). * XOR is symmetric so encrypt and decrypt are the same operation. */ WOLFTPM_TEST_API int TPM2_ParamEnc_XOR( From 5c682e167b1ab91986709c9bfbd0a0f6b70c19e5 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Mon, 13 Jul 2026 13:03:10 -0700 Subject: [PATCH 20/27] F-6839 - Cover truncated-buffer error path in DecodeX509Cert test --- tests/unit_tests.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 45954737..e31682a0 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -4441,6 +4441,7 @@ static void test_TPM2_ASN_DecodeX509Cert_Errors(void) int rc; DecodedX509 x509; byte garbage[16]; + byte trunc[4]; XMEMSET(&x509, 0, sizeof(x509)); XMEMSET(garbage, 0xFF, sizeof(garbage)); @@ -4455,6 +4456,11 @@ static void test_TPM2_ASN_DecodeX509Cert_Errors(void) rc = TPM2_ASN_DecodeX509Cert(garbage, (int)sizeof(garbage), &x509); AssertIntNE(rc, 0); + /* outer SEQUENCE whose length runs past the buffer (TPM_RC_INSUFFICIENT) */ + trunc[0] = 0x30; trunc[1] = 0x20; trunc[2] = 0x00; trunc[3] = 0x00; + rc = TPM2_ASN_DecodeX509Cert(trunc, (int)sizeof(trunc), &x509); + AssertIntNE(rc, 0); + printf("Test TPM Wrapper: %-40s Passed\n", "ASN DecodeX509Cert errors:"); #endif } From 213bfaa5b9271db1847151d135a05a66848b7a2d Mon Sep 17 00:00:00 2001 From: aidan garske Date: Mon, 13 Jul 2026 13:11:20 -0700 Subject: [PATCH 21/27] F-6840 - Bound RsaDecodeSignature size check against TPM_RC error codes --- examples/endorsement/verify_ek_cert.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/endorsement/verify_ek_cert.c b/examples/endorsement/verify_ek_cert.c index f1485851..b7c7e1d1 100644 --- a/examples/endorsement/verify_ek_cert.c +++ b/examples/endorsement/verify_ek_cert.c @@ -288,7 +288,8 @@ int TPM2_EndorsementCertVerify_Example(void* userCtx, int argc, char *argv[]) } if (rc == 0) { rc = TPM2_ASN_RsaDecodeSignature(&sigDigest, sigSz); - if (rc > 0) { + /* positive return is the digest size; larger values are TPM_RC errors */ + if (rc > 0 && rc <= TPM_MAX_DIGEST_SIZE) { sigDigestSz = rc; rc = 0; } From 7028c981baab469a0c7b8fe82e04581947bf5e99 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Mon, 13 Jul 2026 13:25:50 -0700 Subject: [PATCH 22/27] F-6839 - Revert DecodeX509Cert guard changes; ASN errors are negative --- src/tpm2_asn.c | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/tpm2_asn.c b/src/tpm2_asn.c index de08915c..60453e4d 100644 --- a/src/tpm2_asn.c +++ b/src/tpm2_asn.c @@ -93,7 +93,7 @@ int TPM2_ASN_GetLength(const uint8_t* input, word32* inOutIdx, int* len, \param inOutIdx Current position in buffer, updated to new position \param len Decoded length value \param maxIdx Maximum allowed index in buffer - \return 0 on success, TPM_RC_VALUE on tag mismatch, TPM_RC_INSUFFICIENT on buffer error + \return Length on success, TPM_RC_VALUE on tag mismatch, TPM_RC_INSUFFICIENT on buffer error */ static int TPM2_ASN_GetHeader(const uint8_t* input, byte tag, word32* inOutIdx, int* len, word32 maxIdx) @@ -114,7 +114,7 @@ static int TPM2_ASN_GetHeader(const uint8_t* input, byte tag, word32* inOutIdx, *len = length; *inOutIdx = idx; - return 0; + return length; } int TPM2_ASN_DecodeTag(const uint8_t* input, int inputSz, @@ -170,7 +170,7 @@ int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, } /* Store certificate location */ - if (rc == 0) { + if (rc >= 0) { x509->certBegin = idx; x509->cert = &input[idx]; @@ -179,7 +179,7 @@ int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, &idx, &cert_len, inputSz); } - if (rc == 0) { + if (rc >= 0) { x509->certSz = cert_len + (idx - x509->certBegin); /* Decode version */ @@ -187,27 +187,27 @@ int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, &idx, &len, inputSz); } - if (rc == 0) { + if (rc >= 0) { if (len <= 0 || idx >= (word32)inputSz) { rc = TPM_RC_VALUE; } } - if (rc == 0) { + if (rc >= 0) { /* check version tag is INTEGER */ if (input[idx] != TPM2_ASN_INTEGER) { rc = TPM_RC_VALUE; } } - if (rc == 0) { + if (rc >= 0) { idx += len; /* skip version */ /* Skip serial number */ rc = TPM2_ASN_GetHeader(input, TPM2_ASN_INTEGER, &idx, &len, inputSz); } - if (rc == 0) { + if (rc >= 0) { idx += len; /* skip serial */ /* Skip algorithm identifier */ @@ -215,7 +215,7 @@ int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, &idx, &len, inputSz); } - if (rc == 0) { + if (rc >= 0) { idx += len; /* skip signature oid */ /* Skip issuer */ @@ -223,7 +223,7 @@ int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, &idx, &len, inputSz); } - if (rc == 0) { + if (rc >= 0) { idx += len; /* skip issuer */ /* Skip validity */ @@ -231,7 +231,7 @@ int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, &idx, &len, inputSz); } - if (rc == 0) { + if (rc >= 0) { idx += len; /* skip validity */ /* Skip subject */ @@ -239,24 +239,24 @@ int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, &idx, &len, inputSz); } - if (rc == 0) { + if (rc >= 0) { idx += len; /* skip subject */ /* subject public key info */ rc = TPM2_ASN_GetHeader(input, TPM2_ASN_SEQUENCE | TPM2_ASN_CONSTRUCTED, &idx, &len, inputSz); } - if (rc == 0) { + if (rc >= 0) { /* cert - subject public key alg oid */ rc = TPM2_ASN_GetHeader(input, TPM2_ASN_SEQUENCE | TPM2_ASN_CONSTRUCTED, &idx, &len, inputSz); } - if (rc == 0) { + if (rc >= 0) { idx += len; /* skip alg oid */ /* Get public key */ rc = TPM2_ASN_GetHeader(input, TPM2_ASN_BIT_STRING, &idx, &pubkey_len, inputSz); } - if (rc == 0) { + if (rc >= 0) { /* skip leading zero for bit string */ if (pubkey_len > 0 && input[idx] == 0x00) { idx++; @@ -271,26 +271,26 @@ int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, &idx, &len, inputSz); } - if (rc == 0) { + if (rc >= 0) { /* signature oid */ rc = TPM2_ASN_GetHeader(input, TPM2_ASN_OBJECT_ID, &idx, &len, inputSz); } - if (rc == 0) { + if (rc >= 0) { idx += len; /* skip oid */ /* Skip signature algorithm parameters */ rc = TPM2_ASN_GetHeader(input, TPM2_ASN_TAG_NULL, &idx, &len, inputSz); } - if (rc == 0) { + if (rc >= 0) { idx += len; /* skip tag */ /* Get signature */ rc = TPM2_ASN_GetHeader(input, TPM2_ASN_BIT_STRING, &idx, &sig_len, inputSz); } - if (rc == 0) { + if (rc >= 0) { /* skip leading zero for bit string */ if (sig_len > 0 && input[idx] == 0x00) { idx++; From 129191bd3b68fab10abc4169b172167dd22f9e86 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Mon, 13 Jul 2026 13:25:59 -0700 Subject: [PATCH 23/27] F-6840 - Revert DecodeTag change and EK cert bound; ASN errors are negative --- examples/endorsement/verify_ek_cert.c | 3 +-- src/tpm2_asn.c | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/endorsement/verify_ek_cert.c b/examples/endorsement/verify_ek_cert.c index b7c7e1d1..f1485851 100644 --- a/examples/endorsement/verify_ek_cert.c +++ b/examples/endorsement/verify_ek_cert.c @@ -288,8 +288,7 @@ int TPM2_EndorsementCertVerify_Example(void* userCtx, int argc, char *argv[]) } if (rc == 0) { rc = TPM2_ASN_RsaDecodeSignature(&sigDigest, sigSz); - /* positive return is the digest size; larger values are TPM_RC errors */ - if (rc > 0 && rc <= TPM_MAX_DIGEST_SIZE) { + if (rc > 0) { sigDigestSz = rc; rc = 0; } diff --git a/src/tpm2_asn.c b/src/tpm2_asn.c index 60453e4d..8faaa8de 100644 --- a/src/tpm2_asn.c +++ b/src/tpm2_asn.c @@ -122,8 +122,9 @@ int TPM2_ASN_DecodeTag(const uint8_t* input, int inputSz, { word32 idx = *inOutIdx; int rc = TPM2_ASN_GetHeader(input, tag, &idx, tag_len, inputSz); - if (rc == 0) { + if (rc >= 0) { *inOutIdx = idx; + rc = 0; } return rc; } From c76dbe615f7f5e8fc1f870443b333a97781b739c Mon Sep 17 00:00:00 2001 From: aidan garske Date: Mon, 13 Jul 2026 13:36:26 -0700 Subject: [PATCH 24/27] F-6847 - Cover keySz==0 fallback in ECC verify crypto callback test --- tests/unit_tests.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/unit_tests.c b/tests/unit_tests.c index e31682a0..c2a70921 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -4380,6 +4380,7 @@ static void test_wolfTPM2_CryptoDevCb_EccVerifyOversizedRS(void) TpmCryptoDevCtx tpmCtx; wc_CryptoInfo info; ecc_key key; + ecc_key nokey; byte digest[32]; byte sig[128]; word32 sigSz; @@ -4428,6 +4429,33 @@ static void test_wolfTPM2_CryptoDevCb_EccVerifyOversizedRS(void) AssertIntEQ(rc, CRYPTOCB_UNAVAILABLE); } + /* keySz == 0 (key with no curve set) must also fall back to software */ + rc = wc_ecc_init(&nokey); + AssertIntEQ(rc, 0); + sigSz = 0; + sig[sigSz++] = 0x30; + sig[sigSz++] = 0x44; + sig[sigSz++] = 0x02; + sig[sigSz++] = 0x20; + for (i = 0; i < 32; i++) + sig[sigSz++] = 0x11; + sig[sigSz++] = 0x02; + sig[sigSz++] = 0x20; + for (i = 0; i < 32; i++) + sig[sigSz++] = 0x22; + XMEMSET(&info, 0, sizeof(info)); + info.algo_type = WC_ALGO_TYPE_PK; + info.pk.type = WC_PK_TYPE_ECDSA_VERIFY; + info.pk.eccverify.sig = sig; + info.pk.eccverify.siglen = sigSz; + info.pk.eccverify.hash = digest; + info.pk.eccverify.hashlen = (word32)sizeof(digest); + info.pk.eccverify.res = &verifyRes; + info.pk.eccverify.key = &nokey; + rc = wolfTPM2_CryptoDevCb(INVALID_DEVID, &info, &tpmCtx); + AssertIntEQ(rc, CRYPTOCB_UNAVAILABLE); + wc_ecc_free(&nokey); + wc_ecc_free(&key); wolfTPM2_Cleanup(&dev); From 5fdaa79935ad916966d2b1f8266f5279870dee26 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Mon, 13 Jul 2026 13:43:40 -0700 Subject: [PATCH 25/27] F-6847 - Add ChangeLog entry for crypto callback and marshaling hardening --- ChangeLog.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 0f4b1bd5..39caa65b 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -35,6 +35,15 @@ `-DWOLFTPM_MAX_RETRIES=N` at build time. Define `WOLFTPM_NO_RETRY` to compile the handling out entirely. wolfTPM's own key templates set `noDA` and never trigger it. +* Hardened the wolfCrypt crypto callback and TPM2B marshaling. The ECDSA verify + path now rejects signatures whose R/S exceed the key curve size before the + pad-offset arithmetic, preventing an out-of-bounds write on a malformed peer + signature; the public-area and ECC-point append helpers clamp oversized TPM2B + sizes to their buffer capacity, preventing a source over-read; and + `TPM2_CalcCpHash`/`TPM2_CalcRpHash` scrub the stack hash context after use. + Added unit tests for these paths plus the XOR/AES-CFB parameter-encryption + size guards, the response-HMAC session check, and RSA/ECC public-area + round-trips. ## wolfTPM Release 4.0.0 (Apr 22, 2026) From 6ce2ed1db642ba366032b145e721cf1788721abd Mon Sep 17 00:00:00 2001 From: aidan garske Date: Mon, 13 Jul 2026 14:10:12 -0700 Subject: [PATCH 26/27] F-6847 - Remove ChangeLog entry --- ChangeLog.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 39caa65b..0f4b1bd5 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -35,15 +35,6 @@ `-DWOLFTPM_MAX_RETRIES=N` at build time. Define `WOLFTPM_NO_RETRY` to compile the handling out entirely. wolfTPM's own key templates set `noDA` and never trigger it. -* Hardened the wolfCrypt crypto callback and TPM2B marshaling. The ECDSA verify - path now rejects signatures whose R/S exceed the key curve size before the - pad-offset arithmetic, preventing an out-of-bounds write on a malformed peer - signature; the public-area and ECC-point append helpers clamp oversized TPM2B - sizes to their buffer capacity, preventing a source over-read; and - `TPM2_CalcCpHash`/`TPM2_CalcRpHash` scrub the stack hash context after use. - Added unit tests for these paths plus the XOR/AES-CFB parameter-encryption - size guards, the response-HMAC session check, and RSA/ECC public-area - round-trips. ## wolfTPM Release 4.0.0 (Apr 22, 2026) From fac885a804a53f248ce5c409eddce9b947da5584 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Mon, 13 Jul 2026 14:26:08 -0700 Subject: [PATCH 27/27] F-6847 - Drop keySz==0 ECC verify test; old wolfSSL wc_ecc_size derefs NULL dp --- tests/unit_tests.c | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/tests/unit_tests.c b/tests/unit_tests.c index c2a70921..e31682a0 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -4380,7 +4380,6 @@ static void test_wolfTPM2_CryptoDevCb_EccVerifyOversizedRS(void) TpmCryptoDevCtx tpmCtx; wc_CryptoInfo info; ecc_key key; - ecc_key nokey; byte digest[32]; byte sig[128]; word32 sigSz; @@ -4429,33 +4428,6 @@ static void test_wolfTPM2_CryptoDevCb_EccVerifyOversizedRS(void) AssertIntEQ(rc, CRYPTOCB_UNAVAILABLE); } - /* keySz == 0 (key with no curve set) must also fall back to software */ - rc = wc_ecc_init(&nokey); - AssertIntEQ(rc, 0); - sigSz = 0; - sig[sigSz++] = 0x30; - sig[sigSz++] = 0x44; - sig[sigSz++] = 0x02; - sig[sigSz++] = 0x20; - for (i = 0; i < 32; i++) - sig[sigSz++] = 0x11; - sig[sigSz++] = 0x02; - sig[sigSz++] = 0x20; - for (i = 0; i < 32; i++) - sig[sigSz++] = 0x22; - XMEMSET(&info, 0, sizeof(info)); - info.algo_type = WC_ALGO_TYPE_PK; - info.pk.type = WC_PK_TYPE_ECDSA_VERIFY; - info.pk.eccverify.sig = sig; - info.pk.eccverify.siglen = sigSz; - info.pk.eccverify.hash = digest; - info.pk.eccverify.hashlen = (word32)sizeof(digest); - info.pk.eccverify.res = &verifyRes; - info.pk.eccverify.key = &nokey; - rc = wolfTPM2_CryptoDevCb(INVALID_DEVID, &info, &tpmCtx); - AssertIntEQ(rc, CRYPTOCB_UNAVAILABLE); - wc_ecc_free(&nokey); - wc_ecc_free(&key); wolfTPM2_Cleanup(&dev);