From f62070ac46ef7eb3375d998331b912ea4a0b79eb Mon Sep 17 00:00:00 2001 From: Aidan Date: Tue, 28 Apr 2026 09:03:10 -0700 Subject: [PATCH 01/24] F-3503 - https://fenrir.wolfssl.com/finding/3503 - fwtpm: emit TPM_ST_ATTEST_NV_DIGEST when NV_Certify size and offset are both zero --- src/fwtpm/fwtpm_command.c | 80 ++++++++++++++++++++++----- tests/fwtpm_unit_tests.c | 113 ++++++++++++++++++++++++++++++++++++++ wolftpm/tpm2.h | 1 + 3 files changed, 181 insertions(+), 13 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index f560da7f..55cbfaa0 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -11581,6 +11581,7 @@ static TPM_RC FwCmd_NV_Certify(FWTPM_CTX* ctx, TPM2_Packet* cmd, TPM2B_DATA qualifyingData; UINT16 sigScheme, sigHashAlg; UINT16 readSize, readOffset; + int digestMode = 0; FWTPM_Object* sigObj; FWTPM_NvIndex* nv; FWTPM_DECLARE_BUF(attestBuf, FWTPM_MAX_ATTEST_BUF); @@ -11629,6 +11630,13 @@ static TPM_RC FwCmd_NV_Certify(FWTPM_CTX* ctx, TPM2_Packet* cmd, &qualifyingData, &sigScheme, &sigHashAlg); } + /* Per Part 3 §31.16.1: TPM_RH_NULL signHandle requires non-NULL + * scheme and hash algorithm, otherwise return TPM_RC_SCHEME. */ + if (rc == 0 && signHandle == TPM_RH_NULL && + (sigScheme == TPM_ALG_NULL || sigHashAlg == TPM_ALG_NULL)) { + rc = TPM_RC_SCHEME; + } + /* size, offset */ if (rc == 0) { TPM2_Packet_ParseU16(cmd, &readSize); @@ -11639,12 +11647,20 @@ static TPM_RC FwCmd_NV_Certify(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } if (rc == 0) { - if (readSize == 0) + /* Per Part 3 §31.16.1: when both size and offset are zero, the + * response must contain a TPMS_NV_DIGEST_CERTIFY_INFO with the + * digest of the entire NV index, instead of TPMS_NV_CERTIFY_INFO. */ + if (readSize == 0 && readOffset == 0) { + digestMode = 1; + readSize = nv->nvPublic.dataSize; + } + else if (readSize == 0) { readSize = nv->nvPublic.dataSize - readOffset; + } if ((UINT32)(readOffset + readSize) > nv->nvPublic.dataSize) { rc = TPM_RC_NV_RANGE; } - if (rc == 0 && readSize > FWTPM_MAX_NV_DATA) { + if (rc == 0 && !digestMode && readSize > FWTPM_MAX_NV_DATA) { rc = TPM_RC_SIZE; } } @@ -11680,26 +11696,64 @@ static TPM_RC FwCmd_NV_Certify(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* Build TPMS_ATTEST for NV */ if (rc == 0) { + UINT16 attestType = digestMode ? + TPM_ST_ATTEST_NV_DIGEST : TPM_ST_ATTEST_NV; XMEMSET(attestBuf, 0, FWTPM_MAX_ATTEST_BUF); attestPkt.buf = attestBuf; attestPkt.pos = 0; attestPkt.size = (int)FWTPM_MAX_ATTEST_BUF; - FwAppendAttestCommonHeader(&attestPkt, TPM_ST_ATTEST_NV, + FwAppendAttestCommonHeader(&attestPkt, attestType, &sigObj->name, &qualifyingData); - /* attested.nv: indexName + offset + nvContents */ - TPM2_Packet_AppendU16(&attestPkt, nvName.size); - TPM2_Packet_AppendBytes(&attestPkt, (byte*)nvName.name, - nvName.size); - TPM2_Packet_AppendU16(&attestPkt, readOffset); - TPM2_Packet_AppendU16(&attestPkt, readSize); - TPM2_Packet_AppendBytes(&attestPkt, nv->data + readOffset, - readSize); + if (digestMode) { + /* attested.nvDigest: indexName + nvDigest + * nvDigest is hash of entire NV index using signing scheme hash */ + UINT16 hashAlg = sigHashAlg; + byte nvDigest[TPM_MAX_DIGEST_SIZE]; + int hashSz; + enum wc_HashType wcDigH; + + /* Resolve hash from signing key when scheme/hash is NULL */ + if (hashAlg == TPM_ALG_NULL) { + UINT16 keyScheme = TPM_ALG_NULL; + FwResolveSignScheme(sigObj, &keyScheme, &hashAlg); + } + wcDigH = FwGetWcHashType(hashAlg); + hashSz = TPM2_GetHashDigestSize(hashAlg); + if (wcDigH == WC_HASH_TYPE_NONE || hashSz <= 0) { + rc = TPM_RC_HASH; + } + if (rc == 0) { + if (wc_Hash(wcDigH, nv->data, nv->nvPublic.dataSize, + nvDigest, hashSz) != 0) { + rc = TPM_RC_FAILURE; + } + } + if (rc == 0) { + TPM2_Packet_AppendU16(&attestPkt, nvName.size); + TPM2_Packet_AppendBytes(&attestPkt, (byte*)nvName.name, + nvName.size); + TPM2_Packet_AppendU16(&attestPkt, (UINT16)hashSz); + TPM2_Packet_AppendBytes(&attestPkt, nvDigest, hashSz); + } + } + else { + /* attested.nv: indexName + offset + nvContents */ + TPM2_Packet_AppendU16(&attestPkt, nvName.size); + TPM2_Packet_AppendBytes(&attestPkt, (byte*)nvName.name, + nvName.size); + TPM2_Packet_AppendU16(&attestPkt, readOffset); + TPM2_Packet_AppendU16(&attestPkt, readSize); + TPM2_Packet_AppendBytes(&attestPkt, nv->data + readOffset, + readSize); + } /* Build response */ - rc = FwBuildAttestResponse(ctx, rsp, cmdTag, sigObj, - sigScheme, sigHashAlg, attestBuf, attestPkt.pos); + if (rc == 0) { + rc = FwBuildAttestResponse(ctx, rsp, cmdTag, sigObj, + sigScheme, sigHashAlg, attestBuf, attestPkt.pos); + } } FWTPM_FREE_BUF(attestBuf); return rc; diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index e3d804a5..ccecf638 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -1515,6 +1515,116 @@ static void test_fwtpm_nv_counter(void) FWTPM_Cleanup(&ctx); printf("Test fwTPM:\tNV_Increment (counter):\t\tPassed\n"); } + +#ifndef FWTPM_NO_ATTESTATION +/* NV_Certify with size=0 and offset=0 must emit TPMS_NV_DIGEST_CERTIFY_INFO + * inside a TPM_ST_ATTEST_NV_DIGEST (0x801C) attest, not the regular + * TPMS_NV_CERTIFY_INFO inside TPM_ST_ATTEST_NV (0x8014). Per TPM 2.0 + * Part 3 §31.16.1. */ +static void test_fwtpm_nv_certify_digest_mode(void) +{ + FWTPM_CTX ctx; + int pos, rspSize, cmdSz; + UINT32 nvIdx = 0x01500004; + UINT32 attrs = TPMA_NV_OWNERWRITE | TPMA_NV_OWNERREAD | TPMA_NV_NO_DA; + UINT32 keyH; + UINT16 attestType; + byte testData[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + /* Create primary signing key (use existing helper - the FwTPM signer + * does not enforce sign attribute, so a restricted-decrypt key suffices + * to exercise the attest-tag path under test). */ +#ifdef HAVE_ECC + keyH = CreatePrimaryHelper(&ctx, TPM_ALG_ECC); +#else + keyH = CreatePrimaryHelper(&ctx, TPM_ALG_RSA); +#endif + AssertIntNE(keyH, 0); + + /* Define NV index */ + cmdSz = BuildNvDefineCmd(gCmd, nvIdx, 32, attrs); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + /* Write some data so the index is "written" (required for certify) */ + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_NV_Write); pos += 4; + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; + PutU32BE(gCmd + pos, nvIdx); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU16BE(gCmd + pos, (UINT16)sizeof(testData)); pos += 2; + memcpy(gCmd + pos, testData, sizeof(testData)); pos += sizeof(testData); + PutU16BE(gCmd + pos, 0); pos += 2; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + /* NV_Certify with size=0 and offset=0 -> must yield digest-mode attest */ + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_NV_Certify); pos += 4; + PutU32BE(gCmd + pos, keyH); pos += 4; /* signHandle */ + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; /* authHandle */ + PutU32BE(gCmd + pos, nvIdx); pos += 4; /* nvIndex */ + /* Two-session auth area (signHandle + authHandle): 2 * 9 = 18 bytes */ + PutU32BE(gCmd + pos, 18); pos += 4; + PutU32BE(gCmd + pos, TPM_RS_PW); pos += 4; + PutU16BE(gCmd + pos, 0); pos += 2; /* nonce */ + gCmd[pos++] = 0; /* attrs */ + PutU16BE(gCmd + pos, 0); pos += 2; /* hmac */ + PutU32BE(gCmd + pos, TPM_RS_PW); pos += 4; + PutU16BE(gCmd + pos, 0); pos += 2; + gCmd[pos++] = 0; + PutU16BE(gCmd + pos, 0); pos += 2; + /* qualifyingData (TPM2B_DATA) - empty */ + PutU16BE(gCmd + pos, 0); pos += 2; + /* inScheme: explicit ECDSA/RSASSA + SHA-256 */ +#ifdef HAVE_ECC + PutU16BE(gCmd + pos, TPM_ALG_ECDSA); pos += 2; +#else + PutU16BE(gCmd + pos, TPM_ALG_RSASSA); pos += 2; +#endif + PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; + /* size = 0, offset = 0 -> digest mode */ + PutU16BE(gCmd + pos, 0); pos += 2; + PutU16BE(gCmd + pos, 0); pos += 2; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + /* Response layout (TPM_ST_SESSIONS): + * header(10) + paramSize(4) + TPM2B_ATTEST.size(2) + magic(4) + type(2) + * Attest type lives at offset 20. Spec requires 0x801C (NV_DIGEST). */ + AssertIntGT(rspSize, 22); + attestType = GetU16BE(gRsp + 20); + AssertIntEQ(attestType, 0x801C); + + /* Cleanup: flush key and undefine NV */ + FlushHandle(&ctx, keyH); + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_NV_UndefineSpace); pos += 4; + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; + PutU32BE(gCmd + pos, nvIdx); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + + FWTPM_Cleanup(&ctx); + printf("Test fwTPM:\tNV_Certify (digest mode):\tPassed\n"); +} +#endif /* !FWTPM_NO_ATTESTATION */ #endif /* !FWTPM_NO_NV */ /* ================================================================== */ @@ -2195,6 +2305,9 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_nv_define_write_read(); test_fwtpm_nv_read_public(); test_fwtpm_nv_counter(); +#ifndef FWTPM_NO_ATTESTATION + test_fwtpm_nv_certify_digest_mode(); +#endif #endif /* Hierarchy & misc */ diff --git a/wolftpm/tpm2.h b/wolftpm/tpm2.h index ae724e63..b24598c0 100644 --- a/wolftpm/tpm2.h +++ b/wolftpm/tpm2.h @@ -466,6 +466,7 @@ typedef enum { TPM_ST_ATTEST_QUOTE = 0x8018, TPM_ST_ATTEST_TIME = 0x8019, TPM_ST_ATTEST_CREATION = 0x801A, + TPM_ST_ATTEST_NV_DIGEST = 0x801C, TPM_ST_CREATION = 0x8021, TPM_ST_VERIFIED = 0x8022, TPM_ST_AUTH_SECRET = 0x8023, From 4437bef244e7efe3356c6dc192685a3cef4d2342 Mon Sep 17 00:00:00 2001 From: Aidan Date: Tue, 28 Apr 2026 09:10:08 -0700 Subject: [PATCH 02/24] F-3493 - https://fenrir.wolfssl.com/finding/3493 - examples/management/flush: fix copy-paste so HMAC sessions loop walks 0x02000000-0x02000003 --- examples/management/flush.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/management/flush.c b/examples/management/flush.c index f53d42b9..1a55d087 100644 --- a/examples/management/flush.c +++ b/examples/management/flush.c @@ -92,7 +92,7 @@ int TPM2_Flush_Tool(void* userCtx, int argc, char *argv[]) TPM2_FlushContext(&flushCtx); } /* Flush hmac sessions */ - for (handle=0x3000000; handle < 0x3000004; handle++) { + for (handle=0x2000000; handle < 0x2000004; handle++) { flushCtx.flushHandle = handle; printf("Freeing %X object\n", handle); TPM2_FlushContext(&flushCtx); From 1c2c80975147ac07dc76c4e17e1a2dd45c990018 Mon Sep 17 00:00:00 2001 From: Aidan Date: Tue, 28 Apr 2026 09:14:43 -0700 Subject: [PATCH 03/24] F-3494 - https://fenrir.wolfssl.com/finding/3494 - examples/pkcs7: reset offset and fix loop continuation so detached PKCS7 body actually gets written --- examples/pkcs7/pkcs7.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/pkcs7/pkcs7.c b/examples/pkcs7/pkcs7.c index d9052ef5..3bdc248b 100644 --- a/examples/pkcs7/pkcs7.c +++ b/examples/pkcs7/pkcs7.c @@ -196,7 +196,8 @@ static int PKCS7_SignVerifyEx(WOLFTPM2_DEV* dev, int tpmDevId, } /* Body - Data */ - do { + offset = 0; + while (1) { dataChunkSz = GetMyData(dataChunk, sizeof(dataChunk), offset); if (dataChunkSz == 0) break; @@ -208,7 +209,7 @@ static int PKCS7_SignVerifyEx(WOLFTPM2_DEV* dev, int tpmDevId, } offset += dataChunkSz; - } while (rc == 0); + } dataChunkSz = GetMyData(NULL, 0, 0); /* get total size */ /* Footer */ From 3b0147d48aa21fa9b22e2f7c3e757e0dfad9d351 Mon Sep 17 00:00:00 2001 From: Aidan Date: Tue, 28 Apr 2026 09:25:48 -0700 Subject: [PATCH 04/24] F-3496 - https://fenrir.wolfssl.com/finding/3496 - wolfTPM2_StartSession: enable parameter encryption attrs for salted sessions, not only bind+salt --- src/tpm2_wrap.c | 8 ++++---- tests/unit_tests.c | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index 63ff936c..1b9b2d3b 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -2359,10 +2359,10 @@ int wolfTPM2_StartSession(WOLFTPM2_DEV* dev, WOLFTPM2_SESSION* session, /* set session auth for key */ if (tpmKey) { TPMA_SESSION sessionAttributes = 0; - if (bind != NULL && - (encDecAlg == TPM_ALG_CFB || encDecAlg == TPM_ALG_XOR)) { - /* if parameter encryption is enabled and key bind set, enable - * encrypt/decrypt by default */ + if (encDecAlg == TPM_ALG_CFB || encDecAlg == TPM_ALG_XOR) { + /* if parameter encryption is enabled, enable encrypt/decrypt by + * default. Salted (tpmKey-only) sessions also have valid + * shared-secret state for parameter encryption. */ sessionAttributes |= (TPMA_SESSION_decrypt | TPMA_SESSION_encrypt); } wolfTPM2_SetAuth(dev, 0, tpmKey->handle.hndl, &tpmKey->handle.auth, diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 413052c7..174fdcfc 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -585,6 +585,37 @@ static void test_wolfTPM2_SetAuthHandle_PolicyAuthOffset(void) #endif } +/* Verify wolfTPM2_StartSession enables encrypt/decrypt attributes for + * salted (tpmKey-only, bind == NULL) sessions when caller selects a + * symmetric algorithm. Per TPM 2.0 spec, salted sessions have valid + * shared-secret state for parameter encryption. */ +static void test_wolfTPM2_StartSession_SaltedEncryptAttrs(void) +{ +#if !defined(WOLFTPM2_NO_WOLFCRYPT) + WOLFTPM2_DEV dev; + WOLFTPM2_KEY tpmKey; + WOLFTPM2_SESSION session; + TPMA_SESSION expected = TPMA_SESSION_decrypt | TPMA_SESSION_encrypt; + + XMEMSET(&dev, 0, sizeof(dev)); + XMEMSET(&tpmKey, 0, sizeof(tpmKey)); + XMEMSET(&session, 0, sizeof(session)); + + /* tpmKey with a non-NULL handle, no auth */ + tpmKey.handle.hndl = 0x80000000; + + /* Best effort - if no TPM is present the call returns early after the + * SetAuth path, which is what we want to inspect. */ + (void)wolfTPM2_StartSession(&dev, &session, &tpmKey, NULL, + TPM_SE_HMAC, TPM_ALG_CFB); + + AssertIntEQ((int)(dev.session[0].sessionAttributes & expected), + (int)expected); + + printf("Test TPM Wrapper:\tStartSession salted enc attrs:\tPassed\n"); +#endif +} + static void test_wolfTPM2_PolicyHash(void) { #ifndef WOLFTPM2_NO_WOLFCRYPT @@ -3229,6 +3260,7 @@ int unit_tests(int argc, char *argv[]) test_TPM2_Policy_NULL_Args(); test_wolfTPM2_PolicyAuthValue_AuthOffset(); test_wolfTPM2_SetAuthHandle_PolicyAuthOffset(); + test_wolfTPM2_StartSession_SaltedEncryptAttrs(); test_wolfTPM2_PolicyHash(); test_wolfTPM2_SensitiveToPrivate(); test_TPM2_KDFa(); From 1d7f46093ef3c7cf1e193a87d1a5ee19069926c6 Mon Sep 17 00:00:00 2001 From: Aidan Date: Tue, 28 Apr 2026 09:29:10 -0700 Subject: [PATCH 05/24] =?UTF-8?q?F-3272=20-=20https://fenrir.wolfssl.com/f?= =?UTF-8?q?inding/3272=20-=20fwtpm:=20FwParseAttestParams=20consumes=20ECD?= =?UTF-8?q?AA=20count=20field=20per=20Part=202=20=C2=A711.2.1.5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/fwtpm/fwtpm_command.c | 7 ++++ tests/fwtpm_unit_tests.c | 74 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 55cbfaa0..fd1409f8 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -11114,6 +11114,13 @@ static TPM_RC FwParseAttestParams(TPM2_Packet* cmd, int cmdSize, *sigHashAlg = TPM_ALG_NULL; if (*sigScheme != TPM_ALG_NULL) TPM2_Packet_ParseU16(cmd, sigHashAlg); + /* TPMS_SCHEME_ECDAA carries an additional UINT16 count after + * hashAlg per Part 2 §11.2.1.5. */ + if (*sigScheme == TPM_ALG_ECDAA) { + UINT16 ecdaaCount; + TPM2_Packet_ParseU16(cmd, &ecdaaCount); + (void)ecdaaCount; + } } return rc; diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index ccecf638..e0719c7f 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -1517,6 +1517,79 @@ static void test_fwtpm_nv_counter(void) } #ifndef FWTPM_NO_ATTESTATION +/* TPMT_SIG_SCHEME parser must consume the extra UINT16 count field for + * TPMS_SCHEME_ECDAA per Part 2 §11.2.1.5. Sending a Quote with + * inScheme.scheme = TPM_ALG_ECDAA followed by hashAlg + count + a single + * PCR selection (count=1) verifies that subsequent PCRselect parsing + * is not desynchronized. With the bug, pcrSelectionsCount reads as 0 + * because the count field is interpreted as the high 16 bits of the + * PCR-selection count. */ +static void test_fwtpm_quote_ecdaa_scheme(void) +{ + FWTPM_CTX ctx; + int pos, rspSize; + UINT32 keyH; + UINT16 nameSz; + UINT16 extraSz; + UINT32 pcrSelCount; + int pcrSelOffset; + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + +#ifdef HAVE_ECC + keyH = CreatePrimaryHelper(&ctx, TPM_ALG_ECC); +#else + keyH = CreatePrimaryHelper(&ctx, TPM_ALG_RSA); +#endif + AssertIntNE(keyH, 0); + + /* Build TPM2_Quote with ECDAA inScheme. */ + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_Quote); pos += 4; + PutU32BE(gCmd + pos, keyH); pos += 4; /* signHandle */ + pos = AppendPwAuth(gCmd, pos, NULL, 0); + /* qualifyingData (TPM2B_DATA) - empty */ + PutU16BE(gCmd + pos, 0); pos += 2; + /* inScheme: ECDAA + hashAlg + count */ + PutU16BE(gCmd + pos, TPM_ALG_ECDAA); pos += 2; + PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; + PutU16BE(gCmd + pos, 0); pos += 2; /* ECDAA count */ + /* PCRselect: count=1, hashAlg=SHA256, sizeOfSelect=3, no PCRs */ + PutU32BE(gCmd + pos, 1); pos += 4; + PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; + gCmd[pos++] = 3; + gCmd[pos++] = 0; + gCmd[pos++] = 0; + gCmd[pos++] = 0; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + /* Walk response: header(10) + paramSize(4) + TPM2B_ATTEST.size(2) + * + magic(4) + type(2) + qualifiedSigner.size(2) reads N at offset 22 */ + AssertIntGT(rspSize, 24); + nameSz = GetU16BE(gRsp + 22); + AssertIntGT(nameSz, 0); + /* Then qualifiedSigner.name(nameSz) + extraData.size(2) */ + extraSz = GetU16BE(gRsp + 24 + nameSz); + /* Then extraData(extraSz) + clockInfo(17) + firmwareVersion(8) */ + pcrSelOffset = 24 + nameSz + 2 + extraSz + 17 + 8; + AssertIntGT(rspSize, pcrSelOffset + 4); + pcrSelCount = GetU32BE(gRsp + pcrSelOffset); + /* With the ECDAA count consumed correctly, pcrSelCount reflects the + * caller-supplied value of 1. With the bug, the count field is read + * as the high 16 bits of pcrSelCount, yielding 0. */ + AssertIntEQ(pcrSelCount, 1); + + FlushHandle(&ctx, keyH); + FWTPM_Cleanup(&ctx); + printf("Test fwTPM:\tQuote(ECDAA scheme):\t\tPassed\n"); +} + /* NV_Certify with size=0 and offset=0 must emit TPMS_NV_DIGEST_CERTIFY_INFO * inside a TPM_ST_ATTEST_NV_DIGEST (0x801C) attest, not the regular * TPMS_NV_CERTIFY_INFO inside TPM_ST_ATTEST_NV (0x8014). Per TPM 2.0 @@ -2307,6 +2380,7 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_nv_counter(); #ifndef FWTPM_NO_ATTESTATION test_fwtpm_nv_certify_digest_mode(); + test_fwtpm_quote_ecdaa_scheme(); #endif #endif From aa1957895d4b6e8927731aecca8354d19115cee2 Mon Sep 17 00:00:00 2001 From: Aidan Date: Tue, 28 Apr 2026 09:34:06 -0700 Subject: [PATCH 06/24] =?UTF-8?q?F-3273=20-=20https://fenrir.wolfssl.com/f?= =?UTF-8?q?inding/3273=20-=20fwtpm:=20FwCmd=5FSign=20consumes=20ECDAA=20co?= =?UTF-8?q?unt=20field=20per=20Part=202=20=C2=A711.2.1.5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/fwtpm/fwtpm_command.c | 11 ++++ tests/fwtpm_unit_tests.c | 114 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index fd1409f8..0279d210 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -5724,6 +5724,17 @@ static TPM_RC FwCmd_Sign(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (rc == 0) TPM2_Packet_ParseU16(cmd, &sigHashAlg); } + /* TPMS_SCHEME_ECDAA carries an additional UINT16 count after + * hashAlg per Part 2 §11.2.1.5. */ + if (rc == 0 && sigScheme == TPM_ALG_ECDAA) { + UINT16 ecdaaCount; + if (cmd->pos + 2 > cmdSize) + rc = TPM_RC_COMMAND_SIZE; + if (rc == 0) { + TPM2_Packet_ParseU16(cmd, &ecdaaCount); + (void)ecdaaCount; + } + } } if (rc == 0) { diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index e0719c7f..81134a73 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -1096,6 +1096,67 @@ static UINT32 CreatePrimaryHelper(FWTPM_CTX* ctx, TPM_ALG_ID alg) return GetU32BE(gRsp + TPM2_HEADER_SIZE); } +#ifdef HAVE_ECC +/* Build a non-restricted ECC-P256 sign-capable primary (for tests that + * require TPMA_OBJECT_sign and a key with no scheme bound at create time). */ +static int BuildCreatePrimaryEccSignCmd(byte* buf) +{ + int pos = 0; + int pubAreaStart, pubAreaLen; + int sensStart, sensLen; + + PutU16BE(buf + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(buf + pos, 0); pos += 4; + PutU32BE(buf + pos, TPM_CC_CreatePrimary); pos += 4; + PutU32BE(buf + pos, TPM_RH_OWNER); pos += 4; + PutU32BE(buf + pos, 9); pos += 4; + PutU32BE(buf + pos, TPM_RS_PW); pos += 4; + PutU16BE(buf + pos, 0); pos += 2; + buf[pos++] = 0; + PutU16BE(buf + pos, 0); pos += 2; + + sensStart = pos; + PutU16BE(buf + pos, 0); pos += 2; + PutU16BE(buf + pos, 0); pos += 2; + PutU16BE(buf + pos, 0); pos += 2; + sensLen = pos - sensStart - 2; + PutU16BE(buf + sensStart, (UINT16)sensLen); + + pubAreaStart = pos; + PutU16BE(buf + pos, 0); pos += 2; + PutU16BE(buf + pos, TPM_ALG_ECC); pos += 2; + PutU16BE(buf + pos, TPM_ALG_SHA256); pos += 2; + /* fixedTPM | fixedParent | sensitiveDataOrigin | userWithAuth | noDA | + * sign (non-restricted, sign-only) */ + PutU32BE(buf + pos, 0x00040472); pos += 4; + PutU16BE(buf + pos, 0); pos += 2; /* authPolicy */ + PutU16BE(buf + pos, TPM_ALG_NULL); pos += 2; /* sym.algorithm = NULL */ + PutU16BE(buf + pos, TPM_ALG_NULL); pos += 2; /* scheme = NULL */ + PutU16BE(buf + pos, TPM_ECC_NIST_P256); pos += 2; + PutU16BE(buf + pos, TPM_ALG_NULL); pos += 2; /* kdf */ + PutU16BE(buf + pos, 0); pos += 2; /* x */ + PutU16BE(buf + pos, 0); pos += 2; /* y */ + + pubAreaLen = pos - pubAreaStart - 2; + PutU16BE(buf + pubAreaStart, (UINT16)pubAreaLen); + + PutU16BE(buf + pos, 0); pos += 2; /* outsideInfo */ + PutU32BE(buf + pos, 0); pos += 4; /* creationPCR */ + + PutU32BE(buf + 2, (UINT32)pos); + return pos; +} + +static UINT32 CreatePrimaryEccSignHelper(FWTPM_CTX* ctx) +{ + int cmdSz, rspSize = 0; + cmdSz = BuildCreatePrimaryEccSignCmd(gCmd); + FWTPM_ProcessCommand(ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + if (GetRspRC(gRsp) != TPM_RC_SUCCESS) return 0; + return GetU32BE(gRsp + TPM2_HEADER_SIZE); +} +#endif + /* Helper: flush a handle */ static void FlushHandle(FWTPM_CTX* ctx, UINT32 handle) { @@ -1590,6 +1651,56 @@ static void test_fwtpm_quote_ecdaa_scheme(void) printf("Test fwTPM:\tQuote(ECDAA scheme):\t\tPassed\n"); } +#ifdef HAVE_ECC +/* TPM2_Sign inScheme parser must consume the extra UINT16 count for + * TPMS_SCHEME_ECDAA per Part 2 §11.2.1.5. With the bug, the trailing + * TPMT_TK_HASHCHECK ticket parses from the wrong wire offset and the + * command fails. */ +static void test_fwtpm_sign_ecdaa_scheme(void) +{ + FWTPM_CTX ctx; + int pos, rspSize; + UINT32 keyH; + byte digest[32]; + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + keyH = CreatePrimaryEccSignHelper(&ctx); + AssertIntNE(keyH, 0); + + memset(digest, 0xAA, sizeof(digest)); + + /* Build TPM2_Sign with ECDAA inScheme and an empty ticket + * (TPM_RH_NULL hierarchy + zero size, valid for non-restricted keys). */ + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_Sign); pos += 4; + PutU32BE(gCmd + pos, keyH); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + /* digest */ + PutU16BE(gCmd + pos, (UINT16)sizeof(digest)); pos += 2; + memcpy(gCmd + pos, digest, sizeof(digest)); pos += sizeof(digest); + /* inScheme: ECDAA + hashAlg + count */ + PutU16BE(gCmd + pos, TPM_ALG_ECDAA); pos += 2; + PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; + PutU16BE(gCmd + pos, 0); pos += 2; /* ECDAA count */ + /* validation (TPMT_TK_HASHCHECK): tag + hierarchy + digest */ + PutU16BE(gCmd + pos, TPM_ST_HASHCHECK); pos += 2; + PutU32BE(gCmd + pos, TPM_RH_NULL); pos += 4; + PutU16BE(gCmd + pos, 0); pos += 2; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + FlushHandle(&ctx, keyH); + FWTPM_Cleanup(&ctx); + printf("Test fwTPM:\tSign(ECDAA scheme):\t\tPassed\n"); +} +#endif /* HAVE_ECC */ + /* NV_Certify with size=0 and offset=0 must emit TPMS_NV_DIGEST_CERTIFY_INFO * inside a TPM_ST_ATTEST_NV_DIGEST (0x801C) attest, not the regular * TPMS_NV_CERTIFY_INFO inside TPM_ST_ATTEST_NV (0x8014). Per TPM 2.0 @@ -2381,6 +2492,9 @@ int fwtpm_unit_tests(int argc, char *argv[]) #ifndef FWTPM_NO_ATTESTATION test_fwtpm_nv_certify_digest_mode(); test_fwtpm_quote_ecdaa_scheme(); +#ifdef HAVE_ECC + test_fwtpm_sign_ecdaa_scheme(); +#endif #endif #endif From 588455fd391a2746301435cce20c2a4fdfb833b0 Mon Sep 17 00:00:00 2001 From: Aidan Date: Tue, 28 Apr 2026 09:42:41 -0700 Subject: [PATCH 07/24] F-3274 - https://fenrir.wolfssl.com/finding/3274 - fwtpm: FwCmd_CertifyCreation consumes ECDAA count field per Part 2 Sec. 11.2.1.5 --- src/fwtpm/fwtpm_command.c | 15 ++++++++--- tests/fwtpm_unit_tests.c | 56 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 64 insertions(+), 7 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 0279d210..8bbba65b 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -5725,7 +5725,7 @@ static TPM_RC FwCmd_Sign(FWTPM_CTX* ctx, TPM2_Packet* cmd, TPM2_Packet_ParseU16(cmd, &sigHashAlg); } /* TPMS_SCHEME_ECDAA carries an additional UINT16 count after - * hashAlg per Part 2 §11.2.1.5. */ + * hashAlg per Part 2 Sec. 11.2.1.5. */ if (rc == 0 && sigScheme == TPM_ALG_ECDAA) { UINT16 ecdaaCount; if (cmd->pos + 2 > cmdSize) @@ -11126,7 +11126,7 @@ static TPM_RC FwParseAttestParams(TPM2_Packet* cmd, int cmdSize, if (*sigScheme != TPM_ALG_NULL) TPM2_Packet_ParseU16(cmd, sigHashAlg); /* TPMS_SCHEME_ECDAA carries an additional UINT16 count after - * hashAlg per Part 2 §11.2.1.5. */ + * hashAlg per Part 2 Sec. 11.2.1.5. */ if (*sigScheme == TPM_ALG_ECDAA) { UINT16 ecdaaCount; TPM2_Packet_ParseU16(cmd, &ecdaaCount); @@ -11437,6 +11437,13 @@ static TPM_RC FwCmd_CertifyCreation(FWTPM_CTX* ctx, TPM2_Packet* cmd, sigHashAlg = TPM_ALG_NULL; if (sigScheme != TPM_ALG_NULL) TPM2_Packet_ParseU16(cmd, &sigHashAlg); + /* TPMS_SCHEME_ECDAA carries an additional UINT16 count after + * hashAlg per Part 2 Sec. 11.2.1.5. */ + if (sigScheme == TPM_ALG_ECDAA) { + UINT16 ecdaaCount; + TPM2_Packet_ParseU16(cmd, &ecdaaCount); + (void)ecdaaCount; + } } /* creationTicket verification per TPM 2.0 Part 3 Section 18.3 */ @@ -11648,7 +11655,7 @@ static TPM_RC FwCmd_NV_Certify(FWTPM_CTX* ctx, TPM2_Packet* cmd, &qualifyingData, &sigScheme, &sigHashAlg); } - /* Per Part 3 §31.16.1: TPM_RH_NULL signHandle requires non-NULL + /* Per Part 3 Sec. 31.16.1: TPM_RH_NULL signHandle requires non-NULL * scheme and hash algorithm, otherwise return TPM_RC_SCHEME. */ if (rc == 0 && signHandle == TPM_RH_NULL && (sigScheme == TPM_ALG_NULL || sigHashAlg == TPM_ALG_NULL)) { @@ -11665,7 +11672,7 @@ static TPM_RC FwCmd_NV_Certify(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } if (rc == 0) { - /* Per Part 3 §31.16.1: when both size and offset are zero, the + /* Per Part 3 Sec. 31.16.1: when both size and offset are zero, the * response must contain a TPMS_NV_DIGEST_CERTIFY_INFO with the * digest of the entire NV index, instead of TPMS_NV_CERTIFY_INFO. */ if (readSize == 0 && readOffset == 0) { diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index 81134a73..a8306328 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -1579,7 +1579,7 @@ static void test_fwtpm_nv_counter(void) #ifndef FWTPM_NO_ATTESTATION /* TPMT_SIG_SCHEME parser must consume the extra UINT16 count field for - * TPMS_SCHEME_ECDAA per Part 2 §11.2.1.5. Sending a Quote with + * TPMS_SCHEME_ECDAA per Part 2 Sec. 11.2.1.5. Sending a Quote with * inScheme.scheme = TPM_ALG_ECDAA followed by hashAlg + count + a single * PCR selection (count=1) verifies that subsequent PCRselect parsing * is not desynchronized. With the bug, pcrSelectionsCount reads as 0 @@ -1652,8 +1652,57 @@ static void test_fwtpm_quote_ecdaa_scheme(void) } #ifdef HAVE_ECC +/* TPM2_CertifyCreation inScheme parser must consume the extra UINT16 + * count for TPMS_SCHEME_ECDAA per Part 2 Sec. 11.2.1.5. With the bug, the + * trailing TPMT_TK_CREATION ticket parses from a wrong wire offset and + * the tag check fails. */ +static void test_fwtpm_certify_creation_ecdaa_scheme(void) +{ + FWTPM_CTX ctx; + int pos, rspSize; + UINT32 keyH; + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + keyH = CreatePrimaryEccSignHelper(&ctx); + AssertIntNE(keyH, 0); + + /* Build TPM2_CertifyCreation with ECDAA inScheme. Use the same key as + * both signHandle and objectHandle to avoid additional setup. The + * ticket carries hier=TPM_RH_NULL with a zero digest so HMAC + * validation is skipped (only the tag is checked). */ + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_CertifyCreation); pos += 4; + PutU32BE(gCmd + pos, keyH); pos += 4; /* signHandle */ + PutU32BE(gCmd + pos, keyH); pos += 4; /* objectHandle */ + pos = AppendPwAuth(gCmd, pos, NULL, 0); + /* qualifyingData (empty) */ + PutU16BE(gCmd + pos, 0); pos += 2; + /* creationHash (empty) */ + PutU16BE(gCmd + pos, 0); pos += 2; + /* inScheme: ECDAA + hashAlg + count */ + PutU16BE(gCmd + pos, TPM_ALG_ECDAA); pos += 2; + PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; + PutU16BE(gCmd + pos, 0); pos += 2; /* ECDAA count */ + /* creationTicket (TPMT_TK_CREATION): tag + hier + digest */ + PutU16BE(gCmd + pos, TPM_ST_CREATION); pos += 2; + PutU32BE(gCmd + pos, TPM_RH_NULL); pos += 4; + PutU16BE(gCmd + pos, 0); pos += 2; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + FlushHandle(&ctx, keyH); + FWTPM_Cleanup(&ctx); + printf("Test fwTPM:\tCertifyCreation(ECDAA scheme):\tPassed\n"); +} + /* TPM2_Sign inScheme parser must consume the extra UINT16 count for - * TPMS_SCHEME_ECDAA per Part 2 §11.2.1.5. With the bug, the trailing + * TPMS_SCHEME_ECDAA per Part 2 Sec. 11.2.1.5. With the bug, the trailing * TPMT_TK_HASHCHECK ticket parses from the wrong wire offset and the * command fails. */ static void test_fwtpm_sign_ecdaa_scheme(void) @@ -1704,7 +1753,7 @@ static void test_fwtpm_sign_ecdaa_scheme(void) /* NV_Certify with size=0 and offset=0 must emit TPMS_NV_DIGEST_CERTIFY_INFO * inside a TPM_ST_ATTEST_NV_DIGEST (0x801C) attest, not the regular * TPMS_NV_CERTIFY_INFO inside TPM_ST_ATTEST_NV (0x8014). Per TPM 2.0 - * Part 3 §31.16.1. */ + * Part 3 Sec. 31.16.1. */ static void test_fwtpm_nv_certify_digest_mode(void) { FWTPM_CTX ctx; @@ -2494,6 +2543,7 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_quote_ecdaa_scheme(); #ifdef HAVE_ECC test_fwtpm_sign_ecdaa_scheme(); + test_fwtpm_certify_creation_ecdaa_scheme(); #endif #endif #endif From d04a78ee6c88cb97a4a50ec0a3dba5f25a3103e7 Mon Sep 17 00:00:00 2001 From: Aidan Date: Tue, 28 Apr 2026 09:53:12 -0700 Subject: [PATCH 08/24] F-3502 - https://fenrir.wolfssl.com/finding/3502 - wolftpm: TPM2_Packet_ParseAttest decodes TPM_ST_ATTEST_NV_DIGEST as TPMS_NV_DIGEST_CERTIFY_INFO --- src/tpm2_packet.c | 10 +++++++++ tests/unit_tests.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++ wolftpm/tpm2.h | 6 +++++ 3 files changed, 72 insertions(+) diff --git a/src/tpm2_packet.c b/src/tpm2_packet.c index 97322711..3c57bd79 100644 --- a/src/tpm2_packet.c +++ b/src/tpm2_packet.c @@ -1342,6 +1342,16 @@ void TPM2_Packet_ParseAttest(TPM2_Packet* packet, TPMS_ATTEST* out) out->attested.nv.nvContents.buffer, (UINT16)sizeof(out->attested.nv.nvContents.buffer)); break; + case TPM_ST_ATTEST_NV_DIGEST: + TPM2_Packet_ParseU16Buf(packet, + &out->attested.nvDigest.indexName.size, + out->attested.nvDigest.indexName.name, + (UINT16)sizeof(out->attested.nvDigest.indexName.name)); + TPM2_Packet_ParseU16Buf(packet, + &out->attested.nvDigest.nvDigest.size, + out->attested.nvDigest.nvDigest.buffer, + (UINT16)sizeof(out->attested.nvDigest.nvDigest.buffer)); + break; default: /* unknown attestation type */ #ifdef DEBUG_WOLFTPM diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 174fdcfc..7484be4b 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -1902,6 +1902,61 @@ static void test_TPM2_ECC_Parameters_EcdaaResponseParse(void) printf("Test TPM Wrapper:\tEcdaaResponseParse:\t\tPassed\n"); } +/* TPM2_ParseAttest must handle TPM_ST_ATTEST_NV_DIGEST (0x801C) and decode + * TPMS_NV_DIGEST_CERTIFY_INFO. Pre-fix, the switch fell through to default + * and left out->attested zeroed. */ +static void test_TPM2_ParseAttest_NvDigest(void) +{ + TPM2B_ATTEST attestBlob; + TPMS_ATTEST out; + const byte name[] = {0x00, 0x0B, 0x11, 0x22, 0x33, 0x44}; /* alg + 4 bytes */ + const byte digest[] = {0xAA, 0xBB, 0xCC, 0xDD}; + byte* buf; + int pos = 0; + int rc; + + XMEMSET(&attestBlob, 0, sizeof(attestBlob)); + buf = attestBlob.attestationData; + + /* magic */ + buf[pos++] = (byte)((TPM_GENERATED_VALUE >> 24) & 0xFF); + buf[pos++] = (byte)((TPM_GENERATED_VALUE >> 16) & 0xFF); + buf[pos++] = (byte)((TPM_GENERATED_VALUE >> 8) & 0xFF); + buf[pos++] = (byte)(TPM_GENERATED_VALUE & 0xFF); + /* type = TPM_ST_ATTEST_NV_DIGEST (0x801C) */ + buf[pos++] = 0x80; buf[pos++] = 0x1C; + /* qualifiedSigner: empty */ + buf[pos++] = 0; buf[pos++] = 0; + /* extraData: empty */ + buf[pos++] = 0; buf[pos++] = 0; + /* clockInfo: clock(8)+resetCount(4)+restartCount(4)+safe(1) */ + XMEMSET(buf + pos, 0, 17); pos += 17; + /* firmwareVersion */ + XMEMSET(buf + pos, 0, 8); pos += 8; + /* TPMS_NV_DIGEST_CERTIFY_INFO: indexName + nvDigest */ + buf[pos++] = 0; buf[pos++] = (byte)sizeof(name); + XMEMCPY(buf + pos, name, sizeof(name)); pos += sizeof(name); + buf[pos++] = 0; buf[pos++] = (byte)sizeof(digest); + XMEMCPY(buf + pos, digest, sizeof(digest)); pos += sizeof(digest); + + attestBlob.size = (UINT16)pos; + + XMEMSET(&out, 0, sizeof(out)); + rc = TPM2_ParseAttest(&attestBlob, &out); + AssertIntEQ(rc, TPM_RC_SUCCESS); + + AssertIntEQ(out.magic, TPM_GENERATED_VALUE); + AssertIntEQ(out.type, 0x801C); + AssertIntEQ(out.attested.nvDigest.indexName.size, sizeof(name)); + AssertIntEQ(XMEMCMP(out.attested.nvDigest.indexName.name, name, + sizeof(name)), 0); + AssertIntEQ(out.attested.nvDigest.nvDigest.size, sizeof(digest)); + AssertIntEQ(XMEMCMP(out.attested.nvDigest.nvDigest.buffer, digest, + sizeof(digest)), 0); + + printf("Test TPM Wrapper:\tParseAttest NV_DIGEST:\t\tPassed\n"); +} + static void test_TPM2_KeyedHashScheme_XorSerialize(void) { TPM2_Packet packet; @@ -3289,6 +3344,7 @@ int unit_tests(int argc, char *argv[]) #endif test_TPM2_SchemeSerialize(); test_TPM2_ECC_Parameters_EcdaaResponseParse(); + test_TPM2_ParseAttest_NvDigest(); test_TPM2_KeyedHashScheme_XorSerialize(); test_TPM2_Signature_EcSchnorrSm2Serialize(); test_TPM2_Sensitive_Roundtrip(); diff --git a/wolftpm/tpm2.h b/wolftpm/tpm2.h index b24598c0..facc5de3 100644 --- a/wolftpm/tpm2.h +++ b/wolftpm/tpm2.h @@ -1197,6 +1197,11 @@ typedef struct TPMS_NV_CERTIFY_INFO { TPM2B_MAX_NV_BUFFER nvContents; } TPMS_NV_CERTIFY_INFO; +typedef struct TPMS_NV_DIGEST_CERTIFY_INFO { + TPM2B_NAME indexName; + TPM2B_DIGEST nvDigest; +} TPMS_NV_DIGEST_CERTIFY_INFO; + typedef TPM_ST TPMI_ST_ATTEST; typedef union TPMU_ATTEST { @@ -1207,6 +1212,7 @@ typedef union TPMU_ATTEST { TPMS_SESSION_AUDIT_INFO sessionAudit; /* TPM_ST_ATTEST_SESSION_AUDIT */ TPMS_TIME_ATTEST_INFO time; /* TPM_ST_ATTEST_TIME */ TPMS_NV_CERTIFY_INFO nv; /* TPM_ST_ATTEST_NV */ + TPMS_NV_DIGEST_CERTIFY_INFO nvDigest; /* TPM_ST_ATTEST_NV_DIGEST */ } TPMU_ATTEST; typedef struct TPMS_ATTEST { From cae955fa21984c0025668fe8e90c7becafbdf68a Mon Sep 17 00:00:00 2001 From: Aidan Date: Tue, 28 Apr 2026 09:58:36 -0700 Subject: [PATCH 09/24] F-3504 - https://fenrir.wolfssl.com/finding/3504 - wolftpm: map wolfCrypt Brainpool curves to TPM_ECC_BP_*_R1 (0x003X), not Barreto-Naehrig BN_P256 --- src/tpm2.c | 21 +++++++++++++++++++-- tests/unit_tests.c | 30 ++++++++++++++++++++++++++++++ wolftpm/tpm2.h | 3 +++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/tpm2.c b/src/tpm2.c index 1d7e5671..dd427390 100644 --- a/src/tpm2.c +++ b/src/tpm2.c @@ -6488,9 +6488,13 @@ int TPM2_GetCurveSize(TPM_ECC_CURVE curveID) case TPM_ECC_NIST_P256: case TPM_ECC_BN_P256: case TPM_ECC_SM2_P256: + case TPM_ECC_BP_P256_R1: return 32; case TPM_ECC_NIST_P384: + case TPM_ECC_BP_P384_R1: return 48; + case TPM_ECC_BP_P512_R1: + return 64; case TPM_ECC_NIST_P521: return 66; case TPM_ECC_BN_P638: @@ -6520,7 +6524,13 @@ int TPM2_GetTpmCurve(int curve_id) ret = TPM_ECC_NIST_P521; break; case ECC_BRAINPOOLP256R1: - ret = TPM_ECC_BN_P256; + ret = TPM_ECC_BP_P256_R1; + break; + case ECC_BRAINPOOLP384R1: + ret = TPM_ECC_BP_P384_R1; + break; + case ECC_BRAINPOOLP512R1: + ret = TPM_ECC_BP_P512_R1; break; case TPM_ECC_BN_P638: default: @@ -6551,9 +6561,16 @@ int TPM2_GetWolfCurve(int curve_id) case TPM_ECC_NIST_P521: ret = ECC_SECP521R1; break; - case TPM_ECC_BN_P256: + case TPM_ECC_BP_P256_R1: ret = ECC_BRAINPOOLP256R1; break; + case TPM_ECC_BP_P384_R1: + ret = ECC_BRAINPOOLP384R1; + break; + case TPM_ECC_BP_P512_R1: + ret = ECC_BRAINPOOLP512R1; + break; + case TPM_ECC_BN_P256: case TPM_ECC_BN_P638: default: ret = ECC_CURVE_OID_E; diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 7484be4b..d9cc3403 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -1957,6 +1957,35 @@ static void test_TPM2_ParseAttest_NvDigest(void) printf("Test TPM Wrapper:\tParseAttest NV_DIGEST:\t\tPassed\n"); } +/* TPM2_GetTpmCurve / TPM2_GetWolfCurve must map wolfCrypt's + * ECC_BRAINPOOLP256R1 to TPM_ECC_BP_P256_R1 (0x0030), not + * TPM_ECC_BN_P256 (0x0010, Barreto-Naehrig). Pre-fix the two were + * conflated, producing an on-the-wire curve ID that is a different + * mathematical curve. */ +static void test_TPM2_BrainpoolCurveMapping(void) +{ +#if !defined(WOLFTPM2_NO_WOLFCRYPT) && defined(HAVE_ECC) + AssertIntEQ(TPM2_GetTpmCurve(ECC_BRAINPOOLP256R1), TPM_ECC_BP_P256_R1); + AssertIntEQ(TPM2_GetTpmCurve(ECC_BRAINPOOLP384R1), TPM_ECC_BP_P384_R1); + AssertIntEQ(TPM2_GetTpmCurve(ECC_BRAINPOOLP512R1), TPM_ECC_BP_P512_R1); + + AssertIntEQ(TPM2_GetWolfCurve(TPM_ECC_BP_P256_R1), ECC_BRAINPOOLP256R1); + AssertIntEQ(TPM2_GetWolfCurve(TPM_ECC_BP_P384_R1), ECC_BRAINPOOLP384R1); + AssertIntEQ(TPM2_GetWolfCurve(TPM_ECC_BP_P512_R1), ECC_BRAINPOOLP512R1); + + /* TPM_ECC_BN_P256 (Barreto-Naehrig pairing curve) has no wolfCrypt + * equivalent and must report ECC_CURVE_OID_E rather than aliasing + * to a Brainpool ID. */ + AssertIntEQ(TPM2_GetWolfCurve(TPM_ECC_BN_P256), ECC_CURVE_OID_E); + + /* Sanity: NIST mappings still round-trip. */ + AssertIntEQ(TPM2_GetTpmCurve(ECC_SECP256R1), TPM_ECC_NIST_P256); + AssertIntEQ(TPM2_GetWolfCurve(TPM_ECC_NIST_P256), ECC_SECP256R1); + + printf("Test TPM Wrapper:\tBrainpool curve mapping:\tPassed\n"); +#endif +} + static void test_TPM2_KeyedHashScheme_XorSerialize(void) { TPM2_Packet packet; @@ -3345,6 +3374,7 @@ int unit_tests(int argc, char *argv[]) test_TPM2_SchemeSerialize(); test_TPM2_ECC_Parameters_EcdaaResponseParse(); test_TPM2_ParseAttest_NvDigest(); + test_TPM2_BrainpoolCurveMapping(); test_TPM2_KeyedHashScheme_XorSerialize(); test_TPM2_Signature_EcSchnorrSm2Serialize(); test_TPM2_Sensitive_Roundtrip(); diff --git a/wolftpm/tpm2.h b/wolftpm/tpm2.h index facc5de3..545c4ba3 100644 --- a/wolftpm/tpm2.h +++ b/wolftpm/tpm2.h @@ -130,6 +130,9 @@ typedef enum { TPM_ECC_BN_P256 = 0x0010, TPM_ECC_BN_P638 = 0x0011, TPM_ECC_SM2_P256 = 0x0020, + TPM_ECC_BP_P256_R1 = 0x0030, + TPM_ECC_BP_P384_R1 = 0x0031, + TPM_ECC_BP_P512_R1 = 0x0032, } TPM_ECC_CURVE_T; typedef UINT16 TPM_ECC_CURVE; From e7202aac6cb5ddaa52b00c6797c98a4113c180e8 Mon Sep 17 00:00:00 2001 From: Aidan Date: Tue, 28 Apr 2026 10:05:48 -0700 Subject: [PATCH 10/24] F-3495 - https://fenrir.wolfssl.com/finding/3495 - examples/keygen: guard primary unload against NULL on early init failure --- examples/keygen/keygen.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/keygen/keygen.c b/examples/keygen/keygen.c index f54a845e..7ab0fcbd 100644 --- a/examples/keygen/keygen.c +++ b/examples/keygen/keygen.c @@ -481,7 +481,8 @@ int TPM2_Keygen_Example(void* userCtx, int argc, char *argv[]) } /* Close handles */ - wolfTPM2_UnloadHandle(&dev, &primary->handle); + if (primary != NULL) + wolfTPM2_UnloadHandle(&dev, &primary->handle); wolfTPM2_UnloadHandle(&dev, &newKeyBlob.handle); wolfTPM2_UnloadHandle(&dev, &tpmSession.handle); From b7d52b5810031946f63600e55072f750987186b5 Mon Sep 17 00:00:00 2001 From: Aidan Date: Tue, 28 Apr 2026 10:10:31 -0700 Subject: [PATCH 11/24] F-3497 - https://fenrir.wolfssl.com/finding/3497 - wolfTPM2_LoadEccPublicKey_ex: caller-controlled scheme, hashAlg, and objectAttributes for ECDH peer keys --- src/tpm2_wrap.c | 26 ++++++++++++---- tests/unit_tests.c | 75 +++++++++++++++++++++++++++++++++++++++++++++ wolftpm/tpm2_wrap.h | 21 +++++++++++++ 3 files changed, 116 insertions(+), 6 deletions(-) diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index 1b9b2d3b..af66dc36 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -3536,8 +3536,11 @@ int wolfTPM2_LoadRsaPrivateKey(WOLFTPM2_DEV* dev, const WOLFTPM2_KEY* parentKey, exponent, rsaPriv, rsaPrivSz, TPM_ALG_NULL, TPM_ALG_NULL); } -int wolfTPM2_LoadEccPublicKey(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, int curveId, - const byte* eccPubX, word32 eccPubXSz, const byte* eccPubY, word32 eccPubYSz) +int wolfTPM2_LoadEccPublicKey_ex(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, + int curveId, const byte* eccPubX, word32 eccPubXSz, + const byte* eccPubY, word32 eccPubYSz, + TPMI_ALG_ECC_SCHEME scheme, TPMI_ALG_HASH hashAlg, + TPMA_OBJECT objectAttributes) { TPM2B_PUBLIC pub; @@ -3552,11 +3555,13 @@ int wolfTPM2_LoadEccPublicKey(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, int curveId, pub.publicArea.type = TPM_ALG_ECC; /* make sure nameAlg is set for ticket */ pub.publicArea.nameAlg = WOLFTPM2_WRAP_DIGEST; - pub.publicArea.objectAttributes = TPMA_OBJECT_sign | TPMA_OBJECT_noDA; + pub.publicArea.objectAttributes = objectAttributes; pub.publicArea.parameters.eccDetail.symmetric.algorithm = TPM_ALG_NULL; - pub.publicArea.parameters.eccDetail.scheme.scheme = TPM_ALG_ECDSA; - pub.publicArea.parameters.eccDetail.scheme.details.ecdsa.hashAlg = - WOLFTPM2_WRAP_DIGEST; + pub.publicArea.parameters.eccDetail.scheme.scheme = scheme; + if (scheme != TPM_ALG_NULL) { + pub.publicArea.parameters.eccDetail.scheme.details.any.hashAlg = + hashAlg; + } pub.publicArea.parameters.eccDetail.curveID = curveId; pub.publicArea.parameters.eccDetail.kdf.scheme = TPM_ALG_NULL; pub.publicArea.unique.ecc.x.size = eccPubXSz; @@ -3567,6 +3572,15 @@ int wolfTPM2_LoadEccPublicKey(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, int curveId, return wolfTPM2_LoadPublicKey(dev, key, &pub); } +int wolfTPM2_LoadEccPublicKey(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, int curveId, + const byte* eccPubX, word32 eccPubXSz, const byte* eccPubY, word32 eccPubYSz) +{ + return wolfTPM2_LoadEccPublicKey_ex(dev, key, curveId, + eccPubX, eccPubXSz, eccPubY, eccPubYSz, + TPM_ALG_ECDSA, WOLFTPM2_WRAP_DIGEST, + TPMA_OBJECT_sign | TPMA_OBJECT_noDA); +} + int wolfTPM2_ImportEccPrivateKeySeed(WOLFTPM2_DEV* dev, const WOLFTPM2_KEY* parentKey, WOLFTPM2_KEYBLOB* keyBlob, int curveId, const byte* eccPubX, word32 eccPubXSz, diff --git a/tests/unit_tests.c b/tests/unit_tests.c index d9cc3403..e3ed8ba8 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -1957,6 +1957,80 @@ static void test_TPM2_ParseAttest_NvDigest(void) printf("Test TPM Wrapper:\tParseAttest NV_DIGEST:\t\tPassed\n"); } +/* wolfTPM2_LoadEccPublicKey_ex must honor caller-provided scheme, hashAlg + * and objectAttributes (in particular, allow TPMA_OBJECT_decrypt for ECDH + * peer keys). The legacy wolfTPM2_LoadEccPublicKey must continue to default + * to ECDSA + sign attribute. */ +static void test_wolfTPM2_LoadEccPublicKey_Ex(void) +{ +#if !defined(WOLFTPM2_NO_WOLFCRYPT) && defined(HAVE_ECC) + int rc; + WOLFTPM2_DEV dev; + WOLFTPM2_KEY srk; + WOLFTPM2_KEY peer; + TPMT_PUBLIC pub; + byte xBuf[32]; + byte yBuf[32]; + word32 xSz, ySz; + + XMEMSET(&dev, 0, sizeof(dev)); + XMEMSET(&srk, 0, sizeof(srk)); + XMEMSET(&peer, 0, sizeof(peer)); + + rc = wolfTPM2_Init(&dev, TPM2_IoCb, NULL); + if (rc != 0) { + printf("Test TPM Wrapper:\tLoadEccPublicKey_ex:\tSkipped\n"); + return; + } + + /* Create an ECC SRK to harvest valid P-256 X/Y coordinates from. */ + XMEMSET(&pub, 0, sizeof(pub)); + rc = wolfTPM2_GetKeyTemplate_ECC_SRK(&pub); + AssertIntEQ(rc, TPM_RC_SUCCESS); + rc = wolfTPM2_CreatePrimaryKey(&dev, &srk, TPM_RH_OWNER, &pub, NULL, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + + xSz = srk.pub.publicArea.unique.ecc.x.size; + ySz = srk.pub.publicArea.unique.ecc.y.size; + AssertIntGT(xSz, 0); + AssertIntGT(ySz, 0); + XMEMCPY(xBuf, srk.pub.publicArea.unique.ecc.x.buffer, xSz); + XMEMCPY(yBuf, srk.pub.publicArea.unique.ecc.y.buffer, ySz); + + /* Load same coordinates as a peer ECDH key with the decrypt attribute */ + rc = wolfTPM2_LoadEccPublicKey_ex(&dev, &peer, TPM_ECC_NIST_P256, + xBuf, xSz, yBuf, ySz, + TPM_ALG_ECDH, TPM_ALG_SHA256, + TPMA_OBJECT_decrypt | TPMA_OBJECT_userWithAuth | TPMA_OBJECT_noDA); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(peer.pub.publicArea.parameters.eccDetail.scheme.scheme, + TPM_ALG_ECDH); + AssertIntEQ((int)(peer.pub.publicArea.objectAttributes & + TPMA_OBJECT_decrypt), (int)TPMA_OBJECT_decrypt); + AssertIntEQ((int)(peer.pub.publicArea.objectAttributes & + TPMA_OBJECT_sign), 0); + wolfTPM2_UnloadHandle(&dev, &peer.handle); + + /* Legacy wolfTPM2_LoadEccPublicKey: still defaults to ECDSA + sign */ + XMEMSET(&peer, 0, sizeof(peer)); + rc = wolfTPM2_LoadEccPublicKey(&dev, &peer, TPM_ECC_NIST_P256, + xBuf, xSz, yBuf, ySz); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(peer.pub.publicArea.parameters.eccDetail.scheme.scheme, + TPM_ALG_ECDSA); + AssertIntEQ((int)(peer.pub.publicArea.objectAttributes & + TPMA_OBJECT_sign), (int)TPMA_OBJECT_sign); + AssertIntEQ((int)(peer.pub.publicArea.objectAttributes & + TPMA_OBJECT_decrypt), 0); + wolfTPM2_UnloadHandle(&dev, &peer.handle); + + wolfTPM2_UnloadHandle(&dev, &srk.handle); + wolfTPM2_Cleanup(&dev); + + printf("Test TPM Wrapper:\tLoadEccPublicKey_ex:\t\tPassed\n"); +#endif +} + /* TPM2_GetTpmCurve / TPM2_GetWolfCurve must map wolfCrypt's * ECC_BRAINPOOLP256R1 to TPM_ECC_BP_P256_R1 (0x0030), not * TPM_ECC_BN_P256 (0x0010, Barreto-Naehrig). Pre-fix the two were @@ -3375,6 +3449,7 @@ int unit_tests(int argc, char *argv[]) test_TPM2_ECC_Parameters_EcdaaResponseParse(); test_TPM2_ParseAttest_NvDigest(); test_TPM2_BrainpoolCurveMapping(); + test_wolfTPM2_LoadEccPublicKey_Ex(); test_TPM2_KeyedHashScheme_XorSerialize(); test_TPM2_Signature_EcSchnorrSm2Serialize(); test_TPM2_Sensitive_Roundtrip(); diff --git a/wolftpm/tpm2_wrap.h b/wolftpm/tpm2_wrap.h index 42a2c996..80aa68e2 100644 --- a/wolftpm/tpm2_wrap.h +++ b/wolftpm/tpm2_wrap.h @@ -1313,6 +1313,27 @@ WOLFTPM_API int wolfTPM2_LoadEccPublicKey(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, int curveId, const byte* eccPubX, word32 eccPubXSz, const byte* eccPubY, word32 eccPubYSz); +/*! + \ingroup wolfTPM2_Wrappers + \brief Variant of wolfTPM2_LoadEccPublicKey that lets the caller select + the signing scheme, hash algorithm, and object attributes - required + for loading ECDH peer keys (decrypt attribute) or non-default + scheme/hash combinations. + + \return TPM_RC_SUCCESS: successful + \return BAD_FUNC_ARG: check the provided arguments + \return BUFFER_E: caller buffer is too small for ECC point coordinates + + \sa wolfTPM2_LoadEccPublicKey + \sa wolfTPM2_LoadRsaPublicKey_ex +*/ +WOLFTPM_API int wolfTPM2_LoadEccPublicKey_ex(WOLFTPM2_DEV* dev, + WOLFTPM2_KEY* key, int curveId, + const byte* eccPubX, word32 eccPubXSz, + const byte* eccPubY, word32 eccPubYSz, + TPMI_ALG_ECC_SCHEME scheme, TPMI_ALG_HASH hashAlg, + TPMA_OBJECT objectAttributes); + /*! \ingroup wolfTPM2_Wrappers \brief Helper function to import the private material of an external ECC key From ae4ebe764d131bc3d79259d7918c0f23ec20139e Mon Sep 17 00:00:00 2001 From: Aidan Date: Tue, 28 Apr 2026 10:12:58 -0700 Subject: [PATCH 12/24] F-3498 - https://fenrir.wolfssl.com/finding/3498 - wolfTPM2_RsaEncrypt/Decrypt return BUFFER_E for oversized input instead of silently truncating --- src/tpm2_wrap.c | 10 +++++----- tests/unit_tests.c | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index af66dc36..dbb80439 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -5465,10 +5465,10 @@ int wolfTPM2_RsaEncrypt(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, /* RSA Encrypt */ XMEMSET(&rsaEncIn, 0, sizeof(rsaEncIn)); rsaEncIn.keyHandle = key->handle.hndl; - rsaEncIn.message.size = msgSz; - if (rsaEncIn.message.size > sizeof(rsaEncIn.message.buffer)) { - rsaEncIn.message.size = sizeof(rsaEncIn.message.buffer); /* truncate */ + if (msgSz < 0 || (size_t)msgSz > sizeof(rsaEncIn.message.buffer)) { + return BUFFER_E; } + rsaEncIn.message.size = (UINT16)msgSz; XMEMCPY(rsaEncIn.message.buffer, msg, rsaEncIn.message.size); /* TPM_ALG_NULL, TPM_ALG_OAEP, TPM_ALG_RSASSA or TPM_ALG_RSAPSS */ rsaEncIn.inScheme.scheme = padScheme; @@ -5520,8 +5520,8 @@ int wolfTPM2_RsaDecrypt(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, /* RSA Decrypt */ XMEMSET(&rsaDecIn, 0, sizeof(rsaDecIn)); rsaDecIn.keyHandle = key->handle.hndl; - if (inSz > (int)sizeof(rsaDecIn.cipherText.buffer)) { - inSz = (int)sizeof(rsaDecIn.cipherText.buffer); /* truncate */ + if (inSz < 0 || (size_t)inSz > sizeof(rsaDecIn.cipherText.buffer)) { + return BUFFER_E; } rsaDecIn.cipherText.size = (UINT16)inSz; XMEMCPY(rsaDecIn.cipherText.buffer, in, inSz); diff --git a/tests/unit_tests.c b/tests/unit_tests.c index e3ed8ba8..846079e7 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -2031,6 +2031,38 @@ static void test_wolfTPM2_LoadEccPublicKey_Ex(void) #endif } +/* wolfTPM2_RsaEncrypt and wolfTPM2_RsaDecrypt must reject oversized inputs + * with BUFFER_E rather than silently truncating to the message buffer + * length. The bounds check fires before the TPM is contacted, so this + * test does not require a working TPM connection. */ +static void test_wolfTPM2_RsaEncryptDecrypt_OversizedBufferE(void) +{ +#if !defined(WOLFTPM2_NO_WOLFCRYPT) && !defined(NO_RSA) + int rc; + WOLFTPM2_DEV dev; + WOLFTPM2_KEY key; + byte oversized[MAX_RSA_KEY_BYTES + 16]; + byte out[MAX_RSA_KEY_BYTES]; + int outSz = (int)sizeof(out); + + XMEMSET(&dev, 0, sizeof(dev)); + XMEMSET(&key, 0, sizeof(key)); + XMEMSET(oversized, 0xAB, sizeof(oversized)); + key.handle.hndl = 0x80000000; + + rc = wolfTPM2_RsaEncrypt(&dev, &key, TPM_ALG_NULL, + oversized, (int)sizeof(oversized), out, &outSz); + AssertIntEQ(rc, BUFFER_E); + + outSz = (int)sizeof(out); + rc = wolfTPM2_RsaDecrypt(&dev, &key, TPM_ALG_NULL, + oversized, (int)sizeof(oversized), out, &outSz); + AssertIntEQ(rc, BUFFER_E); + + printf("Test TPM Wrapper:\tRsaEncDec oversized:\t\tPassed\n"); +#endif +} + /* TPM2_GetTpmCurve / TPM2_GetWolfCurve must map wolfCrypt's * ECC_BRAINPOOLP256R1 to TPM_ECC_BP_P256_R1 (0x0030), not * TPM_ECC_BN_P256 (0x0010, Barreto-Naehrig). Pre-fix the two were @@ -3449,6 +3481,7 @@ int unit_tests(int argc, char *argv[]) test_TPM2_ECC_Parameters_EcdaaResponseParse(); test_TPM2_ParseAttest_NvDigest(); test_TPM2_BrainpoolCurveMapping(); + test_wolfTPM2_RsaEncryptDecrypt_OversizedBufferE(); test_wolfTPM2_LoadEccPublicKey_Ex(); test_TPM2_KeyedHashScheme_XorSerialize(); test_TPM2_Signature_EcSchnorrSm2Serialize(); From 68bf667c91d7c3f5c2ea468496848a18e2b71c68 Mon Sep 17 00:00:00 2001 From: Aidan Date: Tue, 28 Apr 2026 10:24:06 -0700 Subject: [PATCH 13/24] F-3499 - https://fenrir.wolfssl.com/finding/3499 - wolfTPM2_SignHashScheme/VerifyHashTicket: reject mismatched digest size for RSA, gate ECDSA pad/truncate --- src/tpm2_wrap.c | 59 ++++++++++++++++++++++++++++++++-------------- tests/unit_tests.c | 39 ++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 18 deletions(-) diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index dbb80439..50d37db3 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -4955,10 +4955,6 @@ int wolfTPM2_SignHashScheme(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, /* set session auth for key */ wolfTPM2_SetAuthHandle(dev, 0, &key->handle); - /* verify input cannot exceed buffer */ - if (digestSz > (int)sizeof(signIn.digest.buffer)) - digestSz = (int)sizeof(signIn.digest.buffer); - XMEMSET(&signIn, 0, sizeof(signIn)); signIn.keyHandle = key->handle.hndl; signIn.digest.size = (UINT16)TPM2_GetHashDigestSize(hashAlg); @@ -4966,14 +4962,31 @@ int wolfTPM2_SignHashScheme(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, signIn.digest.size > sizeof(signIn.digest.buffer)) { return BUFFER_E; } - /* if digest provided is smaller than key size then zero pad leading */ - if (digestSz < signIn.digest.size) { - XMEMCPY(&signIn.digest.buffer[signIn.digest.size - digestSz], digest, - digestSz); + /* Hard upper bound: digest must fit the message-buffer. */ + if (digestSz < 0 || digestSz > (int)sizeof(signIn.digest.buffer)) { + return BUFFER_E; } - else { + if (key->pub.publicArea.type != TPM_ALG_ECC) { + /* RSA: digest size must match the declared hash algorithm. + * Silently zero-padding produces a signature over crafted + * but incorrect content, so this is a caller error. */ + if (digestSz != (int)signIn.digest.size) { + return BUFFER_E; + } XMEMCPY(signIn.digest.buffer, digest, digestSz); } + else { + /* ECDSA: digests shorter than hashAlg's size are left-padded with + * zeros; longer digests are silently truncated to hashAlg's size + * (TCG Part 1 - ECDSA admits short or long inputs). */ + if (digestSz < (int)signIn.digest.size) { + XMEMCPY(&signIn.digest.buffer[signIn.digest.size - digestSz], + digest, digestSz); + } + else { + XMEMCPY(signIn.digest.buffer, digest, signIn.digest.size); + } + } signIn.inScheme.scheme = sigAlg; signIn.inScheme.details.any.hashAlg = hashAlg; signIn.validation.tag = TPM_ST_HASHCHECK; @@ -5119,10 +5132,6 @@ int wolfTPM2_VerifyHashTicket(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, return BAD_FUNC_ARG; } - /* verify input cannot exceed buffer */ - if (digestSz > (int)sizeof(verifySigIn.digest.buffer)) - digestSz = (int)sizeof(verifySigIn.digest.buffer); - /* set session auth for key */ wolfTPM2_SetAuthHandle(dev, 0, &key->handle); @@ -5133,14 +5142,28 @@ int wolfTPM2_VerifyHashTicket(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, verifySigIn.digest.size > sizeof(verifySigIn.digest.buffer)) { return BUFFER_E; } - /* if digest provided is smaller than key size then zero pad leading */ - if (digestSz < verifySigIn.digest.size) { - XMEMCPY(&verifySigIn.digest.buffer[verifySigIn.digest.size - digestSz], - digest, digestSz); + /* Hard upper bound: digest must fit the message-buffer. */ + if (digestSz < 0 || digestSz > (int)sizeof(verifySigIn.digest.buffer)) { + return BUFFER_E; } - else { + if (key->pub.publicArea.type != TPM_ALG_ECC) { + /* RSA: digest size must match the declared hash algorithm. */ + if (digestSz != (int)verifySigIn.digest.size) { + return BUFFER_E; + } XMEMCPY(verifySigIn.digest.buffer, digest, digestSz); } + else { + /* ECDSA: short digests are left-padded with zeros, longer digests + * are silently truncated to hashAlg's size. */ + if (digestSz < (int)verifySigIn.digest.size) { + XMEMCPY(&verifySigIn.digest.buffer[verifySigIn.digest.size - + digestSz], digest, digestSz); + } + else { + XMEMCPY(verifySigIn.digest.buffer, digest, verifySigIn.digest.size); + } + } verifySigIn.signature.sigAlg = sigAlg; signature->any.hashAlg = hashAlg; if (key->pub.publicArea.type == TPM_ALG_ECC) { diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 846079e7..e8b990c2 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -1982,6 +1982,9 @@ static void test_wolfTPM2_LoadEccPublicKey_Ex(void) printf("Test TPM Wrapper:\tLoadEccPublicKey_ex:\tSkipped\n"); return; } + /* Flush any transient objects left by previous tests so CreatePrimary + * does not get TPM_RC_OBJECT_MEMORY on a busy simulator. */ + (void)wolfTPM2_UnloadHandles_AllTransient(&dev); /* Create an ECC SRK to harvest valid P-256 X/Y coordinates from. */ XMEMSET(&pub, 0, sizeof(pub)); @@ -2031,6 +2034,41 @@ static void test_wolfTPM2_LoadEccPublicKey_Ex(void) #endif } +/* wolfTPM2_SignHashScheme must reject digest sizes that don't match the + * declared hashAlg for RSA keys, instead of silently zero-padding. The + * pad-to-hash-size convention is preserved for ECDSA per spec. */ +static void test_wolfTPM2_SignHashScheme_DigestSize(void) +{ +#if !defined(WOLFTPM2_NO_WOLFCRYPT) && !defined(NO_RSA) + int rc; + WOLFTPM2_DEV dev; + WOLFTPM2_KEY key; + byte digest[TPM_MAX_DIGEST_SIZE]; + byte sig[MAX_RSA_KEY_BYTES]; + int sigSz = (int)sizeof(sig); + + XMEMSET(&dev, 0, sizeof(dev)); + XMEMSET(&key, 0, sizeof(key)); + XMEMSET(digest, 0xCC, sizeof(digest)); + key.handle.hndl = 0x80000000; + key.pub.publicArea.type = TPM_ALG_RSA; + + /* SHA-256 digest (32) but caller declared SHA-512 (64): for RSA this + * was previously silently zero-padded; now must return BUFFER_E. */ + rc = wolfTPM2_SignHashScheme(&dev, &key, digest, 32, sig, &sigSz, + TPM_ALG_RSASSA, TPM_ALG_SHA512); + AssertIntEQ(rc, BUFFER_E); + + /* Oversized digest (larger than declared hashAlg) is also BUFFER_E. */ + sigSz = (int)sizeof(sig); + rc = wolfTPM2_SignHashScheme(&dev, &key, digest, 64, sig, &sigSz, + TPM_ALG_RSASSA, TPM_ALG_SHA256); + AssertIntEQ(rc, BUFFER_E); + + printf("Test TPM Wrapper:\tSignHashScheme size:\t\tPassed\n"); +#endif +} + /* wolfTPM2_RsaEncrypt and wolfTPM2_RsaDecrypt must reject oversized inputs * with BUFFER_E rather than silently truncating to the message buffer * length. The bounds check fires before the TPM is contacted, so this @@ -3482,6 +3520,7 @@ int unit_tests(int argc, char *argv[]) test_TPM2_ParseAttest_NvDigest(); test_TPM2_BrainpoolCurveMapping(); test_wolfTPM2_RsaEncryptDecrypt_OversizedBufferE(); + test_wolfTPM2_SignHashScheme_DigestSize(); test_wolfTPM2_LoadEccPublicKey_Ex(); test_TPM2_KeyedHashScheme_XorSerialize(); test_TPM2_Signature_EcSchnorrSm2Serialize(); From 3ec5485f190733993239529d74fbabcaa1581b2f Mon Sep 17 00:00:00 2001 From: Aidan Date: Tue, 28 Apr 2026 10:28:42 -0700 Subject: [PATCH 14/24] F-3500 - https://fenrir.wolfssl.com/finding/3500 - wolfTPM2_NVCreateAuthPolicy: derive nameAlg from authPolicySz so SHA-384/SHA-512 policies are satisfiable --- src/tpm2_wrap.c | 32 +++++++++++++++++++++++++++++++- tests/unit_tests.c | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index 50d37db3..409197d3 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -5756,7 +5756,37 @@ int wolfTPM2_NVCreateAuthPolicy(WOLFTPM2_DEV* dev, WOLFTPM2_HANDLE* parent, XMEMCPY(in.auth.buffer, auth, in.auth.size); } in.publicInfo.nvPublic.nvIndex = nvIndex; - in.publicInfo.nvPublic.nameAlg = WOLFTPM2_WRAP_DIGEST; + /* When a policy digest is supplied, nameAlg must match the hash that + * produced it - otherwise the policy can never be satisfied. Infer + * nameAlg from authPolicySz when possible, defaulting to + * WOLFTPM2_WRAP_DIGEST for the no-policy case. */ + if (authPolicy != NULL && authPolicySz > 0) { + switch (authPolicySz) { + #ifndef NO_SHA + case TPM_SHA_DIGEST_SIZE: + in.publicInfo.nvPublic.nameAlg = TPM_ALG_SHA1; + break; + #endif + case TPM_SHA256_DIGEST_SIZE: + in.publicInfo.nvPublic.nameAlg = TPM_ALG_SHA256; + break; + #ifdef WOLFSSL_SHA384 + case TPM_SHA384_DIGEST_SIZE: + in.publicInfo.nvPublic.nameAlg = TPM_ALG_SHA384; + break; + #endif + #ifdef WOLFSSL_SHA512 + case TPM_SHA512_DIGEST_SIZE: + in.publicInfo.nvPublic.nameAlg = TPM_ALG_SHA512; + break; + #endif + default: + return BAD_FUNC_ARG; + } + } + else { + in.publicInfo.nvPublic.nameAlg = WOLFTPM2_WRAP_DIGEST; + } in.publicInfo.nvPublic.attributes = nvAttributes; in.publicInfo.nvPublic.dataSize = (UINT16)maxSize; if (authPolicy != NULL && authPolicySz > 0) { diff --git a/tests/unit_tests.c b/tests/unit_tests.c index e8b990c2..e4e1e16f 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -2034,6 +2034,44 @@ static void test_wolfTPM2_LoadEccPublicKey_Ex(void) #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. + * Mismatched digest sizes must be rejected up front. */ +static void test_wolfTPM2_NVCreateAuthPolicy_NameAlg(void) +{ +#if !defined(WOLFTPM2_NO_WOLFCRYPT) + int rc; + WOLFTPM2_DEV dev; + WOLFTPM2_HANDLE parent; + WOLFTPM2_NV nv; + byte policy[64]; + + XMEMSET(&dev, 0, sizeof(dev)); + XMEMSET(&parent, 0, sizeof(parent)); + XMEMSET(&nv, 0, sizeof(nv)); + XMEMSET(policy, 0xAB, sizeof(policy)); + + /* No real TPM call required to exercise the new size validation - the + * mismatch check fires before TPM2_NV_DefineSpace is contacted. */ + parent.hndl = TPM_RH_OWNER; + + /* 33 bytes is not a recognized hash digest size -> BAD_FUNC_ARG. */ + rc = wolfTPM2_NVCreateAuthPolicy(&dev, &parent, &nv, 0x01400001, + TPMA_NV_OWNERWRITE | TPMA_NV_OWNERREAD | TPMA_NV_NO_DA, 64, + NULL, 0, policy, 33); + AssertIntEQ(rc, BAD_FUNC_ARG); + + /* 17 bytes is not a recognized hash digest size -> BAD_FUNC_ARG. */ + rc = wolfTPM2_NVCreateAuthPolicy(&dev, &parent, &nv, 0x01400002, + TPMA_NV_OWNERWRITE | TPMA_NV_OWNERREAD | TPMA_NV_NO_DA, 64, + NULL, 0, policy, 17); + AssertIntEQ(rc, BAD_FUNC_ARG); + + printf("Test TPM Wrapper:\tNVCreateAuthPolicy nameAlg:\tPassed\n"); +#endif +} + /* wolfTPM2_SignHashScheme must reject digest sizes that don't match the * declared hashAlg for RSA keys, instead of silently zero-padding. The * pad-to-hash-size convention is preserved for ECDSA per spec. */ @@ -3521,6 +3559,7 @@ int unit_tests(int argc, char *argv[]) test_TPM2_BrainpoolCurveMapping(); test_wolfTPM2_RsaEncryptDecrypt_OversizedBufferE(); test_wolfTPM2_SignHashScheme_DigestSize(); + test_wolfTPM2_NVCreateAuthPolicy_NameAlg(); test_wolfTPM2_LoadEccPublicKey_Ex(); test_TPM2_KeyedHashScheme_XorSerialize(); test_TPM2_Signature_EcSchnorrSm2Serialize(); From 07bdb8219561cc211a737e7c645a4f80cc795c01 Mon Sep 17 00:00:00 2001 From: Aidan Date: Tue, 28 Apr 2026 10:33:52 -0700 Subject: [PATCH 15/24] F-3508 - https://fenrir.wolfssl.com/finding/3508 - wolftpm: TPM2_Packet_ParsePublic resyncs to outer pub->size to prevent inner-size desync drift --- src/tpm2_packet.c | 11 +++++++ tests/unit_tests.c | 72 +++++++++++++++++++++++++++++++++++++++++++ wolftpm/tpm2_packet.h | 4 +-- 3 files changed, 85 insertions(+), 2 deletions(-) diff --git a/src/tpm2_packet.c b/src/tpm2_packet.c index 3c57bd79..df69a9b5 100644 --- a/src/tpm2_packet.c +++ b/src/tpm2_packet.c @@ -1095,7 +1095,10 @@ void TPM2_Packet_AppendPublic(TPM2_Packet* packet, TPM2B_PUBLIC* pub) } void TPM2_Packet_ParsePublic(TPM2_Packet* packet, TPM2B_PUBLIC* pub) { + int pubStartPos; + TPM2_Packet_ParseU16(packet, &pub->size); + pubStartPos = (packet != NULL) ? packet->pos : 0; if (pub->size > 0) { TPM2_Packet_ParseU16(packet, &pub->publicArea.type); TPM2_Packet_ParseU16(packet, &pub->publicArea.nameAlg); @@ -1133,6 +1136,14 @@ void TPM2_Packet_ParsePublic(TPM2_Packet* packet, TPM2B_PUBLIC* pub) /* TPMS_DERIVE derive; ? */ break; } + + /* Resync packet position to end of declared outer size so inner + * parses can't cause field drift if declared size and actual + * inner consumption disagree */ + if (packet != NULL && + pubStartPos + pub->size <= packet->size) { + packet->pos = pubStartPos + pub->size; + } } } diff --git a/tests/unit_tests.c b/tests/unit_tests.c index e4e1e16f..14fefdfd 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -1902,6 +1902,77 @@ static void test_TPM2_ECC_Parameters_EcdaaResponseParse(void) printf("Test TPM Wrapper:\tEcdaaResponseParse:\t\tPassed\n"); } +/* TPM2_Packet_ParsePublic must resync the packet position to outerStart + + * pub->size so a malformed wire blob with inner-size disagreement can't + * desynchronize subsequent fields. Pre-fix the parser left the position + * wherever the inner parses ended, drifting from the declared outer size. */ +static void test_TPM2_ParsePublic_OuterResync(void) +{ + TPM2_Packet packet; + byte buf[256]; + TPM2B_PUBLIC pub; + UINT16 sentinel = 0; + int outerStart, fakeOuterSz; + int pos = 0; + int innerStart; + + /* Build a TPM2B_PUBLIC blob by hand with type=RSA, valid inner fields, + * but outer.size declared larger than the actual inner consumption. A + * sentinel is placed at outerStart + 2 + outer.size; only a parser + * that resyncs to that anchor will read the sentinel correctly. */ + XMEMSET(buf, 0, sizeof(buf)); + XMEMSET(&packet, 0, sizeof(packet)); + + outerStart = pos; + pos += 2; /* size placeholder */ + innerStart = pos; + /* type = RSA (0x0001) */ + buf[pos++] = 0x00; buf[pos++] = 0x01; + /* nameAlg = SHA256 (0x000B) */ + buf[pos++] = 0x00; buf[pos++] = 0x0B; + /* objectAttributes = 0 */ + buf[pos++] = 0; buf[pos++] = 0; buf[pos++] = 0; buf[pos++] = 0; + /* authPolicy: size=0 */ + buf[pos++] = 0; buf[pos++] = 0; + /* RSA params: sym.alg=NULL(2), scheme=NULL(2), keyBits=2048(2), + * exponent=0(4) */ + buf[pos++] = 0x00; buf[pos++] = 0x10; /* TPM_ALG_NULL */ + buf[pos++] = 0x00; buf[pos++] = 0x10; /* scheme NULL */ + buf[pos++] = 0x08; buf[pos++] = 0x00; /* keyBits = 2048 */ + buf[pos++] = 0; buf[pos++] = 0; buf[pos++] = 0; buf[pos++] = 0; /* exp */ + /* unique.size = 0 */ + buf[pos++] = 0; buf[pos++] = 0; + + /* Declared outer size = actual inner + 8 padding bytes. */ + fakeOuterSz = (pos - innerStart) + 8; + buf[outerStart] = (byte)((fakeOuterSz >> 8) & 0xFF); + buf[outerStart + 1] = (byte)(fakeOuterSz & 0xFF); + /* 8 zero pad bytes */ + pos += 8; + + /* Sentinel U16 at outerStart + 2 + fakeOuterSz */ + buf[pos++] = 0xCA; + buf[pos++] = 0xFE; + + XMEMSET(&pub, 0, sizeof(pub)); + packet.buf = buf; + packet.size = pos; + packet.pos = 0; + + TPM2_Packet_ParsePublic(&packet, &pub); + AssertIntEQ(pub.publicArea.type, TPM_ALG_RSA); + AssertIntEQ(pub.publicArea.nameAlg, TPM_ALG_SHA256); + + /* Position must land at outerStart + 2 + outer.size; the sentinel U16 + * lives at that offset. Read by hand to avoid pulling in WOLFTPM_LOCAL + * parser helpers from the test binary. */ + AssertIntEQ(packet.pos, 2 + fakeOuterSz); + sentinel = (UINT16)((buf[packet.pos] << 8) | buf[packet.pos + 1]); + AssertIntEQ(sentinel, 0xCAFE); + + printf("Test TPM Wrapper:\tParsePublic outer resync:\tPassed\n"); +} + /* TPM2_ParseAttest must handle TPM_ST_ATTEST_NV_DIGEST (0x801C) and decode * TPMS_NV_DIGEST_CERTIFY_INFO. Pre-fix, the switch fell through to default * and left out->attested zeroed. */ @@ -3556,6 +3627,7 @@ int unit_tests(int argc, char *argv[]) test_TPM2_SchemeSerialize(); test_TPM2_ECC_Parameters_EcdaaResponseParse(); test_TPM2_ParseAttest_NvDigest(); + test_TPM2_ParsePublic_OuterResync(); test_TPM2_BrainpoolCurveMapping(); test_wolfTPM2_RsaEncryptDecrypt_OversizedBufferE(); test_wolfTPM2_SignHashScheme_DigestSize(); diff --git a/wolftpm/tpm2_packet.h b/wolftpm/tpm2_packet.h index 256df287..2aa124e0 100644 --- a/wolftpm/tpm2_packet.h +++ b/wolftpm/tpm2_packet.h @@ -232,8 +232,8 @@ WOLFTPM_LOCAL TPM_RC TPM2_Packet_ParseSensitiveCreate(TPM2_Packet* packet, WOLFTPM_LOCAL void TPM2_Packet_AppendPublicParms(TPM2_Packet* packet, TPMI_ALG_PUBLIC type, TPMU_PUBLIC_PARMS* parameters); WOLFTPM_LOCAL void TPM2_Packet_ParsePublicParms(TPM2_Packet* packet, TPMI_ALG_PUBLIC type, TPMU_PUBLIC_PARMS* parameters); WOLFTPM_LOCAL void TPM2_Packet_AppendPublicArea(TPM2_Packet* packet, TPMT_PUBLIC* publicArea); -WOLFTPM_LOCAL void TPM2_Packet_AppendPublic(TPM2_Packet* packet, TPM2B_PUBLIC* pub); -WOLFTPM_LOCAL void TPM2_Packet_ParsePublic(TPM2_Packet* packet, TPM2B_PUBLIC* pub); +WOLFTPM_TEST_API void TPM2_Packet_AppendPublic(TPM2_Packet* packet, TPM2B_PUBLIC* pub); +WOLFTPM_TEST_API void TPM2_Packet_ParsePublic(TPM2_Packet* packet, TPM2B_PUBLIC* pub); WOLFTPM_TEST_API void TPM2_Packet_AppendSignature(TPM2_Packet* packet, TPMT_SIGNATURE* sig); WOLFTPM_TEST_API void TPM2_Packet_ParseSignature(TPM2_Packet* packet, TPMT_SIGNATURE* sig); WOLFTPM_LOCAL void TPM2_Packet_ParseAttest(TPM2_Packet* packet, TPMS_ATTEST* out); From 2e00720eb47ed5f68bea9042a72e6a01ad8ab76b Mon Sep 17 00:00:00 2001 From: Aidan Date: Tue, 28 Apr 2026 10:37:26 -0700 Subject: [PATCH 16/24] F-3509 - https://fenrir.wolfssl.com/finding/3509 - wolftpm: TPM2_Packet_ParsePoint resyncs to outer point->size to prevent inner-size desync drift --- src/tpm2_packet.c | 10 +++++++++ tests/unit_tests.c | 52 +++++++++++++++++++++++++++++++++++++++++++ wolftpm/tpm2_packet.h | 4 ++-- 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/src/tpm2_packet.c b/src/tpm2_packet.c index df69a9b5..436779a9 100644 --- a/src/tpm2_packet.c +++ b/src/tpm2_packet.c @@ -782,8 +782,18 @@ void TPM2_Packet_AppendPoint(TPM2_Packet* packet, TPM2B_ECC_POINT* point) } void TPM2_Packet_ParsePoint(TPM2_Packet* packet, TPM2B_ECC_POINT* point) { + int pointStartPos; + TPM2_Packet_ParseU16(packet, &point->size); + pointStartPos = (packet != NULL) ? packet->pos : 0; TPM2_Packet_ParseEccPoint(packet, &point->point); + + /* Resync packet position to end of declared outer size so inner + * x.size / y.size disagreement can't desynchronize subsequent fields */ + if (packet != NULL && point->size > 0 && + pointStartPos + point->size <= packet->size) { + packet->pos = pointStartPos + point->size; + } } void TPM2_Packet_AppendSensitive(TPM2_Packet* packet, TPM2B_SENSITIVE* sensitive) diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 14fefdfd..b500e937 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -1902,6 +1902,57 @@ static void test_TPM2_ECC_Parameters_EcdaaResponseParse(void) printf("Test TPM Wrapper:\tEcdaaResponseParse:\t\tPassed\n"); } +/* TPM2_Packet_ParsePoint must resync to outerStart + point->size so a + * malformed wire blob with inner x.size / y.size disagreement can't + * desynchronize subsequent fields. */ +static void test_TPM2_ParsePoint_OuterResync(void) +{ + TPM2_Packet packet; + byte buf[64]; + TPM2B_ECC_POINT point; + UINT16 sentinel; + int outerStart, fakeOuterSz; + int pos = 0; + int innerStart; + + XMEMSET(buf, 0, sizeof(buf)); + XMEMSET(&packet, 0, sizeof(packet)); + + /* Build TPM2B_ECC_POINT: outer.size + x(2+0) + y(2+0). Declare outer + * size larger than actual inner consumption (4 bytes). */ + outerStart = pos; + pos += 2; /* size placeholder */ + innerStart = pos; + /* x.size = 0 */ + buf[pos++] = 0; buf[pos++] = 0; + /* y.size = 0 */ + buf[pos++] = 0; buf[pos++] = 0; + + fakeOuterSz = (pos - innerStart) + 6; /* 6 padding bytes */ + buf[outerStart] = (byte)((fakeOuterSz >> 8) & 0xFF); + buf[outerStart + 1] = (byte)(fakeOuterSz & 0xFF); + pos += 6; + + /* Sentinel U16 right after outer end */ + buf[pos++] = 0xBE; + buf[pos++] = 0xEF; + + XMEMSET(&point, 0, sizeof(point)); + packet.buf = buf; + packet.size = pos; + packet.pos = 0; + + TPM2_Packet_ParsePoint(&packet, &point); + + /* Position must land at outerStart + 2 + outer.size (= 2 + 10 = 12). + * Read sentinel by hand. */ + AssertIntEQ(packet.pos, 2 + fakeOuterSz); + sentinel = (UINT16)((buf[packet.pos] << 8) | buf[packet.pos + 1]); + AssertIntEQ(sentinel, 0xBEEF); + + printf("Test TPM Wrapper:\tParsePoint outer resync:\tPassed\n"); +} + /* TPM2_Packet_ParsePublic must resync the packet position to outerStart + * pub->size so a malformed wire blob with inner-size disagreement can't * desynchronize subsequent fields. Pre-fix the parser left the position @@ -3628,6 +3679,7 @@ int unit_tests(int argc, char *argv[]) test_TPM2_ECC_Parameters_EcdaaResponseParse(); test_TPM2_ParseAttest_NvDigest(); test_TPM2_ParsePublic_OuterResync(); + test_TPM2_ParsePoint_OuterResync(); test_TPM2_BrainpoolCurveMapping(); test_wolfTPM2_RsaEncryptDecrypt_OversizedBufferE(); test_wolfTPM2_SignHashScheme_DigestSize(); diff --git a/wolftpm/tpm2_packet.h b/wolftpm/tpm2_packet.h index 2aa124e0..88f6a52f 100644 --- a/wolftpm/tpm2_packet.h +++ b/wolftpm/tpm2_packet.h @@ -195,8 +195,8 @@ WOLFTPM_LOCAL void TPM2_Packet_AppendAsymScheme(TPM2_Packet* packet, TPMT_ASYM_S WOLFTPM_LOCAL void TPM2_Packet_ParseAsymScheme(TPM2_Packet* packet, TPMT_ASYM_SCHEME* scheme); WOLFTPM_LOCAL void TPM2_Packet_AppendEccPoint(TPM2_Packet* packet, TPMS_ECC_POINT* point); WOLFTPM_LOCAL void TPM2_Packet_ParseEccPoint(TPM2_Packet* packet, TPMS_ECC_POINT* point); -WOLFTPM_LOCAL void TPM2_Packet_AppendPoint(TPM2_Packet* packet, TPM2B_ECC_POINT* point); -WOLFTPM_LOCAL void TPM2_Packet_ParsePoint(TPM2_Packet* packet, TPM2B_ECC_POINT* point); +WOLFTPM_TEST_API void TPM2_Packet_AppendPoint(TPM2_Packet* packet, TPM2B_ECC_POINT* point); +WOLFTPM_TEST_API void TPM2_Packet_ParsePoint(TPM2_Packet* packet, TPM2B_ECC_POINT* point); WOLFTPM_TEST_API void TPM2_Packet_AppendSensitive(TPM2_Packet* packet, TPM2B_SENSITIVE* sensitive); WOLFTPM_TEST_API void TPM2_Packet_ParseSensitive(TPM2_Packet* packet, TPM2B_SENSITIVE* sensitive); WOLFTPM_LOCAL void TPM2_Packet_AppendSensitiveCreate(TPM2_Packet* packet, TPM2B_SENSITIVE_CREATE* sensitive); From d2dc2f726daacceb3b1f6f3e9dabb7479804bb73 Mon Sep 17 00:00:00 2001 From: Aidan Date: Tue, 28 Apr 2026 10:40:39 -0700 Subject: [PATCH 17/24] F-3505 - https://fenrir.wolfssl.com/finding/3505 - examples/attestation/activate_credential: guard primary unload against NULL on early init failure --- examples/attestation/activate_credential.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/attestation/activate_credential.c b/examples/attestation/activate_credential.c index f5dcd4d8..a83b053d 100644 --- a/examples/attestation/activate_credential.c +++ b/examples/attestation/activate_credential.c @@ -230,7 +230,8 @@ int TPM2_ActivateCredential_Example(void* userCtx, int argc, char *argv[]) exit: - wolfTPM2_UnloadHandle(&dev, &primary->handle); + if (primary != NULL) + wolfTPM2_UnloadHandle(&dev, &primary->handle); wolfTPM2_UnloadHandle(&dev, &akKey.handle); wolfTPM2_UnloadHandle(&dev, &tpmSession.handle); wolfTPM2_Cleanup(&dev); From 5e50cff6f5673b70aa7ee30d3bf0ac89544c64c2 Mon Sep 17 00:00:00 2001 From: Aidan Date: Tue, 28 Apr 2026 10:42:57 -0700 Subject: [PATCH 18/24] F-3506 - https://fenrir.wolfssl.com/finding/3506 - examples/keygen/keyload: guard primary unload against NULL on early init failure --- examples/keygen/keyload.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/keygen/keyload.c b/examples/keygen/keyload.c index 5c04bbeb..5a280eef 100644 --- a/examples/keygen/keyload.c +++ b/examples/keygen/keyload.c @@ -224,7 +224,8 @@ int TPM2_Keyload_Example(void* userCtx, int argc, char *argv[]) } /* Close key handles */ - wolfTPM2_UnloadHandle(&dev, &primary->handle); + if (primary != NULL) + wolfTPM2_UnloadHandle(&dev, &primary->handle); /* newKey.handle is already flushed by wolfTPM2_NVStoreKey */ if (!persistent) { wolfTPM2_UnloadHandle(&dev, &newKey.handle); From 5f7a00e41311e90e616fde4a43046f0709a541d7 Mon Sep 17 00:00:00 2001 From: Aidan Date: Tue, 28 Apr 2026 10:46:44 -0700 Subject: [PATCH 19/24] F-3511 - https://fenrir.wolfssl.com/finding/3511 - wolfTPM2_RsaEncrypt: scrub plaintext copy in rsaEncIn on every exit path --- src/tpm2_wrap.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index 409197d3..7bccc13a 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -5509,19 +5509,21 @@ int wolfTPM2_RsaEncrypt(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, printf("TPM2_RSA_Encrypt failed %d: %s\n", rc, wolfTPM2_GetRCString(rc)); #endif - return rc; } - - if (*outSz < rsaEncOut.outData.size) { - return BUFFER_E; + else if (*outSz < rsaEncOut.outData.size) { + rc = BUFFER_E; + } + else { + *outSz = rsaEncOut.outData.size; + XMEMCPY(out, rsaEncOut.outData.buffer, *outSz); + #ifdef DEBUG_WOLFTPM + printf("TPM2_RSA_Encrypt: %d\n", rsaEncOut.outData.size); + #endif } - *outSz = rsaEncOut.outData.size; - XMEMCPY(out, rsaEncOut.outData.buffer, *outSz); - -#ifdef DEBUG_WOLFTPM - printf("TPM2_RSA_Encrypt: %d\n", rsaEncOut.outData.size); -#endif + /* Plaintext copy lingers in the rsaEncIn stack frame after return - + * scrub it on every exit path. */ + TPM2_ForceZero(&rsaEncIn, sizeof(rsaEncIn)); return rc; } From 9beaf2f326762f594c67746e73d773c6c8edf973 Mon Sep 17 00:00:00 2001 From: Aidan Date: Tue, 28 Apr 2026 10:53:43 -0700 Subject: [PATCH 20/24] F-3501 - https://fenrir.wolfssl.com/finding/3501 - wolfTPM2_GetKeyTemplate_KeyedHash: scheme defaults to TPM_ALG_NULL when neither sign nor decrypt is requested --- src/tpm2_wrap.c | 17 ++++++++++++++--- tests/unit_tests.c | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index 7bccc13a..7fd4270e 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -7372,9 +7372,20 @@ int wolfTPM2_GetKeyTemplate_KeyedHash(TPMT_PUBLIC* publicTemplate, TPMA_OBJECT_noDA | (isSign ? TPMA_OBJECT_sign : 0) | (isDecrypt ? TPMA_OBJECT_decrypt : 0)); - publicTemplate->parameters.keyedHashDetail.scheme.scheme = TPM_ALG_HMAC; - publicTemplate->parameters.keyedHashDetail.scheme.details.hmac.hashAlg = - hashAlg; + /* HMAC scheme requires the sign attribute. When the caller asks for + * neither sign nor decrypt, treat this as a data/seal-style keyed-hash + * object and use TPM_ALG_NULL so the template is loadable and not + * stuck with an unusable HMAC binding. */ + if (isSign || isDecrypt) { + publicTemplate->parameters.keyedHashDetail.scheme.scheme = + TPM_ALG_HMAC; + publicTemplate->parameters.keyedHashDetail.scheme.details.hmac.hashAlg + = hashAlg; + } + else { + publicTemplate->parameters.keyedHashDetail.scheme.scheme = + TPM_ALG_NULL; + } return TPM_RC_SUCCESS; } diff --git a/tests/unit_tests.c b/tests/unit_tests.c index b500e937..63b78a17 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -2156,6 +2156,37 @@ static void test_wolfTPM2_LoadEccPublicKey_Ex(void) #endif } +/* wolfTPM2_GetKeyTemplate_KeyedHash must default scheme to TPM_ALG_NULL + * when neither isSign nor isDecrypt is set; an HMAC scheme without the + * sign attribute produces an unusable keyed-hash object. */ +static void test_wolfTPM2_GetKeyTemplate_KeyedHash_Scheme(void) +{ +#if !defined(WOLFTPM2_NO_WOLFCRYPT) + int rc; + TPMT_PUBLIC tpl; + + /* Data/seal-style: isSign=0, isDecrypt=0 -> scheme must be NULL */ + XMEMSET(&tpl, 0, sizeof(tpl)); + rc = wolfTPM2_GetKeyTemplate_KeyedHash(&tpl, TPM_ALG_SHA256, 0, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(tpl.parameters.keyedHashDetail.scheme.scheme, TPM_ALG_NULL); + AssertIntEQ((int)(tpl.objectAttributes & TPMA_OBJECT_sign), 0); + AssertIntEQ((int)(tpl.objectAttributes & TPMA_OBJECT_decrypt), 0); + + /* HMAC-style: isSign=1 -> scheme HMAC + hashAlg + sign attribute */ + XMEMSET(&tpl, 0, sizeof(tpl)); + rc = wolfTPM2_GetKeyTemplate_KeyedHash(&tpl, TPM_ALG_SHA256, 1, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(tpl.parameters.keyedHashDetail.scheme.scheme, TPM_ALG_HMAC); + AssertIntEQ(tpl.parameters.keyedHashDetail.scheme.details.hmac.hashAlg, + TPM_ALG_SHA256); + AssertIntEQ((int)(tpl.objectAttributes & TPMA_OBJECT_sign), + (int)TPMA_OBJECT_sign); + + printf("Test TPM Wrapper:\tKeyedHash template scheme:\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. @@ -3684,6 +3715,7 @@ int unit_tests(int argc, char *argv[]) test_wolfTPM2_RsaEncryptDecrypt_OversizedBufferE(); test_wolfTPM2_SignHashScheme_DigestSize(); test_wolfTPM2_NVCreateAuthPolicy_NameAlg(); + test_wolfTPM2_GetKeyTemplate_KeyedHash_Scheme(); test_wolfTPM2_LoadEccPublicKey_Ex(); test_TPM2_KeyedHashScheme_XorSerialize(); test_TPM2_Signature_EcSchnorrSm2Serialize(); From f66ca1e1897903a220b92ae0a1c8f210e5cb0fa1 Mon Sep 17 00:00:00 2001 From: Aidan Date: Tue, 28 Apr 2026 10:56:30 -0700 Subject: [PATCH 21/24] F-3510 - https://fenrir.wolfssl.com/finding/3510 - wolftpm: TPM2_Packet_AppendSignature/ParseSignature explicitly handles TPM_ALG_NULL and warns on unrecognized sigAlg --- src/tpm2_packet.c | 12 ++++++++++++ tests/unit_tests.c | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/src/tpm2_packet.c b/src/tpm2_packet.c index 436779a9..0a04f1af 100644 --- a/src/tpm2_packet.c +++ b/src/tpm2_packet.c @@ -1191,7 +1191,13 @@ void TPM2_Packet_AppendSignature(TPM2_Packet* packet, TPMT_SIGNATURE* sig) digestSz = TPM2_GetHashDigestSize(sig->signature.hmac.hashAlg); TPM2_Packet_AppendBytes(packet, sig->signature.hmac.digest.H, digestSz); break; + case TPM_ALG_NULL: + /* Legitimate zero-payload signature - nothing to append. */ + break; default: + #ifdef DEBUG_WOLFTPM + printf("AppendSignature: unrecognized sigAlg 0x%x\n", sig->sigAlg); + #endif break; } } @@ -1263,7 +1269,13 @@ void TPM2_Packet_ParseSignature(TPM2_Packet* packet, TPMT_SIGNATURE* sig) digestSz = TPM2_GetHashDigestSize(sig->signature.hmac.hashAlg); TPM2_Packet_ParseBytes(packet, sig->signature.hmac.digest.H, digestSz); break; + case TPM_ALG_NULL: + /* Legitimate zero-payload signature - nothing to consume. */ + break; default: + #ifdef DEBUG_WOLFTPM + printf("ParseSignature: unrecognized sigAlg 0x%x\n", sig->sigAlg); + #endif break; } } diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 63b78a17..12c2fd18 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -1902,6 +1902,43 @@ static void test_TPM2_ECC_Parameters_EcdaaResponseParse(void) printf("Test TPM Wrapper:\tEcdaaResponseParse:\t\tPassed\n"); } +/* TPM2_Packet_ParseSignature must explicitly recognize TPM_ALG_NULL as a + * zero-payload signature so subsequent fields stay aligned. The previous + * default-fallthrough lumped TPM_ALG_NULL together with unknown algorithms, + * making the property "Parse(Append(NULL signature)) consumes exactly the + * sigAlg bytes" depend on undocumented behavior. */ +static void test_TPM2_ParseSignature_NullAlg(void) +{ + TPM2_Packet packet; + byte buf[16]; + TPMT_SIGNATURE sig; + UINT16 sentinel; + int pos = 0; + + XMEMSET(buf, 0, sizeof(buf)); + XMEMSET(&packet, 0, sizeof(packet)); + + /* sigAlg = TPM_ALG_NULL */ + buf[pos++] = (byte)((TPM_ALG_NULL >> 8) & 0xFF); + buf[pos++] = (byte)(TPM_ALG_NULL & 0xFF); + /* sentinel right after the (zero-length) signature payload */ + buf[pos++] = 0xDE; + buf[pos++] = 0xAD; + + XMEMSET(&sig, 0, sizeof(sig)); + packet.buf = buf; + packet.size = pos; + packet.pos = 0; + + TPM2_Packet_ParseSignature(&packet, &sig); + AssertIntEQ(sig.sigAlg, TPM_ALG_NULL); + AssertIntEQ(packet.pos, 2); + sentinel = (UINT16)((buf[packet.pos] << 8) | buf[packet.pos + 1]); + AssertIntEQ(sentinel, 0xDEAD); + + printf("Test TPM Wrapper:\tParseSignature NULL alg:\tPassed\n"); +} + /* TPM2_Packet_ParsePoint must resync to outerStart + point->size so a * malformed wire blob with inner x.size / y.size disagreement can't * desynchronize subsequent fields. */ @@ -3711,6 +3748,7 @@ int unit_tests(int argc, char *argv[]) test_TPM2_ParseAttest_NvDigest(); test_TPM2_ParsePublic_OuterResync(); test_TPM2_ParsePoint_OuterResync(); + test_TPM2_ParseSignature_NullAlg(); test_TPM2_BrainpoolCurveMapping(); test_wolfTPM2_RsaEncryptDecrypt_OversizedBufferE(); test_wolfTPM2_SignHashScheme_DigestSize(); From e2d1c344ad93a1cd857dce6e0f0a87c948bbe8d2 Mon Sep 17 00:00:00 2001 From: Aidan Date: Tue, 28 Apr 2026 10:58:56 -0700 Subject: [PATCH 22/24] F-3507 - https://fenrir.wolfssl.com/finding/3507 - examples/attestation/activate_credential: zero-init tpmSession so early-failure cleanup is safe --- examples/attestation/activate_credential.c | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/attestation/activate_credential.c b/examples/attestation/activate_credential.c index a83b053d..3c716c81 100644 --- a/examples/attestation/activate_credential.c +++ b/examples/attestation/activate_credential.c @@ -99,6 +99,7 @@ int TPM2_ActivateCredential_Example(void* userCtx, int argc, char *argv[]) XMEMSET(&endorse, 0, sizeof(endorse)); XMEMSET(&storage, 0, sizeof(storage)); XMEMSET(&akKey, 0, sizeof(akKey)); + XMEMSET(&tpmSession, 0, sizeof(tpmSession)); printf("Demo how to create a credential blob for remote attestation\n"); rc = wolfTPM2_Init(&dev, TPM2_IoCb, userCtx); From 4817e799271613d63082d12e52d0f70a298eca14 Mon Sep 17 00:00:00 2001 From: Aidan Date: Tue, 28 Apr 2026 11:37:00 -0700 Subject: [PATCH 23/24] Skoll review fixes --- src/fwtpm/fwtpm_command.c | 28 +++++++----- src/tpm2_packet.c | 39 +++++++++++++---- src/tpm2_wrap.c | 57 +++++++++++++++++------- tests/fwtpm_unit_tests.c | 19 ++++---- tests/unit_tests.c | 92 ++++++++++++++++++++++++++++++++++++--- wolftpm/tpm2_wrap.h | 49 ++++++++++++++++++++- 6 files changed, 231 insertions(+), 53 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 8bbba65b..b5f852e2 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -11129,8 +11129,12 @@ static TPM_RC FwParseAttestParams(TPM2_Packet* cmd, int cmdSize, * hashAlg per Part 2 Sec. 11.2.1.5. */ if (*sigScheme == TPM_ALG_ECDAA) { UINT16 ecdaaCount; - TPM2_Packet_ParseU16(cmd, &ecdaaCount); - (void)ecdaaCount; + if (cmd->pos + 2 > cmdSize) + rc = TPM_RC_COMMAND_SIZE; + if (rc == 0) { + TPM2_Packet_ParseU16(cmd, &ecdaaCount); + (void)ecdaaCount; + } } } @@ -11441,8 +11445,12 @@ static TPM_RC FwCmd_CertifyCreation(FWTPM_CTX* ctx, TPM2_Packet* cmd, * hashAlg per Part 2 Sec. 11.2.1.5. */ if (sigScheme == TPM_ALG_ECDAA) { UINT16 ecdaaCount; - TPM2_Packet_ParseU16(cmd, &ecdaaCount); - (void)ecdaaCount; + if (cmd->pos + 2 > cmdSize) + rc = TPM_RC_COMMAND_SIZE; + if (rc == 0) { + TPM2_Packet_ParseU16(cmd, &ecdaaCount); + (void)ecdaaCount; + } } } @@ -11655,13 +11663,6 @@ static TPM_RC FwCmd_NV_Certify(FWTPM_CTX* ctx, TPM2_Packet* cmd, &qualifyingData, &sigScheme, &sigHashAlg); } - /* Per Part 3 Sec. 31.16.1: TPM_RH_NULL signHandle requires non-NULL - * scheme and hash algorithm, otherwise return TPM_RC_SCHEME. */ - if (rc == 0 && signHandle == TPM_RH_NULL && - (sigScheme == TPM_ALG_NULL || sigHashAlg == TPM_ALG_NULL)) { - rc = TPM_RC_SCHEME; - } - /* size, offset */ if (rc == 0) { TPM2_Packet_ParseU16(cmd, &readSize); @@ -11739,10 +11740,13 @@ static TPM_RC FwCmd_NV_Certify(FWTPM_CTX* ctx, TPM2_Packet* cmd, int hashSz; enum wc_HashType wcDigH; - /* Resolve hash from signing key when scheme/hash is NULL */ + /* Resolve hash from signing key when scheme/hash is NULL. + * keyScheme is filled in by the by-pointer interface but + * unused here - we only need the resolved hashAlg. */ if (hashAlg == TPM_ALG_NULL) { UINT16 keyScheme = TPM_ALG_NULL; FwResolveSignScheme(sigObj, &keyScheme, &hashAlg); + (void)keyScheme; } wcDigH = FwGetWcHashType(hashAlg); hashSz = TPM2_GetHashDigestSize(hashAlg); diff --git a/src/tpm2_packet.c b/src/tpm2_packet.c index 0a04f1af..7caa78d2 100644 --- a/src/tpm2_packet.c +++ b/src/tpm2_packet.c @@ -786,13 +786,28 @@ void TPM2_Packet_ParsePoint(TPM2_Packet* packet, TPM2B_ECC_POINT* point) TPM2_Packet_ParseU16(packet, &point->size); pointStartPos = (packet != NULL) ? packet->pos : 0; - TPM2_Packet_ParseEccPoint(packet, &point->point); + /* Skip the inner ECC point parse when the outer size is zero. A + * malformed blob with size=0 but nonzero inner x.size/y.size would + * otherwise advance packet->pos and desync subsequent fields. */ + if (point->size > 0) { + TPM2_Packet_ParseEccPoint(packet, &point->point); + } + else { + XMEMSET(&point->point, 0, sizeof(point->point)); + } /* Resync packet position to end of declared outer size so inner - * x.size / y.size disagreement can't desynchronize subsequent fields */ - if (packet != NULL && point->size > 0 && - pointStartPos + point->size <= packet->size) { - packet->pos = pointStartPos + point->size; + * x.size / y.size disagreement can't desynchronize subsequent fields. + * If the declared outer size runs past the buffer, clamp to packet end + * so subsequent reads return an out-of-bounds sentinel rather than + * leaving the position wherever the inner parses landed. */ + if (packet != NULL) { + if (pointStartPos + point->size <= packet->size) { + packet->pos = pointStartPos + point->size; + } + else { + packet->pos = packet->size; + } } } @@ -1149,10 +1164,16 @@ void TPM2_Packet_ParsePublic(TPM2_Packet* packet, TPM2B_PUBLIC* pub) /* Resync packet position to end of declared outer size so inner * parses can't cause field drift if declared size and actual - * inner consumption disagree */ - if (packet != NULL && - pubStartPos + pub->size <= packet->size) { - packet->pos = pubStartPos + pub->size; + * inner consumption disagree. If the declared outer size runs + * past the buffer, clamp to packet end so subsequent reads + * return an out-of-bounds sentinel. */ + if (packet != NULL) { + if (pubStartPos + pub->size <= packet->size) { + packet->pos = pubStartPos + pub->size; + } + else { + packet->pos = packet->size; + } } } } diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index 7fd4270e..f53796d3 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -3550,6 +3550,11 @@ int wolfTPM2_LoadEccPublicKey_ex(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, return BUFFER_E; if (eccPubYSz > sizeof(pub.publicArea.unique.ecc.y.buffer)) return BUFFER_E; + /* TPMS_SCHEME_ECDAA requires a 'count' field this helper cannot + * supply. Callers needing ECDAA must build TPM2B_PUBLIC manually and + * use wolfTPM2_LoadPublicKey directly. */ + if (scheme == TPM_ALG_ECDAA) + return BAD_FUNC_ARG; XMEMSET(&pub, 0, sizeof(pub)); pub.publicArea.type = TPM_ALG_ECC; @@ -5521,8 +5526,10 @@ int wolfTPM2_RsaEncrypt(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, #endif } - /* Plaintext copy lingers in the rsaEncIn stack frame after return - - * scrub it on every exit path. */ + /* Plaintext copy lingers in rsaEncIn after the XMEMCPY above. Scrub + * before returning so the stack frame doesn't leak it. The early + * BUFFER_E returns above happen before the XMEMCPY so they don't + * need this path. */ TPM2_ForceZero(&rsaEncIn, sizeof(rsaEncIn)); return rc; } @@ -5725,9 +5732,14 @@ int wolfTPM2_UnloadHandle(WOLFTPM2_DEV* dev, WOLFTPM2_HANDLE* handle) /* nv is the populated handle and auth */ /* auth and authSz are optional NV authentication */ /* authPolicy and authPolicySz are optional policy digest */ -int wolfTPM2_NVCreateAuthPolicy(WOLFTPM2_DEV* dev, WOLFTPM2_HANDLE* parent, +/* nameAlg is the index name hash algorithm; must match the hash that + * produced authPolicy when one is supplied, otherwise the policy can + * never be satisfied. Pass TPM_ALG_NULL to use WOLFTPM2_WRAP_DIGEST + * (default) or auto-infer from authPolicySz for SHA-only policies. */ +int wolfTPM2_NVCreateAuthPolicy_ex(WOLFTPM2_DEV* dev, WOLFTPM2_HANDLE* parent, WOLFTPM2_NV* nv, word32 nvIndex, word32 nvAttributes, word32 maxSize, - const byte* auth, int authSz, const byte* authPolicy, int authPolicySz) + const byte* auth, int authSz, const byte* authPolicy, int authPolicySz, + TPMI_ALG_HASH nameAlg) { int rc, rctmp; NV_DefineSpace_In in; @@ -5758,30 +5770,36 @@ int wolfTPM2_NVCreateAuthPolicy(WOLFTPM2_DEV* dev, WOLFTPM2_HANDLE* parent, XMEMCPY(in.auth.buffer, auth, in.auth.size); } in.publicInfo.nvPublic.nvIndex = nvIndex; - /* When a policy digest is supplied, nameAlg must match the hash that - * produced it - otherwise the policy can never be satisfied. Infer - * nameAlg from authPolicySz when possible, defaulting to - * WOLFTPM2_WRAP_DIGEST for the no-policy case. */ - if (authPolicy != NULL && authPolicySz > 0) { + if (nameAlg != TPM_ALG_NULL) { + /* Caller supplied an explicit nameAlg; use it as-is and only + * sanity-check against the policy digest size. */ + if (authPolicy != NULL && authPolicySz > 0) { + int expectSz = TPM2_GetHashDigestSize(nameAlg); + if (expectSz <= 0 || expectSz != authPolicySz) + return BAD_FUNC_ARG; + } + in.publicInfo.nvPublic.nameAlg = nameAlg; + } + else if (authPolicy != NULL && authPolicySz > 0) { + /* No explicit nameAlg supplied. Infer from policy digest size for + * SHA-only policies. The TPM stores the policy digest verbatim, + * so we don't need wolfCrypt to support the hash here - the + * caller has already computed the digest. SM3-256 / SHA3-256 + * also produce 32 bytes; callers needing those must pass + * nameAlg explicitly via the _ex API. */ switch (authPolicySz) { - #ifndef NO_SHA case TPM_SHA_DIGEST_SIZE: in.publicInfo.nvPublic.nameAlg = TPM_ALG_SHA1; break; - #endif case TPM_SHA256_DIGEST_SIZE: in.publicInfo.nvPublic.nameAlg = TPM_ALG_SHA256; break; - #ifdef WOLFSSL_SHA384 case TPM_SHA384_DIGEST_SIZE: in.publicInfo.nvPublic.nameAlg = TPM_ALG_SHA384; break; - #endif - #ifdef WOLFSSL_SHA512 case TPM_SHA512_DIGEST_SIZE: in.publicInfo.nvPublic.nameAlg = TPM_ALG_SHA512; break; - #endif default: return BAD_FUNC_ARG; } @@ -5830,6 +5848,15 @@ int wolfTPM2_NVCreateAuthPolicy(WOLFTPM2_DEV* dev, WOLFTPM2_HANDLE* parent, return rc; } +int wolfTPM2_NVCreateAuthPolicy(WOLFTPM2_DEV* dev, WOLFTPM2_HANDLE* parent, + WOLFTPM2_NV* nv, word32 nvIndex, word32 nvAttributes, word32 maxSize, + const byte* auth, int authSz, const byte* authPolicy, int authPolicySz) +{ + return wolfTPM2_NVCreateAuthPolicy_ex(dev, parent, nv, nvIndex, + nvAttributes, maxSize, auth, authSz, authPolicy, authPolicySz, + TPM_ALG_NULL); +} + int wolfTPM2_NVCreateAuth(WOLFTPM2_DEV* dev, WOLFTPM2_HANDLE* parent, WOLFTPM2_NV* nv, word32 nvIndex, word32 nvAttributes, word32 maxSize, const byte* auth, int authSz) diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index a8306328..fa7bf31d 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -1096,9 +1096,11 @@ static UINT32 CreatePrimaryHelper(FWTPM_CTX* ctx, TPM_ALG_ID alg) return GetU32BE(gRsp + TPM2_HEADER_SIZE); } -#ifdef HAVE_ECC +#if defined(HAVE_ECC) && !defined(FWTPM_NO_ATTESTATION) /* Build a non-restricted ECC-P256 sign-capable primary (for tests that - * require TPMA_OBJECT_sign and a key with no scheme bound at create time). */ + * require TPMA_OBJECT_sign and a key with no scheme bound at create time). + * Only consumed by the attestation tests, so gated to avoid + * -Werror=unused-function in FWTPM_NO_ATTESTATION builds. */ static int BuildCreatePrimaryEccSignCmd(byte* buf) { int pos = 0; @@ -1155,7 +1157,7 @@ static UINT32 CreatePrimaryEccSignHelper(FWTPM_CTX* ctx) if (GetRspRC(gRsp) != TPM_RC_SUCCESS) return 0; return GetU32BE(gRsp + TPM2_HEADER_SIZE); } -#endif +#endif /* HAVE_ECC && !FWTPM_NO_ATTESTATION */ /* Helper: flush a handle */ static void FlushHandle(FWTPM_CTX* ctx, UINT32 handle) @@ -1578,13 +1580,15 @@ static void test_fwtpm_nv_counter(void) } #ifndef FWTPM_NO_ATTESTATION +#ifdef HAVE_ECC /* TPMT_SIG_SCHEME parser must consume the extra UINT16 count field for * TPMS_SCHEME_ECDAA per Part 2 Sec. 11.2.1.5. Sending a Quote with * inScheme.scheme = TPM_ALG_ECDAA followed by hashAlg + count + a single * PCR selection (count=1) verifies that subsequent PCRselect parsing * is not desynchronized. With the bug, pcrSelectionsCount reads as 0 * because the count field is interpreted as the high 16 bits of the - * PCR-selection count. */ + * PCR-selection count. ECC-only because the RSA sign path rejects + * TPM_ALG_ECDAA with TPM_RC_SCHEME. */ static void test_fwtpm_quote_ecdaa_scheme(void) { FWTPM_CTX ctx; @@ -1598,11 +1602,7 @@ static void test_fwtpm_quote_ecdaa_scheme(void) memset(&ctx, 0, sizeof(ctx)); AssertIntEQ(fwtpm_test_startup(&ctx), 0); -#ifdef HAVE_ECC keyH = CreatePrimaryHelper(&ctx, TPM_ALG_ECC); -#else - keyH = CreatePrimaryHelper(&ctx, TPM_ALG_RSA); -#endif AssertIntNE(keyH, 0); /* Build TPM2_Quote with ECDAA inScheme. */ @@ -1650,6 +1650,7 @@ static void test_fwtpm_quote_ecdaa_scheme(void) FWTPM_Cleanup(&ctx); printf("Test fwTPM:\tQuote(ECDAA scheme):\t\tPassed\n"); } +#endif /* HAVE_ECC */ #ifdef HAVE_ECC /* TPM2_CertifyCreation inScheme parser must consume the extra UINT16 @@ -2540,8 +2541,8 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_nv_counter(); #ifndef FWTPM_NO_ATTESTATION test_fwtpm_nv_certify_digest_mode(); - test_fwtpm_quote_ecdaa_scheme(); #ifdef HAVE_ECC + test_fwtpm_quote_ecdaa_scheme(); test_fwtpm_sign_ecdaa_scheme(); test_fwtpm_certify_creation_ecdaa_scheme(); #endif diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 12c2fd18..cdee4c54 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -592,6 +592,7 @@ static void test_wolfTPM2_SetAuthHandle_PolicyAuthOffset(void) static void test_wolfTPM2_StartSession_SaltedEncryptAttrs(void) { #if !defined(WOLFTPM2_NO_WOLFCRYPT) + int rc; WOLFTPM2_DEV dev; WOLFTPM2_KEY tpmKey; WOLFTPM2_SESSION session; @@ -601,17 +602,26 @@ static void test_wolfTPM2_StartSession_SaltedEncryptAttrs(void) XMEMSET(&tpmKey, 0, sizeof(tpmKey)); XMEMSET(&session, 0, sizeof(session)); + /* Initialize so TPM2_GetNonceNoLock and dependent code paths have a + * valid context. Skip if no TPM is reachable. */ + rc = wolfTPM2_Init(&dev, TPM2_IoCb, NULL); + if (rc != 0) { + printf("Test TPM Wrapper:\tStartSession salted enc attrs:\tSkipped\n"); + return; + } + /* tpmKey with a non-NULL handle, no auth */ tpmKey.handle.hndl = 0x80000000; - /* Best effort - if no TPM is present the call returns early after the - * SetAuth path, which is what we want to inspect. */ + /* The call will fail later (no real key with that handle) but the + * SetAuth path that sets sessionAttributes runs first. */ (void)wolfTPM2_StartSession(&dev, &session, &tpmKey, NULL, TPM_SE_HMAC, TPM_ALG_CFB); AssertIntEQ((int)(dev.session[0].sessionAttributes & expected), (int)expected); + wolfTPM2_Cleanup(&dev); printf("Test TPM Wrapper:\tStartSession salted enc attrs:\tPassed\n"); #endif } @@ -1902,11 +1912,11 @@ static void test_TPM2_ECC_Parameters_EcdaaResponseParse(void) printf("Test TPM Wrapper:\tEcdaaResponseParse:\t\tPassed\n"); } -/* TPM2_Packet_ParseSignature must explicitly recognize TPM_ALG_NULL as a - * zero-payload signature so subsequent fields stay aligned. The previous - * default-fallthrough lumped TPM_ALG_NULL together with unknown algorithms, - * making the property "Parse(Append(NULL signature)) consumes exactly the - * sigAlg bytes" depend on undocumented behavior. */ +/* TPM2_Packet_AppendSignature / ParseSignature must explicitly recognize + * TPM_ALG_NULL as a zero-payload signature so subsequent fields stay + * aligned. The previous default-fallthrough lumped TPM_ALG_NULL together + * with unknown algorithms, making the property "Parse(Append(NULL)) + * consumes exactly the sigAlg bytes" depend on undocumented behavior. */ static void test_TPM2_ParseSignature_NullAlg(void) { TPM2_Packet packet; @@ -1936,6 +1946,29 @@ static void test_TPM2_ParseSignature_NullAlg(void) sentinel = (UINT16)((buf[packet.pos] << 8) | buf[packet.pos + 1]); AssertIntEQ(sentinel, 0xDEAD); + /* Round-trip: Append a TPM_ALG_NULL signature into a fresh packet and + * verify only the 2-byte sigAlg was written. A future regression that + * drops the explicit case (defaulting to silent fallthrough) would + * still pass for Parse but the Append side is also locked in here. */ + XMEMSET(buf, 0, sizeof(buf)); + XMEMSET(&packet, 0, sizeof(packet)); + XMEMSET(&sig, 0, sizeof(sig)); + sig.sigAlg = TPM_ALG_NULL; + packet.buf = buf; + packet.size = sizeof(buf); + packet.pos = 0; + TPM2_Packet_AppendSignature(&packet, &sig); + AssertIntEQ(packet.pos, 2); + AssertIntEQ(buf[0], (byte)((TPM_ALG_NULL >> 8) & 0xFF)); + AssertIntEQ(buf[1], (byte)(TPM_ALG_NULL & 0xFF)); + + /* Re-parse confirms the round-trip. */ + XMEMSET(&sig, 0, sizeof(sig)); + packet.pos = 0; + TPM2_Packet_ParseSignature(&packet, &sig); + AssertIntEQ(sig.sigAlg, TPM_ALG_NULL); + AssertIntEQ(packet.pos, 2); + printf("Test TPM Wrapper:\tParseSignature NULL alg:\tPassed\n"); } @@ -2224,6 +2257,44 @@ static void test_wolfTPM2_GetKeyTemplate_KeyedHash_Scheme(void) #endif } +/* wolfTPM2_VerifyHashTicket must apply the same RSA-strict / ECDSA-permissive + * digest size policy as wolfTPM2_SignHashScheme. The bounds check fires + * before any TPM call so this test does not require a working TPM. */ +static void test_wolfTPM2_VerifyHashTicket_DigestSize(void) +{ +#if !defined(WOLFTPM2_NO_WOLFCRYPT) && !defined(NO_RSA) + int rc; + WOLFTPM2_DEV dev; + WOLFTPM2_KEY key; + byte digest[TPM_MAX_DIGEST_SIZE]; + byte sig[MAX_RSA_KEY_BYTES]; + + XMEMSET(&dev, 0, sizeof(dev)); + XMEMSET(&key, 0, sizeof(key)); + XMEMSET(digest, 0xCC, sizeof(digest)); + XMEMSET(sig, 0, sizeof(sig)); + key.handle.hndl = 0x80000000; + key.pub.publicArea.type = TPM_ALG_RSA; + + /* SHA-256 digest (32) + hashAlg=SHA512 -> RSA mismatch -> BUFFER_E */ + rc = wolfTPM2_VerifyHashTicket(&dev, &key, sig, 256, digest, 32, + TPM_ALG_RSASSA, TPM_ALG_SHA512, NULL); + AssertIntEQ(rc, BUFFER_E); + + /* Oversized digest (64) + hashAlg=SHA256 -> BUFFER_E */ + rc = wolfTPM2_VerifyHashTicket(&dev, &key, sig, 256, digest, 64, + TPM_ALG_RSASSA, TPM_ALG_SHA256, NULL); + AssertIntEQ(rc, BUFFER_E); + + /* Negative digestSz -> BUFFER_E */ + rc = wolfTPM2_VerifyHashTicket(&dev, &key, sig, 256, digest, -1, + TPM_ALG_RSASSA, TPM_ALG_SHA256, NULL); + AssertIntEQ(rc, BUFFER_E); + + printf("Test TPM Wrapper:\tVerifyHashTicket size:\t\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. @@ -2354,6 +2425,12 @@ static void test_TPM2_BrainpoolCurveMapping(void) AssertIntEQ(TPM2_GetTpmCurve(ECC_SECP256R1), TPM_ECC_NIST_P256); AssertIntEQ(TPM2_GetWolfCurve(TPM_ECC_NIST_P256), ECC_SECP256R1); + /* TPM2_GetCurveSize must report the correct byte size for the new + * Brainpool curve IDs (32 / 48 / 64). */ + AssertIntEQ(TPM2_GetCurveSize(TPM_ECC_BP_P256_R1), 32); + AssertIntEQ(TPM2_GetCurveSize(TPM_ECC_BP_P384_R1), 48); + AssertIntEQ(TPM2_GetCurveSize(TPM_ECC_BP_P512_R1), 64); + printf("Test TPM Wrapper:\tBrainpool curve mapping:\tPassed\n"); #endif } @@ -3752,6 +3829,7 @@ int unit_tests(int argc, char *argv[]) test_TPM2_BrainpoolCurveMapping(); test_wolfTPM2_RsaEncryptDecrypt_OversizedBufferE(); test_wolfTPM2_SignHashScheme_DigestSize(); + test_wolfTPM2_VerifyHashTicket_DigestSize(); test_wolfTPM2_NVCreateAuthPolicy_NameAlg(); test_wolfTPM2_GetKeyTemplate_KeyedHash_Scheme(); test_wolfTPM2_LoadEccPublicKey_Ex(); diff --git a/wolftpm/tpm2_wrap.h b/wolftpm/tpm2_wrap.h index 80aa68e2..84d74912 100644 --- a/wolftpm/tpm2_wrap.h +++ b/wolftpm/tpm2_wrap.h @@ -1321,9 +1321,25 @@ WOLFTPM_API int wolfTPM2_LoadEccPublicKey(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, scheme/hash combinations. \return TPM_RC_SUCCESS: successful - \return BAD_FUNC_ARG: check the provided arguments + \return BAD_FUNC_ARG: check the provided arguments, or scheme is + TPM_ALG_ECDAA (use wolfTPM2_LoadPublicKey directly for ECDAA) \return BUFFER_E: caller buffer is too small for ECC point coordinates + \param dev pointer to a TPM2_DEV struct + \param key pointer to an empty struct of WOLFTPM2_KEY type + \param curveId integer value, one of the accepted TPM_ECC_CURVE values + \param eccPubX pointer to a byte buffer containing the public material of point X + \param eccPubXSz integer value of word32 type, specifying the point X buffer size + \param eccPubY pointer to a byte buffer containing the public material of point Y + \param eccPubYSz integer value of word32 type, specifying the point Y buffer size + \param scheme ECC signing scheme (e.g. TPM_ALG_ECDSA, TPM_ALG_ECDH, + TPM_ALG_NULL). Use TPM_ALG_ECDH for ECDH peer keys + \param hashAlg hash algorithm bound to scheme; ignored when scheme is + TPM_ALG_NULL + \param objectAttributes TPMA_OBJECT bitmask. Use TPMA_OBJECT_decrypt for + ECDH peer keys, TPMA_OBJECT_sign for verification keys; typically + OR'd with TPMA_OBJECT_userWithAuth and TPMA_OBJECT_noDA + \sa wolfTPM2_LoadEccPublicKey \sa wolfTPM2_LoadRsaPublicKey_ex */ @@ -2339,6 +2355,37 @@ WOLFTPM_API int wolfTPM2_NVCreateAuthPolicy(WOLFTPM2_DEV* dev, WOLFTPM2_HANDLE* WOLFTPM2_NV* nv, word32 nvIndex, word32 nvAttributes, word32 maxSize, const byte* auth, int authSz, const byte* authPolicy, int authPolicySz); +/*! + \ingroup wolfTPM2_Wrappers + \brief Variant of wolfTPM2_NVCreateAuthPolicy that lets the caller pass + an explicit nameAlg. Required when authPolicy was computed with + SM3-256, SHA3-256, or any other non-SHA hash that the size-based + legacy inference cannot distinguish from SHA-256. + + \return TPM_RC_SUCCESS: successful + \return BAD_FUNC_ARG: nameAlg digest size disagrees with authPolicySz, + or other invalid arguments + \return BUFFER_E: auth or authPolicy buffer too large + + \param dev pointer to a TPM2_DEV struct + \param parent pointer to a WOLFTPM2_HANDLE for the hierarchy + \param nv pointer to an empty WOLFTPM2_NV that receives the created index + \param nvIndex NV index handle (e.g. 0x01400001) + \param nvAttributes TPMA_NV bitmask + \param maxSize NV index data size in bytes + \param auth optional NV authValue (NULL/0 for none) + \param authSz optional NV authValue size + \param authPolicy optional policy digest (NULL/0 for none) + \param authPolicySz optional policy digest size + \param nameAlg index name hash algorithm (e.g. TPM_ALG_SHA384). Pass + TPM_ALG_NULL to fall back to size-based inference for SHA-only + policies, or to use WOLFTPM2_WRAP_DIGEST for the no-policy case +*/ +WOLFTPM_API int wolfTPM2_NVCreateAuthPolicy_ex(WOLFTPM2_DEV* dev, + WOLFTPM2_HANDLE* parent, WOLFTPM2_NV* nv, word32 nvIndex, + word32 nvAttributes, word32 maxSize, const byte* auth, int authSz, + const byte* authPolicy, int authPolicySz, TPMI_ALG_HASH nameAlg); + /*! \ingroup wolfTPM2_Wrappers \brief Stores user data to a NV Index, at a given offset From 2cc208906c4076190b48ef0cf417d5fce1c23946 Mon Sep 17 00:00:00 2001 From: Aidan Date: Tue, 28 Apr 2026 13:14:34 -0700 Subject: [PATCH 24/24] F-3274 follow-up - tests/fwtpm_unit_tests: gate ECC sign helper for FWTPM_NO_NV builds too --- tests/fwtpm_unit_tests.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index fa7bf31d..36980bb7 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -1096,11 +1096,13 @@ static UINT32 CreatePrimaryHelper(FWTPM_CTX* ctx, TPM_ALG_ID alg) return GetU32BE(gRsp + TPM2_HEADER_SIZE); } -#if defined(HAVE_ECC) && !defined(FWTPM_NO_ATTESTATION) +#if defined(HAVE_ECC) && !defined(FWTPM_NO_ATTESTATION) && \ + !defined(FWTPM_NO_NV) /* Build a non-restricted ECC-P256 sign-capable primary (for tests that * require TPMA_OBJECT_sign and a key with no scheme bound at create time). - * Only consumed by the attestation tests, so gated to avoid - * -Werror=unused-function in FWTPM_NO_ATTESTATION builds. */ + * Only consumed by the attestation tests nested inside the NV-tests + * section, so gated to avoid -Werror=unused-function in + * FWTPM_NO_ATTESTATION or FWTPM_NO_NV builds. */ static int BuildCreatePrimaryEccSignCmd(byte* buf) { int pos = 0; @@ -1157,7 +1159,7 @@ static UINT32 CreatePrimaryEccSignHelper(FWTPM_CTX* ctx) if (GetRspRC(gRsp) != TPM_RC_SUCCESS) return 0; return GetU32BE(gRsp + TPM2_HEADER_SIZE); } -#endif /* HAVE_ECC && !FWTPM_NO_ATTESTATION */ +#endif /* HAVE_ECC && !FWTPM_NO_ATTESTATION && !FWTPM_NO_NV */ /* Helper: flush a handle */ static void FlushHandle(FWTPM_CTX* ctx, UINT32 handle)