From 657cfa499f37afb6ecd28fee58a8af7541fcb8f0 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 31 Aug 2026 08:32:31 -0700 Subject: [PATCH 1/8] F-12708 - Report live ReadClock counters --- src/fwtpm/fwtpm_command.c | 5 ++-- tests/fwtpm_unit_tests.c | 50 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 553ff53a..4eb3f4ac 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -2991,7 +2991,6 @@ static TPM_RC FwCmd_ReadClock(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, (void)cmd; (void)cmdSize; - (void)ctx; (void)cmdTag; #ifdef DEBUG_WOLFTPM @@ -3005,8 +3004,8 @@ static TPM_RC FwCmd_ReadClock(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, clockMs = FWTPM_Clock_GetMs(ctx); TPM2_Packet_AppendU64(rsp, clockMs); /* time */ TPM2_Packet_AppendU64(rsp, clockMs); /* clock */ - TPM2_Packet_AppendU32(rsp, 0); /* resetCount */ - TPM2_Packet_AppendU32(rsp, 0); /* restartCount */ + TPM2_Packet_AppendU32(rsp, ctx->resetCount); + TPM2_Packet_AppendU32(rsp, ctx->restartCount); TPM2_Packet_AppendU8(rsp, 1); /* safe = YES */ FwRspFinalize(rsp, TPM_ST_NO_SESSIONS, TPM_RC_SUCCESS); diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index e05f5d40..aff4a19f 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -1739,6 +1739,55 @@ static void test_fwtpm_readclock(void) FWTPM_Cleanup(&ctx); fwtpm_pass("ReadClock:", 0); } + +static void AssertReadClockCounters(FWTPM_CTX* ctx) +{ + int rc, rspSize, cmdSz; + int clockInfoPos = TPM2_HEADER_SIZE + 8; + + cmdSz = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 10, + TPM_CC_ReadClock); + rspSize = 0; + rc = FWTPM_ProcessCommand(ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(rspSize, clockInfoPos + 17); + AssertIntEQ((int)GetU32BE(gRsp + clockInfoPos + 8), + (int)ctx->resetCount); + AssertIntEQ((int)GetU32BE(gRsp + clockInfoPos + 12), + (int)ctx->restartCount); +} + +static void test_fwtpm_readclock_counters(void) +{ + FWTPM_CTX ctx; + int rc, rspSize, cmdSz; + + (void)remove(FWTPM_NV_FILE); + XMEMSET(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + AssertIntGT((int)ctx.resetCount, 0); + AssertReadClockCounters(&ctx); + FWTPM_Cleanup(&ctx); + + XMEMSET(&ctx, 0, sizeof(ctx)); + AssertIntEQ(FWTPM_Init(&ctx), 0); + cmdSz = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 12, + TPM_CC_Startup); + PutU16BE(gCmd + cmdSz, TPM_SU_STATE); + cmdSz += 2; + PutU32BE(gCmd + 2, (UINT32)cmdSz); + rspSize = 0; + rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntGT((int)ctx.restartCount, 0); + AssertReadClockCounters(&ctx); + + FWTPM_Cleanup(&ctx); + (void)remove(FWTPM_NV_FILE); + fwtpm_pass("ReadClock state counters:", 0); +} #endif /* !FWTPM_NO_CLOCK */ /* ================================================================== */ @@ -13304,6 +13353,7 @@ int fwtpm_unit_tests(int argc, char *argv[]) /* Clock */ #ifndef FWTPM_NO_CLOCK test_fwtpm_readclock(); + test_fwtpm_readclock_counters(); test_fwtpm_clock_set(); #endif /* !FWTPM_NO_CLOCK */ From 17c6926f808a8beb685de2f1b5f5d9f327b6a1cb Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 31 Aug 2026 08:58:52 -0700 Subject: [PATCH 2/8] F-12709 - Order handle capability pages numerically --- src/fwtpm/fwtpm_command.c | 195 ++++++++++++++++++-------------------- tests/fwtpm_unit_tests.c | 164 ++++++++++++++++++++++++++++++++ 2 files changed, 257 insertions(+), 102 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 4eb3f4ac..eab309ea 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -1207,6 +1207,76 @@ static void FwPatchMoreData(TPM2_Packet* rsp, int pos, byte v) rsp->buf[pos] = v; } +/* Select the numerically lowest handle in handleClass that is at or above + * property and, after the first selection, greater than previous. The slot + * tables are intentionally rescanned per result: their configured defaults + * are small, and this avoids scratch storage proportional to the table sizes. */ +static int FwSelectCapabilityHandle(const FWTPM_CTX* ctx, UINT32 handleClass, + UINT32 property, UINT32 previous, int havePrevious, UINT32* selected) +{ + int idx; + int slotCount = 0; + int found = 0; + int used; + UINT32 candidate; + + if (handleClass == HR_TRANSIENT) { + slotCount = FWTPM_MAX_OBJECTS; + } + else if (handleClass == HR_PERSISTENT) { + slotCount = FWTPM_MAX_PERSISTENT; + } + #ifndef FWTPM_NO_NV + else if (handleClass == HR_NV_INDEX) { + slotCount = FWTPM_MAX_NV_INDICES; + } + #endif + else if (handleClass == HR_HMAC_SESSION || + handleClass == HR_POLICY_SESSION) { + slotCount = FWTPM_MAX_SESSIONS; + } + + for (idx = 0; idx < slotCount; idx++) { + used = 0; + candidate = 0; + if (handleClass == HR_TRANSIENT) { + used = ctx->objects[idx].used; + candidate = ctx->objects[idx].handle; + } + else if (handleClass == HR_PERSISTENT) { + used = ctx->persistent[idx].used; + candidate = ctx->persistent[idx].handle; + } + #ifndef FWTPM_NO_NV + else if (handleClass == HR_NV_INDEX) { + used = ctx->nvIndices[idx].inUse; + candidate = ctx->nvIndices[idx].nvPublic.nvIndex; + } + #endif + else if (handleClass == HR_HMAC_SESSION || + handleClass == HR_POLICY_SESSION) { + used = ctx->sessions[idx].used; + candidate = ctx->sessions[idx].handle; + } + + /* TPM_HT_LOADED_SESSION (0x02) covers both HMAC and policy sessions. + * Preserve each real 0x02/0x03 handle prefix so the reported handle + * remains directly usable instead of normalizing it to 0x02. A 0x03 + * query is deliberately limited to loaded policy sessions because + * fwTPM has no saved-session list. */ + if (used && (handleClass == HR_HMAC_SESSION || + (candidate & HR_RANGE_MASK) == handleClass) && + candidate >= property && + (!havePrevious || candidate > previous) && + (!found || candidate < *selected)) { + *selected = candidate; + found = 1; + } + } + + return found; +} + /* --- TPM2_GetCapability (CC 0x017A) --- */ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, TPM2_Packet* rsp, UINT16 cmdTag) @@ -1219,8 +1289,6 @@ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd, int paramSzPos, paramStart; int moreDataPos; - (void)ctx; - if (cmdSize < TPM2_HEADER_SIZE + 12) { rc = TPM_RC_COMMAND_SIZE; } @@ -1694,107 +1762,30 @@ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd, } case TPM_CAP_HANDLES: { - int count = 0; - int idx; - UINT32 handleClass = property & 0xFF000000; - - /* Filter by handle class per TPM 2.0 spec Part 2 Section 8.4: - * only return handles whose upper byte matches property */ - if (handleClass == 0x80000000) { - /* Transient objects */ - for (idx = 0; idx < FWTPM_MAX_OBJECTS; idx++) { - if (ctx->objects[idx].used && - ctx->objects[idx].handle >= property) { - count++; - } - } - } - else if (handleClass == 0x81000000) { - /* Persistent objects */ - for (idx = 0; idx < FWTPM_MAX_PERSISTENT; idx++) { - if (ctx->persistent[idx].used && - ctx->persistent[idx].handle >= property) { - count++; - } - } - } - #ifndef FWTPM_NO_NV - else if (handleClass == 0x01000000) { - /* NV indices */ - for (idx = 0; idx < FWTPM_MAX_NV_INDICES; idx++) { - if (ctx->nvIndices[idx].inUse && - ctx->nvIndices[idx].nvPublic.nvIndex >= property) { - count++; - } - } - } - #endif - else if (handleClass == 0x02000000 || - handleClass == 0x03000000) { - /* HMAC / policy sessions */ - for (idx = 0; idx < FWTPM_MAX_SESSIONS; idx++) { - if (ctx->sessions[idx].used && - ctx->sessions[idx].handle >= property) { - count++; - } - } + UINT32 handleClass = property & HR_RANGE_MASK; + UINT32 selected = 0; + UINT32 previous = 0; + int countPos = rsp->pos; + int emitted = 0; + int havePrevious = 0; + + TPM2_Packet_AppendU32(rsp, 0); /* back-patched below */ + while ((UINT32)emitted < propertyCount && + FwSelectCapabilityHandle(ctx, handleClass, property, + previous, havePrevious, &selected)) { + TPM2_Packet_AppendU32(rsp, selected); + previous = selected; + havePrevious = 1; + emitted++; } - /* Other classes (PCR, permanent): report 0 */ - - if ((UINT32)count > propertyCount) { - count = (int)propertyCount; - FwPatchMoreData(rsp, moreDataPos, 1); /* more handles available */ - } - TPM2_Packet_AppendU32(rsp, (UINT32)count); - if (count > 0) { - int emitted = 0; - if (handleClass == 0x81000000) { - for (idx = 0; idx < FWTPM_MAX_PERSISTENT && - emitted < count; idx++) { - if (ctx->persistent[idx].used && - ctx->persistent[idx].handle >= property) { - TPM2_Packet_AppendU32(rsp, - ctx->persistent[idx].handle); - emitted++; - } - } - } - else if (handleClass == 0x80000000) { - for (idx = 0; idx < FWTPM_MAX_OBJECTS && - emitted < count; idx++) { - if (ctx->objects[idx].used && - ctx->objects[idx].handle >= property) { - TPM2_Packet_AppendU32(rsp, - ctx->objects[idx].handle); - emitted++; - } - } - } - #ifndef FWTPM_NO_NV - else if (handleClass == 0x01000000) { - for (idx = 0; idx < FWTPM_MAX_NV_INDICES && - emitted < count; idx++) { - if (ctx->nvIndices[idx].inUse && - ctx->nvIndices[idx].nvPublic.nvIndex >= property) { - TPM2_Packet_AppendU32(rsp, - ctx->nvIndices[idx].nvPublic.nvIndex); - emitted++; - } - } - } - #endif - else if (handleClass == 0x02000000 || - handleClass == 0x03000000) { - for (idx = 0; idx < FWTPM_MAX_SESSIONS && - emitted < count; idx++) { - if (ctx->sessions[idx].used && - ctx->sessions[idx].handle >= property) { - TPM2_Packet_AppendU32(rsp, - ctx->sessions[idx].handle); - emitted++; - } - } - } + FwPatchU32BE(rsp, countPos, (UINT32)emitted); + + /* If selection exhausted the table before reaching the requested + * count, it already proved there is no next page. */ + if ((UINT32)emitted == propertyCount && + FwSelectCapabilityHandle(ctx, handleClass, property, + previous, havePrevious, &selected)) { + FwPatchMoreData(rsp, moreDataPos, 1); } break; } diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index aff4a19f..bae3bbd2 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -1161,6 +1161,165 @@ static void test_fwtpm_getcap_paging(void) fwtpm_pass("GetCapability paging convergence:", 0); } +#if FWTPM_MAX_OBJECTS >= 2 || FWTPM_MAX_PERSISTENT >= 2 || \ + FWTPM_MAX_SESSIONS >= 2 || \ + (!defined(FWTPM_NO_NV) && FWTPM_MAX_NV_INDICES >= 2) +static UINT32 getcap_handle_page_ex(FWTPM_CTX* ctx, UINT32 property, + UINT32 propertyCount, byte* moreData, UINT32* handleCount) +{ + int rc, rspSize, cmdSz; + + cmdSz = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, + TPM_CC_GetCapability); + PutU32BE(gCmd + cmdSz, TPM_CAP_HANDLES); cmdSz += 4; + PutU32BE(gCmd + cmdSz, property); cmdSz += 4; + PutU32BE(gCmd + cmdSz, propertyCount); cmdSz += 4; + PutU32BE(gCmd + 2, (UINT32)cmdSz); + rspSize = 0; + rc = FWTPM_ProcessCommand(ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertTrue(rspSize >= TPM2_HEADER_SIZE + 9); + AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE + 1), + (int)TPM_CAP_HANDLES); + *moreData = gRsp[TPM2_HEADER_SIZE]; + *handleCount = GetU32BE(gRsp + TPM2_HEADER_SIZE + 5); + AssertTrue(*handleCount <= propertyCount); + if (*handleCount > 0U) { + AssertTrue(rspSize >= TPM2_HEADER_SIZE + 13); + return GetU32BE(gRsp + TPM2_HEADER_SIZE + 9); + } + return 0; +} + +static UINT32 getcap_handle_page(FWTPM_CTX* ctx, UINT32 property, + byte* moreData) +{ + UINT32 handleCount; + UINT32 handle; + + handle = getcap_handle_page_ex(ctx, property, 1, moreData, + &handleCount); + AssertIntEQ(handleCount, 1); + return handle; +} + +static void check_handle_zero_count(FWTPM_CTX* ctx, UINT32 property) +{ + UINT32 handleCount; + byte moreData; + + (void)getcap_handle_page_ex(ctx, property, 0, &moreData, &handleCount); + AssertIntEQ(handleCount, 0); + AssertIntEQ(moreData, 1); +} + +static void check_empty_handle_class(FWTPM_CTX* ctx, UINT32 property) +{ + UINT32 handleCount; + byte moreData; + + (void)getcap_handle_page_ex(ctx, property, 1, &moreData, &handleCount); + AssertIntEQ(handleCount, 0); + AssertIntEQ(moreData, 0); +} + +static void check_handle_paging(FWTPM_CTX* ctx, UINT32 firstProperty, + UINT32 lowHandle, UINT32 highHandle) +{ + UINT32 handle; + byte moreData; + + handle = getcap_handle_page(ctx, firstProperty, &moreData); + AssertIntEQ(handle, lowHandle); + AssertIntEQ(moreData, 1); + + handle = getcap_handle_page(ctx, handle + 1, &moreData); + AssertIntEQ(handle, highHandle); + AssertIntEQ(moreData, 0); +} + +/* Handle capability pages are numerically ordered independently of their + * backing-slot order, so cursor-based enumeration cannot omit an entry. */ +static void test_fwtpm_getcap_handles_ordered(void) +{ + FWTPM_CTX ctx; + UINT32 lowHandle; + UINT32 highHandle; +#if FWTPM_MAX_SESSIONS >= 2 + byte moreData; +#endif + + (void)remove(FWTPM_NV_FILE); + XMEMSET(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + /* PCR and permanent handles have no backing capability table. */ + check_empty_handle_class(&ctx, PCR_FIRST); + check_empty_handle_class(&ctx, PERMANENT_FIRST); + +#if FWTPM_MAX_OBJECTS >= 2 + lowHandle = TRANSIENT_FIRST + 0x10u; + highHandle = TRANSIENT_FIRST + 0x20u; + ctx.objects[0].used = 1; + ctx.objects[0].handle = highHandle; + ctx.objects[1].used = 1; + ctx.objects[1].handle = lowHandle; + check_handle_zero_count(&ctx, TRANSIENT_FIRST); + check_handle_paging(&ctx, TRANSIENT_FIRST, lowHandle, highHandle); + XMEMSET(ctx.objects, 0, sizeof(ctx.objects)); +#endif /* FWTPM_MAX_OBJECTS >= 2 */ + +#if FWTPM_MAX_PERSISTENT >= 2 + lowHandle = PERSISTENT_FIRST + 0x10u; + highHandle = PERSISTENT_FIRST + 0x20u; + ctx.persistent[0].used = 1; + ctx.persistent[0].handle = highHandle; + ctx.persistent[1].used = 1; + ctx.persistent[1].handle = lowHandle; + check_handle_zero_count(&ctx, PERSISTENT_FIRST); + check_handle_paging(&ctx, PERSISTENT_FIRST, lowHandle, highHandle); + XMEMSET(ctx.persistent, 0, sizeof(ctx.persistent)); +#endif /* FWTPM_MAX_PERSISTENT >= 2 */ + +#if !defined(FWTPM_NO_NV) && FWTPM_MAX_NV_INDICES >= 2 + lowHandle = NV_INDEX_FIRST + 0x10u; + highHandle = NV_INDEX_FIRST + 0x20u; + ctx.nvIndices[0].inUse = 1; + ctx.nvIndices[0].nvPublic.nvIndex = highHandle; + ctx.nvIndices[1].inUse = 1; + ctx.nvIndices[1].nvPublic.nvIndex = lowHandle; + check_handle_zero_count(&ctx, NV_INDEX_FIRST); + check_handle_paging(&ctx, NV_INDEX_FIRST, lowHandle, highHandle); + XMEMSET(ctx.nvIndices, 0, sizeof(ctx.nvIndices)); +#endif /* !FWTPM_NO_NV && FWTPM_MAX_NV_INDICES >= 2 */ + +#if FWTPM_MAX_SESSIONS >= 2 + /* A loaded-session query intentionally spans HMAC and policy sessions; + * fwTPM reports each session's real, directly usable handle prefix. */ + lowHandle = HMAC_SESSION_FIRST + 0x10u; + highHandle = POLICY_SESSION_FIRST + 0x20u; + ctx.sessions[0].used = 1; + ctx.sessions[0].handle = highHandle; + ctx.sessions[1].used = 1; + ctx.sessions[1].handle = lowHandle; + check_handle_zero_count(&ctx, HMAC_SESSION_FIRST); + check_handle_paging(&ctx, HMAC_SESSION_FIRST, lowHandle, highHandle); + + /* A policy-session query must exclude the lower HMAC session and return + * the policy handle without claiming another page. */ + highHandle = getcap_handle_page(&ctx, POLICY_SESSION_FIRST, &moreData); + AssertIntEQ(highHandle, POLICY_SESSION_FIRST + 0x20u); + AssertIntEQ(moreData, 0); + XMEMSET(ctx.sessions, 0, sizeof(ctx.sessions)); +#endif /* FWTPM_MAX_SESSIONS >= 2 */ + + FWTPM_Cleanup(&ctx); + (void)remove(FWTPM_NV_FILE); + fwtpm_pass("GetCapability(HANDLES) ordered paging:", 0); +} +#endif /* handle capability test has at least two slots */ + /* ================================================================== */ /* Command-group gates (FWTPM_NO_* macros) */ /* ================================================================== */ @@ -13328,6 +13487,11 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_getcap_properties(); test_fwtpm_getcap_pcrs(); test_fwtpm_getcap_paging(); +#if FWTPM_MAX_OBJECTS >= 2 || FWTPM_MAX_PERSISTENT >= 2 || \ + FWTPM_MAX_SESSIONS >= 2 || \ + (!defined(FWTPM_NO_NV) && FWTPM_MAX_NV_INDICES >= 2) + test_fwtpm_getcap_handles_ordered(); +#endif /* handle capability test has at least two slots */ test_fwtpm_total_commands(); /* Command-group gates (FWTPM_NO_* macros) */ From 3507da042967de73316b39ff88826b8257c59ea8 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 31 Aug 2026 09:36:59 -0700 Subject: [PATCH 3/8] F-12710 - Advertise supported ECC curves --- CMakeLists.txt | 1 + docs/FWTPM.md | 3 +- src/fwtpm/fwtpm_command.c | 180 +++++++++++++++++++++++++------ src/fwtpm/fwtpm_crypto.c | 41 ++++++-- tests/fwtpm_unit_tests.c | 216 +++++++++++++++++++++++++++++++++++++- tests/include.am | 3 +- wolftpm/fwtpm/fwtpm.h | 9 ++ 7 files changed, 409 insertions(+), 44 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f014bf1..c7675e10 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -556,6 +556,7 @@ if(WOLFTPM_FWTPM) ) target_compile_definitions(fwtpm_unit_test PRIVATE "WOLFTPM_FWTPM" + "WOLFTPM_FWTPM_UNIT_TEST" "FWTPM_NV_FILE=\"fwtpm_test_nv.bin\"" ) if(WOLFTPM_FWTPM_NV_APPEND_ONLY) diff --git a/docs/FWTPM.md b/docs/FWTPM.md index a63b0620..61829959 100644 --- a/docs/FWTPM.md +++ b/docs/FWTPM.md @@ -643,7 +643,8 @@ disabled, the corresponding TPM commands are excluded from the build. | `NO_RSA` | not defined | Excludes RSA keygen, sign, verify, `RSA_Encrypt`, `RSA_Decrypt` | | `HAVE_ECC` | defined | Enables ECC keygen, sign, verify, `ECDH_KeyGen`, `ECDH_ZGen`, `ECC_Parameters` | | `HAVE_ECC384` | defined | Enables P-384 curve support | -| `HAVE_ECC521` | defined | Enables P-521 curve support | +| `HAVE_ECC521` or `HAVE_ALL_CURVES` | build-dependent | Enables P-521 when `MAX_ECC_KEY_BITS >= 521` provides 66-byte TPM ECC fields | +| `ECC_MIN_KEY_SZ` | wolfCrypt-defined | Excludes smaller curves from `ECC_Parameters` and `TPM_CAP_ECC_CURVES` | | `NO_AES` | not defined | Excludes `EncryptDecrypt`, `EncryptDecrypt2`, AES parameter encryption | | `WOLFSSL_SHA384` | defined | Enables SHA-384 PCR bank | diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index eab309ea..787445ca 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -39,6 +39,7 @@ #include #include +#include #include #include @@ -1207,6 +1208,55 @@ static void FwPatchMoreData(TPM2_Packet* rsp, int pos, byte v) rsp->buf[pos] = v; } +#ifdef HAVE_ECC +typedef struct FWTPM_ECC_CURVE_INFO { + UINT16 tpmCurve; + UINT16 keyBits; +} FWTPM_ECC_CURVE_INFO; + +static const FWTPM_ECC_CURVE_INFO gFwEccCurves[] = { + { TPM_ECC_NIST_P256, 256 }, + { TPM_ECC_NIST_P384, 384 }, +#ifdef FWTPM_HAVE_ECC521 + { TPM_ECC_NIST_P521, 521 }, +#endif +}; + +static const FWTPM_ECC_CURVE_INFO* FwGetEccCurveInfo(UINT16 curve) +{ + int i; + + for (i = 0; i < (int)(sizeof(gFwEccCurves) / + sizeof(gFwEccCurves[0])); i++) { + if (gFwEccCurves[i].tpmCurve == curve) { + return &gFwEccCurves[i]; + } + } + return NULL; +} + +static const ecc_set_type* FwGetEccCurveParams(UINT16 curve) +{ + const FWTPM_ECC_CURVE_INFO* curveInfo; + int wcCurve; + int curveIdx; + + curveInfo = FwGetEccCurveInfo(curve); + if (curveInfo == NULL || curveInfo->keyBits < ECC_MIN_KEY_SZ) { + return NULL; + } + wcCurve = FwGetWcCurveId(curve); + if (wcCurve < 0) { + return NULL; + } + curveIdx = wc_ecc_get_curve_idx(wcCurve); + if (curveIdx < 0) { + return NULL; + } + return wc_ecc_get_curve_params(curveIdx); +} +#endif /* HAVE_ECC */ + /* Select the numerically lowest handle in handleClass that is at or above * property and, after the first selection, greater than previous. The slot * tables are intentionally rescanned per result: their configured defaults @@ -1789,9 +1839,35 @@ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd, } break; } + case TPM_CAP_ECC_CURVES: { + #ifdef HAVE_ECC + UINT32 count = 0; + int countPos = rsp->pos; + + TPM2_Packet_AppendU32(rsp, 0); /* back-patched below */ + for (i = 0; i < (UINT32)(sizeof(gFwEccCurves) / + sizeof(gFwEccCurves[0])); i++) { + if ((UINT32)gFwEccCurves[i].tpmCurve >= property && + FwGetEccCurveParams(gFwEccCurves[i].tpmCurve) != NULL) { + if (count < propertyCount) { + TPM2_Packet_AppendU16(rsp, + gFwEccCurves[i].tpmCurve); + count++; + } + else { + FwPatchMoreData(rsp, moreDataPos, 1); + break; + } + } + } + FwPatchU32BE(rsp, countPos, count); + #else + TPM2_Packet_AppendU32(rsp, 0); + #endif + break; + } case TPM_CAP_PP_COMMANDS: case TPM_CAP_AUDIT_COMMANDS: - case TPM_CAP_ECC_CURVES: TPM2_Packet_AppendU32(rsp, 0); break; @@ -15515,59 +15591,89 @@ static TPM_RC FwCmd_ActivateCredential(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* ================================================================== */ #ifndef FWTPM_NO_ECDH -/* Convert hex string to binary. Returns byte count, or -1 on error. */ +static int FwHexNibble(char c) +{ + if (c >= '0' && c <= '9') { + return c - '0'; + } + if (c >= 'A' && c <= 'F') { + return c - 'A' + 10; + } + if (c >= 'a' && c <= 'f') { + return c - 'a' + 10; + } + return -1; +} + +/* Convert a big-endian hex value to binary. An odd leading digit is the low + * nibble of the first output byte. Returns byte count, or -1 on error. */ static int FwHexToBin(const char* hex, byte* out, int outSz) { - int i, len; - if (hex == NULL) return -1; - len = (int)XSTRLEN(hex); - if (len & 1) return -1; - len /= 2; - if (len > outSz) return -1; - for (i = 0; i < len; i++) { - byte hi, lo; - char ch = hex[i * 2]; - char cl = hex[i * 2 + 1]; - hi = (byte)((ch >= 'A' && ch <= 'F') ? (ch - 'A' + 10) : - (ch >= 'a' && ch <= 'f') ? (ch - 'a' + 10) : (ch - '0')); - lo = (byte)((cl >= 'A' && cl <= 'F') ? (cl - 'A' + 10) : - (cl >= 'a' && cl <= 'f') ? (cl - 'a' + 10) : (cl - '0')); - out[i] = (byte)((hi << 4) | lo); - } - return len; + int i, len, decodedLen, outPos = 0; + int hi, lo; + size_t hexLen; + + if (hex == NULL || out == NULL || outSz < 0) return -1; + hexLen = XSTRLEN(hex); + if (hexLen > (size_t)INT_MAX) return -1; + len = (int)hexLen; + decodedLen = len / 2 + (len & 1); + if (decodedLen > outSz) { + return -1; + } + + i = 0; + if ((len & 1) != 0) { + lo = FwHexNibble(hex[i++]); + if (lo < 0) return -1; + out[outPos++] = (byte)lo; + } + while (i < len) { + hi = FwHexNibble(hex[i++]); + lo = FwHexNibble(hex[i++]); + if (hi < 0 || lo < 0) return -1; + out[outPos++] = (byte)((hi << 4) | lo); + } + return outPos; } +#ifdef WOLFTPM_FWTPM_UNIT_TEST +int FWTPM_TestHexToBin(const char* hex, byte* out, int outSz); + +int FWTPM_TestHexToBin(const char* hex, byte* out, int outSz) +{ + return FwHexToBin(hex, out, outSz); +} +#endif + /* --- TPM2_ECC_Parameters (CC 0x0178) --- * Returns curve parameters from wolfCrypt's ecc_set_type via * wc_ecc_get_curve_params(). Automatically supports P-256, P-384, - * and P-521 (when HAVE_ECC521 is defined). */ + * and P-521 when wolfCrypt and the TPM ECC buffers support it. */ static TPM_RC FwCmd_ECC_Parameters(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, TPM2_Packet* rsp, UINT16 cmdTag) { TPM_RC rc = TPM_RC_SUCCESS; UINT16 curveID; - int wcCurve, curveIdx, f; + UINT16 keyBits; + int f; + const FWTPM_ECC_CURVE_INFO* curveInfo = NULL; const ecc_set_type* params = NULL; byte paramBuf[MAX_ECC_BYTES]; int paramSz; + int padSz; const char* fields[6]; (void)ctx; (void)cmdSize; (void)cmdTag; TPM2_Packet_ParseU16(cmd, &curveID); - wcCurve = FwGetWcCurveId(curveID); - if (wcCurve < 0) { + curveInfo = FwGetEccCurveInfo(curveID); + params = FwGetEccCurveParams(curveID); + if (curveInfo == NULL || params == NULL) { rc = TPM_RC_CURVE; } - - if (rc == 0) { - curveIdx = wc_ecc_get_curve_idx(wcCurve); - params = wc_ecc_get_curve_params(curveIdx); - if (params == NULL) { - rc = TPM_RC_CURVE; - } - } + keyBits = (curveInfo != NULL) ? curveInfo->keyBits : 0; #ifdef DEBUG_WOLFTPM if (rc == 0) { @@ -15578,7 +15684,7 @@ static TPM_RC FwCmd_ECC_Parameters(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (rc == 0) { TPM2_Packet_AppendU16(rsp, curveID); - TPM2_Packet_AppendU16(rsp, (UINT16)(params->size * 8)); /* bits */ + TPM2_Packet_AppendU16(rsp, keyBits); TPM2_Packet_AppendU16(rsp, TPM_ALG_NULL); /* kdf */ TPM2_Packet_AppendU16(rsp, TPM_ALG_NULL); /* sign */ @@ -15597,7 +15703,15 @@ static TPM_RC FwCmd_ECC_Parameters(FWTPM_CTX* ctx, TPM2_Packet* cmd, rc = TPM_RC_FAILURE; break; } - TPM2_Packet_AppendU16(rsp, (UINT16)paramSz); + if (paramSz > params->size) { + rc = TPM_RC_FAILURE; + break; + } + padSz = params->size - paramSz; + TPM2_Packet_AppendU16(rsp, (UINT16)(padSz + paramSz)); + while (padSz-- > 0) { + TPM2_Packet_AppendU8(rsp, 0); + } TPM2_Packet_AppendBytes(rsp, paramBuf, paramSz); } diff --git a/src/fwtpm/fwtpm_crypto.c b/src/fwtpm/fwtpm_crypto.c index 95448298..215ce863 100644 --- a/src/fwtpm/fwtpm_crypto.c +++ b/src/fwtpm/fwtpm_crypto.c @@ -417,36 +417,63 @@ int FwAppendCreationHashAndTicket(FWTPM_CTX* ctx, TPM2_Packet* rsp, /* Map TPM ECC curve to wolfCrypt curve ID */ int FwGetWcCurveId(UINT16 tpmCurve) { + int curveIdx; + int keyBits; + int wcCurve; + switch (tpmCurve) { case TPM_ECC_NIST_P256: - return ECC_SECP256R1; + keyBits = 256; + wcCurve = ECC_SECP256R1; + break; case TPM_ECC_NIST_P384: - return ECC_SECP384R1; - #ifdef HAVE_ECC521 + keyBits = 384; + wcCurve = ECC_SECP384R1; + break; + #ifdef FWTPM_HAVE_ECC521 case TPM_ECC_NIST_P521: - return ECC_SECP521R1; + keyBits = 521; + wcCurve = ECC_SECP521R1; + break; #endif default: return -1; } + + if (keyBits < ECC_MIN_KEY_SZ) { + return -1; + } + curveIdx = wc_ecc_get_curve_idx(wcCurve); + if (curveIdx < 0 || wc_ecc_get_curve_params(curveIdx) == NULL) { + return -1; + } + return wcCurve; } #endif /* HAVE_ECC */ /* Get ECC key size in bytes from TPM curve */ int FwGetEccKeySize(UINT16 tpmCurve) { +#ifdef HAVE_ECC + if (FwGetWcCurveId(tpmCurve) < 0) { + return 0; + } switch (tpmCurve) { case TPM_ECC_NIST_P256: return 32; case TPM_ECC_NIST_P384: return 48; - #ifdef HAVE_ECC521 + #ifdef FWTPM_HAVE_ECC521 case TPM_ECC_NIST_P521: return 66; #endif default: return 0; } +#else + (void)tpmCurve; + return 0; +#endif } /* ================================================================== */ @@ -532,7 +559,7 @@ TPM_RC FwGenerateEccKey(WC_RNG* rng, FWTPM_ALLOC_VAR(eccKey, ecc_key); - if (wcCurve < 0 || keySz == 0) { + if (wcCurve < 0 || keySz == 0 || keySz > MAX_ECC_KEY_BYTES) { FWTPM_FREE_VAR(eccKey); return TPM_RC_CURVE; } @@ -1147,7 +1174,7 @@ static int FwDhkemParamsLookup(int wcCurve, TPMI_ALG_HASH kdfHash, *hkdfHashOut = WC_HASH_TYPE_SHA384; return 0; } -#ifdef HAVE_ECC521 +#ifdef FWTPM_HAVE_ECC521 if (wcCurve == ECC_SECP521R1 && kdfHash == TPM_ALG_SHA512) { *kemIdOut = 0x0012; *nSecretOut = 64; *nPkOut = 133; *hkdfHashOut = WC_HASH_TYPE_SHA512; diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index bae3bbd2..86bd9052 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -1161,6 +1161,213 @@ static void test_fwtpm_getcap_paging(void) fwtpm_pass("GetCapability paging convergence:", 0); } +static int getcap_ecc_curves(FWTPM_CTX* ctx, UINT32 property, + UINT32 propertyCount, UINT16* curves, int curveCapacity, byte* moreData) +{ + UINT32 count; + int rc, rspSize, cmdSz, i; + + cmdSz = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, + TPM_CC_GetCapability); + PutU32BE(gCmd + cmdSz, TPM_CAP_ECC_CURVES); cmdSz += 4; + PutU32BE(gCmd + cmdSz, property); cmdSz += 4; + PutU32BE(gCmd + cmdSz, propertyCount); cmdSz += 4; + PutU32BE(gCmd + 2, (UINT32)cmdSz); + rspSize = 0; + rc = FWTPM_ProcessCommand(ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertTrue(rspSize >= TPM2_HEADER_SIZE + 9); + AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE + 1), + (int)TPM_CAP_ECC_CURVES); + + *moreData = gRsp[TPM2_HEADER_SIZE]; + count = GetU32BE(gRsp + TPM2_HEADER_SIZE + 5); + AssertTrue((int)count <= curveCapacity); + AssertTrue(rspSize >= TPM2_HEADER_SIZE + 9 + ((int)count * 2)); + for (i = 0; i < (int)count; i++) { + curves[i] = GetU16BE(gRsp + TPM2_HEADER_SIZE + 9 + (i * 2)); + } + return (int)count; +} + +#if defined(HAVE_ECC) && !defined(FWTPM_NO_ECDH) +static UINT16 test_ecc_curve_bits(UINT16 curve) +{ + switch (curve) { + case TPM_ECC_NIST_P256: + return 256; + case TPM_ECC_NIST_P384: + return 384; + case TPM_ECC_NIST_P521: + return 521; + default: + return 0; + } +} + +static void check_ecc_parameters(FWTPM_CTX* ctx, UINT16 curve) +{ + UINT16 fieldSz; + UINT16 keyBits; + int rc, rspSize, cmdSz, pos, field; + int keyBytes; + + cmdSz = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, + TPM_CC_ECC_Parameters); + PutU16BE(gCmd + cmdSz, curve); cmdSz += 2; + PutU32BE(gCmd + 2, (UINT32)cmdSz); + rspSize = 0; + rc = FWTPM_ProcessCommand(ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + pos = TPM2_HEADER_SIZE; + AssertIntEQ(GetU16BE(gRsp + pos), curve); + pos += 2; + + keyBits = test_ecc_curve_bits(curve); + AssertIntNE(keyBits, 0); + AssertIntEQ(GetU16BE(gRsp + pos), keyBits); + pos += 2; + keyBytes = (keyBits + 7) / 8; + + AssertIntEQ(GetU16BE(gRsp + pos), TPM_ALG_NULL); /* kdf */ + pos += 2; + AssertIntEQ(GetU16BE(gRsp + pos), TPM_ALG_NULL); /* sign */ + pos += 2; + + /* Curve parameters use a consistent fixed-width big-endian encoding. */ + for (field = 0; field < 6; field++) { + AssertTrue(pos + 2 <= rspSize); + fieldSz = GetU16BE(gRsp + pos); + pos += 2; + AssertIntEQ(fieldSz, keyBytes); + AssertTrue(pos + fieldSz <= rspSize); + if (curve == TPM_ECC_NIST_P521 && field == 0) { + /* p = 0x01 followed by 65 0xff bytes. */ + AssertIntEQ(gRsp[pos], 0x01); + AssertIntEQ(gRsp[pos + fieldSz - 1], 0xff); + } + else if (curve == TPM_ECC_NIST_P521 && field == 3) { + /* Gx is 65 bytes and must be left-padded to the 66-byte field. */ + AssertIntEQ(gRsp[pos], 0x00); + AssertIntEQ(gRsp[pos + 1], 0xc6); + } + pos += fieldSz; + } + + AssertTrue(pos + 2 <= rspSize); + fieldSz = GetU16BE(gRsp + pos); /* cofactor h */ + pos += 2; + AssertIntEQ(fieldSz, 1); + AssertTrue(pos + fieldSz <= rspSize); + pos += fieldSz; + AssertIntEQ(pos, rspSize); +} + +#endif /* HAVE_ECC && !FWTPM_NO_ECDH */ + +/* Curve capability pages expose every compiled curve in ascending order and + * each advertised curve is accepted by ECC_Parameters when that command is + * enabled. */ +static void test_fwtpm_getcap_ecc_curves(void) +{ + FWTPM_CTX ctx; + UINT16 expected[3]; + UINT16 actual[3]; + UINT32 property; + byte moreData; + int expectedCount = 0; + int count, i; + +#ifdef HAVE_ECC + int curveIdx; + static const struct { + UINT16 tpmCurve; + UINT16 keyBits; + int wcCurve; + } candidates[] = { + { TPM_ECC_NIST_P256, 256, ECC_SECP256R1 }, + { TPM_ECC_NIST_P384, 384, ECC_SECP384R1 }, + #ifdef FWTPM_HAVE_ECC521 + { TPM_ECC_NIST_P521, 521, ECC_SECP521R1 }, + #endif + }; + + for (i = 0; i < (int)(sizeof(candidates) / + sizeof(candidates[0])); i++) { + curveIdx = wc_ecc_get_curve_idx(candidates[i].wcCurve); + if (candidates[i].keyBits >= ECC_MIN_KEY_SZ && curveIdx >= 0 && + wc_ecc_get_curve_params(curveIdx) != NULL) { + AssertIntEQ(FwGetWcCurveId(candidates[i].tpmCurve), + candidates[i].wcCurve); + AssertIntEQ(FwGetEccKeySize(candidates[i].tpmCurve), + (candidates[i].keyBits + 7) / 8); + expected[expectedCount++] = candidates[i].tpmCurve; + } + else { + AssertIntEQ(FwGetWcCurveId(candidates[i].tpmCurve), -1); + AssertIntEQ(FwGetEccKeySize(candidates[i].tpmCurve), 0); + } + } +#endif + + XMEMSET(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + count = getcap_ecc_curves(&ctx, 0, 0, actual, 3, &moreData); + AssertIntEQ(count, 0); + AssertIntEQ(moreData, expectedCount > 0); + + count = getcap_ecc_curves(&ctx, 0, 3, actual, 3, &moreData); + AssertIntEQ(count, expectedCount); + AssertIntEQ(moreData, 0); + for (i = 0; i < expectedCount; i++) { + AssertIntEQ(actual[i], expected[i]); + #if defined(HAVE_ECC) && !defined(FWTPM_NO_ECDH) + check_ecc_parameters(&ctx, actual[i]); + #endif + } + + property = 0; + for (i = 0; i < expectedCount; i++) { + count = getcap_ecc_curves(&ctx, property, 1, actual, 3, + &moreData); + AssertIntEQ(count, 1); + AssertIntEQ(actual[0], expected[i]); + AssertIntEQ(moreData, i + 1 < expectedCount); + property = (UINT32)actual[0] + 1u; + } + count = getcap_ecc_curves(&ctx, property, 1, actual, 3, &moreData); + AssertIntEQ(count, 0); + AssertIntEQ(moreData, 0); + + FWTPM_Cleanup(&ctx); + fwtpm_pass("GetCapability(ECC_CURVES):", 0); +} + +#if defined(HAVE_ECC) && !defined(FWTPM_NO_ECDH) && \ + defined(WOLFTPM_FWTPM_UNIT_TEST) +int FWTPM_TestHexToBin(const char* hex, byte* out, int outSz); + +static void test_fwtpm_hex_to_bin(void) +{ + byte out[2]; + + XMEMSET(out, 0, sizeof(out)); + AssertIntEQ(FWTPM_TestHexToBin("abc", out, sizeof(out)), 2); + AssertIntEQ(out[0], 0x0a); + AssertIntEQ(out[1], 0xbc); + AssertIntEQ(FWTPM_TestHexToBin("f", out, sizeof(out)), 1); + AssertIntEQ(out[0], 0x0f); + AssertIntEQ(FWTPM_TestHexToBin("0g", out, sizeof(out)), -1); + AssertIntEQ(FWTPM_TestHexToBin("1234", out, 1), -1); + AssertIntEQ(FWTPM_TestHexToBin(NULL, out, sizeof(out)), -1); + + fwtpm_pass("ECC parameter hex decoding:", 0); +} +#endif + #if FWTPM_MAX_OBJECTS >= 2 || FWTPM_MAX_PERSISTENT >= 2 || \ FWTPM_MAX_SESSIONS >= 2 || \ (!defined(FWTPM_NO_NV) && FWTPM_MAX_NV_INDICES >= 2) @@ -2681,7 +2888,7 @@ static void test_fwtpm_ecc_dhkem_p384_roundtrip(void) "Encap/Decap ECC DHKEM (P-384/HKDF-SHA384) Roundtrip:"); } -#ifdef HAVE_ECC521 +#ifdef FWTPM_HAVE_ECC521 static void test_fwtpm_ecc_dhkem_p521_roundtrip(void) { RunEccDhkemRoundtrip(TPM_ECC_NIST_P521, TPM_ALG_SHA512, @@ -13487,6 +13694,11 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_getcap_properties(); test_fwtpm_getcap_pcrs(); test_fwtpm_getcap_paging(); + test_fwtpm_getcap_ecc_curves(); +#if defined(HAVE_ECC) && !defined(FWTPM_NO_ECDH) && \ + defined(WOLFTPM_FWTPM_UNIT_TEST) + test_fwtpm_hex_to_bin(); +#endif #if FWTPM_MAX_OBJECTS >= 2 || FWTPM_MAX_PERSISTENT >= 2 || \ FWTPM_MAX_SESSIONS >= 2 || \ (!defined(FWTPM_NO_NV) && FWTPM_MAX_NV_INDICES >= 2) @@ -13567,7 +13779,7 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_signsequence_handle_auth_required(); test_fwtpm_verifysequence_long_message(); test_fwtpm_ecc_dhkem_p384_roundtrip(); -#ifdef HAVE_ECC521 +#ifdef FWTPM_HAVE_ECC521 test_fwtpm_ecc_dhkem_p521_roundtrip(); #endif #ifdef WOLFTPM_HASH_MLDSA diff --git a/tests/include.am b/tests/include.am index 05b58006..4b6985a8 100644 --- a/tests/include.am +++ b/tests/include.am @@ -32,7 +32,8 @@ tests_fwtpm_unit_test_SOURCES = \ src/tpm2_crypto.c \ src/tpm2_param_enc.c tests_fwtpm_unit_test_CFLAGS = -DWOLFTPM_FWTPM -DFWTPM_NV_FILE=\"fwtpm_test_nv.bin\" $(AM_CFLAGS) -tests_fwtpm_unit_test_CPPFLAGS = -DWOLFTPM_FWTPM $(AM_CPPFLAGS) +tests_fwtpm_unit_test_CPPFLAGS = \ + -DWOLFTPM_FWTPM -DWOLFTPM_FWTPM_UNIT_TEST $(AM_CPPFLAGS) tests_fwtpm_unit_test_LDADD = $(LIB_STATIC_ADD) endif diff --git a/wolftpm/fwtpm/fwtpm.h b/wolftpm/fwtpm/fwtpm.h index a6f7ceb1..f3c110cc 100644 --- a/wolftpm/fwtpm/fwtpm.h +++ b/wolftpm/fwtpm/fwtpm.h @@ -44,6 +44,15 @@ #include #endif +/* P-521 needs both wolfCrypt curve support and 66-byte TPM ECC fields. */ +#if defined(HAVE_ECC) && \ + (defined(HAVE_ECC521) || defined(HAVE_ALL_CURVES)) && \ + MAX_ECC_KEY_BYTES >= 66 + #if !defined(ECC_MIN_KEY_SZ) || ECC_MIN_KEY_SZ <= 521 + #define FWTPM_HAVE_ECC521 + #endif +#endif + /* Endian byte-array helpers - use shared TPM2_Packet helpers. * Note: argument order differs (Fw: buf,val; TPM2_Packet: val,buf) */ #define FwStoreU16BE(buf, val) TPM2_Packet_U16ToByteArray((val), (buf)) From 5e3cbd2241169f30e62e5631a675fdeea7165bca Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 31 Aug 2026 11:07:25 -0700 Subject: [PATCH 4/8] F-12735 - Pin the SPDM responder identity --- .github/workflows/spdm-test.yml | 36 ++- docs/FWTPM.md | 4 + examples/spdm/README.md | 39 ++- examples/spdm/spdm_ctrl.c | 417 ++++++++++++++++++++++++++------ examples/spdm/spdm_test.sh | 279 ++++++++++++++++++--- src/fwtpm/README.md | 5 +- src/fwtpm/fwtpm_main.c | 9 + src/spdm/README.md | 95 +++++--- src/spdm/include.am | 1 + src/spdm/spdm_context.c | 2 +- src/spdm/spdm_crypto.c | 48 +++- src/spdm/spdm_internal.h | 4 +- src/spdm/spdm_tcg.c | 84 ++++--- src/spdm/unit_test.c | 274 ++++++++++++++++++++- src/tpm2.c | 12 +- src/tpm2_wrap.c | 288 ++++++++++++++++++---- tests/fwtpm_check.sh | 87 +++++++ tests/unit_tests.c | 287 +++++++++++++++++++++- wolftpm/spdm/spdm.h | 5 +- wolftpm/spdm/spdm_tcg.h | 2 + wolftpm/tpm2_wrap.h | 163 +++++++++++++ 21 files changed, 1890 insertions(+), 251 deletions(-) diff --git a/.github/workflows/spdm-test.yml b/.github/workflows/spdm-test.yml index c3a31ef7..a9370e7b 100644 --- a/.github/workflows/spdm-test.yml +++ b/.github/workflows/spdm-test.yml @@ -25,9 +25,9 @@ on: permissions: read-all jobs: - # Build-only matrix: every supported SPDM configure permutation compiles - # cleanly. Catches missing `#ifdef WOLFTPM_SPDM_*` guards in headers/sources - # and per-mode include.am gating regressions. + # Build matrix: every supported SPDM configure permutation compiles cleanly; + # the single-vendor entries also smoke-test unavailable-adapter rejection. + # Catches missing `#ifdef WOLFTPM_SPDM_*` guards and include.am gating. build-only: runs-on: ubuntu-latest timeout-minutes: 15 @@ -36,6 +36,8 @@ jobs: fail-fast: false matrix: include: + - name: spdm-base-only + wolftpm_config: "--enable-spdm --disable-fwtpm --disable-tcg --disable-psk" - name: spdm-tcg-only wolftpm_config: "--enable-fwtpm --enable-spdm --enable-tcg --disable-psk" - name: spdm-both @@ -50,6 +52,8 @@ jobs: wolftpm_config: "--enable-spdm --enable-nuvoton --enable-nations" - name: spdm-debug-on wolftpm_config: "--enable-fwtpm --enable-spdm --enable-tcg --enable-psk --enable-debug" + - name: spdm-no-getenv + wolftpm_config: "--enable-fwtpm --enable-spdm --enable-tcg --enable-psk CFLAGS=-DNO_GETENV" steps: - name: Harden Runner @@ -76,10 +80,28 @@ jobs: --with-wolfcrypt=$HOME/wolfssl-install make -j"$(nproc)" + - name: Test unavailable vendor rejection (${{ matrix.name }}) + if: matrix.name == 'spdm-nuvoton' || matrix.name == 'spdm-nations' + run: | + set -eo pipefail + if [ "${{ matrix.name }}" = "spdm-nuvoton" ]; then + vendor=nations + expected="Nations adapter is not available in this build" + else + vendor=nuvoton + expected="Nuvoton adapter is not available in this build" + fi + if output=$(./examples/spdm/spdm_ctrl \ + "--vendor=$vendor" --status 2>&1); then + echo "Expected unavailable vendor rejection" + exit 1 + fi + grep -F "$expected" <<< "$output" + # End-to-end matrix: same protocol coverage as the old hw-spdm-test, but - # against fwtpm_server instead of real silicon. fwtpm-tcg mirrors the - # Nuvoton 6-step sequence (status, connect, lock, unit.test, unlock, - # cleartext caps). fwtpm-psk mirrors the Nations-PSK 10-step sequence. + # against fwtpm_server instead of real silicon. fwtpm-tcg covers pinned and + # rejected initialization, the full unit suite over SPDM, lock/status/caps, + # unlock, and cleartext recovery. fwtpm-psk mirrors the Nations-PSK flow. e2e: runs-on: ubuntu-latest timeout-minutes: 25 @@ -150,5 +172,5 @@ jobs: config.log test-suite.log tests/*.log - /tmp/fwtpm_spdm_test.log + /tmp/fwtpm_spdm_test.* retention-days: 14 diff --git a/docs/FWTPM.md b/docs/FWTPM.md index 61829959..9117de04 100644 --- a/docs/FWTPM.md +++ b/docs/FWTPM.md @@ -177,6 +177,10 @@ wolfTPM fwTPM Server v0.1.0 Model: fwTPM ``` +In `--spdm-tcg` test mode the server also prints its generated responder +public key. This is a local test-harness convenience, not a provisioning or +trust-anchor channel for hardware responders. + ### Connecting wolfTPM Clients Any wolfTPM application built with `--enable-swtpm` connects to the fwTPM diff --git a/examples/spdm/README.md b/examples/spdm/README.md index 9580a096..61dca041 100644 --- a/examples/spdm/README.md +++ b/examples/spdm/README.md @@ -6,8 +6,13 @@ and Nations NS350 TPMs with wolfTPM. ## Overview The `spdm_ctrl` tool establishes SPDM secure sessions between the host and a -TPM over SPI, enabling AES-256-GCM encrypted bus communication. Once active, -all TPM commands are automatically encrypted with no application changes. +TPM over SPI, enabling AES-256-GCM encrypted bus communication. Identity mode +requires the responder key from a trusted provisioning source. + +`spdm_ctrl` is the only example that accepts SPDM credentials. Other wolfTPM +examples use uncredentialed `wolfTPM2_Init()` and intentionally return +`WOLFSPDM_E_BAD_STATE` while a TPM is locked in SPDM-only mode; unlock it with +`spdm_ctrl` before running those examples. Supported hardware: - **Nuvoton NPCT75x** — Identity key mode (ECDHE P-384) @@ -51,13 +56,20 @@ make | Option | Description | |--------|-------------| +| `--vendor=nuvoton\|nations` | Select the identity/vendor adapter explicitly | | `--enable` | Enable SPDM on TPM via NTC2_PreConfig (one-time, requires reset) | | `--disable` | Disable SPDM on TPM via NTC2_PreConfig (requires reset) | | `--status` | Query SPDM status from TPM | | `--get-pubkey` | Get TPM's SPDM-Identity P-384 public key | +| `--responder-pubkey ` | Pin a trusted raw P-384 X\|\|Y key (192 hex characters) | | `--connect` | Establish SPDM session (ECDH P-384 handshake) | +| `--caps` | Read TPM capabilities over the current transport | +| `--psk ` | Start a PSK session | +| `--psk-set ` | Provision a 64-byte PSK and 32-byte ClearAuth | +| `--psk-clear ` | Clear a provisioned PSK | | `--lock` | Lock SPDM-only mode (use with `--connect`) | | `--unlock` | Unlock SPDM-only mode (use with `--connect`) | +| `--tpm-clear` | Send `TPM2_Clear` over the current transport | ## Usage Examples @@ -69,23 +81,22 @@ make # Query SPDM status ./examples/spdm/spdm_ctrl --status -# Get TPM identity key +# Discover TPM identity key (unauthenticated; do not use as its own trust source) ./examples/spdm/spdm_ctrl --get-pubkey -# Establish SPDM session -./examples/spdm/spdm_ctrl --connect +# Establish SPDM session with a key from trusted provisioning records +./examples/spdm/spdm_ctrl \ + --vendor=nuvoton --responder-pubkey --connect # Lock SPDM-only mode (connect + lock in one session) -./examples/spdm/spdm_ctrl --connect --lock +./examples/spdm/spdm_ctrl \ + --responder-pubkey --connect --lock # Reset the TPM -# All commands now auto-encrypt: -./examples/wrap/caps # auto-SPDM, AES-256-GCM encrypted -./tests/unit.test # full test suite over encrypted bus - # Unlock SPDM-only mode # Reset the TPM -./examples/spdm/spdm_ctrl --connect --unlock +./examples/spdm/spdm_ctrl \ + --responder-pubkey --connect --unlock # Reset the TPM ``` @@ -115,11 +126,17 @@ wolfTPM can also drive this from code: build with `--enable-hal-reset` and call Runs the full SPDM setup lifecycle on hardware: ```bash +export SPDM_RESPONDER_PUBKEY= ./examples/spdm/spdm_test.sh ./examples/spdm/spdm_ctrl nuvoton ./examples/spdm/spdm_test.sh ./examples/spdm/spdm_ctrl nations ./examples/spdm/spdm_test.sh ./examples/spdm/spdm_ctrl nations-psk ``` +The identity-mode hardware runs require `SPDM_RESPONDER_PUBKEY` from a trusted +provisioning source. The PSK run does not use it. The `fwtpm-tcg` test obtains +the freshly generated public key from the local server's protected startup log +and passes it through the same pinning interface. + ## Support For production use with hardware TPMs and SPDM support, contact **support@wolfssl.com**. diff --git a/examples/spdm/spdm_ctrl.c b/examples/spdm/spdm_ctrl.c index 0dc24150..e393f9c4 100644 --- a/examples/spdm/spdm_ctrl.c +++ b/examples/spdm/spdm_ctrl.c @@ -42,6 +42,14 @@ int TPM2_SPDM_Ctrl(void* userCtx, int argc, char *argv[]); +static const char* ctrl_init_rc_string(int rc) +{ + if (rc >= WOLFSPDM_E_NOT_IMPL && rc <= WOLFSPDM_E_INVALID_ARG) { + return wolfSPDM_GetErrorString(rc); + } + return TPM2_GetRCString(rc); +} + static void usage(void) { printf("SPDM Demo - TPM secure session\n\n" @@ -56,12 +64,17 @@ static void usage(void) #ifdef WOLFSPDM_NATIONS " --identity-key-set Provision SPDM identity key\n" " --identity-key-unset Un-provision SPDM identity key\n" - " --psk PSK mode connect (64-byte PSK)\n" " --psk-set Provision PSK (64-byte PSK, 32-byte ClearAuth)\n" " --psk-clear Clear PSK (32-byte ClearAuth from psk-set)\n" " --lock Lock SPDM-only mode (PSK mode, use with --psk)\n" " --unlock Unlock SPDM-only mode (PSK mode, use with --psk)\n" " --status Query SPDM status (PSK mode)\n" +#endif +#ifdef WOLFTPM_SPDM_PSK + " --psk Start a PSK session\n" +#endif +#ifdef WOLFTPM_SPDM_TCG + " --responder-pubkey Trust P-384 X||Y key (192 hex chars)\n" #endif " --get-pubkey Get TPM's SPDM-Identity public key\n" " --connect Establish SPDM session\n" @@ -75,6 +88,23 @@ static void usage(void) ); } +static int ctrl_caps(WOLFTPM2_DEV* dev) +{ + int rc; + WOLFTPM2_CAPS caps; + + printf("\n=== TPM Capabilities ===\n"); + XMEMSET(&caps, 0, sizeof(caps)); + rc = wolfTPM2_GetCapabilities(dev, &caps); + if (rc == 0) { + printf(" wolfTPM caps read successfully\n"); + } + else { + printf(" FAILED: 0x%x: %s\n", rc, ctrl_init_rc_string(rc)); + } + return rc; +} + #ifdef WOLFSPDM_NUVOTON static int ctrl_enable(WOLFTPM2_DEV* dev) { @@ -199,7 +229,8 @@ static int ctrl_lock(WOLFTPM2_DEV* dev, int lock) } #endif /* WOLFSPDM_NUVOTON */ -#ifdef WOLFSPDM_NATIONS +#if defined(WOLFTPM_SPDM_TCG) || defined(WOLFTPM_SPDM_PSK) || \ + defined(WOLFSPDM_NATIONS) static int hex2bin(const char* hex, byte* bin, word32* binSz) { word32 hexLen = (word32)XSTRLEN(hex); @@ -219,12 +250,15 @@ static int hex2bin(const char* hex, byte* bin, word32* binSz) *binSz = hexLen / 2; return 0; } +#endif /* WOLFTPM_SPDM_TCG || WOLFTPM_SPDM_PSK || WOLFSPDM_NATIONS */ +#ifdef WOLFSPDM_NATIONS static int ctrl_nations_status(WOLFTPM2_DEV* dev) { int rc; int isConn; int stsRc; + const char* stsError = "GET_STS failed"; GetCapability_In capIn; GetCapability_Out capOut; WOLFSPDM_NATIONS_STATUS status; @@ -247,24 +281,35 @@ static int ctrl_nations_status(WOLFTPM2_DEV* dev) printf(" Identity Key: unknown (GetCap failed: 0x%x)\n", rc); } - /* 2. Try GET_STS_ vendor command (PSK mode only — may fail) */ - stsRc = wolfSPDM_GetVersion(dev->spdmCtx->spdmCtx); - if (stsRc == 0) { + /* 2. GET_VERSION resets the SPDM state machine, so do not probe it when + * credentialed initialization has already established a session. */ + isConn = wolfTPM2_SpdmIsConnected(dev); + if (isConn) { XMEMSET(&status, 0, sizeof(status)); stsRc = wolfTPM2_SpdmNationsGetStatus(dev, &status); + } + else { + stsRc = wolfSPDM_GetVersion(dev->spdmCtx->spdmCtx); if (stsRc == 0) { - printf(" PSK: %s SPDM-Only: %s\n", - status.pskProvisioned ? "provisioned" : "not provisioned", - !status.spdmOnlyLocked ? "disabled" : - status.spdmOnlyPending ? "PENDING_DISABLE" : "ENABLED"); - } else { - printf(" PSK Status: unknown (GET_STS failed)\n"); + XMEMSET(&status, 0, sizeof(status)); + stsRc = wolfTPM2_SpdmNationsGetStatus(dev, &status); + } + else { + stsError = "GET_VERSION failed"; } - } else { - printf(" PSK Status: GET_VERSION failed\n"); + } + if (stsRc == 0) { + printf(" PSK: %s SPDM-Only: %s\n", + status.pskProvisioned ? "provisioned" : "not provisioned", + !status.spdmOnlyLocked ? "disabled" : + status.spdmOnlyPending ? "PENDING_DISABLE" : "ENABLED"); + } + else { + printf(" PSK Status: unknown (%s)\n", stsError); } - /* 3. Local session state */ + /* 3. Re-sample local state after GET_STS_ so a session teardown is + * reported instead of returning the pre-query snapshot. */ isConn = wolfTPM2_SpdmIsConnected(dev); printf(" Session: %s\n", isConn ? "active" : "none"); if (isConn) { @@ -274,30 +319,6 @@ static int ctrl_nations_status(WOLFTPM2_DEV* dev) return 0; /* status is informational, don't fail */ } -static int ctrl_nations_psk_connect(WOLFTPM2_DEV* dev, const char* pskHex) -{ - int rc; - byte psk[128]; - word32 pskSz = sizeof(psk); - - printf("\n=== Nations PSK Connect ===\n"); - rc = hex2bin(pskHex, psk, &pskSz); - if (rc != 0) { - printf(" Invalid PSK hex string\n"); - return BAD_FUNC_ARG; - } - - rc = wolfTPM2_SpdmConnectNationsPsk(dev, psk, pskSz, NULL, 0); - XMEMSET(psk, 0, sizeof(psk)); - if (rc == 0) { - printf(" PSK session established (SessionID: 0x%08x)\n", - wolfTPM2_SpdmGetSessionId(dev)); - } else { - printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); - } - return rc; -} - static int ctrl_nations_psk_set(WOLFTPM2_DEV* dev, const char* pskHex, const char* clearAuthHex) { @@ -313,12 +334,15 @@ static int ctrl_nations_psk_set(WOLFTPM2_DEV* dev, rc = hex2bin(pskHex, psk, &pskSz); if (rc != 0 || pskSz != 64) { printf(" Error: PSK must be exactly 64 bytes, got %u\n", pskSz); + wc_ForceZero(psk, sizeof(psk)); return BAD_FUNC_ARG; } rc = hex2bin(clearAuthHex, clearAuth, &clearAuthSz); if (rc != 0 || clearAuthSz != 32) { printf(" Error: ClearAuth must be exactly 32 bytes, got %u\n", clearAuthSz); + wc_ForceZero(psk, sizeof(psk)); + wc_ForceZero(clearAuth, sizeof(clearAuth)); return BAD_FUNC_ARG; } @@ -328,10 +352,11 @@ static int ctrl_nations_psk_set(WOLFTPM2_DEV* dev, if (rc == 0) rc = wc_Sha384Update(&sha, clearAuth, clearAuthSz); if (rc == 0) rc = wc_Sha384Final(&sha, payload + 64); wc_Sha384Free(&sha); - XMEMSET(psk, 0, sizeof(psk)); + wc_ForceZero(psk, sizeof(psk)); + wc_ForceZero(clearAuth, sizeof(clearAuth)); if (rc != 0) { printf(" SHA-384 failed: %d\n", rc); - XMEMSET(payload, 0, sizeof(payload)); + wc_ForceZero(payload, sizeof(payload)); return rc; } @@ -341,12 +366,12 @@ static int ctrl_nations_psk_set(WOLFTPM2_DEV* dev, rc = wolfSPDM_GetVersion(dev->spdmCtx->spdmCtx); if (rc != 0) { printf(" GET_VERSION failed: %d\n", rc); - XMEMSET(payload, 0, sizeof(payload)); + wc_ForceZero(payload, sizeof(payload)); return rc; } rc = wolfTPM2_SpdmNationsPskSet(dev, payload, sizeof(payload)); - XMEMSET(payload, 0, sizeof(payload)); + wc_ForceZero(payload, sizeof(payload)); if (rc == 0) printf(" PSK provisioned (64-byte PSK + 48-byte digest)\n"); else @@ -370,13 +395,14 @@ static int ctrl_nations_psk_clear(WOLFTPM2_DEV* dev, const char* authHex) if (rc != 0 || clearAuthSz != 32) { printf(" Error: ClearAuth must be exactly 32 bytes, got %u\n", clearAuthSz); + wc_ForceZero(clearAuth, sizeof(clearAuth)); return BAD_FUNC_ARG; } /* PSK_CLEAR: sends raw 32-byte ClearAuth. TPM computes SHA-384 * internally and compares against stored ClearAuthDigest. */ rc = wolfSPDM_Nations_PskClearWithVCA(dev->spdmCtx->spdmCtx, clearAuth, clearAuthSz); - XMEMSET(clearAuth, 0, sizeof(clearAuth)); + wc_ForceZero(clearAuth, sizeof(clearAuth)); if (rc == 0) printf(" PSK cleared\n"); else @@ -528,61 +554,292 @@ static int ctrl_nations_connect(WOLFTPM2_DEV* dev) int TPM2_SPDM_Ctrl(void* userCtx, int argc, char *argv[]) { - int rc, i; + int rc = TPM_RC_SUCCESS; + int i; + int didInit = 0; int useNations = 0; +#if defined(WOLFTPM_SPDM_TCG) && defined(WOLFSPDM_NUVOTON) && \ + defined(WOLFSPDM_NATIONS) + int explicitVendor = 0; + int haveNationsOnlyCommand = 0; +#endif +#ifdef WOLFSPDM_NATIONS + int badPskSetArgs = 0; + int badPskClearArgs = 0; +#endif WOLFTPM2_DEV dev; +#ifdef WOLFTPM_SPDM_TCG + byte rspPubKey[WOLFSPDM_ECC_POINT_SIZE]; + word32 rspPubKeySz = 0; + int haveRspPubKey = 0; +#endif +#ifdef WOLFTPM_SPDM_PSK + byte initPsk[128]; + word32 initPskSz = 0; + int haveInitPsk = 0; +#endif if (argc <= 1) { usage(); return 0; } for (i = 1; i < argc; i++) { if (XSTRCMP(argv[i], "-h") == 0 || XSTRCMP(argv[i], "--help") == 0) { - usage(); return 0; + usage(); + #ifdef WOLFTPM_SPDM_PSK + wc_ForceZero(initPsk, sizeof(initPsk)); + #endif + return 0; } if (XSTRNCMP(argv[i], "--vendor=", 9) == 0) { if (XSTRCMP(argv[i] + 9, "nations") == 0) { + #ifdef WOLFSPDM_NATIONS useNations = 1; + #if defined(WOLFTPM_SPDM_TCG) && \ + defined(WOLFSPDM_NUVOTON) && \ + defined(WOLFSPDM_NATIONS) + explicitVendor = 1; + #endif + #else + printf("Nations adapter is not available in this build\n"); + #ifdef WOLFTPM_SPDM_PSK + wc_ForceZero(initPsk, sizeof(initPsk)); + #endif + return BAD_FUNC_ARG; + #endif } else if (XSTRCMP(argv[i] + 9, "nuvoton") == 0) { + #ifdef WOLFSPDM_NUVOTON useNations = 0; + #if defined(WOLFTPM_SPDM_TCG) && \ + defined(WOLFSPDM_NUVOTON) && \ + defined(WOLFSPDM_NATIONS) + explicitVendor = 1; + #endif + #else + printf("Nuvoton adapter is not available in this build\n"); + #ifdef WOLFTPM_SPDM_PSK + wc_ForceZero(initPsk, sizeof(initPsk)); + #endif + return BAD_FUNC_ARG; + #endif } else { printf("Unknown --vendor= value: %s\n", argv[i] + 9); + #ifdef WOLFTPM_SPDM_PSK + wc_ForceZero(initPsk, sizeof(initPsk)); + #endif return BAD_FUNC_ARG; } + continue; } +#ifdef WOLFTPM_SPDM_TCG + if (XSTRCMP(argv[i], "--responder-pubkey") == 0) { + if (i + 1 >= argc || XSTRNCMP(argv[i + 1], "--", 2) == 0) { + printf("--responder-pubkey requires 192 hex characters\n"); + #ifdef WOLFTPM_SPDM_PSK + wc_ForceZero(initPsk, sizeof(initPsk)); + #endif + return BAD_FUNC_ARG; + } + rspPubKeySz = sizeof(rspPubKey); + if (hex2bin(argv[++i], rspPubKey, &rspPubKeySz) != 0 || + rspPubKeySz != WOLFSPDM_ECC_POINT_SIZE) { + printf("Invalid responder public key\n"); + #ifdef WOLFTPM_SPDM_PSK + wc_ForceZero(initPsk, sizeof(initPsk)); + #endif + return BAD_FUNC_ARG; + } + haveRspPubKey = 1; + continue; + } +#endif +#ifdef WOLFTPM_SPDM_PSK + if (XSTRCMP(argv[i], "--psk") == 0) { + if (i + 1 >= argc || XSTRNCMP(argv[i + 1], "--", 2) == 0) { + printf("--psk requires a hexadecimal key\n"); + wc_ForceZero(initPsk, sizeof(initPsk)); + return BAD_FUNC_ARG; + } + initPskSz = sizeof(initPsk); + if (hex2bin(argv[++i], initPsk, &initPskSz) != 0 || + initPskSz == 0U) { + printf("Invalid PSK hex string\n"); + wc_ForceZero(initPsk, sizeof(initPsk)); + return BAD_FUNC_ARG; + } + haveInitPsk = 1; + #ifdef WOLFSPDM_NATIONS + #if defined(WOLFTPM_SPDM_TCG) && \ + defined(WOLFSPDM_NUVOTON) + if (!explicitVendor) { + useNations = 1; + } + #else + useNations = 1; + #endif + #endif + continue; + } +#endif +#ifdef WOLFSPDM_NATIONS + if (XSTRCMP(argv[i], "--psk-set") == 0) { + #if defined(WOLFTPM_SPDM_TCG) && defined(WOLFSPDM_NUVOTON) + haveNationsOnlyCommand = 1; + if (!explicitVendor) { + useNations = 1; + } + #else + useNations = 1; + #endif + if (i + 2 >= argc || XSTRNCMP(argv[i + 1], "--", 2) == 0 || + XSTRNCMP(argv[i + 2], "--", 2) == 0) { + badPskSetArgs = 1; + } + i += 2; + continue; + } + if (XSTRCMP(argv[i], "--psk-clear") == 0) { + #if defined(WOLFTPM_SPDM_TCG) && defined(WOLFSPDM_NUVOTON) + haveNationsOnlyCommand = 1; + if (!explicitVendor) { + useNations = 1; + } + #else + useNations = 1; + #endif + if (i + 1 >= argc || XSTRNCMP(argv[i + 1], "--", 2) == 0) { + badPskClearArgs = 1; + } + i++; + continue; + } + if (XSTRCMP(argv[i], "--identity-key-set") == 0 || + XSTRCMP(argv[i], "--identity-key-unset") == 0) { + #if defined(WOLFTPM_SPDM_TCG) && defined(WOLFSPDM_NUVOTON) + haveNationsOnlyCommand = 1; + if (!explicitVendor) { + useNations = 1; + } + #else + useNations = 1; + #endif + continue; + } +#endif } - rc = wolfTPM2_Init(&dev, TPM2_IoCb, userCtx); +#if defined(WOLFTPM_SPDM_PSK) && defined(WOLFTPM_SPDM_TCG) && \ + defined(WOLFSPDM_NUVOTON) && defined(WOLFSPDM_NATIONS) + if (haveInitPsk && explicitVendor && !useNations) { + printf("PSK sessions require --vendor=nations\n"); + wc_ForceZero(initPsk, sizeof(initPsk)); + return BAD_FUNC_ARG; + } +#endif +#if defined(WOLFTPM_SPDM_TCG) && defined(WOLFSPDM_NUVOTON) && \ + defined(WOLFSPDM_NATIONS) + if (haveNationsOnlyCommand && explicitVendor && !useNations) { + printf("Nations-only commands require --vendor=nations\n"); + #ifdef WOLFTPM_SPDM_PSK + wc_ForceZero(initPsk, sizeof(initPsk)); + #endif + return BAD_FUNC_ARG; + } +#endif +#ifdef WOLFSPDM_NATIONS + if (badPskSetArgs || badPskClearArgs) { + printf("%s requires %s hex argument%s\n", + badPskSetArgs ? "--psk-set" : "--psk-clear", + badPskSetArgs ? "PSK and ClearAuth" : "a ClearAuth", + badPskSetArgs ? "s" : ""); + #ifdef WOLFTPM_SPDM_PSK + wc_ForceZero(initPsk, sizeof(initPsk)); + #endif + return BAD_FUNC_ARG; + } +#endif +#if defined(WOLFTPM_SPDM_TCG) && defined(WOLFTPM_SPDM_PSK) + if (haveRspPubKey && haveInitPsk) { + printf("Choose either --responder-pubkey or --psk\n"); + wc_ForceZero(initPsk, sizeof(initPsk)); + return BAD_FUNC_ARG; + } +#endif +#ifdef WOLFTPM_SPDM_PSK + if (haveInitPsk) { + rc = wolfTPM2_InitWithSpdmPsk(&dev, TPM2_IoCb, userCtx, + initPsk, initPskSz, NULL, 0); + wc_ForceZero(initPsk, sizeof(initPsk)); + didInit = 1; + } +#endif +#ifdef WOLFTPM_SPDM_TCG + if (!didInit && haveRspPubKey) { + #if defined(WOLFSPDM_NUVOTON) && defined(WOLFSPDM_NATIONS) + rc = wolfTPM2_InitWithSpdmKey_ex(&dev, TPM2_IoCb, userCtx, + rspPubKey, rspPubKeySz, explicitVendor ? + (useNations ? WOLFSPDM_MODE_NATIONS : WOLFSPDM_MODE_NUVOTON) : + WOLFSPDM_MODE_AUTO); + #else + rc = wolfTPM2_InitWithSpdmKey(&dev, TPM2_IoCb, userCtx, + rspPubKey, rspPubKeySz); + #endif + didInit = 1; + } +#endif + if (!didInit) { + rc = wolfTPM2_Init(&dev, TPM2_IoCb, userCtx); + } if (rc != 0) { - printf("wolfTPM2_Init failed: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + printf("TPM initialization failed: 0x%x: %s\n", rc, + ctrl_init_rc_string(rc)); return rc; } rc = wolfTPM2_SpdmInit(&dev); if (rc != 0) { - printf("wolfTPM2_SpdmInit failed: %s\n", TPM2_GetRCString(rc)); + printf("wolfTPM2_SpdmInit failed: %s\n", + ctrl_init_rc_string(rc)); wolfTPM2_Cleanup(&dev); return rc; } +#if defined(WOLFSPDM_NUVOTON) && defined(WOLFSPDM_NATIONS) + /* AUTO selects from DID/VID during credentialed initialization. Keep + * command dispatch aligned with the adapter that owns the live session. */ + if (wolfTPM2_SpdmIsConnected(&dev)) { + WOLFSPDM_MODE mode = wolfSPDM_GetMode(dev.spdmCtx->spdmCtx); + + useNations = (mode == WOLFSPDM_MODE_NATIONS || + mode == WOLFSPDM_MODE_NATIONS_PSK); + } +#endif + /* Vendor selection: --vendor=nuvoton|nations chooses at runtime when * both adapters are built. Single-vendor builds default to that * vendor; dual builds default to Nuvoton unless overridden. */ -#if defined(WOLFSPDM_NUVOTON) && defined(WOLFSPDM_NATIONS) - if (useNations) { - wolfTPM2_SpdmSetNationsMode(&dev); - } - else { +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + /* Credentialed initialization may already have established the session. + * Do not reset negotiated mode state or transport callbacks on a live + * connection. */ + if (!wolfTPM2_SpdmIsConnected(&dev)) { + #if defined(WOLFSPDM_NUVOTON) && defined(WOLFSPDM_NATIONS) + if (useNations) { + wolfTPM2_SpdmSetNationsMode(&dev); + } + else { + wolfTPM2_SpdmSetNuvotonMode(&dev); + } + wolfTPM2_SPDM_SetTisIO(dev.spdmCtx); + #elif defined(WOLFSPDM_NUVOTON) + (void)useNations; wolfTPM2_SpdmSetNuvotonMode(&dev); + wolfTPM2_SPDM_SetTisIO(dev.spdmCtx); + #elif defined(WOLFSPDM_NATIONS) + (void)useNations; + wolfTPM2_SpdmSetNationsMode(&dev); + wolfTPM2_SPDM_SetTisIO(dev.spdmCtx); + #endif } - wolfTPM2_SPDM_SetTisIO(dev.spdmCtx); -#elif defined(WOLFSPDM_NUVOTON) - (void)useNations; - wolfTPM2_SpdmSetNuvotonMode(&dev); - wolfTPM2_SPDM_SetTisIO(dev.spdmCtx); -#elif defined(WOLFSPDM_NATIONS) - (void)useNations; - wolfTPM2_SpdmSetNationsMode(&dev); - wolfTPM2_SPDM_SetTisIO(dev.spdmCtx); #else (void)useNations; #endif @@ -599,6 +856,31 @@ int TPM2_SPDM_Ctrl(void* userCtx, int argc, char *argv[]) if (XSTRNCMP(argv[i], "--vendor=", 9) == 0) { continue; } +#ifdef WOLFTPM_SPDM_TCG + if (XSTRCMP(argv[i], "--responder-pubkey") == 0) { + i++; + continue; + } +#endif +#ifdef WOLFTPM_SPDM_PSK + if (XSTRCMP(argv[i], "--psk") == 0) { + i++; + printf("\n=== SPDM PSK Connect ===\n"); + if (wolfTPM2_SpdmIsConnected(&dev)) { + printf(" Already connected (SessionID: 0x%08x)\n", + wolfTPM2_SpdmGetSessionId(&dev)); + rc = TPM_RC_SUCCESS; + } + else { + printf(" PSK initialization did not establish a session\n"); + rc = WOLFSPDM_E_BAD_STATE; + } + if (rc != TPM_RC_SUCCESS) { + break; + } + continue; + } +#endif #ifdef WOLFSPDM_NUVOTON /* Nuvoton wire-format adapter: TCG handshake (Nuvoton flavor - @@ -654,11 +936,6 @@ int TPM2_SPDM_Ctrl(void* userCtx, int argc, char *argv[]) else if (!matched && XSTRCMP(argv[i], "--status") == 0) { rc = ctrl_nations_status(&dev); matched = 1; } - else if (!matched && XSTRCMP(argv[i], "--psk") == 0 - && i + 1 < argc) { - rc = ctrl_nations_psk_connect(&dev, argv[++i]); - matched = 1; - } else if (!matched && XSTRCMP(argv[i], "--psk-set") == 0 && i + 2 < argc) { const char* pskArg = argv[++i]; @@ -682,7 +959,11 @@ int TPM2_SPDM_Ctrl(void* userCtx, int argc, char *argv[]) #endif /* WOLFSPDM_NATIONS */ /* Generic admin - available whenever the library is built. */ - if (!matched && XSTRCMP(argv[i], "--tpm-clear") == 0) { + if (!matched && XSTRCMP(argv[i], "--caps") == 0) { + rc = ctrl_caps(&dev); + matched = 1; + } + else if (!matched && XSTRCMP(argv[i], "--tpm-clear") == 0) { printf("\n=== TPM2_Clear ===\n"); rc = wolfTPM2_Clear(&dev); printf(" %s (rc=0x%x)\n", rc == 0 ? "Success" : "FAILED", rc); diff --git a/examples/spdm/spdm_test.sh b/examples/spdm/spdm_test.sh index 30312c92..159c7d1a 100755 --- a/examples/spdm/spdm_test.sh +++ b/examples/spdm/spdm_test.sh @@ -28,7 +28,10 @@ VENDOR="${2:-nuvoton}" # nuvoton, nations, nations-psk, fwtpm-tcg, fwtpm-psk FWTPM="${FWTPM:-./src/fwtpm/fwtpm_server}" FWTPM_PORT="${FWTPM_PORT:-22321}" FWTPM_PLAT_PORT="${FWTPM_PLAT_PORT:-22322}" +FWTPM_LOG="${FWTPM_LOG:-}" +FWTPM_LOG_CREATED=0 FWTPM_PID="" +SPDM_RESPONDER_PUBKEY="${SPDM_RESPONDER_PUBKEY:-}" PASS=0 FAIL=0 TOTAL=0 # Nations PSK test data (from Vision/NSING reference PSK_DEMO_3) @@ -38,6 +41,7 @@ NATIONS_PSK="dbc2192291d807742441b963f6712841f7697e2e39c45931f3abc53658c8b9338bd # PSK_SET sends SHA-384(ClearAuth) as the 48-byte ClearAuthDigest # PSK_CLEAR sends raw ClearAuth; TPM verifies SHA-384 match internally NATIONS_CLEARAUTH="dbc2192291d807742441b963f6712841f7697e2e39c45931f3abc53658c8b933" +TEST_RESPONDER_PUBKEY="${NATIONS_CLEARAUTH}${NATIONS_CLEARAUTH}${NATIONS_CLEARAUTH}" if [ -t 1 ]; then GREEN='\033[0;32m' RED='\033[0;31m' YELLOW='\033[0;33m' NC='\033[0m' @@ -51,6 +55,51 @@ is_fwtpm_mode() { [ "$VENDOR" = "fwtpm-tcg" ] || [ "$VENDOR" = "fwtpm-psk" ] } +valid_responder_pubkey() { + [ "${#SPDM_RESPONDER_PUBKEY}" -eq 192 ] && + [[ "$SPDM_RESPONDER_PUBKEY" =~ ^[[:xdigit:]]+$ ]] +} + +require_responder_pubkey() { + if ! valid_responder_pubkey; then + echo "Error: identity mode requires SPDM_RESPONDER_PUBKEY." + echo "Set it to the trusted 192-character P-384 X||Y key." + return 1 + fi +} + +# Invoked indirectly by run_test and run_test_no_reset. +# shellcheck disable=SC2329 +run_identity() { + # Leave Nations unforced so a dual-vendor build exercises DID/VID AUTO + # selection and the resulting vendor-specific command dispatch. + if [ "$VENDOR" = "nations" ]; then + "$SPDM_DEMO" --responder-pubkey "$SPDM_RESPONDER_PUBKEY" "$@" + else + "$SPDM_DEMO" --vendor=nuvoton \ + --responder-pubkey "$SPDM_RESPONDER_PUBKEY" "$@" + fi +} + +# Invoked indirectly by run_test while identity mode is locked. +# shellcheck disable=SC2329 +run_unit_with_pin() { + local identity_vendor=nuvoton + + if [ "$VENDOR" = "nations" ]; then + identity_vendor=nations + fi + SPDM_RESPONDER_PUBKEY="$SPDM_RESPONDER_PUBKEY" \ + SPDM_IDENTITY_VENDOR="$identity_vendor" \ + WOLFTPM_TEST_SPDM_ONLY=1 "$UNIT_TEST" +} + +# Invoked indirectly by run_test in the fwTPM PSK flow. +# shellcheck disable=SC2329 +run_unit_with_psk() { + WOLFTPM_TEST_SPDM_PSK="$NATIONS_PSK" "$UNIT_TEST" +} + gpio_reset() { if is_fwtpm_mode; then return 0 @@ -63,28 +112,65 @@ gpio_reset() { fwtpm_start() { local mode="$1" + local attempt + if [ -z "$FWTPM_LOG" ]; then + FWTPM_LOG=$(mktemp "/tmp/fwtpm_spdm_test.XXXXXX") || + return 1 + FWTPM_LOG_CREATED=1 + fi rm -f fwtpm_nv.bin NVChip 2>/dev/null if [ "$mode" = "psk" ]; then "$FWTPM" --spdm-psk --spdm-psk-hex "$NATIONS_PSK" \ --port "$FWTPM_PORT" --platform-port "$FWTPM_PLAT_PORT" \ - --clear > /tmp/fwtpm_spdm_test.log 2>&1 & + --clear > "$FWTPM_LOG" 2>&1 & else "$FWTPM" --spdm-tcg --port "$FWTPM_PORT" \ --platform-port "$FWTPM_PLAT_PORT" --clear \ - > /tmp/fwtpm_spdm_test.log 2>&1 & + > "$FWTPM_LOG" 2>&1 & fi FWTPM_PID=$! - sleep 1 + for ((attempt = 0; attempt < 50; attempt++)); do + if [ "$mode" = "tcg" ]; then + SPDM_RESPONDER_PUBKEY=$(sed -n \ + '/^ SPDM responder public key: /{s///;p;q;}' "$FWTPM_LOG") + fi + if grep -q '^fwTPM: Listening on command port' "$FWTPM_LOG" && + { [ "$mode" != "tcg" ] || valid_responder_pubkey; }; then + break + fi + if ! kill -0 "$FWTPM_PID" 2>/dev/null; then + echo "Error: fwTPM exited during startup. Log: $FWTPM_LOG" + sed -n '1,120p' "$FWTPM_LOG" + return 1 + fi + sleep 0.1 + done + if ! grep -q '^fwTPM: Listening on command port' "$FWTPM_LOG"; then + echo "Error: fwTPM did not become ready. Log: $FWTPM_LOG" + return 1 + fi + if [ "$mode" = "tcg" ] && ! valid_responder_pubkey; then + echo "Error: fwTPM did not publish its responder key." + echo "Log: $FWTPM_LOG" + return 1 + fi export TPM2_SWTPM_HOST=127.0.0.1 export TPM2_SWTPM_PORT="$FWTPM_PORT" } +# Invoked by the EXIT trap in fwTPM modes. +# shellcheck disable=SC2329 fwtpm_stop() { if [ -n "$FWTPM_PID" ]; then kill "$FWTPM_PID" 2>/dev/null || true wait "$FWTPM_PID" 2>/dev/null || true FWTPM_PID="" fi + if [ "$FWTPM_LOG_CREATED" -eq 1 ] && [ "$FAIL" -eq 0 ] && + [ "$TOTAL" -gt 0 ]; then + rm -f -- "$FWTPM_LOG" + FWTPM_LOG="" + fi } # normalize_nations_chip: bring NS350 to canonical clean state @@ -158,9 +244,59 @@ run_test_no_reset() { echo "" } +run_test_output() { + local name="$1"; local expected="$2"; shift 2 + local output result + TOTAL=$((TOTAL + 1)) + echo "[$TOTAL] $name" + gpio_reset + output=$("$@" 2>&1) + result=$? + printf '%s\n' "$output" + if [ "$result" -eq 0 ] && grep -Fq -- "$expected" <<< "$output"; then + echo -e " ${GREEN}PASS${NC}"; PASS=$((PASS + 1)) + else + echo -e " ${RED}FAIL${NC}"; FAIL=$((FAIL + 1)) + fi + echo "" +} + +run_test_output_no_reset() { + local name="$1"; local expected="$2"; shift 2 + local output result + TOTAL=$((TOTAL + 1)) + echo "[$TOTAL] $name" + output=$("$@" 2>&1) + result=$? + printf '%s\n' "$output" + if [ "$result" -eq 0 ] && grep -Fq -- "$expected" <<< "$output"; then + echo -e " ${GREEN}PASS${NC}"; PASS=$((PASS + 1)) + else + echo -e " ${RED}FAIL${NC}"; FAIL=$((FAIL + 1)) + fi + echo "" +} + +run_test_rejected() { + local name="$1"; local expected="$2"; shift 2 + local output result + TOTAL=$((TOTAL + 1)) + echo "[$TOTAL] $name" + gpio_reset + output=$("$@" 2>&1) + result=$? + printf '%s\n' "$output" + if [ "$result" -ne 0 ] && grep -Fq -- "$expected" <<< "$output"; then + echo -e " ${GREEN}PASS${NC}"; PASS=$((PASS + 1)) + else + echo -e " ${RED}FAIL${NC}"; FAIL=$((FAIL + 1)) + fi + echo "" +} + if [ ! -x "$SPDM_DEMO" ]; then echo "Error: $SPDM_DEMO not found." - echo "Usage: $0 [path-to-spdm_ctrl] [nuvoton|nations|nations-psk]" + echo "Usage: $0 [path-to-spdm_ctrl] [nuvoton|nations|nations-psk|fwtpm-tcg|fwtpm-psk]" exit 1 fi @@ -170,17 +306,21 @@ echo "" if [ "$VENDOR" = "nuvoton" ]; then # Nuvoton test flow (identity key mode) + require_responder_pubkey || exit 1 + run_test_output "Pinned-key initialization establishes SPDM" \ + "Already connected" run_identity --connect run_test "SPDM status query" "$SPDM_DEMO" --status - run_test "SPDM session connect" "$SPDM_DEMO" --connect - run_test "Lock SPDM-only mode" "$SPDM_DEMO" --connect --lock - + run_test "Lock SPDM-only mode" run_identity --connect --lock + run_test_rejected "Unpinned initialization rejected while locked" \ + "Invalid state" "$SPDM_DEMO" --status if [ -x "$UNIT_TEST" ]; then - run_test "Unit test over SPDM" "$UNIT_TEST" + run_test "Unit test over pinned SPDM" run_unit_with_pin else echo -e " ${YELLOW}Skipping: $UNIT_TEST not found${NC}" fi - - run_test "Unlock SPDM-only mode" "$SPDM_DEMO" --connect --unlock + run_test "Status in SPDM-only mode" run_identity --status + run_test "TPM capabilities in SPDM-only mode" run_identity --caps + run_test "Unlock SPDM-only mode" run_identity --connect --unlock if [ -x "$CAPS_DEMO" ]; then run_test_caps "Cleartext caps (no SPDM)" "$CAPS_DEMO" @@ -194,13 +334,15 @@ elif [ "$VENDOR" = "nations" ]; then # identity-key/PSK are NV-persistent across reset. The entry/exit # normalization ensures the chip is always at a known starting state # and always left clean, regardless of prior runs or mid-test failures. + require_responder_pubkey || exit 1 normalize_nations_chip trap 'normalize_nations_chip' EXIT run_test_no_reset "Unset identity key" "$SPDM_DEMO" --identity-key-unset run_test_no_reset "Set identity key" "$SPDM_DEMO" --identity-key-set - run_test_no_reset "SPDM session connect" "$SPDM_DEMO" --connect - run_test_no_reset "Status query" "$SPDM_DEMO" --status + run_test_no_reset "SPDM session connect" run_identity --connect + run_test_no_reset "AUTO Nations status/lock/unlock dispatch" run_identity \ + --status --lock --unlock if [ -x "$CAPS_DEMO" ]; then run_test_caps_no_reset "Cleartext caps (no SPDM)" "$CAPS_DEMO" @@ -232,7 +374,8 @@ elif [ "$VENDOR" = "nations-psk" ]; then run_test_no_reset "PSK provision (PSK_SET)" "$SPDM_DEMO" --psk-set "$NATIONS_PSK" "$NATIONS_CLEARAUTH" # Step 3: Status check (should show PSK provisioned) - run_test_no_reset "Status (PSK provisioned)" "$SPDM_DEMO" --status + run_test_output_no_reset "Status (PSK provisioned)" "PSK: provisioned" \ + "$SPDM_DEMO" --status # Step 4: PSK connect (VCA → PSK_EXCHANGE → PSK_FINISH) run_test_no_reset "PSK session connect" "$SPDM_DEMO" --psk "$NATIONS_PSK" @@ -244,7 +387,8 @@ elif [ "$VENDOR" = "nations-psk" ]; then run_test_no_reset "PSK clear (PSK_CLEAR)" "$SPDM_DEMO" --psk-clear "$NATIONS_CLEARAUTH" # Step 7: Status check (should show PSK not provisioned) - run_test_no_reset "Status (PSK cleared)" "$SPDM_DEMO" --status + run_test_output_no_reset "Status (PSK cleared)" \ + "PSK: not provisioned" "$SPDM_DEMO" --status # Step 8: Re-provision PSK (verify PSK_SET works after clear) run_test_no_reset "PSK re-provision (PSK_SET)" "$SPDM_DEMO" --psk-set "$NATIONS_PSK" "$NATIONS_CLEARAUTH" @@ -266,7 +410,7 @@ elif [ "$VENDOR" = "nations-psk" ]; then fi elif [ "$VENDOR" = "fwtpm-tcg" ]; then - # fwtpm in TCG cert mode - mirrors the Nuvoton 6-test sequence against + # fwtpm in TCG cert mode - mirrors the Nuvoton sequence against # the software responder. Lock/unlock toggles the SPDMONLY runtime # state in the responder; while locked, plaintext TPM frames are # rejected with TPM_RC_DISABLED. @@ -274,20 +418,36 @@ elif [ "$VENDOR" = "fwtpm-tcg" ]; then echo "Error: $FWTPM not found" exit 1 fi - fwtpm_start tcg trap 'fwtpm_stop' EXIT + if ! fwtpm_start tcg; then + exit 1 + fi + run_test_rejected "Unknown vendor is rejected" \ + "Unknown --vendor= value" "$SPDM_DEMO" --vendor=unknown --status + run_test_rejected "Responder key operand is required" \ + "--responder-pubkey requires 192 hex characters" "$SPDM_DEMO" \ + --responder-pubkey --status + run_test_rejected "Malformed responder key is rejected" \ + "Invalid responder public key" "$SPDM_DEMO" \ + --responder-pubkey 00 --status + run_test_rejected "AUTO rejects an unrecognized TPM vendor" \ + "Invalid state" "$SPDM_DEMO" \ + --responder-pubkey "$SPDM_RESPONDER_PUBKEY" --connect + run_test_output "Pinned-key initialization establishes SPDM" \ + "Already connected" run_identity --connect run_test "SPDM status query" "$SPDM_DEMO" --status - run_test "SPDM session connect" "$SPDM_DEMO" --connect - run_test "Lock SPDM-only mode" "$SPDM_DEMO" --connect --lock - + run_test "Lock SPDM-only mode" run_identity --connect --lock + run_test_rejected "Unpinned initialization rejected while locked" \ + "Invalid state" "$SPDM_DEMO" --status if [ -x "$UNIT_TEST" ]; then - run_test "Unit test over SPDM" "$UNIT_TEST" + run_test "Unit test over pinned SPDM" run_unit_with_pin else echo -e " ${YELLOW}Skipping: $UNIT_TEST not found${NC}" fi - - run_test "Unlock SPDM-only mode" "$SPDM_DEMO" --connect --unlock + run_test "Status in SPDM-only mode" run_identity --status + run_test "TPM capabilities in SPDM-only mode" run_identity --caps + run_test "Unlock SPDM-only mode" run_identity --connect --unlock if [ -x "$CAPS_DEMO" ]; then run_test_caps "Cleartext caps (no SPDM)" "$CAPS_DEMO" @@ -304,21 +464,76 @@ elif [ "$VENDOR" = "fwtpm-psk" ]; then echo "Error: $FWTPM not found" exit 1 fi - fwtpm_start psk trap 'fwtpm_stop' EXIT + if ! fwtpm_start psk; then + exit 1 + fi - run_test "PSK provision (PSK_SET)" "$SPDM_DEMO" \ + run_test_rejected "PSK operand is required" \ + "--psk requires a hexadecimal key" "$SPDM_DEMO" --psk --status + run_test_rejected "Malformed PSK is rejected" \ + "Invalid PSK hex string" "$SPDM_DEMO" --psk zz --status + run_test_rejected "Responder key and PSK are mutually exclusive" \ + "Choose either --responder-pubkey or --psk" "$SPDM_DEMO" \ + --vendor=nations --responder-pubkey "$TEST_RESPONDER_PUBKEY" \ + --psk "$NATIONS_PSK" + run_test_rejected "PSK rejects an explicit Nuvoton vendor (vendor first)" \ + "PSK sessions require --vendor=nations" "$SPDM_DEMO" \ + --vendor=nuvoton --psk "$NATIONS_PSK" + run_test_rejected "PSK rejects an explicit Nuvoton vendor (PSK first)" \ + "PSK sessions require --vendor=nations" "$SPDM_DEMO" \ + --psk "$NATIONS_PSK" --vendor=nuvoton + run_test_rejected \ + "Nations PSK clear rejects an explicit Nuvoton vendor (vendor first)" \ + "Nations-only commands require --vendor=nations" "$SPDM_DEMO" \ + --vendor=nuvoton --psk-clear "$NATIONS_CLEARAUTH" + run_test_rejected \ + "Nations PSK set rejects an explicit Nuvoton vendor (command first)" \ + "Nations-only commands require --vendor=nations" "$SPDM_DEMO" \ + --psk-set "$NATIONS_PSK" "$NATIONS_CLEARAUTH" --vendor=nuvoton + run_test_rejected "PSK clear operand is not re-parsed as --psk" \ + "Nations-only commands require --vendor=nations" "$SPDM_DEMO" \ + --vendor=nuvoton --psk-clear --psk + run_test_rejected "PSK clear requires its ClearAuth operand" \ + "--psk-clear requires a ClearAuth hex argument" "$SPDM_DEMO" \ + --psk-clear --status + run_test_rejected "PSK set requires both operands" \ + "--psk-set requires PSK and ClearAuth hex arguments" "$SPDM_DEMO" \ + --psk-set AABB --status + run_test "PSK provision (PSK_SET)" "$SPDM_DEMO" --vendor=nations \ --psk-set "$NATIONS_PSK" "$NATIONS_CLEARAUTH" - run_test "Status (PSK provisioned)" "$SPDM_DEMO" --status - run_test "PSK session connect" "$SPDM_DEMO" --psk "$NATIONS_PSK" - run_test "PSK session connect (repeat)" "$SPDM_DEMO" --psk "$NATIONS_PSK" - run_test "PSK clear (PSK_CLEAR)" "$SPDM_DEMO" --psk-clear "$NATIONS_CLEARAUTH" - run_test "Status (PSK cleared)" "$SPDM_DEMO" --status - run_test "PSK re-provision (PSK_SET)" "$SPDM_DEMO" \ + run_test_output "Status (PSK provisioned)" "PSK: provisioned" \ + "$SPDM_DEMO" --vendor=nations --status + run_test_output "PSK initialization establishes SPDM" \ + "Already connected" "$SPDM_DEMO" --vendor=nations \ + --psk "$NATIONS_PSK" + if [ -x "$UNIT_TEST" ]; then + run_test "Unit test exercises PSK initialization" run_unit_with_psk + else + echo -e " ${YELLOW}Skipping: $UNIT_TEST not found${NC}" + fi + run_test "Lock PSK SPDM-only mode" "$SPDM_DEMO" --vendor=nations \ + --psk "$NATIONS_PSK" --lock + run_test_rejected "Uncredentialed initialization rejected while locked" \ + "Invalid state" "$SPDM_DEMO" --vendor=nations --status + run_test_output "PSK status in SPDM-only mode" \ + "PSK: provisioned SPDM-Only: ENABLED" "$SPDM_DEMO" \ + --vendor=nations --psk "$NATIONS_PSK" --status + run_test_output "Status preserves the PSK session for TPM commands" \ + "Session: active" "$SPDM_DEMO" --vendor=nations \ + --psk "$NATIONS_PSK" --status --caps + run_test "Unlock PSK SPDM-only mode" "$SPDM_DEMO" --vendor=nations \ + --psk "$NATIONS_PSK" --unlock + run_test "PSK clear (PSK_CLEAR)" "$SPDM_DEMO" --vendor=nations \ + --psk-clear "$NATIONS_CLEARAUTH" + run_test_output "Status (PSK cleared)" "PSK: not provisioned" \ + "$SPDM_DEMO" --vendor=nations --status + run_test "PSK re-provision (PSK_SET)" "$SPDM_DEMO" --vendor=nations \ --psk-set "$NATIONS_PSK" "$NATIONS_CLEARAUTH" run_test "PSK session connect (after re-provision)" "$SPDM_DEMO" \ - --psk "$NATIONS_PSK" - run_test "Final PSK clear" "$SPDM_DEMO" --psk-clear "$NATIONS_CLEARAUTH" + --vendor=nations --psk "$NATIONS_PSK" + run_test "Final PSK clear" "$SPDM_DEMO" --vendor=nations \ + --psk-clear "$NATIONS_CLEARAUTH" if [ -x "$CAPS_DEMO" ]; then run_test_caps "Cleartext caps (no SPDM)" "$CAPS_DEMO" diff --git a/src/fwtpm/README.md b/src/fwtpm/README.md index cfbb39ef..1b0589c5 100644 --- a/src/fwtpm/README.md +++ b/src/fwtpm/README.md @@ -197,7 +197,10 @@ Build with `--enable-fwtpm --enable-spdm` plus at least one of The responder generates a fresh P-384 identity keypair at startup (used for `GET_PUBK`/`KEY_EXCHANGE` signing); the private key never leaves `fwtpm_server` memory and the stack copy is zeroed with `wc_ForceZero` -after handoff to the responder context. +after handoff to the responder context. In TCG mode, the server prints the +public half during startup so the local test harness can pass it to the +requester through the responder-key pinning API. This process-owned log is a +test bootstrap channel, not a substitute for authenticated device provisioning. End-to-end coverage runs via the same script that drives real silicon: diff --git a/src/fwtpm/fwtpm_main.c b/src/fwtpm/fwtpm_main.c index 6beedde7..7a85cd77 100644 --- a/src/fwtpm/fwtpm_main.c +++ b/src/fwtpm/fwtpm_main.c @@ -393,6 +393,13 @@ int main(int argc, char* argv[]) rc = wolfSPDM_RespSetIdentityKey(ctx.spdmRespCtx, idPriv, sizeof(idPriv), idPub, sizeof(idPub)); } + if (rc == 0 && useTcg) { + printf(" SPDM responder public key: "); + for (i = 0; i < (int)sizeof(idPub); i++) { + printf("%02x", idPub[i]); + } + printf("\n"); + } wc_ForceZero(idPriv, sizeof(idPriv)); } if (rc != 0) { @@ -410,6 +417,7 @@ int main(int argc, char* argv[]) (useTcg && usePsk) ? "+" : "", usePsk ? "PSK" : ""); printf(" Bus-snooping defence: armed (plaintext rejected after SPDMONLY lock)\n"); + fflush(stdout); } #endif @@ -432,6 +440,7 @@ int main(int argc, char* argv[]) FWTPM_Cleanup(&ctx); return 1; } + fflush(stdout); /* Run server loop (blocks until stopped) */ rc = FWTPM_IO_ServerLoop(&ctx); diff --git a/src/spdm/README.md b/src/spdm/README.md index e4654806..65d8bd2b 100644 --- a/src/spdm/README.md +++ b/src/spdm/README.md @@ -4,8 +4,9 @@ wolfTPM includes built-in SPDM support for Nuvoton NPCT75x and Nations NS350 TPMs using wolfSSL/wolfCrypt. Both vendors support identity key mode (ECDHE P-384) for session establishment. The Nations NS350 additionally supports PSK (pre-shared key) mode. Once a session is established, all TPM commands and -responses are encrypted with AES-256-GCM over the existing SPI/I2C bus — no -application code changes needed. +responses are encrypted with AES-256-GCM over the existing SPI/I2C bus. Identity +key mode requires the responder's P-384 public key from a trusted provisioning +source. For standard SPDM protocol testing with the DMTF spdm-emu emulator, see the [wolfSPDM](https://github.com/aidangarske/wolfSPDM) standalone library. @@ -26,7 +27,7 @@ make && sudo make install && sudo ldconfig && popd # Enable SPDM (one-time), reset, connect ./examples/spdm/spdm_ctrl --enable gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 -./examples/spdm/spdm_ctrl --connect +./examples/spdm/spdm_ctrl --responder-pubkey --connect ``` See [Building](#building) and [Nuvoton NPCT75x Details](#nuvoton-npct75x) for @@ -44,7 +45,7 @@ make && sudo make install && sudo ldconfig && popd ./autogen.sh && ./configure --enable-spdm --enable-nations && make # Connect (identity key is factory default) -./examples/spdm/spdm_ctrl --connect +./examples/spdm/spdm_ctrl --responder-pubkey --connect ``` See [Building](#building) and [Nations NS350 Details](#nations-ns350) for full @@ -135,16 +136,15 @@ Both vendors support this. The typical lifecycle: 2. Connect (handshake, derives session keys) 3. Lock SPDM-only (TPM rejects all cleartext commands) 4. Reset (TPM enters SPDM-only enforcement) -5. Run any commands (each auto-establishes SPDM, all AES-256-GCM encrypted) +5. Initialize with the trusted key or PSK and run commands (all encrypted) 6. Unlock (connect + unlock in one session) 7. Reset (TPM back to normal cleartext mode) ``` -Step 5 is fully automatic. When wolfTPM detects SPDM-only mode (TPM2_Startup -returns `TPM_RC_DISABLED`), it transparently establishes an SPDM session. -Existing applications like `caps`, `wrap_test`, and `unit.test` work without -modification — all commands are encrypted over the bus. See -[How Auto-SPDM Works](#how-auto-spdm-works) for details. +After an application supplies the responder key through +`wolfTPM2_InitWithSpdmKey()`, wolfTPM authenticates the responder and +establishes the encrypted session regardless of the cleartext startup result. +See [How Auto-SPDM Works](#how-auto-spdm-works) for details. **Reset method differs by vendor:** - **Nuvoton:** GPIO reset — `gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2` @@ -219,11 +219,14 @@ When both `--enable-nuvoton` and `--enable-nations` are compiled in, the runtime flag: ```bash -./examples/spdm/spdm_ctrl --vendor=nuvoton --connect # default -./examples/spdm/spdm_ctrl --vendor=nations --connect +./examples/spdm/spdm_ctrl --vendor=nuvoton \ + --responder-pubkey --connect +./examples/spdm/spdm_ctrl --vendor=nations \ + --responder-pubkey --connect ``` -Single-vendor builds ignore `--vendor=`. +Single-vendor builds accept only the adapter compiled into the binary and +reject an unavailable `--vendor=` value. ## Usage @@ -257,14 +260,17 @@ unset, restore with: ```bash # Establish SPDM session (VERSION → GET_PUBK → KEY_EXCHANGE → GIVE_PUB → FINISH) -./examples/spdm/spdm_ctrl --connect +./examples/spdm/spdm_ctrl \ + --responder-pubkey --connect # Query SPDM status ./examples/spdm/spdm_ctrl --status ``` -**Note:** `--get-pubkey` retrieves the TPM's identity key as part of the full -handshake within `--connect`. It is not intended as a standalone command. +`--responder-pubkey` takes the trusted raw P-384 X||Y point as 192 hex +characters. Obtain it from device provisioning records or another authenticated +manufacturer channel. `--get-pubkey` is unauthenticated discovery and must not +be used by itself to establish trust. #### PSK Mode (Nations) @@ -284,25 +290,25 @@ enforcement to take effect. **Nuvoton (identity key):** ```bash -./examples/spdm/spdm_ctrl --connect --lock +./examples/spdm/spdm_ctrl \ + --responder-pubkey --connect --lock gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 -# TPM now requires SPDM — all commands auto-encrypted: -./examples/wrap/caps # auto-SPDM session, all AES-256-GCM -./tests/unit.test # full test suite over encrypted bus - # Unlock -./examples/spdm/spdm_ctrl --connect --unlock +./examples/spdm/spdm_ctrl \ + --responder-pubkey --connect --unlock gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 ``` **Nations (identity key):** ```bash -./examples/spdm/spdm_ctrl --connect --lock +./examples/spdm/spdm_ctrl \ + --responder-pubkey --connect --lock # Power cycle required (unplug and re-plug Raspberry Pi) -./examples/spdm/spdm_ctrl --connect --unlock +./examples/spdm/spdm_ctrl \ + --responder-pubkey --connect --unlock # Power cycle again ``` @@ -347,6 +353,7 @@ to verify. Using the wrong size makes PSK_CLEAR impossible. ```bash # Nuvoton (identity key — includes GPIO resets between tests) +export SPDM_RESPONDER_PUBKEY= ./examples/spdm/spdm_test.sh ./examples/spdm/spdm_ctrl nuvoton # Nations (identity key — no GPIO resets) @@ -356,6 +363,11 @@ to verify. Using the wrong size makes PSK_CLEAR impossible. ./examples/spdm/spdm_test.sh ./examples/spdm/spdm_ctrl nations-psk ``` +Identity-mode hardware tests require `SPDM_RESPONDER_PUBKEY` from trusted +device provisioning records. The local `fwtpm-tcg` test instead reads the +freshly generated public key from the owner-only server log created by the +test harness; this local bootstrap is not a hardware provisioning mechanism. + ## TCG SPDM Vendor Commands Both Nuvoton and Nations TPMs implement the TCG "TPM Communication over SPDM @@ -382,8 +394,11 @@ All `spdm_ctrl` options in one table: | `--disable` | Nuvoton | Disable SPDM via NTC2_PreConfig | | `--identity-key-set` | Nations | Provision SPDM identity key (factory default) | | `--identity-key-unset` | Nations | Un-provision identity key (required before PSK) | -| `--get-pubkey` | Both | Get TPM's SPDM-Identity P-384 public key (used within `--connect`) | +| `--vendor=nuvoton\|nations` | Both | Select the identity/vendor adapter explicitly | +| `--get-pubkey` | Both | Discover the TPM identity key without authenticating it | +| `--responder-pubkey ` | Both | Pin a trusted raw P-384 X\|\|Y responder key | | `--connect` | Both | Establish identity key SPDM session | +| `--caps` | Both | Read TPM capabilities over the current transport | | `--status` | Both | Query SPDM status | | `--lock` | Both | Lock SPDM-only mode (requires active session) | | `--unlock` | Both | Unlock SPDM-only mode (requires active session) | @@ -440,13 +455,28 @@ to verify. ## How Auto-SPDM Works -When the TPM is in SPDM-only mode, `wolfTPM2_Init()` handles everything: - -1. `TPM2_Startup` is sent in cleartext — TPM returns `TPM_RC_DISABLED` -2. wolfTPM detects this and sets `spdmOnlyDetected` -3. An SPDM session is automatically established (P-384 keygen + handshake) -4. `TPM2_Startup` is retried over the encrypted channel — succeeds -5. All subsequent commands go through the SPDM encrypted channel +Call `wolfTPM2_InitWithSpdmKey()` with the trusted responder key for identity +mode, or `wolfTPM2_InitWithSpdmPsk()` with the provisioned PSK for PSK mode. +Both entry points recover a TPM that is already locked in SPDM-only mode. The +identity-mode initialization sequence is: + +1. `TPM2_Startup` probes whether the TPM is already in SPDM-only mode +2. The caller-provided responder key is installed as the trust anchor +3. The discovered responder key is compared with that trusted key +4. An SPDM session is always established (P-384 keygen + handshake) +5. If the probe returned `TPM_RC_DISABLED`, `TPM2_Startup` is retried securely +6. All subsequent commands go through the SPDM encrypted channel + +In a dual-vendor build, `wolfTPM2_InitWithSpdmKey()` selects the identity +adapter from TPM DID/VID. A transport that does not expose DID/VID must call +`wolfTPM2_InitWithSpdmKey_ex()` with `WOLFSPDM_MODE_NUVOTON` or +`WOLFSPDM_MODE_NATIONS`; automatic mode fails closed rather than guessing. + +`wolfTPM2_Init()` without a credential fails closed if it detects any +SPDM-only mode. For a normal-mode TPM that does not require an immediate secure +channel, identity-mode applications may instead call +`wolfTPM2_SpdmInit()`, `wolfTPM2_SpdmSetResponderPubKey()`, and then the +vendor-specific connect function. Both `TPM2_SendCommand` (non-auth commands) and `TPM2_SendCommandAuth` (auth-session commands like PCR operations, key creation, signing) are @@ -470,6 +500,7 @@ Useful on platforms with small stacks. | `wolfSPDM_Free()` | Free context (releases resources; frees heap only if dynamic) | | `wolfSPDM_GetCtxSize()` | Return `sizeof(WOLFSPDM_CTX)` at runtime | | `wolfSPDM_SetIO()` | Set transport I/O callback | +| `wolfSPDM_SetResponderPubKey()` | Pin the trusted responder P-384 key | | `wolfSPDM_SetDebug()` | Enable/disable debug output | | `wolfSPDM_Connect()` | Full SPDM handshake | | `wolfSPDM_IsConnected()` | Check session status | diff --git a/src/spdm/include.am b/src/spdm/include.am index 299eb6fb..2e7ed2e2 100644 --- a/src/spdm/include.am +++ b/src/spdm/include.am @@ -36,6 +36,7 @@ src_libwolftpm_la_SOURCES += src/spdm/spdm_responder.c endif check_PROGRAMS += src/spdm/unit_test +noinst_PROGRAMS += src/spdm/unit_test src_spdm_unit_test_SOURCES = src/spdm/unit_test.c src_spdm_unit_test_LDADD = src/libwolftpm.la $(LIB_STATIC_ADD) src_spdm_unit_test_CFLAGS = $(AM_CFLAGS) diff --git a/src/spdm/spdm_context.c b/src/spdm/spdm_context.c index 9aebd46a..cc6b17f9 100644 --- a/src/spdm/spdm_context.c +++ b/src/spdm/spdm_context.c @@ -253,7 +253,7 @@ int wolfSPDM_SetMode(WOLFSPDM_CTX* ctx, WOLFSPDM_MODE mode) WOLFSPDM_MODE wolfSPDM_GetMode(WOLFSPDM_CTX* ctx) { if (ctx == NULL) { - return (WOLFSPDM_MODE)0; + return WOLFSPDM_MODE_AUTO; } return ctx->mode; } diff --git a/src/spdm/spdm_crypto.c b/src/spdm/spdm_crypto.c index f5d3c573..82fc0d67 100644 --- a/src/spdm/spdm_crypto.c +++ b/src/spdm/spdm_crypto.c @@ -203,6 +203,37 @@ int wolfSPDM_ComputeSharedSecret(WOLFSPDM_CTX* ctx, /* ----- ECDSA Signature Verification (P-384) ----- */ +int wolfSPDM_ExtractEccPoint(const byte* pubKey, word32 pubKeySz, + const byte** pubKeyX, const byte** pubKeyY) +{ + word32 pointOffset; + + if (pubKey == NULL || pubKeyX == NULL || pubKeyY == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (pubKeySz == WOLFSPDM_ECC_POINT_SIZE) { + *pubKeyX = pubKey; + *pubKeyY = pubKey + WOLFSPDM_ECC_KEY_SIZE; + } + else if (pubKeySz >= WOLFSPDM_ECC_POINT_SIZE + 4) { + pointOffset = pubKeySz - (WOLFSPDM_ECC_POINT_SIZE + 4); + if (SPDM_Get16BE(pubKey + pointOffset) != + WOLFSPDM_ECC_KEY_SIZE || + SPDM_Get16BE(pubKey + pointOffset + 2 + + WOLFSPDM_ECC_KEY_SIZE) != WOLFSPDM_ECC_KEY_SIZE) { + return WOLFSPDM_E_INVALID_ARG; + } + *pubKeyX = pubKey + pointOffset + 2; + *pubKeyY = pubKey + pointOffset + 4 + WOLFSPDM_ECC_KEY_SIZE; + } + else { + return WOLFSPDM_E_INVALID_ARG; + } + + return WOLFSPDM_SUCCESS; +} + int wolfSPDM_VerifySignature(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, const byte* sig, word32 sigSz) { @@ -228,19 +259,10 @@ int wolfSPDM_VerifySignature(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, return WOLFSPDM_E_INVALID_ARG; } - /* Extract X/Y coordinates from rspPubKey. - * If len == 96: raw X||Y format. - * If len > 96: TPMT_PUBLIC format — X/Y are at the tail: - * [len-100]: X size(2 BE) + X(48) + Y size(2 BE) + Y(48) */ - if (ctx->rspPubKeyLen == WOLFSPDM_ECC_POINT_SIZE) { - pubKeyX = ctx->rspPubKey; - pubKeyY = ctx->rspPubKey + WOLFSPDM_ECC_KEY_SIZE; - } else if (ctx->rspPubKeyLen >= WOLFSPDM_ECC_POINT_SIZE + 4) { - /* TPMT_PUBLIC: skip 2-byte size prefixes on each coordinate */ - pubKeyX = ctx->rspPubKey + (ctx->rspPubKeyLen - 100 + 2); - pubKeyY = ctx->rspPubKey + (ctx->rspPubKeyLen - 48); - } else { - return WOLFSPDM_E_INVALID_ARG; + rc = wolfSPDM_ExtractEccPoint(ctx->rspPubKey, ctx->rspPubKeyLen, + &pubKeyX, &pubKeyY); + if (rc != WOLFSPDM_SUCCESS) { + return rc; } rc = wc_ecc_init(&verifyKey); diff --git a/src/spdm/spdm_internal.h b/src/spdm/spdm_internal.h index ccad7fc3..ba8df3cb 100644 --- a/src/spdm/spdm_internal.h +++ b/src/spdm/spdm_internal.h @@ -155,7 +155,7 @@ struct WOLFSPDM_CTX { word32 sessionId; /* Combined: reqSessionId | (rspSessionId << 16) */ /* Responder's identity public key (for cert-less mode like Nuvoton) */ - byte rspPubKey[WOLFSPDM_PUBKEY_BUF_SZ / 2]; /* TPMT_PUBLIC or raw X||Y */ + byte rspPubKey[WOLFSPDM_PUBKEY_BUF_SZ / 2]; /* pinned raw X||Y */ word32 rspPubKeyLen; /* Mutual auth fields from KEY_EXCHANGE_RSP */ @@ -306,6 +306,8 @@ WOLFTPM_API int wolfSPDM_ComputeSharedSecret(WOLFSPDM_CTX* ctx, WOLFTPM_API int wolfSPDM_GetRandom(WOLFSPDM_CTX* ctx, byte* out, word32 outSz); WOLFTPM_API int wolfSPDM_SignHash(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, byte* sig, word32* sigSz); +WOLFTPM_TEST_API int wolfSPDM_ExtractEccPoint(const byte* pubKey, + word32 pubKeySz, const byte** pubKeyX, const byte** pubKeyY); WOLFTPM_API int wolfSPDM_VerifySignature(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, const byte* sig, word32 sigSz); diff --git a/src/spdm/spdm_tcg.c b/src/spdm/spdm_tcg.c index 1a46d2d2..e80184bb 100644 --- a/src/spdm/spdm_tcg.c +++ b/src/spdm/spdm_tcg.c @@ -300,6 +300,35 @@ int wolfSPDM_ParseVendorDefined( /* ----- Shared TCG SPDM Functions ----- */ +static int wolfSPDM_TCG_CheckPubKey(WOLFSPDM_CTX* ctx, + const byte* pubKey, word32 pubKeySz) +{ + const byte* pubKeyX; + const byte* pubKeyY; + int rc; + + if (!ctx->flags.hasRspPubKey) { + return WOLFSPDM_SUCCESS; + } + if (ctx->rspPubKeyLen != WOLFSPDM_ECC_POINT_SIZE) { + return WOLFSPDM_E_BAD_STATE; + } + + rc = wolfSPDM_ExtractEccPoint(pubKey, pubKeySz, &pubKeyX, &pubKeyY); + if (rc != WOLFSPDM_SUCCESS) { + return WOLFSPDM_E_PEER_ERROR; + } + + if (XMEMCMP(pubKeyX, ctx->rspPubKey, WOLFSPDM_ECC_KEY_SIZE) != 0 || + XMEMCMP(pubKeyY, ctx->rspPubKey + WOLFSPDM_ECC_KEY_SIZE, + WOLFSPDM_ECC_KEY_SIZE) != 0) { + wolfSPDM_DebugPrint(ctx, "GET_PUBK: Responder key mismatch\n"); + return WOLFSPDM_E_PEER_ERROR; + } + + return WOLFSPDM_SUCCESS; +} + int wolfSPDM_TCG_GetPubKey( WOLFSPDM_CTX* ctx, byte* pubKey, word32* pubKeySz) @@ -329,21 +358,17 @@ int wolfSPDM_TCG_GetPubKey( wolfSPDM_DebugPrint(ctx, "GET_PUBK: Got TPMT_PUBLIC (%u bytes)\n", rsp.payloadSz); + rc = wolfSPDM_TCG_CheckPubKey(ctx, rsp.payload, rsp.payloadSz); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + if (*pubKeySz < rsp.payloadSz) { return WOLFSPDM_E_BUFFER_SMALL; } XMEMCPY(pubKey, rsp.payload, rsp.payloadSz); *pubKeySz = rsp.payloadSz; - /* Store for cert_chain_buffer_hash computation. Skipping it silently - * surfaces later as a misleading handshake state error. */ - if (rsp.payloadSz > sizeof(ctx->rspPubKey)) { - return WOLFSPDM_E_BUFFER_SMALL; - } - XMEMCPY(ctx->rspPubKey, rsp.payload, rsp.payloadSz); - ctx->rspPubKeyLen = rsp.payloadSz; - ctx->flags.hasRspPubKey = 1; - return WOLFSPDM_SUCCESS; } @@ -603,6 +628,13 @@ int wolfSPDM_ConnectTCG(WOLFSPDM_CTX* ctx) return WOLFSPDM_E_BAD_STATE; } + if (!ctx->flags.hasRspPubKey || + ctx->rspPubKeyLen != WOLFSPDM_ECC_POINT_SIZE) { + wolfSPDM_DebugPrint(ctx, + "TCG: Trusted responder public key is not configured\n"); + return WOLFSPDM_E_BAD_STATE; + } + if (ctx->ioCb == NULL) { return WOLFSPDM_E_IO_FAIL; } @@ -637,25 +669,21 @@ int wolfSPDM_ConnectTCG(WOLFSPDM_CTX* ctx) } ctx->state = WOLFSPDM_STATE_CERT; - /* Compute Ct = SHA-384(TPMT_PUBLIC) and add to transcript */ - if (ctx->flags.hasRspPubKey && ctx->rspPubKeyLen > 0) { - wolfSPDM_DebugPrint(ctx, "TCG: Computing Ct = SHA-384(TPMT_PUBLIC[%u])\n", - ctx->rspPubKeyLen); - rc = wolfSPDM_Sha384Hash(ctx->certChainHash, - ctx->rspPubKey, ctx->rspPubKeyLen, NULL, 0, NULL, 0); - if (rc != WOLFSPDM_SUCCESS) { - ctx->state = WOLFSPDM_STATE_ERROR; - return rc; - } - rc = wolfSPDM_TranscriptAdd(ctx, ctx->certChainHash, - WOLFSPDM_HASH_SIZE); - if (rc != WOLFSPDM_SUCCESS) { - ctx->state = WOLFSPDM_STATE_ERROR; - return rc; - } - } else { - wolfSPDM_DebugPrint(ctx, - "TCG: Warning - no responder public key for Ct\n"); + /* Compute Ct from the fetched wire object after its public point has + * matched the separately configured responder key. */ + wolfSPDM_DebugPrint(ctx, "TCG: Computing Ct = SHA-384(GET_PUBK[%u])\n", + pubKeySz); + rc = wolfSPDM_Sha384Hash(ctx->certChainHash, + pubKey, pubKeySz, NULL, 0, NULL, 0); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + rc = wolfSPDM_TranscriptAdd(ctx, ctx->certChainHash, + WOLFSPDM_HASH_SIZE); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; } /* Step 5: KEY_EXCHANGE */ diff --git a/src/spdm/unit_test.c b/src/spdm/unit_test.c index 259452b6..6c4a1109 100644 --- a/src/spdm/unit_test.c +++ b/src/spdm/unit_test.c @@ -747,7 +747,199 @@ static int test_invalid_curve_point(void) TEST_PASS(); } +static int test_extract_ecc_point(void) +{ + byte point[WOLFSPDM_ECC_POINT_SIZE]; + const byte* pointX = NULL; + const byte* pointY = NULL; + + printf("test_extract_ecc_point...\n"); + + XMEMSET(point, 0xA5, sizeof(point)); + ASSERT_EQ(wolfSPDM_ExtractEccPoint(NULL, sizeof(point), &pointX, + &pointY), WOLFSPDM_E_INVALID_ARG, "NULL public key must fail"); + ASSERT_EQ(wolfSPDM_ExtractEccPoint(point, sizeof(point), NULL, + &pointY), WOLFSPDM_E_INVALID_ARG, "NULL X output must fail"); + ASSERT_EQ(wolfSPDM_ExtractEccPoint(point, sizeof(point), &pointX, + NULL), WOLFSPDM_E_INVALID_ARG, "NULL Y output must fail"); + ASSERT_EQ(wolfSPDM_ExtractEccPoint(point, sizeof(point) - 1, &pointX, + &pointY), WOLFSPDM_E_INVALID_ARG, "short point must fail"); + ASSERT_SUCCESS(wolfSPDM_ExtractEccPoint(point, sizeof(point), &pointX, + &pointY)); + TEST_ASSERT(pointX == point, "raw point X offset mismatch"); + TEST_ASSERT(pointY == point + WOLFSPDM_ECC_KEY_SIZE, + "raw point Y offset mismatch"); + + TEST_PASS(); +} + #ifdef WOLFTPM_SPDM_TCG +#define SPDM_TEST_VENDOR_DEFINED_RSP 0x7E + +typedef struct TCG_GET_PUB_KEY_IO_CTX { + const byte* pubKey; + word32 pubKeySz; +} TCG_GET_PUB_KEY_IO_CTX; + +static int tcg_get_pub_key_io_cb(WOLFSPDM_CTX* ctx, const byte* txBuf, + word32 txSz, byte* rxBuf, word32* rxSz, void* userCtx) +{ + TCG_GET_PUB_KEY_IO_CTX* ioCtx = (TCG_GET_PUB_KEY_IO_CTX*)userCtx; + byte spdmRsp[WOLFSPDM_VENDOR_BUF_SZ]; + word32 totalSz; + int spdmRspSz; + + (void)txBuf; + (void)txSz; + + if (ctx == NULL || rxBuf == NULL || rxSz == NULL || ioCtx == NULL) { + return -1; + } + + spdmRspSz = wolfSPDM_BuildVendorDefined(SPDM_VERSION_13, + WOLFSPDM_VDCODE_GET_PUBK, ioCtx->pubKey, ioCtx->pubKeySz, + spdmRsp, sizeof(spdmRsp)); + if (spdmRspSz < 0) { + return -1; + } + spdmRsp[1] = SPDM_TEST_VENDOR_DEFINED_RSP; + + totalSz = WOLFSPDM_TCG_HEADER_SIZE + (word32)spdmRspSz; + if (*rxSz < totalSz) { + return -1; + } + wolfSPDM_WriteTcgHeader(rxBuf, WOLFSPDM_TCG_TAG_CLEAR, totalSz, + ctx->connectionHandle, ctx->fipsIndicator); + XMEMCPY(rxBuf + WOLFSPDM_TCG_HEADER_SIZE, spdmRsp, + (word32)spdmRspSz); + *rxSz = totalSz; + + return 0; +} + +static int test_tcg_get_pub_key_preserves_pin(void) +{ + byte trustedKey[WOLFSPDM_ECC_POINT_SIZE]; + byte differentKey[WOLFSPDM_ECC_POINT_SIZE]; + byte tpmtPublic[120]; + byte pubKey[sizeof(tpmtPublic)]; + word32 pubKeySz; + int rc; + TCG_GET_PUB_KEY_IO_CTX ioCtx; + TEST_CTX_SETUP(); + + printf("test_tcg_get_pub_key_preserves_pin...\n"); + + XMEMSET(trustedKey, 0xA5, sizeof(trustedKey)); + XMEMSET(differentKey, 0x5A, sizeof(differentKey)); + ctx->mode = WOLFSPDM_MODE_NUVOTON; + ASSERT_SUCCESS(wolfSPDM_SetIO(ctx, tcg_get_pub_key_io_cb, &ioCtx)); + ASSERT_SUCCESS(wolfSPDM_SetResponderPubKey(ctx, trustedKey, + sizeof(trustedKey))); + + ioCtx.pubKey = differentKey; + ioCtx.pubKeySz = sizeof(differentKey); + XMEMSET(pubKey, 0xCC, sizeof(pubKey)); + pubKeySz = sizeof(pubKey); + rc = wolfSPDM_TCG_GetPubKey(ctx, pubKey, &pubKeySz); + ASSERT_EQ(rc, WOLFSPDM_E_PEER_ERROR, + "mismatched responder key must be rejected"); + ASSERT_EQ(ctx->rspPubKeyLen, sizeof(trustedKey), + "configured key length changed"); + TEST_ASSERT(XMEMCMP(ctx->rspPubKey, trustedKey, sizeof(trustedKey)) == 0, + "configured responder key changed"); + + ioCtx.pubKey = trustedKey; + ioCtx.pubKeySz = sizeof(trustedKey); + pubKeySz = sizeof(pubKey); + ASSERT_SUCCESS(wolfSPDM_TCG_GetPubKey(ctx, pubKey, &pubKeySz)); + ASSERT_EQ(pubKeySz, sizeof(trustedKey), "raw key size mismatch"); + TEST_ASSERT(XMEMCMP(pubKey, trustedKey, sizeof(trustedKey)) == 0, + "raw key output mismatch"); + + XMEMSET(tpmtPublic, 0, sizeof(tpmtPublic)); + SPDM_Set16BE(tpmtPublic + 20, WOLFSPDM_ECC_KEY_SIZE); + XMEMCPY(tpmtPublic + 22, trustedKey, WOLFSPDM_ECC_KEY_SIZE); + SPDM_Set16BE(tpmtPublic + 70, WOLFSPDM_ECC_KEY_SIZE); + XMEMCPY(tpmtPublic + 72, trustedKey + WOLFSPDM_ECC_KEY_SIZE, + WOLFSPDM_ECC_KEY_SIZE); + ioCtx.pubKey = tpmtPublic; + ioCtx.pubKeySz = sizeof(tpmtPublic); + pubKeySz = sizeof(pubKey); + ASSERT_SUCCESS(wolfSPDM_TCG_GetPubKey(ctx, pubKey, &pubKeySz)); + ASSERT_EQ(pubKeySz, sizeof(tpmtPublic), "TPMT_PUBLIC size mismatch"); + TEST_ASSERT(XMEMCMP(pubKey, tpmtPublic, sizeof(tpmtPublic)) == 0, + "TPMT_PUBLIC output mismatch"); + TEST_ASSERT(XMEMCMP(ctx->rspPubKey, trustedKey, sizeof(trustedKey)) == 0, + "TPMT_PUBLIC response replaced configured key"); + + SPDM_Set16BE(tpmtPublic + 20, WOLFSPDM_ECC_KEY_SIZE - 1); + pubKeySz = sizeof(pubKey); + rc = wolfSPDM_TCG_GetPubKey(ctx, pubKey, &pubKeySz); + ASSERT_EQ(rc, WOLFSPDM_E_PEER_ERROR, + "malformed TPMT_PUBLIC X size must be rejected"); + + SPDM_Set16BE(tpmtPublic + 20, WOLFSPDM_ECC_KEY_SIZE); + SPDM_Set16BE(tpmtPublic + 70, WOLFSPDM_ECC_KEY_SIZE - 1); + pubKeySz = sizeof(pubKey); + rc = wolfSPDM_TCG_GetPubKey(ctx, pubKey, &pubKeySz); + ASSERT_EQ(rc, WOLFSPDM_E_PEER_ERROR, + "malformed TPMT_PUBLIC Y size must be rejected"); + + ioCtx.pubKeySz = WOLFSPDM_ECC_POINT_SIZE + 1; + pubKeySz = sizeof(pubKey); + rc = wolfSPDM_TCG_GetPubKey(ctx, pubKey, &pubKeySz); + ASSERT_EQ(rc, WOLFSPDM_E_PEER_ERROR, + "truncated TPMT_PUBLIC point must be rejected"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_tcg_get_pub_key_discovery_is_not_trusted(void) +{ + byte discoveredKey[WOLFSPDM_ECC_POINT_SIZE]; + byte pubKey[WOLFSPDM_ECC_POINT_SIZE]; + word32 pubKeySz = sizeof(pubKey); + TCG_GET_PUB_KEY_IO_CTX ioCtx; + TEST_CTX_SETUP(); + + printf("test_tcg_get_pub_key_discovery_is_not_trusted...\n"); + + XMEMSET(discoveredKey, 0x3C, sizeof(discoveredKey)); + ioCtx.pubKey = discoveredKey; + ioCtx.pubKeySz = sizeof(discoveredKey); + ctx->mode = WOLFSPDM_MODE_NUVOTON; + ASSERT_SUCCESS(wolfSPDM_SetIO(ctx, tcg_get_pub_key_io_cb, &ioCtx)); + ASSERT_SUCCESS(wolfSPDM_TCG_GetPubKey(ctx, pubKey, &pubKeySz)); + TEST_ASSERT(XMEMCMP(pubKey, discoveredKey, sizeof(discoveredKey)) == 0, + "discovered key output mismatch"); + ASSERT_EQ(ctx->flags.hasRspPubKey, 0, + "cleartext discovery must not configure trust"); + ASSERT_EQ(ctx->rspPubKeyLen, 0, + "cleartext discovery must not store responder key"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_tcg_connect_requires_responder_key(void) +{ + int rc; + TEST_CTX_SETUP(); + + printf("test_tcg_connect_requires_responder_key...\n"); + + ctx->mode = WOLFSPDM_MODE_NUVOTON; + ASSERT_SUCCESS(wolfSPDM_SetIO(ctx, dummy_io_cb, NULL)); + rc = wolfSPDM_ConnectTCG(ctx); + ASSERT_EQ(rc, WOLFSPDM_E_BAD_STATE, + "identity connection must require a responder key"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + /* I/O callback that returns a TCG response with msgSize < TCG_HEADER_SIZE */ static int tcg_underflow_io_cb(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, byte* rxBuf, word32* rxSz, void* userCtx) @@ -2422,7 +2614,7 @@ static int test_responder_psk_replace_guard(void) #endif /* WOLFSPDM_NATIONS */ #endif /* WOLFTPM_SPDM_TCG */ -#if defined(WOLFTPM_SPDM_PSK) && defined(WOLFTPM_SPDM_TCG) +#ifdef WOLFTPM_SPDM_TCG /* In-process I/O glue: route the requester's outbound TCG frame to the * responder's HandleMessage and copy the response back. */ @@ -2436,6 +2628,78 @@ static int requester_to_responder_iocb(WOLFSPDM_CTX* spdmCtx, return wolfSPDM_RespHandleMessage(rctx, txBuf, txSz, rxBuf, rxSz); } +/* Pinned identity handshake + tunneled TPM2_CMD + END_SESSION, end-to-end. */ +static int test_responder_identity_roundtrip(void) +{ + WOLFSPDM_CTX req; + byte rctxBuf[WOLFSPDM_RESP_CTX_STATIC_SIZE]; + WOLFSPDM_RESP_CTX* rctx = (WOLFSPDM_RESP_CTX*)rctxBuf; + ecc_key idKey; + byte idPriv[WOLFSPDM_ECC_KEY_SIZE]; + byte idPubX[WOLFSPDM_ECC_KEY_SIZE]; + byte idPubY[WOLFSPDM_ECC_KEY_SIZE]; + byte idPub[WOLFSPDM_ECC_POINT_SIZE]; + byte cmd[10]; + word32 idPrivSz = sizeof(idPriv); + word32 idPubXSz = sizeof(idPubX); + word32 idPubYSz = sizeof(idPubY); + int rc; + + printf("test_responder_identity_roundtrip...\n"); + + ASSERT_SUCCESS(wolfSPDM_Init(&req)); + ASSERT_SUCCESS(wolfSPDM_RespInit(rctx)); + ASSERT_SUCCESS(wc_ecc_init(&idKey)); + ASSERT_SUCCESS(wc_ecc_make_key(&req.rng, WOLFSPDM_ECC_KEY_SIZE, + &idKey)); + ASSERT_SUCCESS(wc_ecc_export_private_only(&idKey, idPriv, &idPrivSz)); + ASSERT_SUCCESS(wc_ecc_export_public_raw(&idKey, idPubX, &idPubXSz, + idPubY, &idPubYSz)); + wc_ecc_free(&idKey); + ASSERT_EQ(idPrivSz, WOLFSPDM_ECC_KEY_SIZE, + "identity private-key size mismatch"); + ASSERT_EQ(idPubXSz, WOLFSPDM_ECC_KEY_SIZE, + "identity public X size mismatch"); + ASSERT_EQ(idPubYSz, WOLFSPDM_ECC_KEY_SIZE, + "identity public Y size mismatch"); + XMEMCPY(idPub, idPubX, sizeof(idPubX)); + XMEMCPY(idPub + sizeof(idPubX), idPubY, sizeof(idPubY)); + + /* Use the simplified Nuvoton TCG flow without requiring a vendor adapter + * in this loopback-only unit build. */ + req.mode = WOLFSPDM_MODE_NUVOTON; + ASSERT_SUCCESS(wolfSPDM_SetResponderPubKey(&req, idPub, + sizeof(idPub))); + ASSERT_SUCCESS(wolfSPDM_RespSetMode(rctx, 1, 0)); + ASSERT_SUCCESS(wolfSPDM_RespSetIdentityKey(rctx, idPriv, + sizeof(idPriv), idPub, sizeof(idPub))); + g_tpmCbInvocations = 0; + ASSERT_SUCCESS(wolfSPDM_RespSetTpmCallback(rctx, responder_tpm_stub, + NULL)); + ASSERT_SUCCESS(wolfSPDM_SetIO(&req, requester_to_responder_iocb, rctx)); + + rc = wolfSPDM_ConnectTCG(&req); + TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "identity ConnectTCG failed"); + ASSERT_EQ(wolfSPDM_IsConnected(&req), 1, "Requester not connected"); + + XMEMSET(cmd, 0, sizeof(cmd)); + cmd[0] = 0x80; cmd[1] = 0x01; + cmd[5] = 0x0A; + cmd[8] = 0x01; cmd[9] = 0x44; + rc = wolfSPDM_TCG_VendorCmdSecured(&req, WOLFSPDM_VDCODE_TPM2_CMD, + cmd, sizeof(cmd)); + TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "TPM2_CMD passthrough failed"); + ASSERT_EQ(g_tpmCbInvocations, 1, "TPM stub must have run once"); + + ASSERT_SUCCESS(wolfSPDM_Disconnect(&req)); + + wc_ForceZero(idPriv, sizeof(idPriv)); + wolfSPDM_RespFree(rctx); + wolfSPDM_Free(&req); + TEST_PASS(); +} + +#ifdef WOLFTPM_SPDM_PSK /* PSK handshake + tunneled TPM2_CMD round-trip + END_SESSION, end-to-end. */ static int test_responder_psk_roundtrip(void) { @@ -2510,8 +2774,9 @@ static int test_responder_psk_roundtrip(void) wolfSPDM_Free(&req); TEST_PASS(); } +#endif /* WOLFTPM_SPDM_PSK */ -#endif /* WOLFTPM_SPDM_PSK && WOLFTPM_SPDM_TCG */ +#endif /* WOLFTPM_SPDM_TCG */ #endif /* WOLFTPM_SPDM_RESPONDER */ @@ -2564,7 +2829,11 @@ int main(void) test_mitm_signature_rejected(); test_key_exchange_rsp_hmac_check(); test_invalid_curve_point(); + test_extract_ecc_point(); #ifdef WOLFTPM_SPDM_TCG + test_tcg_get_pub_key_preserves_pin(); + test_tcg_get_pub_key_discovery_is_not_trusted(); + test_tcg_connect_requires_responder_key(); test_tcg_underflow(); #endif #ifdef WOLFSPDM_NATIONS @@ -2657,6 +2926,7 @@ int main(void) #ifdef WOLFSPDM_NATIONS test_responder_psk_replace_guard(); #endif + test_responder_identity_roundtrip(); #endif #if defined(WOLFTPM_SPDM_PSK) && defined(WOLFTPM_SPDM_TCG) test_responder_psk_roundtrip(); diff --git a/src/tpm2.c b/src/tpm2.c index a7550f2c..21e518b1 100644 --- a/src/tpm2.c +++ b/src/tpm2.c @@ -918,6 +918,10 @@ TPM_RC TPM2_Init_ex(TPM2_CTX* ctx, TPM2HalIoCb ioCb, void* userCtx, /* set before any early return so cleanup cannot act on fd 0 */ ctx->tcpCtx.fd = -1; #endif +#if defined(WOLFTPM_LINUX_DEV) || defined(WOLFTPM_LINUX_DEV_AUTODETECT) + /* set before any early return so cleanup cannot act on fd 0 */ + ctx->fd = -1; +#endif #ifndef WOLFTPM_NO_RETRY ctx->retries = WOLFTPM_MAX_RETRIES; @@ -953,10 +957,6 @@ TPM_RC TPM2_Init_ex(TPM2_CTX* ctx, TPM2HalIoCb ioCb, void* userCtx, goto exit; #endif -#if defined(WOLFTPM_LINUX_DEV) || defined(WOLFTPM_LINUX_DEV_AUTODETECT) - ctx->fd = -1; -#endif - #ifdef WOLFTPM_LINUX_DEV_AUTODETECT /* Probe here, not only in wolfTPM2_Init_ex, so the native API autodetects * /dev/tpmX too. TryOpen leaves fd < 0 whether the node was absent or @@ -6708,6 +6708,10 @@ int TPM2_ST33_FieldUpgradeCommand(TPM_CC cc, uint8_t* data, uint32_t size) int rc; TPM2_CTX* ctx = TPM2_GetActiveCtx(); + if (ctx == NULL) { + return BAD_FUNC_ARG; + } + rc = TPM2_AcquireLock(ctx); if (rc == TPM_RC_SUCCESS) { TPM2_Packet packet; diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index de938bf3..2fe73d79 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -273,7 +273,30 @@ int wolfTPM2_Test(TPM2HalIoCb ioCb, void* userCtx, WOLFTPM2_CAPS* caps) return rc; } -int wolfTPM2_Init(WOLFTPM2_DEV* dev, TPM2HalIoCb ioCb, void* userCtx) +/* Initialization has not exposed a device to the caller yet, so do not use + * wolfTPM2_Cleanup_ex() here: it unregisters the did_vid crypto callback even + * though this initialization path never registered one. */ +static void wolfTPM2_InitFailureCleanup(WOLFTPM2_DEV* dev) +{ + if (dev == NULL) { + return; + } + +#ifdef WOLFTPM_SPDM + if (dev->spdmCtx != NULL && dev->spdmCtx->spdmCtx != NULL && + wolfSPDM_IsConnected(dev->spdmCtx->spdmCtx)) { + (void)wolfTPM2_SpdmDisconnect(dev); + } + (void)wolfTPM2_SpdmCleanup(dev); +#endif + + (void)TPM2_Cleanup(&dev->ctx); + TPM2_ForceZero(dev->session, sizeof(dev->session)); + dev->ctx.session = NULL; +} + +static int wolfTPM2_InitDevice(WOLFTPM2_DEV* dev, TPM2HalIoCb ioCb, + void* userCtx) { int rc; @@ -284,6 +307,11 @@ int wolfTPM2_Init(WOLFTPM2_DEV* dev, TPM2HalIoCb ioCb, void* userCtx) rc = wolfTPM2_Init_ex(&dev->ctx, ioCb, userCtx, TPM_TIMEOUT_TRIES); if (rc != TPM_RC_SUCCESS) { + /* TPM_RC_UPGRADE leaves a usable active context so callers can + * continue the vendor firmware-recovery flow. */ + if (rc != TPM_RC_UPGRADE) { + wolfTPM2_InitFailureCleanup(dev); + } return rc; } @@ -291,60 +319,228 @@ int wolfTPM2_Init(WOLFTPM2_DEV* dev, TPM2HalIoCb ioCb, void* userCtx) XMEMSET(dev->session, 0, sizeof(dev->session)); wolfTPM2_SetAuthPassword(dev, 0, NULL); + return TPM_RC_SUCCESS; +} + +#if defined(WOLFTPM_SPDM) && (defined(WOLFTPM_SPDM_PSK) || \ + (defined(WOLFTPM_SPDM_TCG) && (defined(WOLFSPDM_NUVOTON) || \ + defined(WOLFSPDM_NATIONS)))) +static int wolfTPM2_RetryStartupOverSpdm(WOLFTPM2_DEV* dev) +{ + int rc; + Startup_In startupIn; + + if (!dev->ctx.spdmOnlyDetected) { + return TPM_RC_SUCCESS; + } + + /* Retry TPM2_Startup over the SPDM encrypted channel. */ + XMEMSET(&startupIn, 0, sizeof(startupIn)); + startupIn.startupType = TPM_SU_CLEAR; + rc = TPM2_Startup(&startupIn); + if (rc == TPM_RC_INITIALIZE) { + rc = TPM_RC_SUCCESS; + } +#ifdef DEBUG_WOLFTPM + if (rc != TPM_RC_SUCCESS) { + printf("TPM2_Startup over SPDM failed: 0x%x\n", rc); + } +#endif + return rc; +} +#endif /* WOLFTPM_SPDM and a credentialed transport */ + #if defined(WOLFTPM_SPDM) && defined(WOLFTPM_SPDM_TCG) - /* If TPM is in SPDM-only mode, transparently establish an SPDM session - * so all subsequent TPM commands are encrypted over the bus. - * This allows existing binaries (caps, wrap_test, unit.test) to work - * without any SPDM-specific code. */ - if (dev->ctx.spdmOnlyDetected) { - Startup_In startupIn; - - rc = wolfTPM2_SpdmInit(dev); - if (rc != 0) { - #ifdef DEBUG_WOLFTPM - printf("SPDM auto-init failed: %d\n", rc); - #endif - return rc; - } +static int wolfTPM2_IdentityModeSupported(WOLFSPDM_MODE mode) +{ + if (mode == WOLFSPDM_MODE_AUTO) { + return 1; + } +#ifdef WOLFSPDM_NUVOTON + if (mode == WOLFSPDM_MODE_NUVOTON) { + return 1; + } +#endif +#ifdef WOLFSPDM_NATIONS + if (mode == WOLFSPDM_MODE_NATIONS) { + return 1; + } +#endif + return 0; +} - /* Vendor-specific connect handles SetTisIO, SetMode, and auto-generates - * a host ephemeral key pair for mutual authentication (MutAuth=1). - * Plain wolfTPM2_SpdmConnect() skips that setup and FINISH fails. */ - #if defined(WOLFSPDM_NUVOTON) - rc = wolfTPM2_SpdmConnectNuvoton(dev, NULL, 0, NULL, 0); - #elif defined(WOLFSPDM_NATIONS) - rc = wolfTPM2_SpdmConnectNations(dev, NULL, 0, NULL, 0); - #else - rc = wolfTPM2_SpdmConnect(dev); - #endif - if (rc != 0) { - #ifdef DEBUG_WOLFTPM - printf("SPDM auto-connect failed: %d\n", rc); - #endif +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +#if defined(WOLFSPDM_NUVOTON) && defined(WOLFSPDM_NATIONS) +WOLFTPM_TEST_API int wolfTPM2_SpdmModeFromDidVid(UINT32 didVid, + WOLFSPDM_MODE* mode) +{ + UINT16 vendorId; + + if (mode == NULL) { + return BAD_FUNC_ARG; + } + *mode = WOLFSPDM_MODE_AUTO; + vendorId = (UINT16)(didVid & 0xFFFFU); + if (vendorId == TPM_VENDOR_NATIONTECH) { + *mode = WOLFSPDM_MODE_NATIONS; + } + else if (vendorId == TPM_VENDOR_NUVOTON) { + *mode = WOLFSPDM_MODE_NUVOTON; + } + else { + return WOLFSPDM_E_BAD_STATE; + } + return TPM_RC_SUCCESS; +} +#endif + +static int wolfTPM2_SpdmConnectIdentity(WOLFTPM2_DEV* dev, + WOLFSPDM_MODE mode) +{ + /* Vendor-specific connect handles SetTisIO, SetMode, and auto-generates + * a host ephemeral key pair for mutual authentication (MutAuth=1). + * Plain wolfTPM2_SpdmConnect() skips that setup and FINISH fails. */ +#if defined(WOLFSPDM_NUVOTON) && defined(WOLFSPDM_NATIONS) + if (mode == WOLFSPDM_MODE_AUTO) { + int rc; + + /* did_vid is populated by direct TIS transports only. Refuse to guess + * on kernel/socket transports; callers can select with the _ex API. */ + rc = wolfTPM2_SpdmModeFromDidVid(dev->ctx.did_vid, &mode); + if (rc != TPM_RC_SUCCESS) { return rc; } + } + if (mode == WOLFSPDM_MODE_NATIONS) { + return wolfTPM2_SpdmConnectNations(dev, NULL, 0, NULL, 0); + } + return wolfTPM2_SpdmConnectNuvoton(dev, NULL, 0, NULL, 0); +#elif defined(WOLFSPDM_NUVOTON) + (void)mode; + return wolfTPM2_SpdmConnectNuvoton(dev, NULL, 0, NULL, 0); +#elif defined(WOLFSPDM_NATIONS) + (void)mode; + return wolfTPM2_SpdmConnectNations(dev, NULL, 0, NULL, 0); +#endif +} +#endif /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ + +int wolfTPM2_InitWithSpdmKey_ex(WOLFTPM2_DEV* dev, TPM2HalIoCb ioCb, + void* userCtx, const byte* rspPubKey, word32 rspPubKeySz, + WOLFSPDM_MODE mode) +{ +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + int rc; +#endif + if (dev == NULL || rspPubKey == NULL || + rspPubKeySz != WOLFSPDM_ECC_POINT_SIZE || + !wolfTPM2_IdentityModeSupported(mode)) { + return BAD_FUNC_ARG; + } + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + rc = wolfTPM2_InitDevice(dev, ioCb, userCtx); + if (rc != TPM_RC_SUCCESS) { + return rc; + } + + /* A supplied key opts into authenticated transport regardless of the + * cleartext startup result. */ + rc = wolfTPM2_SpdmInit(dev); + if (rc == 0) { + rc = wolfTPM2_SpdmSetResponderPubKey(dev, rspPubKey, rspPubKeySz); + } + if (rc == 0) { + rc = wolfTPM2_SpdmConnectIdentity(dev, mode); + } + if (rc != 0) { #ifdef DEBUG_WOLFTPM - printf("SPDM session established (auto), SessionID=0x%08x\n", - wolfTPM2_SpdmGetSessionId(dev)); + printf("SPDM auto-connect failed: %d\n", rc); #endif + wolfTPM2_InitFailureCleanup(dev); + return rc; + } - /* Retry TPM2_Startup over the SPDM encrypted channel */ - XMEMSET(&startupIn, 0, sizeof(startupIn)); - startupIn.startupType = TPM_SU_CLEAR; - rc = TPM2_Startup(&startupIn); - if (rc != TPM_RC_SUCCESS && rc != TPM_RC_INITIALIZE) { - #ifdef DEBUG_WOLFTPM - printf("TPM2_Startup over SPDM failed: 0x%x\n", rc); - #endif - return rc; - } - rc = TPM_RC_SUCCESS; +#ifdef DEBUG_WOLFTPM + printf("SPDM session established (auto), SessionID=0x%08x\n", + wolfTPM2_SpdmGetSessionId(dev)); +#endif + + rc = wolfTPM2_RetryStartupOverSpdm(dev); + if (rc != TPM_RC_SUCCESS) { + wolfTPM2_InitFailureCleanup(dev); } + return rc; +#else + /* TCG framing alone does not select an identity-mode wire adapter. Fail + * before probing the TPM so unsupported builds have no side effects. */ + (void)ioCb; + (void)userCtx; + return WOLFSPDM_E_NOT_AVAILABLE; +#endif +} #endif /* WOLFTPM_SPDM && WOLFTPM_SPDM_TCG */ +int wolfTPM2_Init(WOLFTPM2_DEV* dev, TPM2HalIoCb ioCb, void* userCtx) +{ + int rc; + + rc = wolfTPM2_InitDevice(dev, ioCb, userCtx); +#ifdef WOLFTPM_SPDM + if (rc == TPM_RC_SUCCESS && dev->ctx.spdmOnlyDetected) { + #ifdef DEBUG_WOLFTPM + printf("SPDM-only mode requires a trusted responder key or PSK\n"); + #endif + wolfTPM2_InitFailureCleanup(dev); + rc = WOLFSPDM_E_BAD_STATE; + } +#endif + return rc; +} + +#if defined(WOLFTPM_SPDM) && defined(WOLFTPM_SPDM_TCG) +int wolfTPM2_InitWithSpdmKey(WOLFTPM2_DEV* dev, TPM2HalIoCb ioCb, + void* userCtx, const byte* rspPubKey, word32 rspPubKeySz) +{ + if (rspPubKey == NULL || rspPubKeySz != WOLFSPDM_ECC_POINT_SIZE) { + return BAD_FUNC_ARG; + } + + return wolfTPM2_InitWithSpdmKey_ex(dev, ioCb, userCtx, rspPubKey, + rspPubKeySz, WOLFSPDM_MODE_AUTO); +} +#endif + +#if defined(WOLFTPM_SPDM) && defined(WOLFTPM_SPDM_PSK) +int wolfTPM2_InitWithSpdmPsk(WOLFTPM2_DEV* dev, TPM2HalIoCb ioCb, + void* userCtx, const byte* psk, word32 pskSz, const byte* hint, + word32 hintSz) +{ + int rc; + + if (dev == NULL || psk == NULL || pskSz == 0U || + (hint == NULL && hintSz != 0U)) { + return BAD_FUNC_ARG; + } + + rc = wolfTPM2_InitDevice(dev, ioCb, userCtx); + if (rc != TPM_RC_SUCCESS) { + return rc; + } + rc = wolfTPM2_SpdmInit(dev); + if (rc == TPM_RC_SUCCESS) { + rc = wolfTPM2_SpdmConnectPsk(dev, psk, pskSz, hint, hintSz); + } + if (rc == TPM_RC_SUCCESS) { + rc = wolfTPM2_RetryStartupOverSpdm(dev); + } + if (rc != TPM_RC_SUCCESS) { + wolfTPM2_InitFailureCleanup(dev); + } return rc; } +#endif /* WOLFTPM_SPDM && WOLFTPM_SPDM_PSK */ #ifndef WOLFTPM2_NO_HEAP WOLFTPM2_DEV* wolfTPM2_New(void) @@ -1218,6 +1414,14 @@ int wolfTPM2_SpdmCleanup(WOLFTPM2_DEV* dev) #ifdef WOLFTPM_SPDM_TCG /* Shared TCG SPDM functions */ +int wolfTPM2_SpdmSetResponderPubKey(WOLFTPM2_DEV* dev, + const byte* pubKey, word32 pubKeySz) +{ + WOLFTPM2_SPDM_CHECK_CTX(dev); + return wolfSPDM_SetResponderPubKey(dev->spdmCtx->spdmCtx, + pubKey, pubKeySz); +} + int wolfTPM2_SpdmGetPubKey(WOLFTPM2_DEV* dev, byte* pubKey, word32* pubKeySz) { WOLFTPM2_SPDM_CHECK_CTX(dev); diff --git a/tests/fwtpm_check.sh b/tests/fwtpm_check.sh index dd3f38ec..581f0f7d 100755 --- a/tests/fwtpm_check.sh +++ b/tests/fwtpm_check.sh @@ -87,6 +87,75 @@ pick_available_port() { return 1 } +# Return TPM_RC_UPGRADE from Startup using the simulator socket framing, then +# accept the cleanup Shutdown. This drives the real initialization path without +# putting a hardware TPM into firmware-recovery mode. +run_init_upgrade_test() { + local port server_pid rc + + command -v python3 >/dev/null 2>&1 || return 77 + port=$(pick_available_port) || return 1 + + python3 - "$port" <<'PY' & +import socket +import struct +import sys + +def recv_exact(conn, size): + data = b"" + while len(data) < size: + chunk = conn.recv(size - len(data)) + if not chunk: + raise RuntimeError("unexpected EOF") + data += chunk + return data + +def recv_command(conn): + command = struct.unpack(">I", recv_exact(conn, 4))[0] + if command != 8: # TPM_SEND_COMMAND + raise RuntimeError("unexpected simulator command") + recv_exact(conn, 1) # locality + command_size = struct.unpack(">I", recv_exact(conn, 4))[0] + recv_exact(conn, command_size) + +def send_response(conn, response_code): + response = struct.pack(">HII", 0x8001, 10, response_code) + conn.sendall(struct.pack(">I", len(response)) + response + b"\0" * 4) + +with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as listener: + listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + listener.bind(("127.0.0.1", int(sys.argv[1]))) + listener.listen(1) + conn, _ = listener.accept() + with conn: + recv_command(conn) + send_response(conn, 0x12D) # TPM_RC_UPGRADE from Startup + recv_command(conn) + send_response(conn, 0) # TPM_RC_SUCCESS from Shutdown + session_end = struct.unpack(">I", recv_exact(conn, 4))[0] + if session_end != 20: # TPM_SESSION_END + raise RuntimeError("unexpected simulator session command") +PY + server_pid=$! + + if ! wait_for_port "$port" 500; then + kill "$server_pid" 2>/dev/null + wait "$server_pid" 2>/dev/null + return 1 + fi + + TPM2_SWTPM_HOST=127.0.0.1 TPM2_SWTPM_PORT="$port" \ + "$UNIT_TEST" --init-upgrade + rc=$? + if [ $rc -ne 0 ]; then + kill "$server_pid" 2>/dev/null + fi + if ! wait "$server_pid"; then + rc=1 + fi + return $rc +} + # --- wolfSSL dependency resolution --- find_wolfssl_options() { @@ -366,6 +435,24 @@ fi # --- Run unit tests --- +if [ $IS_SWTPM_MODE -eq 1 ] && [ $HAS_GETENV -eq 1 ] && \ + [ -x "$UNIT_TEST" ]; then + echo "" + echo "=== Running init upgrade context regression ===" + run_init_upgrade_test + rc=$? + if [ $rc -eq 0 ]; then + PASS=$((PASS + 1)) + echo "PASS: init upgrade context" + elif [ $rc -eq 77 ]; then + SKIP=$((SKIP + 1)) + echo "SKIP: init upgrade context (python3 unavailable)" + else + FAIL=$((FAIL + 1)) + echo "FAIL: init upgrade context" + fi +fi + if [ -x "$UNIT_TEST" ]; then echo "" echo "=== Running unit.test ===" diff --git a/tests/unit_tests.c b/tests/unit_tests.c index a5f3f23e..b34d7cb9 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -46,6 +46,9 @@ #if defined(__linux__) || defined(__APPLE__) || defined(__unix__) #include #endif +#ifdef WOLFTPM_SPDM +#include +#endif /* Test Fail Helpers */ #ifndef NO_ABORT @@ -131,16 +134,203 @@ static int test_tpm_alg_supported(TPM_ALG_ID alg) } #endif /* !WOLFTPM2_NO_WOLFCRYPT && HAVE_ECC && !WOLFTPM2_NO_ASN */ +#if defined(WOLFTPM_SPDM) && defined(WOLFTPM_SPDM_TCG) && \ + !defined(NO_GETENV) +static int TestWolfTPM2_HasResponderPin(void) +{ + const char* keyHex = getenv("SPDM_RESPONDER_PUBKEY"); + + return keyHex != NULL && keyHex[0] != '\0'; +} + +static int TestWolfTPM2_InitConfigured(WOLFTPM2_DEV* dev, + TPM2HalIoCb ioCb, void* userCtx) +{ + const char* keyHex = getenv("SPDM_RESPONDER_PUBKEY"); + const char* vendor = getenv("SPDM_IDENTITY_VENDOR"); + byte key[WOLFSPDM_ECC_POINT_SIZE]; + WOLFSPDM_MODE mode = WOLFSPDM_MODE_AUTO; + int keySz; + + if (keyHex == NULL || keyHex[0] == '\0') { + return wolfTPM2_Init(dev, ioCb, userCtx); + } + if (XSTRLEN(keyHex) != sizeof(key) * 2U) { + return BAD_FUNC_ARG; + } + keySz = hexToByte(keyHex, key, (unsigned long)XSTRLEN(keyHex)); + if (keySz != (int)sizeof(key)) { + return BAD_FUNC_ARG; + } + if (vendor != NULL && XSTRCMP(vendor, "nuvoton") == 0) { + mode = WOLFSPDM_MODE_NUVOTON; + } + else if (vendor != NULL && XSTRCMP(vendor, "nations") == 0) { + mode = WOLFSPDM_MODE_NATIONS; + } + else if (vendor != NULL && vendor[0] != '\0') { + return BAD_FUNC_ARG; + } + return wolfTPM2_InitWithSpdmKey_ex(dev, ioCb, userCtx, key, + (word32)sizeof(key), mode); +} +#else +static int TestWolfTPM2_HasResponderPin(void) +{ + return 0; +} + +static int TestWolfTPM2_InitConfigured(WOLFTPM2_DEV* dev, + TPM2HalIoCb ioCb, void* userCtx) +{ + return wolfTPM2_Init(dev, ioCb, userCtx); +} +#endif + +#if defined(WOLFTPM_SPDM) && defined(WOLFTPM_SPDM_TCG) && \ + defined(WOLFSPDM_NUVOTON) && defined(WOLFSPDM_NATIONS) +static void test_wolfTPM2_SpdmModeFromDidVid(void) +{ + WOLFSPDM_MODE mode; + int rc; + + mode = WOLFSPDM_MODE_AUTO; + rc = wolfTPM2_SpdmModeFromDidVid( + 0x12340000U | TPM_VENDOR_NUVOTON, &mode); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(mode, WOLFSPDM_MODE_NUVOTON); + + mode = WOLFSPDM_MODE_AUTO; + rc = wolfTPM2_SpdmModeFromDidVid( + 0x56780000U | TPM_VENDOR_NATIONTECH, &mode); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(mode, WOLFSPDM_MODE_NATIONS); + + mode = WOLFSPDM_MODE_NUVOTON; + rc = wolfTPM2_SpdmModeFromDidVid(0x12345678U, &mode); + AssertIntEQ(rc, WOLFSPDM_E_BAD_STATE); + AssertIntEQ(mode, WOLFSPDM_MODE_AUTO); + AssertIntEQ(wolfTPM2_SpdmModeFromDidVid(0, NULL), BAD_FUNC_ARG); + + printf("Test TPM Wrapper: %-40s Passed\n", "SPDM DID/VID mode:"); +} +#endif + +#if defined(WOLFTPM_SPDM) && defined(WOLFTPM_SPDM_PSK) && \ + !defined(NO_GETENV) +static void test_wolfTPM2_InitWithSpdmPsk_success(void) +{ + const char* pskHex = getenv("WOLFTPM_TEST_SPDM_PSK"); + byte psk[128]; + size_t hexSz; + int pskSz; + int rc; + WOLFTPM2_DEV dev; + + if (pskHex == NULL || pskHex[0] == '\0') { + return; + } + + hexSz = XSTRLEN(pskHex); + AssertTrue((hexSz & 1U) == 0U); + AssertTrue(hexSz <= sizeof(psk) * 2U); + if ((hexSz & 1U) != 0U || hexSz > sizeof(psk) * 2U) { + return; + } + pskSz = hexToByte(pskHex, psk, (unsigned long)hexSz); + AssertIntGT(pskSz, 0); + if (pskSz <= 0) { + wc_ForceZero(psk, sizeof(psk)); + return; + } + + rc = wolfTPM2_InitWithSpdmPsk(&dev, TPM2_IoCb, NULL, psk, + (word32)pskSz, NULL, 0); + wc_ForceZero(psk, sizeof(psk)); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(wolfTPM2_SpdmIsConnected(&dev), 1); + AssertIntNE(wolfTPM2_SpdmGetSessionId(&dev), 0); + AssertIntEQ(wolfTPM2_Cleanup(&dev), TPM_RC_SUCCESS); + AssertNull(dev.spdmCtx); +} +#endif + static void test_wolfTPM2_Init(void) { int rc; WOLFTPM2_DEV dev; +#ifdef WOLFTPM_LINUX_DEV + TPM2_CTX initCtx; +#endif +#if defined(WOLFTPM_SPDM) && defined(WOLFTPM_SPDM_TCG) + byte rspPubKey[WOLFSPDM_ECC_POINT_SIZE]; + #if !defined(WOLFSPDM_NUVOTON) && !defined(WOLFSPDM_NATIONS) + WOLFTPM2_DEV untouchedDev; + #endif +#endif +#if defined(WOLFTPM_SPDM) && defined(WOLFTPM_SPDM_PSK) + byte psk[32]; +#endif + +#ifdef WOLFTPM_LINUX_DEV + rc = TPM2_Init_ex(&initCtx, NULL, &initCtx, 0); + AssertIntEQ(rc, BAD_FUNC_ARG); + AssertIntEQ(initCtx.fd, -1); + AssertIntEQ(TPM2_Cleanup(&initCtx), TPM_RC_SUCCESS); +#endif +#if defined(WOLFTPM_SPDM) && defined(WOLFTPM_SPDM_TCG) + XMEMSET(rspPubKey, 0xA5, sizeof(rspPubKey)); + rc = wolfTPM2_InitWithSpdmKey(NULL, TPM2_IoCb, NULL, + rspPubKey, sizeof(rspPubKey)); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_InitWithSpdmKey(&dev, TPM2_IoCb, NULL, NULL, 0); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_InitWithSpdmKey(&dev, TPM2_IoCb, NULL, + rspPubKey, sizeof(rspPubKey) - 1); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_InitWithSpdmKey_ex(&dev, TPM2_IoCb, NULL, + rspPubKey, sizeof(rspPubKey), (WOLFSPDM_MODE)99); + AssertIntEQ(rc, BAD_FUNC_ARG); + #if !defined(WOLFSPDM_NUVOTON) && !defined(WOLFSPDM_NATIONS) + XMEMSET(&dev, 0xA5, sizeof(dev)); + XMEMCPY(&untouchedDev, &dev, sizeof(untouchedDev)); + rc = wolfTPM2_InitWithSpdmKey(&dev, TPM2_IoCb, NULL, + rspPubKey, sizeof(rspPubKey)); + AssertIntEQ(rc, WOLFSPDM_E_NOT_AVAILABLE); + AssertIntEQ(XMEMCMP(&dev, &untouchedDev, sizeof(dev)), 0); + #endif +#endif +#if defined(WOLFTPM_SPDM) && defined(WOLFTPM_SPDM_PSK) + XMEMSET(psk, 0x5A, sizeof(psk)); + rc = wolfTPM2_InitWithSpdmPsk(NULL, TPM2_IoCb, NULL, psk, + sizeof(psk), NULL, 0); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_InitWithSpdmPsk(&dev, TPM2_IoCb, NULL, NULL, 0, + NULL, 0); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_InitWithSpdmPsk(&dev, TPM2_IoCb, NULL, psk, + sizeof(psk), NULL, 1); + AssertIntEQ(rc, BAD_FUNC_ARG); + + #ifndef NO_GETENV + test_wolfTPM2_InitWithSpdmPsk_success(); + #endif +#endif /* Test first argument, wolfTPM2 context */ rc = wolfTPM2_Init(NULL, TPM2_IoCb, NULL); AssertIntNE(rc, 0); +#if defined(WOLFTPM_SPDM) && !defined(NO_GETENV) + if (getenv("WOLFTPM_TEST_SPDM_ONLY") != NULL) { + rc = wolfTPM2_Init(&dev, TPM2_IoCb, NULL); + AssertIntEQ(rc, WOLFSPDM_E_BAD_STATE); + AssertNull(TPM2_GetActiveCtx()); + AssertIntEQ(dev.ctx.locality, -1); + AssertNull(dev.spdmCtx); + } +#endif /* Test second argument, TPM2 IO Callbacks */ - rc = wolfTPM2_Init(&dev, NULL, NULL); + rc = TestWolfTPM2_InitConfigured(&dev, NULL, NULL); #if defined(WOLFTPM_LINUX_DEV) || defined(WOLFTPM_SWTPM) || \ defined(WOLFTPM_WINAPI) /* Custom IO Callbacks are not needed for Linux TIS driver */ @@ -152,7 +342,7 @@ static void test_wolfTPM2_Init(void) #endif /* Test success */ - rc = wolfTPM2_Init(&dev, TPM2_IoCb, NULL); + rc = TestWolfTPM2_InitConfigured(&dev, TPM2_IoCb, NULL); AssertIntEQ(rc, 0); wolfTPM2_Cleanup(&dev); @@ -161,6 +351,28 @@ static void test_wolfTPM2_Init(void) rc == 0 ? "Passed" : "Failed"); } +#if defined(WOLFTPM_SWTPM) && !defined(NO_GETENV) +/* The test server returns TPM_RC_UPGRADE from Startup. The wrapper must keep + * the active context so the caller can issue vendor recovery commands. */ +static void test_wolfTPM2_InitUpgrade(void) +{ + int rc; + WOLFTPM2_DEV dev; + + rc = wolfTPM2_Init(&dev, NULL, NULL); + AssertIntEQ(rc, TPM_RC_UPGRADE); + AssertTrue(TPM2_GetActiveCtx() == &dev.ctx); + AssertIntEQ(wolfTPM2_Cleanup(&dev), TPM_RC_SUCCESS); + AssertNull(TPM2_GetActiveCtx()); + + printf("Test TPM Wrapper: %-40s Passed\n", "Init upgrade context:"); +} +#endif + +/* When the SPDM integration harness supplies a trusted pin, route the + * remaining wrapper tests through authenticated initialization. */ +#define wolfTPM2_Init TestWolfTPM2_InitConfigured + /* test for WOLFTPM2_DEV restore */ static void test_wolfTPM2_OpenExisting(void) @@ -181,6 +393,15 @@ static void test_wolfTPM2_OpenExisting(void) rc = wolfTPM2_Cleanup_ex(&dev, 0); AssertIntEQ(rc, 0); + /* OpenExisting deliberately does not recreate transport sessions. The + * pinned SPDM integration run covers authenticated reinitialization in + * every other wrapper test. */ + if (TestWolfTPM2_HasResponderPin()) { + printf("Test TPM Wrapper: %-40s Skipped (SPDM pinned mode)\n", + "Open Existing:"); + return; + } + /* Restore TPM access */ rc = wolfTPM2_OpenExisting(&dev, TPM2_IoCb, NULL); @@ -287,6 +508,7 @@ static void test_wolfTPM2_ST33_FirmwareUpgrade(void) int isImpl; int fromTpm = 0; TPM_CC ccStart, ccData, ccStartRule, ccDataRule; + TPM2_CTX* savedCtx; WOLFTPM2_DEV dev; WOLFTPM2_CAPS caps; #if !defined(WOLFTPM2_NO_WOLFCRYPT) && defined(WOLFSSL_SHA384) @@ -294,6 +516,13 @@ static void test_wolfTPM2_ST33_FirmwareUpgrade(void) uint8_t dummy_manifest[10] = {0}; #endif + /* A missing active context must fail before raw packet marshalling. */ + savedCtx = TPM2_GetActiveCtx(); + TPM2_SetActiveCtx(NULL); + rc = TPM2_ST33_FieldUpgradeCommand(TPM_CC_FieldUpgradeData, NULL, 0); + AssertIntEQ(rc, BAD_FUNC_ARG); + TPM2_SetActiveCtx(savedCtx); + /* Initialize TPM */ rc = wolfTPM2_Init(&dev, TPM2_IoCb, NULL); if (rc != 0) { @@ -7123,6 +7352,9 @@ static void test_wolfTPM2_SPDM_Functions(void) { int rc; WOLFTPM2_DEV dev; +#if defined(WOLFTPM_SPDM) && defined(WOLFTPM_SPDM_TCG) + byte rspPubKey[WOLFSPDM_ECC_POINT_SIZE]; +#endif #ifdef WOLFSPDM_NUVOTON WOLFSPDM_NUVOTON_STATUS nuvStatus; #endif @@ -7135,13 +7367,14 @@ static void test_wolfTPM2_SPDM_Functions(void) TPM2_AUTH_SESSION nationsOrigSess; #endif + AssertStrEQ(wolfSPDM_GetErrorString(WOLFSPDM_E_BAD_STATE), + "Invalid state"); + AssertStrNE(TPM2_GetRCString(WOLFSPDM_E_BAD_STATE), + "SPDM invalid state"); + /* Initialize device */ rc = wolfTPM2_Init(&dev, TPM2_IoCb, NULL); - if (rc != 0) { - printf("Test TPM Wrapper: %-40s Failed (Init 0x%x)\n", - "SPDM Functions:", rc); - return; - } + AssertIntEQ(rc, TPM_RC_SUCCESS); /* Test 1: Parameter validation - NULL args */ rc = wolfTPM2_SpdmInit(NULL); @@ -7154,17 +7387,44 @@ static void test_wolfTPM2_SPDM_Functions(void) AssertIntEQ(rc, BAD_FUNC_ARG); rc = wolfTPM2_SpdmCleanup(NULL); AssertIntEQ(rc, BAD_FUNC_ARG); +#if defined(WOLFTPM_SPDM) && defined(WOLFTPM_SPDM_TCG) + XMEMSET(rspPubKey, 0xA5, sizeof(rspPubKey)); + rc = wolfTPM2_SpdmSetResponderPubKey(NULL, rspPubKey, + sizeof(rspPubKey)); + AssertIntEQ(rc, BAD_FUNC_ARG); +#endif /* Test 2: Context lifecycle - init, check state, cleanup */ rc = wolfTPM2_SpdmInit(&dev); AssertIntEQ(rc, TPM_RC_SUCCESS); +#if defined(WOLFTPM_SPDM) && defined(WOLFTPM_SPDM_TCG) + rc = wolfTPM2_SpdmSetResponderPubKey(&dev, NULL, 0); + AssertIntEQ(rc, WOLFSPDM_E_INVALID_ARG); + rc = wolfTPM2_SpdmSetResponderPubKey(&dev, rspPubKey, + sizeof(rspPubKey) - 1); + AssertIntEQ(rc, WOLFSPDM_E_INVALID_ARG); + if (!TestWolfTPM2_HasResponderPin()) { + rc = wolfTPM2_SpdmSetResponderPubKey(&dev, rspPubKey, + sizeof(rspPubKey)); + AssertIntEQ(rc, WOLFSPDM_SUCCESS); + } +#endif /* When SPDM-only mode is active, auto-SPDM connects during Init. * Otherwise, just initialized but not yet connected. */ - if (!dev.ctx.spdmOnlyDetected) { + if (!dev.ctx.spdmOnlyDetected && !TestWolfTPM2_HasResponderPin()) { AssertIntEQ(wolfTPM2_SpdmIsConnected(&dev), 0); AssertIntEQ(wolfTPM2_SpdmGetSessionId(&dev), 0); } + else if (TestWolfTPM2_HasResponderPin()) { + AssertIntEQ(wolfTPM2_SpdmIsConnected(&dev), 1); + AssertIntNE(wolfTPM2_SpdmGetSessionId(&dev), 0); + } /* Cleanup */ + if (TestWolfTPM2_HasResponderPin() && + wolfTPM2_SpdmIsConnected(&dev)) { + rc = wolfTPM2_SpdmDisconnect(&dev); + AssertIntEQ(rc, WOLFSPDM_SUCCESS); + } rc = wolfTPM2_SpdmCleanup(&dev); AssertIntEQ(rc, TPM_RC_SUCCESS); /* Idempotent cleanup */ @@ -8808,10 +9068,21 @@ int main(int argc, char *argv[]) int unit_tests(int argc, char *argv[]) #endif { +#if defined(WOLFTPM_SWTPM) && !defined(NO_GETENV) && \ + !defined(WOLFTPM2_NO_WRAPPER) + if (argc == 2 && XSTRCMP(argv[1], "--init-upgrade") == 0) { + test_wolfTPM2_InitUpgrade(); + return 0; + } +#endif (void)argc; (void)argv; #ifndef WOLFTPM2_NO_WRAPPER +#if defined(WOLFTPM_SPDM) && defined(WOLFTPM_SPDM_TCG) && \ + defined(WOLFSPDM_NUVOTON) && defined(WOLFSPDM_NATIONS) + test_wolfTPM2_SpdmModeFromDidVid(); +#endif test_wolfTPM2_Init(); test_wolfTPM2_OpenExisting(); test_wolfTPM2_GetCapabilities(); diff --git a/wolftpm/spdm/spdm.h b/wolftpm/spdm/spdm.h index 1e7464e7..a694bfba 100644 --- a/wolftpm/spdm/spdm.h +++ b/wolftpm/spdm/spdm.h @@ -36,6 +36,7 @@ extern "C" { /* Protocol mode: TCG binding + vendor commands. * For standard SPDM (emulator, measurements, challenge), see wolfSPDM standalone. */ typedef enum { + WOLFSPDM_MODE_AUTO = 0, WOLFSPDM_MODE_NUVOTON = 1, WOLFSPDM_MODE_NATIONS = 2, WOLFSPDM_MODE_NATIONS_PSK = 3 @@ -48,6 +49,7 @@ typedef enum { * WOLFSPDM_CTX ctx; * wolfSPDM_Init(&ctx); * wolfSPDM_SetIO(&ctx, callback, userPtr); + * wolfSPDM_SetResponderPubKey(&ctx, trustedPubKey, trustedPubKeySz); * wolfSPDM_Connect(&ctx); * wolfSPDM_SecuredExchange(&ctx, ...); * wolfSPDM_Disconnect(&ctx); @@ -104,7 +106,8 @@ WOLFTPM_API int wolfSPDM_SetIO(WOLFSPDM_CTX* ctx, WOLFSPDM_IO_CB ioCb, void* userCtx); WOLFTPM_API int wolfSPDM_SetMode(WOLFSPDM_CTX* ctx, WOLFSPDM_MODE mode); WOLFTPM_API WOLFSPDM_MODE wolfSPDM_GetMode(WOLFSPDM_CTX* ctx); -/* Set responder pub key for cert-less operation (96 bytes P-384 X||Y) */ +/* Pin the responder key for cert-less operation (96 bytes P-384 X||Y). + * Required before a TCG identity-key connection. */ WOLFTPM_API int wolfSPDM_SetResponderPubKey(WOLFSPDM_CTX* ctx, const byte* pubKey, word32 pubKeySz); /* Set requester key pair for mutual auth (privKey=48, pubKey=96 bytes) */ diff --git a/wolftpm/spdm/spdm_tcg.h b/wolftpm/spdm/spdm_tcg.h index da5117c5..fd9ac122 100644 --- a/wolftpm/spdm/spdm_tcg.h +++ b/wolftpm/spdm/spdm_tcg.h @@ -136,6 +136,8 @@ WOLFTPM_API int wolfSPDM_ParseVendorDefined( /* ----- Shared TCG SPDM Functions ----- */ +/* Cleartext key discovery. The result is never installed as trusted state. + * If a responder key is pinned, the discovered public point must match it. */ WOLFTPM_API int wolfSPDM_TCG_GetPubKey(WOLFSPDM_CTX* ctx, byte* pubKey, word32* pubKeySz); diff --git a/wolftpm/tpm2_wrap.h b/wolftpm/tpm2_wrap.h index 9824e2ed..81bfd711 100644 --- a/wolftpm/tpm2_wrap.h +++ b/wolftpm/tpm2_wrap.h @@ -216,10 +216,17 @@ WOLFTPM_API int wolfTPM2_Test(TPM2HalIoCb ioCb, void* userCtx, WOLFTPM2_CAPS* ca /*! \ingroup wolfTPM2_Wrappers \brief Complete initialization of a TPM + On failure after argument validation, the device is fully torn down, + except for TPM_RC_UPGRADE, which leaves an active context for the vendor + firmware-recovery flow. \return TPM_RC_SUCCESS: successful \return TPM_RC_FAILURE: generic failure (check TPM IO communication) + \return TPM_RC_UPGRADE: TPM firmware recovery is active; call cleanup + when the recovery flow is complete \return BAD_FUNC_ARG: check the provided arguments + \return WOLFSPDM_E_BAD_STATE: TPM is in SPDM-only mode and no trusted + identity key or PSK was supplied; use the matching SPDM init API \param dev pointer to an empty structure of WOLFTPM2_DEV type \param ioCb function pointer to a IO callback (see hal/tpm_io.h) @@ -238,11 +245,123 @@ WOLFTPM_API int wolfTPM2_Test(TPM2HalIoCb ioCb, void* userCtx, WOLFTPM2_CAPS* ca \endcode \sa wolfTPM2_OpenExisting + \sa wolfTPM2_InitWithSpdmKey + \sa wolfTPM2_InitWithSpdmPsk \sa wolfTPM2_Test \sa TPM2_Init */ WOLFTPM_API int wolfTPM2_Init(WOLFTPM2_DEV* dev, TPM2HalIoCb ioCb, void* userCtx); +#if defined(WOLFTPM_SPDM) && defined(WOLFTPM_SPDM_TCG) +/*! + \ingroup wolfTPM2_Wrappers + \brief Initialize a TPM and pin a trusted SPDM responder public key. + A successful call establishes an authenticated SPDM session even when the + TPM accepted the initial cleartext startup probe. When the TPM is already + in SPDM-only identity mode, startup is retried over that secure session. + Identity initialization requires a Nuvoton or Nations vendor adapter; + builds without either adapter return WOLFSPDM_E_NOT_AVAILABLE. + In dual-vendor builds, automatic selection requires TPM DID/VID; use + wolfTPM2_InitWithSpdmKey_ex for transports that do not expose it. + On failure after argument validation, the device is fully torn down, + except for TPM_RC_UPGRADE, which leaves an active context for the vendor + firmware-recovery flow. WOLFSPDM_E_NOT_AVAILABLE is returned before the + device is modified. + + \return TPM_RC_SUCCESS: successful + \return TPM_RC_UPGRADE: TPM firmware recovery is active; call cleanup + when the recovery flow is complete + \return BAD_FUNC_ARG: invalid parameters or responder key size + \return WOLFSPDM_E_BAD_STATE: SPDM identity mode cannot be initialized + \return WOLFSPDM_E_NOT_AVAILABLE: no identity vendor adapter was built + \return a WOLFSPDM_E_* error if the authenticated handshake fails + + \param dev pointer to an empty WOLFTPM2_DEV structure + \param ioCb function pointer to an I/O callback + \param userCtx pointer to a user context (can be NULL) + \param rspPubKey trusted raw P-384 responder key in X||Y format + \param rspPubKeySz size of rspPubKey; must be 96 bytes + + \sa wolfTPM2_Init + \sa wolfTPM2_SpdmSetResponderPubKey + \sa wolfTPM2_SpdmConnect + \sa wolfTPM2_SpdmIsConnected +*/ +WOLFTPM_API int wolfTPM2_InitWithSpdmKey(WOLFTPM2_DEV* dev, + TPM2HalIoCb ioCb, void* userCtx, const byte* rspPubKey, + word32 rspPubKeySz); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Initialize with a trusted responder key and explicit identity mode. + This API requires a Nuvoton or Nations vendor adapter. + Use this form when both vendor adapters are compiled and the transport does + not expose TPM DID/VID. WOLFSPDM_MODE_AUTO selects from DID/VID and fails + closed when it is unavailable instead of guessing a vendor handshake. + TPM_RC_UPGRADE leaves an active context for the vendor firmware-recovery + flow; call cleanup when that flow is complete. WOLFSPDM_E_NOT_AVAILABLE + is returned before the device is modified. + + \return TPM_RC_SUCCESS: successful + \return TPM_RC_UPGRADE: TPM firmware recovery is active + \return BAD_FUNC_ARG: invalid parameters or unsupported identity mode + \return WOLFSPDM_E_BAD_STATE: automatic vendor selection is unavailable + \return WOLFSPDM_E_NOT_AVAILABLE: no identity vendor adapter was built + \return a WOLFSPDM_E_* error if the authenticated handshake fails + + \param dev pointer to an empty WOLFTPM2_DEV structure + \param ioCb function pointer to an I/O callback + \param userCtx pointer to a user context (can be NULL) + \param rspPubKey trusted raw P-384 responder key in X||Y format + \param rspPubKeySz size of rspPubKey; must be 96 bytes + \param mode WOLFSPDM_MODE_AUTO, WOLFSPDM_MODE_NUVOTON, or + WOLFSPDM_MODE_NATIONS + + \sa wolfTPM2_InitWithSpdmKey +*/ +WOLFTPM_API int wolfTPM2_InitWithSpdmKey_ex(WOLFTPM2_DEV* dev, + TPM2HalIoCb ioCb, void* userCtx, const byte* rspPubKey, + word32 rspPubKeySz, WOLFSPDM_MODE mode); + +#if defined(WOLFSPDM_NUVOTON) && defined(WOLFSPDM_NATIONS) +/* Test-visible helper used by automatic dual-vendor selection. */ +WOLFTPM_TEST_API int wolfTPM2_SpdmModeFromDidVid(UINT32 didVid, + WOLFSPDM_MODE* mode); +#endif +#endif + +#if defined(WOLFTPM_SPDM) && defined(WOLFTPM_SPDM_PSK) +/*! + \ingroup wolfTPM2_Wrappers + \brief Initialize a TPM and authenticate an SPDM session with a PSK. + This entry point also recovers a PSK-configured TPM that is already locked + in SPDM-only mode by retrying TPM2_Startup over the secure session. + On failure after argument validation, the device is fully torn down, + except for TPM_RC_UPGRADE, which leaves an active context for the vendor + firmware-recovery flow. + + \return TPM_RC_SUCCESS: successful + \return TPM_RC_UPGRADE: TPM firmware recovery is active; call cleanup + when the recovery flow is complete + \return BAD_FUNC_ARG: invalid parameters + \return a WOLFSPDM_E_* error if the authenticated handshake fails + + \param dev pointer to an empty WOLFTPM2_DEV structure + \param ioCb function pointer to an I/O callback + \param userCtx pointer to a user context (can be NULL) + \param psk pre-shared key bytes + \param pskSz size of psk; must be nonzero + \param hint optional PSK hint + \param hintSz size of hint + + \sa wolfTPM2_Init + \sa wolfTPM2_SpdmConnectPsk +*/ +WOLFTPM_API int wolfTPM2_InitWithSpdmPsk(WOLFTPM2_DEV* dev, + TPM2HalIoCb ioCb, void* userCtx, const byte* psk, word32 pskSz, + const byte* hint, word32 hintSz); +#endif + /*! \ingroup wolfTPM2_Wrappers \brief Use an already initialized TPM, in its current TPM locality @@ -498,11 +617,16 @@ WOLFTPM_API int wolfTPM2_SpdmInit(WOLFTPM2_DEV* dev); \brief Establish an SPDM secure session (full handshake). Uses standard SPDM flow: GET_VERSION -> GET_CAPABILITIES -> NEGOTIATE_ALGORITHMS -> KEY_EXCHANGE -> FINISH. + Identity-key modes require a responder key pinned with + wolfTPM2_SpdmSetResponderPubKey before this call. \return TPM_RC_SUCCESS: session established \return TPM_RC_FAILURE: handshake failed + \return WOLFSPDM_E_BAD_STATE: trusted responder key is not pinned \param dev pointer to a WOLFTPM2_DEV structure + + \sa wolfTPM2_SpdmSetResponderPubKey */ WOLFTPM_API int wolfTPM2_SpdmConnect(WOLFTPM2_DEV* dev); @@ -548,9 +672,31 @@ WOLFTPM_API int wolfTPM2_SpdmDisconnect(WOLFTPM2_DEV* dev); WOLFTPM_API int wolfTPM2_SpdmCleanup(WOLFTPM2_DEV* dev); #ifdef WOLFTPM_SPDM_TCG +/*! + \ingroup wolfTPM2_Wrappers + \brief Pin the trusted responder key before an identity-key connection. + + \return WOLFSPDM_SUCCESS: successful + \return BAD_FUNC_ARG: invalid device context + \return WOLFSPDM_E_INVALID_ARG: invalid key pointer or size + + \param dev pointer to an initialized WOLFTPM2_DEV structure + \param pubKey trusted raw P-384 responder key in X||Y format + \param pubKeySz size of pubKey; must be 96 bytes + + \sa wolfTPM2_InitWithSpdmKey + \sa wolfTPM2_SpdmConnect +*/ +WOLFTPM_API int wolfTPM2_SpdmSetResponderPubKey(WOLFTPM2_DEV* dev, + const byte* pubKey, word32 pubKeySz); + /*! \ingroup wolfTPM2_Wrappers \brief Get the TPM's SPDM-Identity public key (shared TCG function). + \note This command runs in cleartext and its result is not a trust source. + Use it only for diagnostics or compare it with a key obtained from a + trusted provisioning channel. Do not install the returned key as its + own trust anchor. \return TPM_RC_SUCCESS: successful \return BAD_FUNC_ARG: invalid parameters @@ -558,6 +704,8 @@ WOLFTPM_API int wolfTPM2_SpdmCleanup(WOLFTPM2_DEV* dev); \param dev pointer to a WOLFTPM2_DEV structure \param pubKey output buffer for the public key \param pubKeySz in/out: buffer size / actual key size + + \sa wolfTPM2_SpdmSetResponderPubKey */ WOLFTPM_API int wolfTPM2_SpdmGetPubKey(WOLFTPM2_DEV* dev, byte* pubKey, word32* pubKeySz); @@ -602,15 +750,19 @@ WOLFTPM_API int wolfTPM2_SpdmDisable(WOLFTPM2_DEV* dev); \brief Establish Nuvoton SPDM secure session with mutual authentication. Uses Nuvoton flow: GET_VERSION -> GET_PUB_KEY -> KEY_EXCHANGE -> GIVE_PUB_KEY -> FINISH. + Requires a responder key pinned with wolfTPM2_SpdmSetResponderPubKey. \return TPM_RC_SUCCESS: session established \return TPM_RC_FAILURE: handshake failed + \return WOLFSPDM_E_BAD_STATE: trusted responder key is not pinned \param dev pointer to a WOLFTPM2_DEV structure \param reqPubKey host's ECDSA P-384 public key (TPMT_PUBLIC format) \param reqPubKeySz size of reqPubKey in bytes \param reqPrivKey host's ECDSA P-384 private key (raw 48 bytes) \param reqPrivKeySz size of reqPrivKey in bytes + + \sa wolfTPM2_SpdmSetResponderPubKey */ WOLFTPM_API int wolfTPM2_SpdmConnectNuvoton(WOLFTPM2_DEV* dev, const byte* reqPubKey, word32 reqPubKeySz, @@ -662,15 +814,19 @@ WOLFTPM_API int wolfTPM2_SpdmSetNationsMode(WOLFTPM2_DEV* dev); \brief Establish Nations SPDM secure session (identity key mode). Uses TCG flow: GET_VERSION -> GET_PUB_KEY -> KEY_EXCHANGE -> GIVE_PUB_KEY -> FINISH. + Requires a responder key pinned with wolfTPM2_SpdmSetResponderPubKey. \return TPM_RC_SUCCESS: session established \return TPM_RC_FAILURE: handshake failed + \return WOLFSPDM_E_BAD_STATE: trusted responder key is not pinned \param dev pointer to a WOLFTPM2_DEV structure \param reqPubKey host's ECDSA P-384 public key (TPMT_PUBLIC format, or NULL for auto-gen) \param reqPubKeySz size of reqPubKey in bytes \param reqPrivKey host's ECDSA P-384 private key (raw 48 bytes, or NULL for auto-gen) \param reqPrivKeySz size of reqPrivKey in bytes + + \sa wolfTPM2_SpdmSetResponderPubKey */ WOLFTPM_API int wolfTPM2_SpdmConnectNations(WOLFTPM2_DEV* dev, const byte* reqPubKey, word32 reqPubKeySz, @@ -4512,10 +4668,17 @@ WOLFTPM_API int wolfTPM_PK_SetCbCtx(WOLFSSL* ssl, void* userCtx); \ingroup wolfTPM2_Wrappers \brief Allocate and initialize a WOLFTPM2_DEV + \note This convenience constructor uses uncredentialed wolfTPM2_Init. + An SPDM-only TPM requires a caller-allocated WOLFTPM2_DEV initialized with + wolfTPM2_InitWithSpdmKey, wolfTPM2_InitWithSpdmKey_ex, or + wolfTPM2_InitWithSpdmPsk instead. + \return pointer to new device struct \return NULL: on any error \sa wolfTPM2_Free + \sa wolfTPM2_InitWithSpdmKey + \sa wolfTPM2_InitWithSpdmPsk */ WOLFTPM_API WOLFTPM2_DEV* wolfTPM2_New(void); From 95696b36ef9f904700861e0eae81640d79029b10 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 31 Aug 2026 12:44:18 -0700 Subject: [PATCH 5/8] F-12736 - Authenticate fwTPM shared-memory endpoints --- .github/workflows/cmake-build.yml | 19 +- .github/workflows/fwtpm-test.yml | 11 +- CMakeLists.txt | 97 +++- cmake/wolftpm-config.cmake.in | 8 + configure.ac | 19 + docs/FWTPM.md | 19 +- hal/tpm_io_fwtpm.c | 136 +++++- src/fwtpm/fwtpm_tis.c | 13 +- src/fwtpm/fwtpm_tis_sem.c | 64 +++ src/fwtpm/fwtpm_tis_shm.c | 74 ++- src/fwtpm/include.am | 5 + src/include.am | 3 +- tests/cmake_static_consumer/CMakeLists.txt | 8 + tests/fwtpm_hal_unit_tests.c | 498 +++++++++++++++++++++ tests/fwtpm_tis_sem_unit_tests.c | 71 +++ tests/include.am | 17 + tests/unit_tests.c | 8 +- wolftpm/fwtpm/fwtpm_tis.h | 15 +- 18 files changed, 1025 insertions(+), 60 deletions(-) create mode 100644 cmake/wolftpm-config.cmake.in create mode 100644 src/fwtpm/fwtpm_tis_sem.c create mode 100644 tests/cmake_static_consumer/CMakeLists.txt create mode 100644 tests/fwtpm_hal_unit_tests.c create mode 100644 tests/fwtpm_tis_sem_unit_tests.c diff --git a/.github/workflows/cmake-build.yml b/.github/workflows/cmake-build.yml index 4223ab71..9a7a2ee3 100644 --- a/.github/workflows/cmake-build.yml +++ b/.github/workflows/cmake-build.yml @@ -212,11 +212,9 @@ jobs: fi # Verify that a static-library build can be installed and its exported - # CMake targets are self-consistent. The matrix builds above use - # WITH_WOLFSSL (shared library) and do not catch this because CMake only - # enforces export-set completeness for static libraries: any CMake target - # in a static library's PRIVATE link chain must be exported or wrapped in - # $. + # CMake targets are self-consistent. The TIS build also verifies that an + # installed package resolves imported static-link dependencies before its + # exported target is loaded by a downstream project. cmake-static-export: name: CMake static export check if: github.event_name != 'pull_request' || github.event.pull_request.draft == false @@ -269,10 +267,19 @@ jobs: cmake \ -DBUILD_SHARED_LIBS=OFF \ -DWOLFTPM_EXAMPLES=no \ - -DWOLFTPM_FWTPM=no \ + -DWOLFTPM_FWTPM=yes \ + -DWOLFTPM_INTERFACE=SPI \ -DWOLFTPM_BUILD_OUT_OF_TREE=yes \ + -DCMAKE_C_FLAGS="-DWOLFSSL_AES_CFB -DWOLFSSL_PUBLIC_MP" \ -DCMAKE_PREFIX_PATH="$GITHUB_WORKSPACE/install" \ -DCMAKE_INSTALL_PREFIX="$GITHUB_WORKSPACE/install" \ .. cmake --build . --parallel cmake --install . + + - name: Configure installed static consumer + run: | + cmake \ + -S tests/cmake_static_consumer \ + -B build-static-consumer \ + -DCMAKE_PREFIX_PATH="$GITHUB_WORKSPACE/install" diff --git a/.github/workflows/fwtpm-test.yml b/.github/workflows/fwtpm-test.yml index 6bf40d55..429311ed 100644 --- a/.github/workflows/fwtpm-test.yml +++ b/.github/workflows/fwtpm-test.yml @@ -34,7 +34,7 @@ jobs: # fwTPM with TIS/shared-memory transport - name: fwtpm-tis os: ubuntu-latest - wolftpm_config: --enable-fwtpm --enable-debug + wolftpm_config: --enable-fwtpm --disable-swtpm --enable-debug wolfssl_config: --enable-wolftpm --enable-pkcallbacks --enable-keygen build_only: false @@ -254,6 +254,15 @@ jobs: cc: clang make_cflags: "-Wall -Wextra -Wpedantic -Werror -Wshadow -Wstrict-prototypes -Wmissing-prototypes -Wformat=2" + # Build-only: pedantic warnings on the POSIX TIS transport and its + # dedicated HAL/semaphore tests. + - name: fwtpm-pedantic-tis + os: ubuntu-latest + wolftpm_config: --enable-fwtpm --disable-swtpm + wolfssl_config: --enable-wolftpm --enable-pkcallbacks --enable-keygen + build_only: true + make_cflags: "-Wall -Wextra -Wpedantic -Werror -Wshadow -Wstrict-prototypes -Wmissing-prototypes -Wformat=2" + # Build-only: pedantic fwTPM-only (no client library) - name: fwtpm-pedantic-only os: ubuntu-latest diff --git a/CMakeLists.txt b/CMakeLists.txt index c7675e10..275b957b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -404,6 +404,30 @@ if(WOLFTPM_FWTPM_ONLY) list(APPEND WOLFTPM_DEFINITIONS "-DWOLFTPM2_NO_WRAPPER") endif() +set(WOLFTPM_FWTPM_THREAD_LIBS) +set(WOLFTPM_FWTPM_TIS_LIBS) +if(WOLFTPM_FWTPM AND UNIX) + find_package(Threads REQUIRED) + list(APPEND WOLFTPM_FWTPM_THREAD_LIBS Threads::Threads) + if(WOLFTPM_FWTPM_TIS) + set(WOLFTPM_FWTPM_TIS_LIBS ${WOLFTPM_FWTPM_THREAD_LIBS}) + if(NOT APPLE) + include(CheckLibraryExists) + check_library_exists(rt sem_open "" WOLFTPM_FWTPM_HAVE_LIBRT) + if(WOLFTPM_FWTPM_HAVE_LIBRT) + list(APPEND WOLFTPM_FWTPM_TIS_LIBS rt) + endif() + endif() + endif() +endif() + +if(WOLFTPM_FWTPM_TIS AND BUILD_WOLFTPM_LIB) + target_sources(wolftpm PRIVATE src/fwtpm/fwtpm_tis_sem.c) + if(UNIX) + target_link_libraries(wolftpm PRIVATE ${WOLFTPM_FWTPM_TIS_LIBS}) + endif() +endif() + # Examples set(WOLFTPM_EXAMPLES "yes" CACHE BOOL "Build examples") @@ -522,6 +546,7 @@ if(WOLFTPM_FWTPM) if(WOLFTPM_FWTPM_TIS) list(APPEND FWTPM_SERVER_SOURCES src/fwtpm/fwtpm_tis.c + src/fwtpm/fwtpm_tis_sem.c src/fwtpm/fwtpm_tis_shm.c ) endif() @@ -541,9 +566,12 @@ if(WOLFTPM_FWTPM) ${CMAKE_CURRENT_BINARY_DIR} ) if(UNIX) - target_link_libraries(fwtpm_server PRIVATE pthread) - if(NOT APPLE) - target_link_libraries(fwtpm_server PRIVATE rt) + if(WOLFTPM_FWTPM_TIS) + target_link_libraries(fwtpm_server PRIVATE + ${WOLFTPM_FWTPM_TIS_LIBS}) + else() + target_link_libraries(fwtpm_server PRIVATE + ${WOLFTPM_FWTPM_THREAD_LIBS}) endif() elseif(WIN32) target_link_libraries(fwtpm_server PRIVATE ws2_32) @@ -568,7 +596,43 @@ if(WOLFTPM_FWTPM) ${CMAKE_CURRENT_BINARY_DIR} ) if(UNIX) - target_link_libraries(fwtpm_unit_test PRIVATE pthread) + target_link_libraries(fwtpm_unit_test PRIVATE + ${WOLFTPM_FWTPM_THREAD_LIBS}) + endif() + + if(WOLFTPM_FWTPM_TIS) + add_executable(fwtpm_hal_unit_test + tests/fwtpm_hal_unit_tests.c + ) + target_compile_definitions(fwtpm_hal_unit_test PRIVATE + "WOLFTPM_FWTPM_HAL" + "WOLFTPM_ADV_IO" + "WOLFTPM_EXAMPLE_HAL" + ) + target_link_libraries(fwtpm_hal_unit_test PRIVATE wolftpm_wolfssl_dep) + target_include_directories(fwtpm_hal_unit_test PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ) + if(UNIX) + target_link_libraries(fwtpm_hal_unit_test PRIVATE + ${WOLFTPM_FWTPM_TIS_LIBS}) + endif() + + add_executable(fwtpm_tis_sem_unit_test + tests/fwtpm_tis_sem_unit_tests.c + src/fwtpm/fwtpm_tis_sem.c + ) + target_compile_definitions(fwtpm_tis_sem_unit_test PRIVATE + "WOLFTPM_FWTPM_TIS" + ) + target_link_libraries(fwtpm_tis_sem_unit_test PRIVATE + wolftpm_wolfssl_dep + ) + target_include_directories(fwtpm_tis_sem_unit_test PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ) endif() # fwTPM fuzz target (libFuzzer) @@ -596,6 +660,16 @@ if(WOLFTPM_FWTPM) COMMAND fwtpm_unit_test WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) + if(WOLFTPM_FWTPM_TIS) + add_test(NAME fwtpm_hal_unit_test + COMMAND fwtpm_hal_unit_test + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + add_test(NAME fwtpm_tis_sem_unit_test + COMMAND fwtpm_tis_sem_unit_test + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + endif() message(STATUS "fwTPM server: enabled") if(WOLFTPM_FWTPM_TIS) @@ -765,6 +839,17 @@ endif() include(GNUInstallDirs) if(BUILD_WOLFTPM_LIB) + include(CMakePackageConfigHelpers) + set(WOLFTPM_CONFIG_NEEDS_THREADS OFF) + if(WOLFTPM_FWTPM_TIS AND UNIX) + set(WOLFTPM_CONFIG_NEEDS_THREADS ON) + endif() + configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/wolftpm-config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/wolftpm-config.cmake" + INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/wolftpm" + ) + # Note: wolftpm_wolfssl_dep is an INTERFACE helper used only at build # time to share wolfSSL include/link settings. It is intentionally NOT # installed/exported — downstream `find_package(wolftpm)` should only @@ -779,8 +864,10 @@ if(BUILD_WOLFTPM_LIB) # Install the export set install(EXPORT wolftpm-targets DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/wolftpm - FILE wolftpm-config.cmake + FILE wolftpm-targets.cmake NAMESPACE wolfssl::) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/wolftpm-config.cmake" + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/wolftpm) endif() # Install fwTPM server diff --git a/cmake/wolftpm-config.cmake.in b/cmake/wolftpm-config.cmake.in new file mode 100644 index 00000000..4d8861c0 --- /dev/null +++ b/cmake/wolftpm-config.cmake.in @@ -0,0 +1,8 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +if(@WOLFTPM_CONFIG_NEEDS_THREADS@) + find_dependency(Threads) +endif() + +include("${CMAKE_CURRENT_LIST_DIR}/wolftpm-targets.cmake") diff --git a/configure.ac b/configure.ac index 4c2bccb5..3af17644 100644 --- a/configure.ac +++ b/configure.ac @@ -424,6 +424,25 @@ then fi fi +# POSIX TIS/shared-memory consumers need the platform's semaphore link flags. +# Keep these target-scoped instead of adding them to LIBS. +FWTPM_TIS_LIBS="" +if test "x$ENABLED_FWTPM_TIS" = "xyes" +then + fwtpm_tis_saved_LIBS="$LIBS" + LIBS="$PTHREAD_LIBS $LIBS" + AC_SEARCH_LIBS([sem_open], [rt pthread], [ + AS_IF([test "x$ac_cv_search_sem_open" != "xnone required"], [ + FWTPM_TIS_LIBS="$FWTPM_TIS_LIBS $ac_cv_search_sem_open" + ]) + ], [ + AC_MSG_ERROR([fwTPM TIS transport requires POSIX sem_open]) + ]) + LIBS="$fwtpm_tis_saved_LIBS" + FWTPM_TIS_LIBS="$PTHREAD_LIBS $FWTPM_TIS_LIBS" +fi +AC_SUBST([FWTPM_TIS_LIBS]) + # Append-only NV journal for write-once flash fwTPM ports (opt-in) AC_ARG_ENABLE([fwtpm-nv-appendonly], [AS_HELP_STRING([--enable-fwtpm-nv-appendonly],[Enable the append-only NV journal mode for write-once flash fwTPM ports (default: disabled)])], diff --git a/docs/FWTPM.md b/docs/FWTPM.md index 9117de04..d9bcc599 100644 --- a/docs/FWTPM.md +++ b/docs/FWTPM.md @@ -45,6 +45,7 @@ fwTPM can replace a hardware TPM for: | `fwtpm_io.c` | Transport layer -- SWTPM TCP socket protocol (default) | | `fwtpm_nv.c` | NV storage -- file-based (default); HAL-abstracted, with a built-in append-only mode for write-once flash | | `fwtpm_tis.c` | TIS register state machine (transport-agnostic) | +| `fwtpm_tis_sem.c` | POSIX semaphore-name derivation shared by server and client HAL | | `fwtpm_tis_shm.c` | POSIX shared memory + semaphore TIS transport | | `fwtpm_main.c` | Server entry point, CLI argument parsing | | `tpm2_util.c` | Shared utilities (hash helpers, ForceZero, PrintBin) | @@ -754,7 +755,7 @@ register-level access. This mode simulates an SPI-attached TPM. | Field | Description | |-------|-------------| -| `magic` / `version` | Validation header (`0x57544953` / `"WTIS"`) | +| `magic` / `version` | Validation header (`0x57544953` / `"WTIS"`, protocol version 2) | | `reg_addr`, `reg_len`, `reg_is_write`, `reg_data` | Register access request | | TIS register shadow: `access`, `sts`, `int_enable`, `int_status`, `intf_caps`, `did_vid`, `rid` | Emulated TIS registers | | `cmd_buf[4096]`, `cmd_len`, `fifo_write_pos` | Command FIFO | @@ -764,9 +765,19 @@ register-level access. This mode simulates an SPI-attached TPM. | Define | Default | Description | |--------|---------|-------------| -| `FWTPM_TIS_SHM_PATH` | `/tmp/fwtpm.shm` | Shared memory file | -| `FWTPM_TIS_SEM_CMD` | `/fwtpm_cmd` | Command semaphore name | -| `FWTPM_TIS_SEM_RSP` | `/fwtpm_rsp` | Response semaphore name | +| `FWTPM_TIS_SHM_PATH` | `/tmp/fwtpm.shm` | Shared memory file; clients require a regular, single-link, same-UID, exact-size `0600` endpoint | +| `FWTPM_TIS_SEM_CMD` | `/fwtpm_cmd` | Command semaphore prefix; a per-UID suffix is always appended | +| `FWTPM_TIS_SEM_RSP` | `/fwtpm_rsp` | Response semaphore prefix; a per-UID suffix is always appended | + +Protocol version 2 requires an exact version and shared-region-size match. +Rebuild the client library and `fwtpm_server` together when upgrading or when +changing options that affect `FWTPM_TIS_FIFO_SIZE`; version 1 and version 2 +peers do not interoperate. + +The default shared-memory path is global, so it supports one server per host. +The per-UID semaphore suffix enables safe stale-object cleanup; it does not +enable concurrent per-user servers unless each build also uses a distinct +`FWTPM_TIS_SHM_PATH`. **Server-side API:** diff --git a/hal/tpm_io_fwtpm.c b/hal/tpm_io_fwtpm.c index f76cfa34..ed6bcdfe 100644 --- a/hal/tpm_io_fwtpm.c +++ b/hal/tpm_io_fwtpm.c @@ -79,23 +79,48 @@ static void FWTPM_TIS_ClientUnlock(int fd) } while (rc != 0 && errno == EINTR); } -static int FWTPM_TIS_ServerActive(const FWTPM_TIS_REGS* shm) +static UINT32 FWTPM_TIS_LoadMagic(const FWTPM_TIS_REGS* shm) { +#if defined(__GNUC__) || defined(__clang__) + return __atomic_load_n(&shm->magic, __ATOMIC_ACQUIRE); +#else const volatile UINT32* magic = &shm->magic; - return *magic == FWTPM_TIS_MAGIC; + return *magic; +#endif +} + +static int FWTPM_TIS_ServerActive(const FWTPM_TIS_REGS* shm) +{ + return FWTPM_TIS_LoadMagic(shm) == FWTPM_TIS_MAGIC; +} + +static int FWTPM_TIS_ClientValidateShm(const struct stat* st) +{ + mode_t expectedMode = S_IRUSR | S_IWUSR; + + if (!S_ISREG(st->st_mode) || st->st_uid != geteuid() || + st->st_nlink != 1 || + (st->st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) != expectedMode || + st->st_size != (off_t)sizeof(FWTPM_TIS_REGS)) { + return TPM_RC_FAILURE; + } + return TPM_RC_SUCCESS; } int FWTPM_TIS_ClientConnect(FWTPM_TIS_CLIENT_CTX* client) { int fd; int openFlags; - int fdFlags; + int fdFlags = 0; + UINT32 magic; struct stat st; struct stat pathSt; FWTPM_TIS_REGS* shm; sem_t* semCmd; sem_t* semRsp; + char semCmdName[FWTPM_TIS_SEM_NAME_SIZE]; + char semRspName[FWTPM_TIS_SEM_NAME_SIZE]; if (client == NULL) { return BAD_FUNC_ARG; @@ -106,7 +131,8 @@ int FWTPM_TIS_ClientConnect(FWTPM_TIS_CLIENT_CTX* client) /* Open existing shared memory file. O_NOFOLLOW and O_CLOEXEC are not * universally available across POSIX targets — guard at compile time - * and fall back to fcntl(FD_CLOEXEC) for the close-on-exec semantics. */ + * and fall back to lstat/inode checks and fcntl(FD_CLOEXEC). O_NONBLOCK + * prevents a substituted FIFO from blocking before fstat rejects it. */ openFlags = O_RDWR; #ifdef O_NOFOLLOW openFlags |= O_NOFOLLOW; @@ -114,6 +140,23 @@ int FWTPM_TIS_ClientConnect(FWTPM_TIS_CLIENT_CTX* client) #ifdef O_CLOEXEC openFlags |= O_CLOEXEC; #endif +#ifdef O_NONBLOCK + openFlags |= O_NONBLOCK; +#endif + if (lstat(FWTPM_TIS_SHM_PATH, &pathSt) != 0) { + #ifdef DEBUG_WOLFTPM + printf("fwTPM HAL: lstat(%s) failed: %d (%s)\n", + FWTPM_TIS_SHM_PATH, errno, strerror(errno)); + #endif + return TPM_RC_FAILURE; + } + if (!S_ISREG(pathSt.st_mode)) { + #ifdef DEBUG_WOLFTPM + printf("fwTPM HAL: %s is not a regular file\n", + FWTPM_TIS_SHM_PATH); + #endif + return TPM_RC_FAILURE; + } fd = open(FWTPM_TIS_SHM_PATH, openFlags); if (fd < 0) { #ifdef DEBUG_WOLFTPM @@ -127,16 +170,60 @@ int FWTPM_TIS_ClientConnect(FWTPM_TIS_CLIENT_CTX* client) if (fdFlags >= 0) { (void)fcntl(fd, F_SETFD, fdFlags | FD_CLOEXEC); } -#else +#elif !defined(O_NONBLOCK) (void)fdFlags; #endif - /* Verify file is large enough before mapping */ - if (fstat(fd, &st) != 0 || - st.st_size < (off_t)sizeof(FWTPM_TIS_REGS)) { + /* Authenticate the data-bearing endpoint before mapping it. Named + * semaphores carry wakeups only and are derived from this validated file + * owner below. */ + if (fstat(fd, &st) != 0) { #ifdef DEBUG_WOLFTPM - printf("fwTPM HAL: shm file too small (expected %lu)\n", - (unsigned long)sizeof(FWTPM_TIS_REGS)); + printf("fwTPM HAL: fstat(%s) failed: %d (%s)\n", + FWTPM_TIS_SHM_PATH, errno, strerror(errno)); + #endif + close(fd); + return TPM_RC_FAILURE; + } + /* Check this ahead of the shared validator so a size mismatch gets the + * specific client/server rebuild diagnostic. */ + if (st.st_size != (off_t)sizeof(FWTPM_TIS_REGS)) { + #ifdef DEBUG_WOLFTPM + printf("fwTPM HAL: endpoint is %llu bytes; this build expects %llu " + "(rebuild client and server with matching options)\n", + (unsigned long long)st.st_size, + (unsigned long long)sizeof(FWTPM_TIS_REGS)); + #endif + close(fd); + return TPM_RC_FAILURE; + } + if (FWTPM_TIS_ClientValidateShm(&st) != TPM_RC_SUCCESS || + st.st_dev != pathSt.st_dev || st.st_ino != pathSt.st_ino) { + #ifdef DEBUG_WOLFTPM + printf("fwTPM HAL: untrusted shm metadata (expected %lu bytes, " + "mode 0600, uid %lu)\n", + (unsigned long)sizeof(FWTPM_TIS_REGS), + (unsigned long)geteuid()); + #endif + close(fd); + return TPM_RC_FAILURE; + } +#ifdef O_NONBLOCK + fdFlags = fcntl(fd, F_GETFL); + if (fdFlags < 0 || + fcntl(fd, F_SETFL, fdFlags & ~O_NONBLOCK) != 0) { + #ifdef DEBUG_WOLFTPM + printf("fwTPM HAL: failed to clear O_NONBLOCK\n"); + #endif + close(fd); + return TPM_RC_FAILURE; + } +#endif + if (FWTPM_TIS_MakeSemNames((UINT64)st.st_uid, semCmdName, + sizeof(semCmdName), semRspName, sizeof(semRspName)) != 0) { + #ifdef DEBUG_WOLFTPM + printf("fwTPM HAL: failed to derive semaphore names for uid %lu\n", + (unsigned long)st.st_uid); #endif close(fd); return TPM_RC_FAILURE; @@ -152,11 +239,13 @@ int FWTPM_TIS_ClientConnect(FWTPM_TIS_CLIENT_CTX* client) return TPM_RC_FAILURE; } - /* Validate magic */ - if (shm->magic != FWTPM_TIS_MAGIC) { + /* Acquire the validity sentinel before reading the published header. */ + magic = FWTPM_TIS_LoadMagic(shm); + if (magic != FWTPM_TIS_MAGIC || + shm->version != FWTPM_TIS_VERSION) { #ifdef DEBUG_WOLFTPM - printf("fwTPM HAL: bad magic 0x%08x (expected 0x%08x)\n", - (unsigned int)shm->magic, (unsigned int)FWTPM_TIS_MAGIC); + printf("fwTPM HAL: bad header magic=0x%08x version=%u\n", + (unsigned int)magic, (unsigned int)shm->version); #endif munmap(shm, sizeof(FWTPM_TIS_REGS)); close(fd); @@ -164,22 +253,22 @@ int FWTPM_TIS_ClientConnect(FWTPM_TIS_CLIENT_CTX* client) } /* Open existing semaphores (server creates them) */ - semCmd = sem_open(FWTPM_TIS_SEM_CMD, 0); + semCmd = sem_open(semCmdName, 0); if (semCmd == SEM_FAILED) { #ifdef DEBUG_WOLFTPM printf("fwTPM HAL: sem_open(%s) failed: %d (%s)\n", - FWTPM_TIS_SEM_CMD, errno, strerror(errno)); + semCmdName, errno, strerror(errno)); #endif munmap(shm, sizeof(FWTPM_TIS_REGS)); close(fd); return TPM_RC_FAILURE; } - semRsp = sem_open(FWTPM_TIS_SEM_RSP, 0); + semRsp = sem_open(semRspName, 0); if (semRsp == SEM_FAILED) { #ifdef DEBUG_WOLFTPM printf("fwTPM HAL: sem_open(%s) failed: %d (%s)\n", - FWTPM_TIS_SEM_RSP, errno, strerror(errno)); + semRspName, errno, strerror(errno)); #endif sem_close(semCmd); munmap(shm, sizeof(FWTPM_TIS_REGS)); @@ -187,10 +276,13 @@ int FWTPM_TIS_ClientConnect(FWTPM_TIS_CLIENT_CTX* client) return TPM_RC_FAILURE; } - /* Reject a mapping from an old server generation paired with newly - * recreated semaphore names. */ - if (stat(FWTPM_TIS_SHM_PATH, &pathSt) != 0 || - st.st_dev != pathSt.st_dev || st.st_ino != pathSt.st_ino) { + /* Reject a replaced pathname or metadata change after opening the + * per-owner semaphore pair. */ + if (fstat(fd, &st) != 0 || + FWTPM_TIS_ClientValidateShm(&st) != TPM_RC_SUCCESS || + lstat(FWTPM_TIS_SHM_PATH, &pathSt) != 0 || + !S_ISREG(pathSt.st_mode) || st.st_dev != pathSt.st_dev || + st.st_ino != pathSt.st_ino) { sem_close(semRsp); sem_close(semCmd); munmap(shm, sizeof(FWTPM_TIS_REGS)); diff --git a/src/fwtpm/fwtpm_tis.c b/src/fwtpm/fwtpm_tis.c index 212708b4..6bbea2eb 100644 --- a/src/fwtpm/fwtpm_tis.c +++ b/src/fwtpm/fwtpm_tis.c @@ -68,6 +68,15 @@ static UINT32 TisBuildSts(BYTE stsFlags, UINT16 burstCount) return ((UINT32)burstCount << 8) | (UINT32)stsFlags; } +static void TisPublishMagic(FWTPM_TIS_REGS* regs) +{ +#if defined(__GNUC__) || defined(__clang__) + __atomic_store_n(®s->magic, FWTPM_TIS_MAGIC, __ATOMIC_RELEASE); +#else + regs->magic = FWTPM_TIS_MAGIC; +#endif +} + /* Handle a single TIS register access */ static void TisHandleRegAccess(FWTPM_CTX* ctx, FWTPM_TIS_REGS* regs) { @@ -436,7 +445,6 @@ int FWTPM_TIS_Init(FWTPM_CTX* ctx) /* Initialize register state */ XMEMSET(regs, 0, sizeof(FWTPM_TIS_REGS)); - regs->magic = FWTPM_TIS_MAGIC; regs->version = FWTPM_TIS_VERSION; /* Power-on register defaults. ACCESS is not stored here: it is served @@ -451,6 +459,9 @@ int FWTPM_TIS_Init(FWTPM_CTX* ctx) regs->did_vid = FWTPM_TIS_DID_VID_VAL; regs->rid = FWTPM_VERSION_PATCH; + /* Release-publish the validity sentinel after all client-visible state. */ + TisPublishMagic(regs); + /* Auto power-on in TIS mode (no platform port to signal power) */ ctx->powerOn = 1; diff --git a/src/fwtpm/fwtpm_tis_sem.c b/src/fwtpm/fwtpm_tis_sem.c new file mode 100644 index 00000000..29f84949 --- /dev/null +++ b/src/fwtpm/fwtpm_tis_sem.c @@ -0,0 +1,64 @@ +/* fwtpm_tis_sem.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#if defined(WOLFTPM_FWTPM_TIS) || defined(WOLFTPM_FWTPM_HAL) + +#include + +static int TisMakeSemName(const char* base, UINT64 ownerId, char* name, + size_t nameSz) +{ + int nameLen; + + if (base == NULL || name == NULL || nameSz == 0U) { + return -1; + } + nameLen = XSNPRINTF(name, nameSz, "%s-%016llx", base, + (unsigned long long)ownerId); + if (nameLen <= 0 || (size_t)nameLen >= nameSz) { + return -1; + } + return 0; +} + +WOLFTPM_LOCAL int FWTPM_TIS_MakeSemNames(UINT64 ownerUid, char* semCmd, + size_t semCmdSz, char* semRsp, size_t semRspSz) +{ + /* Namespacing only, not a cryptographic identity. The authenticated UID + * remains stable when the shared-memory pathname is removed after a + * crash, allowing the next server to collect both semaphore objects. */ + UINT64 ownerId = ownerUid * 0x9E3779B185EBCA87ULL; + + if (TisMakeSemName(FWTPM_TIS_SEM_CMD, ownerId, semCmd, semCmdSz) != 0 || + TisMakeSemName(FWTPM_TIS_SEM_RSP, ownerId, semRsp, + semRspSz) != 0) { + return -1; + } + return 0; +} + +#endif /* WOLFTPM_FWTPM_TIS || WOLFTPM_FWTPM_HAL */ diff --git a/src/fwtpm/fwtpm_tis_shm.c b/src/fwtpm/fwtpm_tis_shm.c index acdb0d5a..4d8edf55 100644 --- a/src/fwtpm/fwtpm_tis_shm.c +++ b/src/fwtpm/fwtpm_tis_shm.c @@ -58,11 +58,20 @@ typedef struct { int shmFd; /* shm file descriptor */ sem_t* semCmd; /* command semaphore */ sem_t* semRsp; /* response semaphore */ + char semCmdName[FWTPM_TIS_SEM_NAME_SIZE]; + char semRspName[FWTPM_TIS_SEM_NAME_SIZE]; } FWTPM_TIS_SHM_CTX; /* Single server per process */ static FWTPM_TIS_SHM_CTX gTisShmCtx; +static void TisShmUnlinkOldEndpoint(const char* semCmdName, + const char* semRspName) +{ + (void)sem_unlink(semCmdName); + (void)sem_unlink(semRspName); +} + /* --- HAL Callbacks --- */ static int TisShmInit(void* ctx, FWTPM_TIS_REGS** regs) @@ -86,6 +95,8 @@ static int TisShmInit(void* ctx, FWTPM_TIS_REGS** regs) shm->shmFd = -1; shm->semCmd = NULL; shm->semRsp = NULL; + shm->semCmdName[0] = '\0'; + shm->semRspName[0] = '\0'; /* Use a fresh inode so locks from a crashed server generation do not * prevent new clients from connecting to the replacement server. */ @@ -94,7 +105,6 @@ static int TisShmInit(void* ctx, FWTPM_TIS_REGS** regs) FWTPM_TIS_SHM_PATH, errno, strerror(errno)); return -1; } - /* Create shared memory file */ fd = open(FWTPM_TIS_SHM_PATH, openFlags, 0600); if (fd < 0) { @@ -103,13 +113,13 @@ static int TisShmInit(void* ctx, FWTPM_TIS_REGS** regs) return -1; } - /* A pre-existing file may carry attacker-controlled ownership or - * permissions; require our own UID and force 0600 (O_CREAT without - * O_EXCL cannot prevent the pre-creation race on its own). */ - if (fstat(fd, &shmStat) != 0 || shmStat.st_uid != getuid() || + /* Require our own UID and force 0600 on the newly created endpoint. */ + if (fstat(fd, &shmStat) != 0 || !S_ISREG(shmStat.st_mode) || + shmStat.st_uid != geteuid() || shmStat.st_nlink != 1 || fchmod(fd, S_IRUSR | S_IWUSR) != 0) { fprintf(stderr, "fwTPM TIS: shm ownership/permission check failed\n"); close(fd); + (void)unlink(FWTPM_TIS_SHM_PATH); return -1; } @@ -117,6 +127,25 @@ static int TisShmInit(void* ctx, FWTPM_TIS_REGS** regs) fprintf(stderr, "fwTPM TIS: ftruncate failed: %d (%s)\n", errno, strerror(errno)); close(fd); + (void)unlink(FWTPM_TIS_SHM_PATH); + return -1; + } + + if (fstat(fd, &shmStat) != 0 || + shmStat.st_size != (off_t)sizeof(FWTPM_TIS_REGS) || + (shmStat.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) != + (S_IRUSR | S_IWUSR)) { + fprintf(stderr, "fwTPM TIS: invalid shm endpoint metadata\n"); + close(fd); + (void)unlink(FWTPM_TIS_SHM_PATH); + return -1; + } + if (FWTPM_TIS_MakeSemNames((UINT64)shmStat.st_uid, + shm->semCmdName, sizeof(shm->semCmdName), + shm->semRspName, sizeof(shm->semRspName)) != 0) { + fprintf(stderr, "fwTPM TIS: failed to derive semaphore names\n"); + close(fd); + (void)unlink(FWTPM_TIS_SHM_PATH); return -1; } @@ -127,38 +156,41 @@ static int TisShmInit(void* ctx, FWTPM_TIS_REGS** regs) errno, strerror(errno)); shm->regs = NULL; close(fd); + (void)unlink(FWTPM_TIS_SHM_PATH); return -1; } shm->shmFd = fd; - /* Create semaphores (remove stale ones first) */ - sem_unlink(FWTPM_TIS_SEM_CMD); - sem_unlink(FWTPM_TIS_SEM_RSP); - - shm->semCmd = sem_open(FWTPM_TIS_SEM_CMD, O_CREAT | O_EXCL, 0600, 0); + /* Remove stale names only after the data endpoint is ready, immediately + * before O_EXCL recreates them. Early startup failures therefore leave a + * still-running server generation's semaphore names intact. */ + TisShmUnlinkOldEndpoint(shm->semCmdName, shm->semRspName); + shm->semCmd = sem_open(shm->semCmdName, O_CREAT | O_EXCL, 0600, 0); if (shm->semCmd == SEM_FAILED) { fprintf(stderr, "fwTPM TIS: sem_open(%s) failed: %d (%s)\n", - FWTPM_TIS_SEM_CMD, errno, strerror(errno)); + shm->semCmdName, errno, strerror(errno)); shm->semCmd = NULL; munmap(shm->regs, sizeof(FWTPM_TIS_REGS)); shm->regs = NULL; close(fd); shm->shmFd = -1; + (void)unlink(FWTPM_TIS_SHM_PATH); return -1; } - shm->semRsp = sem_open(FWTPM_TIS_SEM_RSP, O_CREAT | O_EXCL, 0600, 0); + shm->semRsp = sem_open(shm->semRspName, O_CREAT | O_EXCL, 0600, 0); if (shm->semRsp == SEM_FAILED) { fprintf(stderr, "fwTPM TIS: sem_open(%s) failed: %d (%s)\n", - FWTPM_TIS_SEM_RSP, errno, strerror(errno)); + shm->semRspName, errno, strerror(errno)); sem_close(shm->semCmd); - sem_unlink(FWTPM_TIS_SEM_CMD); + sem_unlink(shm->semCmdName); shm->semCmd = NULL; shm->semRsp = NULL; munmap(shm->regs, sizeof(FWTPM_TIS_REGS)); shm->regs = NULL; close(fd); shm->shmFd = -1; + (void)unlink(FWTPM_TIS_SHM_PATH); return -1; } @@ -167,7 +199,7 @@ static int TisShmInit(void* ctx, FWTPM_TIS_REGS** regs) printf("fwTPM TIS: Shared memory at %s (%zu bytes)\n", FWTPM_TIS_SHM_PATH, sizeof(FWTPM_TIS_REGS)); printf("fwTPM TIS: Semaphores: cmd=%s, rsp=%s\n", - FWTPM_TIS_SEM_CMD, FWTPM_TIS_SEM_RSP); + shm->semCmdName, shm->semRspName); return 0; } @@ -203,7 +235,11 @@ static void TisShmCleanup(void* ctx) FWTPM_TIS_SHM_CTX* shm = (FWTPM_TIS_SHM_CTX*)ctx; if (shm->regs != NULL) { + #if defined(__GNUC__) || defined(__clang__) + __atomic_store_n(&shm->regs->magic, 0U, __ATOMIC_RELEASE); + #else shm->regs->magic = 0; + #endif TPM2_ForceZero(shm->regs->reg_data, sizeof(shm->regs->reg_data)); if (shm->semRsp != NULL && shm->semRsp != SEM_FAILED) { @@ -212,12 +248,16 @@ static void TisShmCleanup(void* ctx) } if (shm->semRsp != NULL && shm->semRsp != SEM_FAILED) { sem_close(shm->semRsp); - sem_unlink(FWTPM_TIS_SEM_RSP); + if (shm->semRspName[0] != '\0') { + sem_unlink(shm->semRspName); + } shm->semRsp = NULL; } if (shm->semCmd != NULL && shm->semCmd != SEM_FAILED) { sem_close(shm->semCmd); - sem_unlink(FWTPM_TIS_SEM_CMD); + if (shm->semCmdName[0] != '\0') { + sem_unlink(shm->semCmdName); + } shm->semCmd = NULL; } if (shm->regs != NULL) { diff --git a/src/fwtpm/include.am b/src/fwtpm/include.am index 60310d2c..378ac67e 100644 --- a/src/fwtpm/include.am +++ b/src/fwtpm/include.am @@ -21,9 +21,14 @@ src_fwtpm_fwtpm_server_CPPFLAGS = -DWOLFTPM_FWTPM $(AM_CPPFLAGS) src_fwtpm_fwtpm_server_LDADD = $(LIB_STATIC_ADD) if BUILD_FWTPM_TIS src_fwtpm_fwtpm_server_SOURCES += src/fwtpm/fwtpm_tis.c \ + src/fwtpm/fwtpm_tis_sem.c \ src/fwtpm/fwtpm_tis_shm.c src_fwtpm_fwtpm_server_CFLAGS += -DWOLFTPM_FWTPM_TIS src_fwtpm_fwtpm_server_CPPFLAGS += -DWOLFTPM_FWTPM_TIS +src_fwtpm_fwtpm_server_LDADD += $(FWTPM_TIS_LIBS) +if !BUILD_FWTPM_ONLY +src_libwolftpm_la_SOURCES += src/fwtpm/fwtpm_tis_sem.c +endif endif # fwtpm_server doesn't link libwolftpm.la (it compiles its own copies with diff --git a/src/include.am b/src/include.am index 5a1597dc..c07f4a75 100644 --- a/src/include.am +++ b/src/include.am @@ -4,6 +4,7 @@ if !BUILD_FWTPM_ONLY lib_LTLIBRARIES+= src/libwolftpm.la +src_libwolftpm_la_LIBADD = $(FWTPM_TIS_LIBS) src_libwolftpm_la_SOURCES = \ src/tpm2.c \ src/tpm2_util.c \ @@ -20,7 +21,7 @@ src_libwolftpm_la_SOURCES += src/tpm2_swtpm.c endif if BUILD_WINAPI src_libwolftpm_la_SOURCES += src/tpm2_winapi.c -src_libwolftpm_la_LIBADD = -ltbs +src_libwolftpm_la_LIBADD += -ltbs endif if BUILD_SPDM # SPDM support using wolfSPDM library diff --git a/tests/cmake_static_consumer/CMakeLists.txt b/tests/cmake_static_consumer/CMakeLists.txt new file mode 100644 index 00000000..34985722 --- /dev/null +++ b/tests/cmake_static_consumer/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.16) + +project(wolftpm_static_consumer C) + +find_package(wolftpm CONFIG REQUIRED) + +add_library(wolftpm_static_consumer INTERFACE) +target_link_libraries(wolftpm_static_consumer INTERFACE wolfssl::wolftpm) diff --git a/tests/fwtpm_hal_unit_tests.c b/tests/fwtpm_hal_unit_tests.c new file mode 100644 index 00000000..72517f8e --- /dev/null +++ b/tests/fwtpm_hal_unit_tests.c @@ -0,0 +1,498 @@ +/* fwtpm_hal_unit_tests.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* Isolated tests for the POSIX fwTPM client HAL. The production source is + * included directly so each test can use PID-specific endpoint names without + * colliding with a running fwTPM server. */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#include +#include +#include +/* Keep this before sem_open interposition below so the macro cannot rewrite + * the system declaration into a conflicting TestSemOpen prototype. */ +#include +#include +#include +#include + +static char gTestShmPath[96]; +static char gTestAuxPath[96]; +static char gTestSemCmdBase[16]; +static char gTestSemRspBase[16]; +static int gSwapAfterSemOpen; +static int gSemOpenCount; +static int gSwapFailed; + +static int CreateEndpointFile(const char* path, mode_t mode, + off_t sizeAdjust, UINT32 magic, UINT32 version); +static int ReplaceEndpoint(void); + +#define FWTPM_TIS_SHM_PATH gTestShmPath +#define FWTPM_TIS_SEM_CMD gTestSemCmdBase +#define FWTPM_TIS_SEM_RSP gTestSemRspBase +#define WOLFTPM_INCLUDE_IO_FILE + +#include "../hal/tpm_io.h" +#include + +static char gTestSemCmd[FWTPM_TIS_SEM_NAME_SIZE]; +static char gTestSemRsp[FWTPM_TIS_SEM_NAME_SIZE]; + +/* tpm_io_fwtpm.c normally gets this helper from libwolftpm. */ +static void TestForceZero(void* mem, word32 len) +{ + volatile byte* p = (volatile byte*)mem; + + while (len-- > 0U) { + *p++ = 0; + } +} + +#include "../src/fwtpm/fwtpm_tis_sem.c" + +static sem_t* TestSemOpen(const char* name, int oflag, ...) +{ + sem_t* sem = sem_open(name, oflag); + + if (sem != SEM_FAILED && gSwapAfterSemOpen) { + gSemOpenCount++; + if (gSemOpenCount == 2 && ReplaceEndpoint() != 0) { + gSwapFailed = 1; + } + } + return sem; +} + +#define TPM2_ForceZero TestForceZero +#define sem_open TestSemOpen +#include "../hal/tpm_io_fwtpm.c" +#undef sem_open +#undef TPM2_ForceZero + +static void CleanupEndpoint(void) +{ + (void)sem_unlink(gTestSemRsp); + (void)sem_unlink(gTestSemCmd); + (void)unlink(gTestShmPath); + (void)unlink(gTestAuxPath); + gSwapAfterSemOpen = 0; + gSemOpenCount = 0; + gSwapFailed = 0; +} + +static int CreateEndpointFile(const char* path, mode_t mode, + off_t sizeAdjust, UINT32 magic, UINT32 version) +{ + FWTPM_TIS_REGS* regs; + off_t fileSize = (off_t)sizeof(FWTPM_TIS_REGS) + sizeAdjust; + int fd = -1; + int rc = -1; + + if (fileSize <= 0) { + return -1; + } + fd = open(path, O_CREAT | O_EXCL | O_RDWR, 0600); + if (fd < 0 || fchmod(fd, mode) != 0 || + ftruncate(fd, fileSize) != 0) { + goto exit; + } + + if (fileSize >= (off_t)sizeof(FWTPM_TIS_REGS)) { + regs = (FWTPM_TIS_REGS*)mmap(NULL, sizeof(FWTPM_TIS_REGS), + PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (regs == MAP_FAILED) { + goto exit; + } + XMEMSET(regs, 0, sizeof(*regs)); + regs->magic = magic; + regs->version = version; + (void)munmap(regs, sizeof(FWTPM_TIS_REGS)); + } + + rc = 0; + +exit: + if (fd >= 0) { + (void)close(fd); + } + if (rc != 0) { + (void)unlink(path); + } + return rc; +} + +static int ReplaceEndpoint(void) +{ + (void)unlink(gTestAuxPath); + if (CreateEndpointFile(gTestAuxPath, 0600, 0, FWTPM_TIS_MAGIC, + FWTPM_TIS_VERSION) != 0) { + return -1; + } + if (rename(gTestAuxPath, gTestShmPath) != 0) { + (void)unlink(gTestAuxPath); + return -1; + } + return 0; +} + +static int CreateEndpoint(mode_t mode, off_t sizeAdjust, UINT32 magic, + UINT32 version) +{ + sem_t* semCmd = SEM_FAILED; + sem_t* semRsp = SEM_FAILED; + int rc = -1; + + CleanupEndpoint(); + if (FWTPM_TIS_MakeSemNames((UINT64)geteuid(), gTestSemCmd, + sizeof(gTestSemCmd), gTestSemRsp, sizeof(gTestSemRsp)) != 0 || + CreateEndpointFile(gTestShmPath, mode, sizeAdjust, magic, + version) != 0) { + goto exit; + } + + semCmd = sem_open(gTestSemCmd, O_CREAT | O_EXCL, 0600, 0); + semRsp = sem_open(gTestSemRsp, O_CREAT | O_EXCL, 0600, 0); + if (semCmd == SEM_FAILED || semRsp == SEM_FAILED) { + goto exit; + } + rc = 0; + +exit: + if (semRsp != SEM_FAILED) { + (void)sem_close(semRsp); + } + if (semCmd != SEM_FAILED) { + (void)sem_close(semCmd); + } + if (rc != 0) { + CleanupEndpoint(); + } + return rc; +} + +static int ExpectRejected(const char* name, mode_t mode, off_t sizeAdjust, + UINT32 magic, UINT32 version) +{ + FWTPM_TIS_CLIENT_CTX client; + int rc; + + if (CreateEndpoint(mode, sizeAdjust, magic, version) != 0) { + printf("FAIL: %s setup\n", name); + return 1; + } + + rc = FWTPM_TIS_ClientConnect(&client); + if (rc == TPM_RC_SUCCESS) { + FWTPM_TIS_ClientDisconnect(&client); + printf("FAIL: accepted %s endpoint\n", name); + CleanupEndpoint(); + return 1; + } + + printf("PASS: rejected %s endpoint\n", name); + CleanupEndpoint(); + return 0; +} + +static int ExpectAccepted(void) +{ + FWTPM_TIS_CLIENT_CTX client; + int rc; + + /* Default UID-derived names are covered by fwtpm_tis_sem_unit.test. */ + if (CreateEndpoint(0600, 0, FWTPM_TIS_MAGIC, + FWTPM_TIS_VERSION) != 0) { + printf("FAIL: trusted endpoint setup\n"); + return 1; + } + + rc = FWTPM_TIS_ClientConnect(&client); + if (rc != TPM_RC_SUCCESS) { + printf("FAIL: rejected trusted endpoint\n"); + CleanupEndpoint(); + return 1; + } + + FWTPM_TIS_ClientDisconnect(&client); + printf("PASS: accepted trusted endpoint\n"); + CleanupEndpoint(); + return 0; +} + +static int ExpectDisconnectZeroized(void) +{ + FWTPM_TIS_CLIENT_CTX client; + FWTPM_TIS_REGS* observer = MAP_FAILED; + int observerFd = -1; + int rc; + int idx; + int failed = 0; + + if (CreateEndpoint(0600, 0, FWTPM_TIS_MAGIC, + FWTPM_TIS_VERSION) != 0) { + printf("FAIL: disconnect zeroization setup\n"); + return 1; + } + observerFd = open(gTestShmPath, O_RDWR); + if (observerFd < 0) { + failed = 1; + goto exit; + } + observer = (FWTPM_TIS_REGS*)mmap(NULL, sizeof(*observer), + PROT_READ | PROT_WRITE, MAP_SHARED, observerFd, 0); + if (observer == MAP_FAILED) { + failed = 1; + goto exit; + } + + rc = FWTPM_TIS_ClientConnect(&client); + if (rc != TPM_RC_SUCCESS) { + failed = 1; + goto exit; + } + XMEMSET(client.shm->reg_data, 0xA5, sizeof(client.shm->reg_data)); + FWTPM_TIS_ClientDisconnect(&client); + + for (idx = 0; idx < (int)sizeof(observer->reg_data); idx++) { + if (observer->reg_data[idx] != 0U) { + failed = 1; + break; + } + } + +exit: + if (observer != MAP_FAILED) { + (void)munmap(observer, sizeof(*observer)); + } + if (observerFd >= 0) { + (void)close(observerFd); + } + printf("%s: disconnect zeroized shared register data\n", + failed ? "FAIL" : "PASS"); + CleanupEndpoint(); + return failed; +} + +static int ExpectSymlinkRejected(void) +{ + FWTPM_TIS_CLIENT_CTX client; + int rc; + + if (CreateEndpoint(0600, 0, FWTPM_TIS_MAGIC, + FWTPM_TIS_VERSION) != 0 || + rename(gTestShmPath, gTestAuxPath) != 0 || + symlink(gTestAuxPath, gTestShmPath) != 0) { + printf("FAIL: symlink setup\n"); + CleanupEndpoint(); + return 1; + } + rc = FWTPM_TIS_ClientConnect(&client); + if (rc == TPM_RC_SUCCESS) { + FWTPM_TIS_ClientDisconnect(&client); + printf("FAIL: accepted symlink endpoint\n"); + CleanupEndpoint(); + return 1; + } + printf("PASS: rejected symlink endpoint\n"); + CleanupEndpoint(); + return 0; +} + +static int ExpectHardlinkRejected(void) +{ + FWTPM_TIS_CLIENT_CTX client; + int rc; + + if (CreateEndpoint(0600, 0, FWTPM_TIS_MAGIC, + FWTPM_TIS_VERSION) != 0 || + link(gTestShmPath, gTestAuxPath) != 0) { + printf("FAIL: hardlink setup\n"); + CleanupEndpoint(); + return 1; + } + rc = FWTPM_TIS_ClientConnect(&client); + if (rc == TPM_RC_SUCCESS) { + FWTPM_TIS_ClientDisconnect(&client); + printf("FAIL: accepted hardlink endpoint\n"); + CleanupEndpoint(); + return 1; + } + printf("PASS: rejected hardlink endpoint\n"); + CleanupEndpoint(); + return 0; +} + +static int ExpectFifoRejected(void) +{ + FWTPM_TIS_CLIENT_CTX client; + int rc; + + CleanupEndpoint(); + if (mkfifo(gTestShmPath, 0600) != 0) { + printf("FAIL: FIFO setup\n"); + return 1; + } + rc = FWTPM_TIS_ClientConnect(&client); + if (rc == TPM_RC_SUCCESS) { + FWTPM_TIS_ClientDisconnect(&client); + printf("FAIL: accepted FIFO endpoint\n"); + CleanupEndpoint(); + return 1; + } + printf("PASS: rejected FIFO endpoint\n"); + CleanupEndpoint(); + return 0; +} + +static int ExpectSwapRejected(void) +{ + FWTPM_TIS_CLIENT_CTX client; + int rc; + + if (CreateEndpoint(0600, 0, FWTPM_TIS_MAGIC, + FWTPM_TIS_VERSION) != 0) { + printf("FAIL: pathname-swap setup\n"); + return 1; + } + gSwapAfterSemOpen = 1; + rc = FWTPM_TIS_ClientConnect(&client); + if (gSwapFailed || gSemOpenCount != 2 || rc == TPM_RC_SUCCESS) { + if (rc == TPM_RC_SUCCESS) { + FWTPM_TIS_ClientDisconnect(&client); + } + printf("FAIL: accepted or failed to exercise pathname swap\n"); + CleanupEndpoint(); + return 1; + } + printf("PASS: rejected endpoint swapped after semaphore open\n"); + CleanupEndpoint(); + return 0; +} + +static int TestMetadataValidation(void) +{ + struct stat st; + uid_t expectedUid = geteuid(); + + XMEMSET(&st, 0, sizeof(st)); + st.st_mode = S_IFREG | S_IRUSR | S_IWUSR; + st.st_uid = expectedUid; + st.st_nlink = 1; + st.st_size = (off_t)sizeof(FWTPM_TIS_REGS); + if (FWTPM_TIS_ClientValidateShm(&st) != TPM_RC_SUCCESS) { + printf("FAIL: rejected trusted metadata\n"); + return 1; + } + + st.st_uid = (uid_t)(expectedUid + 1U); + if (FWTPM_TIS_ClientValidateShm(&st) == TPM_RC_SUCCESS) { + printf("FAIL: accepted wrong-owner metadata\n"); + return 1; + } + st.st_uid = expectedUid; + st.st_mode = S_IFIFO | S_IRUSR | S_IWUSR; + if (FWTPM_TIS_ClientValidateShm(&st) == TPM_RC_SUCCESS) { + printf("FAIL: accepted non-regular metadata\n"); + return 1; + } + st.st_mode = S_IFREG | S_IRUSR | S_IWUSR; + st.st_nlink = 2; + if (FWTPM_TIS_ClientValidateShm(&st) == TPM_RC_SUCCESS) { + printf("FAIL: accepted multiply-linked metadata\n"); + return 1; + } + printf("PASS: validated endpoint metadata\n"); + return 0; +} + +static int TestCustomSemPrefixes(void) +{ + char semCmd[FWTPM_TIS_SEM_NAME_SIZE]; + char semRsp[FWTPM_TIS_SEM_NAME_SIZE]; + char otherCmd[FWTPM_TIS_SEM_NAME_SIZE]; + char otherRsp[FWTPM_TIS_SEM_NAME_SIZE]; + UINT64 ownerUid = (UINT64)geteuid(); + size_t cmdPrefixSz = XSTRLEN(gTestSemCmdBase); + size_t rspPrefixSz = XSTRLEN(gTestSemRspBase); + + if (FWTPM_TIS_MakeSemNames(ownerUid, semCmd, sizeof(semCmd), semRsp, + sizeof(semRsp)) != 0 || + FWTPM_TIS_MakeSemNames(ownerUid + 1U, otherCmd, + sizeof(otherCmd), otherRsp, sizeof(otherRsp)) != 0 || + XSTRNCMP(semCmd, gTestSemCmdBase, cmdPrefixSz) != 0 || + XSTRNCMP(semRsp, gTestSemRspBase, rspPrefixSz) != 0 || + semCmd[cmdPrefixSz] != '-' || semRsp[rspPrefixSz] != '-' || + XSTRCMP(semCmd, otherCmd) == 0 || + XSTRCMP(semRsp, otherRsp) == 0) { + printf("FAIL: caller semaphore prefixes lost UID namespacing\n"); + return 1; + } + printf("PASS: namespaced caller-defined semaphore prefixes\n"); + return 0; +} + +int main(void) +{ + int failures = 0; + long pid = (long)getpid(); + unsigned int semId = (unsigned int)pid; + + (void)snprintf(gTestShmPath, sizeof(gTestShmPath), + "/tmp/wolftpm-fwtpm-hal-%ld.shm", pid); + (void)snprintf(gTestAuxPath, sizeof(gTestAuxPath), + "/tmp/wolftpm-fwtpm-hal-%ld.aux", pid); + (void)snprintf(gTestSemCmdBase, sizeof(gTestSemCmdBase), + "/c%x", semId); + (void)snprintf(gTestSemRspBase, sizeof(gTestSemRspBase), + "/r%x", semId); + + failures += ExpectRejected("group-readable", 0640, 0, + FWTPM_TIS_MAGIC, FWTPM_TIS_VERSION); + failures += ExpectRejected("world-readable", 0604, 0, + FWTPM_TIS_MAGIC, FWTPM_TIS_VERSION); + failures += ExpectRejected("undersized", 0600, -1, + FWTPM_TIS_MAGIC, FWTPM_TIS_VERSION); + failures += ExpectRejected("oversized", 0600, 1, + FWTPM_TIS_MAGIC, FWTPM_TIS_VERSION); + failures += ExpectRejected("bad magic", 0600, 0, + FWTPM_TIS_MAGIC ^ 1U, FWTPM_TIS_VERSION); + failures += ExpectRejected("bad version", 0600, 0, + FWTPM_TIS_MAGIC, FWTPM_TIS_VERSION + 1U); + failures += ExpectSymlinkRejected(); + failures += ExpectHardlinkRejected(); + failures += ExpectFifoRejected(); + failures += ExpectSwapRejected(); + failures += ExpectAccepted(); + failures += ExpectDisconnectZeroized(); + failures += TestMetadataValidation(); + failures += TestCustomSemPrefixes(); + + CleanupEndpoint(); + printf("fwTPM HAL endpoint tests: %s\n", + failures == 0 ? "passed" : "failed"); + return failures == 0 ? 0 : 1; +} diff --git a/tests/fwtpm_tis_sem_unit_tests.c b/tests/fwtpm_tis_sem_unit_tests.c new file mode 100644 index 00000000..c44eb989 --- /dev/null +++ b/tests/fwtpm_tis_sem_unit_tests.c @@ -0,0 +1,71 @@ +/* fwtpm_tis_sem_unit_tests.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include + +#include +#include + +static int TestSemNames(void) +{ + const UINT64 uidA = 17U; + const UINT64 uidB = 18U; + char cmdA[FWTPM_TIS_SEM_NAME_SIZE]; + char rspA[FWTPM_TIS_SEM_NAME_SIZE]; + char cmdB[FWTPM_TIS_SEM_NAME_SIZE]; + char rspB[FWTPM_TIS_SEM_NAME_SIZE]; + char expectedCmd[FWTPM_TIS_SEM_NAME_SIZE]; + char expectedRsp[FWTPM_TIS_SEM_NAME_SIZE]; + + if (FWTPM_TIS_MakeSemNames(uidA, cmdA, sizeof(cmdA), rspA, + sizeof(rspA)) != 0 || + FWTPM_TIS_MakeSemNames(uidB, cmdB, sizeof(cmdB), rspB, + sizeof(rspB)) != 0) { + printf("FAIL: could not derive semaphore names\n"); + return 1; + } + /* Pinned vector for uid 17: do not recompute the production hash here. */ + (void)XSNPRINTF(expectedCmd, sizeof(expectedCmd), "%s-%s", + FWTPM_TIS_SEM_CMD, "81af14c9e4a872f7"); + (void)XSNPRINTF(expectedRsp, sizeof(expectedRsp), "%s-%s", + FWTPM_TIS_SEM_RSP, "81af14c9e4a872f7"); + if (strcmp(cmdA, expectedCmd) != 0 || strcmp(rspA, expectedRsp) != 0) { + printf("FAIL: semaphore name has the wrong UID suffix\n"); + return 1; + } + if (strcmp(cmdA, cmdB) == 0 || strcmp(rspA, rspB) == 0) { + printf("FAIL: different UIDs produced the same semaphore names\n"); + return 1; + } + + printf("fwTPM TIS semaphore tests: passed\n"); + return 0; +} + +int main(void) +{ + return TestSemNames(); +} diff --git a/tests/include.am b/tests/include.am index 4b6985a8..75bcbbb8 100644 --- a/tests/include.am +++ b/tests/include.am @@ -37,6 +37,23 @@ tests_fwtpm_unit_test_CPPFLAGS = \ tests_fwtpm_unit_test_LDADD = $(LIB_STATIC_ADD) endif +if BUILD_FWTPM_TIS +check_PROGRAMS += tests/fwtpm_hal_unit.test tests/fwtpm_tis_sem_unit.test +noinst_PROGRAMS += tests/fwtpm_hal_unit.test tests/fwtpm_tis_sem_unit.test +tests_fwtpm_hal_unit_test_SOURCES = tests/fwtpm_hal_unit_tests.c +tests_fwtpm_hal_unit_test_CFLAGS = \ + -DWOLFTPM_FWTPM_HAL -DWOLFTPM_ADV_IO -DWOLFTPM_EXAMPLE_HAL $(AM_CFLAGS) +tests_fwtpm_hal_unit_test_CPPFLAGS = \ + -DWOLFTPM_FWTPM_HAL -DWOLFTPM_ADV_IO -DWOLFTPM_EXAMPLE_HAL $(AM_CPPFLAGS) +tests_fwtpm_hal_unit_test_LDADD = $(FWTPM_TIS_LIBS) +tests_fwtpm_tis_sem_unit_test_SOURCES = \ + tests/fwtpm_tis_sem_unit_tests.c \ + src/fwtpm/fwtpm_tis_sem.c +tests_fwtpm_tis_sem_unit_test_CFLAGS = -DWOLFTPM_FWTPM_TIS $(AM_CFLAGS) +tests_fwtpm_tis_sem_unit_test_CPPFLAGS = -DWOLFTPM_FWTPM_TIS $(AM_CPPFLAGS) +tests_fwtpm_tis_sem_unit_test_LDADD = $(FWTPM_TIS_LIBS) +endif + # TPM make check: manages server, runs unit.test + run_examples.sh # Used for --enable-fwtpm builds if BUILD_FWTPM diff --git a/tests/unit_tests.c b/tests/unit_tests.c index b34d7cb9..21d15721 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -4447,9 +4447,15 @@ static void test_TPM2_DispatchCommand_overflow(void) XMEMSET(small, 0x5A, sizeof(small)); XMEMSET(&ctx, 0, sizeof(ctx)); - /* timeoutTries 0, so this performs no IO but does set the active ctx */ + /* timeoutTries 0, so this performs no IO but does set the active ctx. */ +#if defined(WOLFTPM_LINUX_DEV) || defined(WOLFTPM_SWTPM) || \ + defined(WOLFTPM_WINAPI) AssertIntEQ(TPM2_Init_minimal(&ctx), TPM_RC_SUCCESS); ctx.ioCb = test_ovf_ioCb; +#else + AssertIntEQ(TPM2_Init_ex(&ctx, test_ovf_ioCb, NULL, 0), + TPM_RC_SUCCESS); +#endif /* Control: a payload that fits is not refused here, it goes on to the * transport and fails there instead. This is what makes the guard diff --git a/wolftpm/fwtpm/fwtpm_tis.h b/wolftpm/fwtpm/fwtpm_tis.h index 0377ff89..7486d36b 100644 --- a/wolftpm/fwtpm/fwtpm_tis.h +++ b/wolftpm/fwtpm/fwtpm_tis.h @@ -36,7 +36,8 @@ extern "C" { #endif -/* Shared memory and semaphore paths (POSIX transport defaults) */ +/* Shared-memory path and semaphore-name prefixes (POSIX transport defaults). + * Every semaphore prefix receives a per-UID suffix. */ #ifndef FWTPM_TIS_SHM_PATH #define FWTPM_TIS_SHM_PATH "/tmp/fwtpm.shm" #endif @@ -47,9 +48,13 @@ #define FWTPM_TIS_SEM_RSP "/fwtpm_rsp" #endif +/* Holds a leading slash, the 251 characters Linux permits after it, and NUL. + * Shorter platform limits are enforced by sem_open(). */ +#define FWTPM_TIS_SEM_NAME_SIZE 253 + /* Magic and version for shared memory validation */ #define FWTPM_TIS_MAGIC 0x57544953UL /* "WTIS" */ -#define FWTPM_TIS_VERSION 1 +#define FWTPM_TIS_VERSION 2 /* Default burst count (bytes per FIFO transfer) */ #ifndef FWTPM_TIS_BURST_COUNT @@ -151,6 +156,12 @@ typedef struct FWTPM_TIS_REGS { /* Backward compatibility alias */ typedef FWTPM_TIS_REGS FWTPM_TIS_SHM; +#if defined(WOLFTPM_FWTPM_TIS) || defined(WOLFTPM_FWTPM_HAL) +/* Build the effective POSIX semaphore names for an authenticated owner UID. */ +WOLFTPM_LOCAL int FWTPM_TIS_MakeSemNames(UINT64 ownerUid, char* semCmd, + size_t semCmdSz, char* semRsp, size_t semRspSz); +#endif + /* --- TIS Transport HAL (server-side) --- * Abstracts the transport between client and server for TIS register From ab1bdb36027d9daf96060565fe336add07a138a3 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 31 Aug 2026 13:20:40 -0700 Subject: [PATCH 6/8] F-12738 - Reject truncated signing sequence buffers --- src/fwtpm/fwtpm_command.c | 15 ++++++++--- tests/fwtpm_unit_tests.c | 55 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 787445ca..f5affad0 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -16960,9 +16960,18 @@ static TPM_RC FwCmd_SignSequenceComplete(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* Parse buffer (TPM2B_MAX_BUFFER) */ if (rc == 0) { - TPM2_Packet_ParseU16(cmd, &bufSize); - if (bufSize > (UINT16)FWTPM_MAX_DATA_BUF) { - rc = TPM_RC_SIZE; + if (cmd->pos > cmdSize || + (int)sizeof(UINT16) > cmdSize - cmd->pos) { + rc = TPM_RC_INSUFFICIENT; + } + else { + TPM2_Packet_ParseU16(cmd, &bufSize); + if (bufSize > (UINT16)FWTPM_MAX_DATA_BUF) { + rc = TPM_RC_SIZE; + } + else if ((int)bufSize > cmdSize - cmd->pos) { + rc = TPM_RC_INSUFFICIENT; + } } } if (rc == 0) { diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index 86bd9052..43b3c82f 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -4991,6 +4991,60 @@ static void test_fwtpm_signseqcomplete_neg(void) fwtpm_pass("SignSeqComplete negatives (HANDLE):", 1); } +/* Reject a TPM2B_MAX_BUFFER whose declared size extends past the command + * before any prefix capture, hash finalization, or signing occurs. */ +static void test_fwtpm_signseqcomplete_truncated_buffer(void) +{ + FWTPM_CTX ctx; + int rc, rspSize, pos; + UINT32 keyHandle, seqHandle; + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + keyHandle = fwtpm_neg_mk_mldsa_primary(&ctx); + + /* Start a valid Pure-ML-DSA signing sequence. */ + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_NO_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_SignSequenceStart); pos += 4; + PutU32BE(gCmd + pos, keyHandle); pos += 4; + PutU16BE(gCmd + pos, 0); pos += 2; + PutU16BE(gCmd + pos, 0); pos += 2; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + rc = FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + seqHandle = GetU32BE(gRsp + TPM2_HEADER_SIZE); + + /* Declare four trailing bytes but provide only one. */ + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_SignSequenceComplete); pos += 4; + PutU32BE(gCmd + pos, seqHandle); pos += 4; + PutU32BE(gCmd + pos, keyHandle); pos += 4; + PutU32BE(gCmd + pos, 18); pos += 4; + PutU32BE(gCmd + pos, TPM_RS_PW); pos += 4; + PutU16BE(gCmd + pos, 0); pos += 2; + gCmd[pos++] = 0; PutU16BE(gCmd + pos, 0); pos += 2; + PutU32BE(gCmd + pos, TPM_RS_PW); pos += 4; + PutU16BE(gCmd + pos, 0); pos += 2; + gCmd[pos++] = 0; PutU16BE(gCmd + pos, 0); pos += 2; + PutU16BE(gCmd + pos, 4); pos += 2; + gCmd[pos++] = 0xA5; + PutU32BE(gCmd + 2, (UINT32)pos); + + rspSize = 0; + rc = FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_INSUFFICIENT); + + FWTPM_Cleanup(&ctx); + fwtpm_pass("SignSeqComplete truncated buffer rejected:", 1); +} + /* Handler 6: TPM2_VerifySequenceComplete. Part 3 Sec.20.3. */ static void test_fwtpm_verifyseqcomplete_neg(void) { @@ -13804,6 +13858,7 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_signseqstart_neg(); test_fwtpm_verifyseqstart_neg(); test_fwtpm_signseqcomplete_neg(); + test_fwtpm_signseqcomplete_truncated_buffer(); test_fwtpm_verifyseqcomplete_neg(); test_fwtpm_signdigest_neg(); #ifdef WOLFTPM_HASH_MLDSA From a7b1aa6c3b4307f688f2127853835c7cce29c355 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 2 Sep 2026 11:59:24 -0700 Subject: [PATCH 7/8] Inline fwTPM TIS atomics and semaphore name helpers --- CMakeLists.txt | 27 +----------- docs/FWTPM.md | 1 - hal/tpm_io_fwtpm.c | 31 ++++++++------ src/fwtpm/fwtpm_tis.c | 13 +----- src/fwtpm/fwtpm_tis_sem.c | 64 ---------------------------- src/fwtpm/fwtpm_tis_shm.c | 26 +++++++++--- src/fwtpm/include.am | 4 -- tests/fwtpm_hal_unit_tests.c | 33 ++++++++------- tests/fwtpm_tis_sem_unit_tests.c | 71 -------------------------------- tests/include.am | 10 +---- wolftpm/fwtpm/fwtpm_tis.h | 18 +++++--- 11 files changed, 76 insertions(+), 222 deletions(-) delete mode 100644 src/fwtpm/fwtpm_tis_sem.c delete mode 100644 tests/fwtpm_tis_sem_unit_tests.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 275b957b..2f36c418 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -421,11 +421,8 @@ if(WOLFTPM_FWTPM AND UNIX) endif() endif() -if(WOLFTPM_FWTPM_TIS AND BUILD_WOLFTPM_LIB) - target_sources(wolftpm PRIVATE src/fwtpm/fwtpm_tis_sem.c) - if(UNIX) - target_link_libraries(wolftpm PRIVATE ${WOLFTPM_FWTPM_TIS_LIBS}) - endif() +if(WOLFTPM_FWTPM_TIS AND BUILD_WOLFTPM_LIB AND UNIX) + target_link_libraries(wolftpm PRIVATE ${WOLFTPM_FWTPM_TIS_LIBS}) endif() # Examples @@ -546,7 +543,6 @@ if(WOLFTPM_FWTPM) if(WOLFTPM_FWTPM_TIS) list(APPEND FWTPM_SERVER_SOURCES src/fwtpm/fwtpm_tis.c - src/fwtpm/fwtpm_tis_sem.c src/fwtpm/fwtpm_tis_shm.c ) endif() @@ -618,21 +614,6 @@ if(WOLFTPM_FWTPM) target_link_libraries(fwtpm_hal_unit_test PRIVATE ${WOLFTPM_FWTPM_TIS_LIBS}) endif() - - add_executable(fwtpm_tis_sem_unit_test - tests/fwtpm_tis_sem_unit_tests.c - src/fwtpm/fwtpm_tis_sem.c - ) - target_compile_definitions(fwtpm_tis_sem_unit_test PRIVATE - "WOLFTPM_FWTPM_TIS" - ) - target_link_libraries(fwtpm_tis_sem_unit_test PRIVATE - wolftpm_wolfssl_dep - ) - target_include_directories(fwtpm_tis_sem_unit_test PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_BINARY_DIR} - ) endif() # fwTPM fuzz target (libFuzzer) @@ -665,10 +646,6 @@ if(WOLFTPM_FWTPM) COMMAND fwtpm_hal_unit_test WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) - add_test(NAME fwtpm_tis_sem_unit_test - COMMAND fwtpm_tis_sem_unit_test - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - ) endif() message(STATUS "fwTPM server: enabled") diff --git a/docs/FWTPM.md b/docs/FWTPM.md index d9bcc599..9cd2fbff 100644 --- a/docs/FWTPM.md +++ b/docs/FWTPM.md @@ -45,7 +45,6 @@ fwTPM can replace a hardware TPM for: | `fwtpm_io.c` | Transport layer -- SWTPM TCP socket protocol (default) | | `fwtpm_nv.c` | NV storage -- file-based (default); HAL-abstracted, with a built-in append-only mode for write-once flash | | `fwtpm_tis.c` | TIS register state machine (transport-agnostic) | -| `fwtpm_tis_sem.c` | POSIX semaphore-name derivation shared by server and client HAL | | `fwtpm_tis_shm.c` | POSIX shared memory + semaphore TIS transport | | `fwtpm_main.c` | Server entry point, CLI argument parsing | | `tpm2_util.c` | Shared utilities (hash helpers, ForceZero, PrintBin) | diff --git a/hal/tpm_io_fwtpm.c b/hal/tpm_io_fwtpm.c index ed6bcdfe..ed7f22be 100644 --- a/hal/tpm_io_fwtpm.c +++ b/hal/tpm_io_fwtpm.c @@ -79,20 +79,27 @@ static void FWTPM_TIS_ClientUnlock(int fd) } while (rc != 0 && errno == EINTR); } -static UINT32 FWTPM_TIS_LoadMagic(const FWTPM_TIS_REGS* shm) +static int FWTPM_TIS_ServerActive(const FWTPM_TIS_REGS* shm) { -#if defined(__GNUC__) || defined(__clang__) - return __atomic_load_n(&shm->magic, __ATOMIC_ACQUIRE); -#else - const volatile UINT32* magic = &shm->magic; - - return *magic; -#endif + return FWTPM_TIS_ATOMIC_LOAD(shm->magic) == FWTPM_TIS_MAGIC; } -static int FWTPM_TIS_ServerActive(const FWTPM_TIS_REGS* shm) +/* Must match the server's naming in fwtpm_tis_shm.c. */ +static int FWTPM_TIS_ClientMakeSemNames(uid_t ownerUid, char* semCmd, + size_t semCmdSz, char* semRsp, size_t semRspSz) { - return FWTPM_TIS_LoadMagic(shm) == FWTPM_TIS_MAGIC; + int cmdLen; + int rspLen; + + cmdLen = XSNPRINTF(semCmd, semCmdSz, "%s-%lu", FWTPM_TIS_SEM_CMD, + (unsigned long)ownerUid); + rspLen = XSNPRINTF(semRsp, semRspSz, "%s-%lu", FWTPM_TIS_SEM_RSP, + (unsigned long)ownerUid); + if (cmdLen <= 0 || (size_t)cmdLen >= semCmdSz || + rspLen <= 0 || (size_t)rspLen >= semRspSz) { + return -1; + } + return 0; } static int FWTPM_TIS_ClientValidateShm(const struct stat* st) @@ -219,7 +226,7 @@ int FWTPM_TIS_ClientConnect(FWTPM_TIS_CLIENT_CTX* client) return TPM_RC_FAILURE; } #endif - if (FWTPM_TIS_MakeSemNames((UINT64)st.st_uid, semCmdName, + if (FWTPM_TIS_ClientMakeSemNames(st.st_uid, semCmdName, sizeof(semCmdName), semRspName, sizeof(semRspName)) != 0) { #ifdef DEBUG_WOLFTPM printf("fwTPM HAL: failed to derive semaphore names for uid %lu\n", @@ -240,7 +247,7 @@ int FWTPM_TIS_ClientConnect(FWTPM_TIS_CLIENT_CTX* client) } /* Acquire the validity sentinel before reading the published header. */ - magic = FWTPM_TIS_LoadMagic(shm); + magic = FWTPM_TIS_ATOMIC_LOAD(shm->magic); if (magic != FWTPM_TIS_MAGIC || shm->version != FWTPM_TIS_VERSION) { #ifdef DEBUG_WOLFTPM diff --git a/src/fwtpm/fwtpm_tis.c b/src/fwtpm/fwtpm_tis.c index 6bbea2eb..b78d38ea 100644 --- a/src/fwtpm/fwtpm_tis.c +++ b/src/fwtpm/fwtpm_tis.c @@ -68,15 +68,6 @@ static UINT32 TisBuildSts(BYTE stsFlags, UINT16 burstCount) return ((UINT32)burstCount << 8) | (UINT32)stsFlags; } -static void TisPublishMagic(FWTPM_TIS_REGS* regs) -{ -#if defined(__GNUC__) || defined(__clang__) - __atomic_store_n(®s->magic, FWTPM_TIS_MAGIC, __ATOMIC_RELEASE); -#else - regs->magic = FWTPM_TIS_MAGIC; -#endif -} - /* Handle a single TIS register access */ static void TisHandleRegAccess(FWTPM_CTX* ctx, FWTPM_TIS_REGS* regs) { @@ -459,8 +450,8 @@ int FWTPM_TIS_Init(FWTPM_CTX* ctx) regs->did_vid = FWTPM_TIS_DID_VID_VAL; regs->rid = FWTPM_VERSION_PATCH; - /* Release-publish the validity sentinel after all client-visible state. */ - TisPublishMagic(regs); + /* Publish the validity sentinel only after all client-visible state. */ + FWTPM_TIS_ATOMIC_STORE(regs->magic, FWTPM_TIS_MAGIC); /* Auto power-on in TIS mode (no platform port to signal power) */ ctx->powerOn = 1; diff --git a/src/fwtpm/fwtpm_tis_sem.c b/src/fwtpm/fwtpm_tis_sem.c deleted file mode 100644 index 29f84949..00000000 --- a/src/fwtpm/fwtpm_tis_sem.c +++ /dev/null @@ -1,64 +0,0 @@ -/* fwtpm_tis_sem.c - * - * Copyright (C) 2006-2026 wolfSSL Inc. - * - * This file is part of wolfTPM. - * - * wolfTPM is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * wolfTPM is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA - */ - -#ifdef HAVE_CONFIG_H - #include -#endif - -#include - -#if defined(WOLFTPM_FWTPM_TIS) || defined(WOLFTPM_FWTPM_HAL) - -#include - -static int TisMakeSemName(const char* base, UINT64 ownerId, char* name, - size_t nameSz) -{ - int nameLen; - - if (base == NULL || name == NULL || nameSz == 0U) { - return -1; - } - nameLen = XSNPRINTF(name, nameSz, "%s-%016llx", base, - (unsigned long long)ownerId); - if (nameLen <= 0 || (size_t)nameLen >= nameSz) { - return -1; - } - return 0; -} - -WOLFTPM_LOCAL int FWTPM_TIS_MakeSemNames(UINT64 ownerUid, char* semCmd, - size_t semCmdSz, char* semRsp, size_t semRspSz) -{ - /* Namespacing only, not a cryptographic identity. The authenticated UID - * remains stable when the shared-memory pathname is removed after a - * crash, allowing the next server to collect both semaphore objects. */ - UINT64 ownerId = ownerUid * 0x9E3779B185EBCA87ULL; - - if (TisMakeSemName(FWTPM_TIS_SEM_CMD, ownerId, semCmd, semCmdSz) != 0 || - TisMakeSemName(FWTPM_TIS_SEM_RSP, ownerId, semRsp, - semRspSz) != 0) { - return -1; - } - return 0; -} - -#endif /* WOLFTPM_FWTPM_TIS || WOLFTPM_FWTPM_HAL */ diff --git a/src/fwtpm/fwtpm_tis_shm.c b/src/fwtpm/fwtpm_tis_shm.c index 4d8edf55..e9564039 100644 --- a/src/fwtpm/fwtpm_tis_shm.c +++ b/src/fwtpm/fwtpm_tis_shm.c @@ -72,6 +72,24 @@ static void TisShmUnlinkOldEndpoint(const char* semCmdName, (void)sem_unlink(semRspName); } +/* Per-UID names keep concurrent users' wakeup semaphores apart. */ +static int TisShmMakeSemNames(uid_t ownerUid, char* semCmd, size_t semCmdSz, + char* semRsp, size_t semRspSz) +{ + int cmdLen; + int rspLen; + + cmdLen = XSNPRINTF(semCmd, semCmdSz, "%s-%lu", FWTPM_TIS_SEM_CMD, + (unsigned long)ownerUid); + rspLen = XSNPRINTF(semRsp, semRspSz, "%s-%lu", FWTPM_TIS_SEM_RSP, + (unsigned long)ownerUid); + if (cmdLen <= 0 || (size_t)cmdLen >= semCmdSz || + rspLen <= 0 || (size_t)rspLen >= semRspSz) { + return -1; + } + return 0; +} + /* --- HAL Callbacks --- */ static int TisShmInit(void* ctx, FWTPM_TIS_REGS** regs) @@ -140,7 +158,7 @@ static int TisShmInit(void* ctx, FWTPM_TIS_REGS** regs) (void)unlink(FWTPM_TIS_SHM_PATH); return -1; } - if (FWTPM_TIS_MakeSemNames((UINT64)shmStat.st_uid, + if (TisShmMakeSemNames(shmStat.st_uid, shm->semCmdName, sizeof(shm->semCmdName), shm->semRspName, sizeof(shm->semRspName)) != 0) { fprintf(stderr, "fwTPM TIS: failed to derive semaphore names\n"); @@ -235,11 +253,7 @@ static void TisShmCleanup(void* ctx) FWTPM_TIS_SHM_CTX* shm = (FWTPM_TIS_SHM_CTX*)ctx; if (shm->regs != NULL) { - #if defined(__GNUC__) || defined(__clang__) - __atomic_store_n(&shm->regs->magic, 0U, __ATOMIC_RELEASE); - #else - shm->regs->magic = 0; - #endif + FWTPM_TIS_ATOMIC_STORE(shm->regs->magic, 0U); TPM2_ForceZero(shm->regs->reg_data, sizeof(shm->regs->reg_data)); if (shm->semRsp != NULL && shm->semRsp != SEM_FAILED) { diff --git a/src/fwtpm/include.am b/src/fwtpm/include.am index 378ac67e..866351ae 100644 --- a/src/fwtpm/include.am +++ b/src/fwtpm/include.am @@ -21,14 +21,10 @@ src_fwtpm_fwtpm_server_CPPFLAGS = -DWOLFTPM_FWTPM $(AM_CPPFLAGS) src_fwtpm_fwtpm_server_LDADD = $(LIB_STATIC_ADD) if BUILD_FWTPM_TIS src_fwtpm_fwtpm_server_SOURCES += src/fwtpm/fwtpm_tis.c \ - src/fwtpm/fwtpm_tis_sem.c \ src/fwtpm/fwtpm_tis_shm.c src_fwtpm_fwtpm_server_CFLAGS += -DWOLFTPM_FWTPM_TIS src_fwtpm_fwtpm_server_CPPFLAGS += -DWOLFTPM_FWTPM_TIS src_fwtpm_fwtpm_server_LDADD += $(FWTPM_TIS_LIBS) -if !BUILD_FWTPM_ONLY -src_libwolftpm_la_SOURCES += src/fwtpm/fwtpm_tis_sem.c -endif endif # fwtpm_server doesn't link libwolftpm.la (it compiles its own copies with diff --git a/tests/fwtpm_hal_unit_tests.c b/tests/fwtpm_hal_unit_tests.c index 72517f8e..557af00d 100644 --- a/tests/fwtpm_hal_unit_tests.c +++ b/tests/fwtpm_hal_unit_tests.c @@ -72,8 +72,6 @@ static void TestForceZero(void* mem, word32 len) } } -#include "../src/fwtpm/fwtpm_tis_sem.c" - static sem_t* TestSemOpen(const char* name, int oflag, ...) { sem_t* sem = sem_open(name, oflag); @@ -167,7 +165,7 @@ static int CreateEndpoint(mode_t mode, off_t sizeAdjust, UINT32 magic, int rc = -1; CleanupEndpoint(); - if (FWTPM_TIS_MakeSemNames((UINT64)geteuid(), gTestSemCmd, + if (FWTPM_TIS_ClientMakeSemNames(geteuid(), gTestSemCmd, sizeof(gTestSemCmd), gTestSemRsp, sizeof(gTestSemRsp)) != 0 || CreateEndpointFile(gTestShmPath, mode, sizeAdjust, magic, version) != 0) { @@ -223,7 +221,6 @@ static int ExpectAccepted(void) FWTPM_TIS_CLIENT_CTX client; int rc; - /* Default UID-derived names are covered by fwtpm_tis_sem_unit.test. */ if (CreateEndpoint(0600, 0, FWTPM_TIS_MAGIC, FWTPM_TIS_VERSION) != 0) { printf("FAIL: trusted endpoint setup\n"); @@ -435,22 +432,30 @@ static int TestCustomSemPrefixes(void) char semRsp[FWTPM_TIS_SEM_NAME_SIZE]; char otherCmd[FWTPM_TIS_SEM_NAME_SIZE]; char otherRsp[FWTPM_TIS_SEM_NAME_SIZE]; - UINT64 ownerUid = (UINT64)geteuid(); - size_t cmdPrefixSz = XSTRLEN(gTestSemCmdBase); - size_t rspPrefixSz = XSTRLEN(gTestSemRspBase); - - if (FWTPM_TIS_MakeSemNames(ownerUid, semCmd, sizeof(semCmd), semRsp, - sizeof(semRsp)) != 0 || - FWTPM_TIS_MakeSemNames(ownerUid + 1U, otherCmd, + char expectedCmd[FWTPM_TIS_SEM_NAME_SIZE]; + char expectedRsp[FWTPM_TIS_SEM_NAME_SIZE]; + uid_t ownerUid = geteuid(); + + (void)snprintf(expectedCmd, sizeof(expectedCmd), "%s-%lu", + gTestSemCmdBase, (unsigned long)ownerUid); + (void)snprintf(expectedRsp, sizeof(expectedRsp), "%s-%lu", + gTestSemRspBase, (unsigned long)ownerUid); + if (FWTPM_TIS_ClientMakeSemNames(ownerUid, semCmd, sizeof(semCmd), + semRsp, sizeof(semRsp)) != 0 || + FWTPM_TIS_ClientMakeSemNames(ownerUid + 1U, otherCmd, sizeof(otherCmd), otherRsp, sizeof(otherRsp)) != 0 || - XSTRNCMP(semCmd, gTestSemCmdBase, cmdPrefixSz) != 0 || - XSTRNCMP(semRsp, gTestSemRspBase, rspPrefixSz) != 0 || - semCmd[cmdPrefixSz] != '-' || semRsp[rspPrefixSz] != '-' || + XSTRCMP(semCmd, expectedCmd) != 0 || + XSTRCMP(semRsp, expectedRsp) != 0 || XSTRCMP(semCmd, otherCmd) == 0 || XSTRCMP(semRsp, otherRsp) == 0) { printf("FAIL: caller semaphore prefixes lost UID namespacing\n"); return 1; } + if (FWTPM_TIS_ClientMakeSemNames(ownerUid, semCmd, 2U, semRsp, + sizeof(semRsp)) == 0) { + printf("FAIL: accepted a truncated semaphore name\n"); + return 1; + } printf("PASS: namespaced caller-defined semaphore prefixes\n"); return 0; } diff --git a/tests/fwtpm_tis_sem_unit_tests.c b/tests/fwtpm_tis_sem_unit_tests.c deleted file mode 100644 index c44eb989..00000000 --- a/tests/fwtpm_tis_sem_unit_tests.c +++ /dev/null @@ -1,71 +0,0 @@ -/* fwtpm_tis_sem_unit_tests.c - * - * Copyright (C) 2006-2026 wolfSSL Inc. - * - * This file is part of wolfTPM. - * - * wolfTPM is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * wolfTPM is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA - */ - -#ifdef HAVE_CONFIG_H - #include -#endif - -#include -#include - -#include -#include - -static int TestSemNames(void) -{ - const UINT64 uidA = 17U; - const UINT64 uidB = 18U; - char cmdA[FWTPM_TIS_SEM_NAME_SIZE]; - char rspA[FWTPM_TIS_SEM_NAME_SIZE]; - char cmdB[FWTPM_TIS_SEM_NAME_SIZE]; - char rspB[FWTPM_TIS_SEM_NAME_SIZE]; - char expectedCmd[FWTPM_TIS_SEM_NAME_SIZE]; - char expectedRsp[FWTPM_TIS_SEM_NAME_SIZE]; - - if (FWTPM_TIS_MakeSemNames(uidA, cmdA, sizeof(cmdA), rspA, - sizeof(rspA)) != 0 || - FWTPM_TIS_MakeSemNames(uidB, cmdB, sizeof(cmdB), rspB, - sizeof(rspB)) != 0) { - printf("FAIL: could not derive semaphore names\n"); - return 1; - } - /* Pinned vector for uid 17: do not recompute the production hash here. */ - (void)XSNPRINTF(expectedCmd, sizeof(expectedCmd), "%s-%s", - FWTPM_TIS_SEM_CMD, "81af14c9e4a872f7"); - (void)XSNPRINTF(expectedRsp, sizeof(expectedRsp), "%s-%s", - FWTPM_TIS_SEM_RSP, "81af14c9e4a872f7"); - if (strcmp(cmdA, expectedCmd) != 0 || strcmp(rspA, expectedRsp) != 0) { - printf("FAIL: semaphore name has the wrong UID suffix\n"); - return 1; - } - if (strcmp(cmdA, cmdB) == 0 || strcmp(rspA, rspB) == 0) { - printf("FAIL: different UIDs produced the same semaphore names\n"); - return 1; - } - - printf("fwTPM TIS semaphore tests: passed\n"); - return 0; -} - -int main(void) -{ - return TestSemNames(); -} diff --git a/tests/include.am b/tests/include.am index 75bcbbb8..3d352b0e 100644 --- a/tests/include.am +++ b/tests/include.am @@ -38,20 +38,14 @@ tests_fwtpm_unit_test_LDADD = $(LIB_STATIC_ADD) endif if BUILD_FWTPM_TIS -check_PROGRAMS += tests/fwtpm_hal_unit.test tests/fwtpm_tis_sem_unit.test -noinst_PROGRAMS += tests/fwtpm_hal_unit.test tests/fwtpm_tis_sem_unit.test +check_PROGRAMS += tests/fwtpm_hal_unit.test +noinst_PROGRAMS += tests/fwtpm_hal_unit.test tests_fwtpm_hal_unit_test_SOURCES = tests/fwtpm_hal_unit_tests.c tests_fwtpm_hal_unit_test_CFLAGS = \ -DWOLFTPM_FWTPM_HAL -DWOLFTPM_ADV_IO -DWOLFTPM_EXAMPLE_HAL $(AM_CFLAGS) tests_fwtpm_hal_unit_test_CPPFLAGS = \ -DWOLFTPM_FWTPM_HAL -DWOLFTPM_ADV_IO -DWOLFTPM_EXAMPLE_HAL $(AM_CPPFLAGS) tests_fwtpm_hal_unit_test_LDADD = $(FWTPM_TIS_LIBS) -tests_fwtpm_tis_sem_unit_test_SOURCES = \ - tests/fwtpm_tis_sem_unit_tests.c \ - src/fwtpm/fwtpm_tis_sem.c -tests_fwtpm_tis_sem_unit_test_CFLAGS = -DWOLFTPM_FWTPM_TIS $(AM_CFLAGS) -tests_fwtpm_tis_sem_unit_test_CPPFLAGS = -DWOLFTPM_FWTPM_TIS $(AM_CPPFLAGS) -tests_fwtpm_tis_sem_unit_test_LDADD = $(FWTPM_TIS_LIBS) endif # TPM make check: manages server, runs unit.test + run_examples.sh diff --git a/wolftpm/fwtpm/fwtpm_tis.h b/wolftpm/fwtpm/fwtpm_tis.h index 7486d36b..b676b5b5 100644 --- a/wolftpm/fwtpm/fwtpm_tis.h +++ b/wolftpm/fwtpm/fwtpm_tis.h @@ -56,6 +56,18 @@ #define FWTPM_TIS_MAGIC 0x57544953UL /* "WTIS" */ #define FWTPM_TIS_VERSION 2 +/* Publish/observe the magic sentinel with release/acquire ordering so a client + * never sees FWTPM_TIS_MAGIC before the header it guards (wc_port.h ladder). */ +#if !defined(WOLFSSL_NO_ATOMICS) && defined(__GNUC__) && \ + defined(__ATOMIC_RELEASE) + #define FWTPM_TIS_ATOMIC_STORE(x, val) \ + __atomic_store_n(&(x), (val), __ATOMIC_RELEASE) + #define FWTPM_TIS_ATOMIC_LOAD(x) __atomic_load_n(&(x), __ATOMIC_ACQUIRE) +#else + #define FWTPM_TIS_ATOMIC_STORE(x, val) ((x) = (val)) + #define FWTPM_TIS_ATOMIC_LOAD(x) (x) +#endif + /* Default burst count (bytes per FIFO transfer) */ #ifndef FWTPM_TIS_BURST_COUNT #define FWTPM_TIS_BURST_COUNT 64 @@ -156,12 +168,6 @@ typedef struct FWTPM_TIS_REGS { /* Backward compatibility alias */ typedef FWTPM_TIS_REGS FWTPM_TIS_SHM; -#if defined(WOLFTPM_FWTPM_TIS) || defined(WOLFTPM_FWTPM_HAL) -/* Build the effective POSIX semaphore names for an authenticated owner UID. */ -WOLFTPM_LOCAL int FWTPM_TIS_MakeSemNames(UINT64 ownerUid, char* semCmd, - size_t semCmdSz, char* semRsp, size_t semRspSz); -#endif - /* --- TIS Transport HAL (server-side) --- * Abstracts the transport between client and server for TIS register From 18d9ead84f9c4ad105767f7bee98aa643651a44c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 2 Sep 2026 12:44:45 -0700 Subject: [PATCH 8/8] Keep fixed fwTPM TIS semaphore names and protocol version --- docs/FWTPM.md | 19 ++++------ hal/tpm_io_fwtpm.c | 37 +++---------------- src/fwtpm/fwtpm_tis_shm.c | 70 ++++++------------------------------ tests/fwtpm_hal_unit_tests.c | 54 ++++------------------------ wolftpm/fwtpm/fwtpm_tis.h | 9 ++--- 5 files changed, 30 insertions(+), 159 deletions(-) diff --git a/docs/FWTPM.md b/docs/FWTPM.md index 9cd2fbff..4dcac6d1 100644 --- a/docs/FWTPM.md +++ b/docs/FWTPM.md @@ -765,18 +765,13 @@ register-level access. This mode simulates an SPI-attached TPM. | Define | Default | Description | |--------|---------|-------------| | `FWTPM_TIS_SHM_PATH` | `/tmp/fwtpm.shm` | Shared memory file; clients require a regular, single-link, same-UID, exact-size `0600` endpoint | -| `FWTPM_TIS_SEM_CMD` | `/fwtpm_cmd` | Command semaphore prefix; a per-UID suffix is always appended | -| `FWTPM_TIS_SEM_RSP` | `/fwtpm_rsp` | Response semaphore prefix; a per-UID suffix is always appended | - -Protocol version 2 requires an exact version and shared-region-size match. -Rebuild the client library and `fwtpm_server` together when upgrading or when -changing options that affect `FWTPM_TIS_FIFO_SIZE`; version 1 and version 2 -peers do not interoperate. - -The default shared-memory path is global, so it supports one server per host. -The per-UID semaphore suffix enables safe stale-object cleanup; it does not -enable concurrent per-user servers unless each build also uses a distinct -`FWTPM_TIS_SHM_PATH`. +| `FWTPM_TIS_SEM_CMD` | `/fwtpm_cmd` | Command semaphore name | +| `FWTPM_TIS_SEM_RSP` | `/fwtpm_rsp` | Response semaphore name | + +Clients require an exact protocol version and shared-region-size match, so +rebuild the client library and `fwtpm_server` together when changing options +that affect `FWTPM_TIS_FIFO_SIZE`. The default paths are global, so one server +per host. **Server-side API:** diff --git a/hal/tpm_io_fwtpm.c b/hal/tpm_io_fwtpm.c index ed7f22be..780d3beb 100644 --- a/hal/tpm_io_fwtpm.c +++ b/hal/tpm_io_fwtpm.c @@ -84,24 +84,6 @@ static int FWTPM_TIS_ServerActive(const FWTPM_TIS_REGS* shm) return FWTPM_TIS_ATOMIC_LOAD(shm->magic) == FWTPM_TIS_MAGIC; } -/* Must match the server's naming in fwtpm_tis_shm.c. */ -static int FWTPM_TIS_ClientMakeSemNames(uid_t ownerUid, char* semCmd, - size_t semCmdSz, char* semRsp, size_t semRspSz) -{ - int cmdLen; - int rspLen; - - cmdLen = XSNPRINTF(semCmd, semCmdSz, "%s-%lu", FWTPM_TIS_SEM_CMD, - (unsigned long)ownerUid); - rspLen = XSNPRINTF(semRsp, semRspSz, "%s-%lu", FWTPM_TIS_SEM_RSP, - (unsigned long)ownerUid); - if (cmdLen <= 0 || (size_t)cmdLen >= semCmdSz || - rspLen <= 0 || (size_t)rspLen >= semRspSz) { - return -1; - } - return 0; -} - static int FWTPM_TIS_ClientValidateShm(const struct stat* st) { mode_t expectedMode = S_IRUSR | S_IWUSR; @@ -126,8 +108,6 @@ int FWTPM_TIS_ClientConnect(FWTPM_TIS_CLIENT_CTX* client) FWTPM_TIS_REGS* shm; sem_t* semCmd; sem_t* semRsp; - char semCmdName[FWTPM_TIS_SEM_NAME_SIZE]; - char semRspName[FWTPM_TIS_SEM_NAME_SIZE]; if (client == NULL) { return BAD_FUNC_ARG; @@ -226,15 +206,6 @@ int FWTPM_TIS_ClientConnect(FWTPM_TIS_CLIENT_CTX* client) return TPM_RC_FAILURE; } #endif - if (FWTPM_TIS_ClientMakeSemNames(st.st_uid, semCmdName, - sizeof(semCmdName), semRspName, sizeof(semRspName)) != 0) { - #ifdef DEBUG_WOLFTPM - printf("fwTPM HAL: failed to derive semaphore names for uid %lu\n", - (unsigned long)st.st_uid); - #endif - close(fd); - return TPM_RC_FAILURE; - } shm = (FWTPM_TIS_REGS*)mmap(NULL, sizeof(FWTPM_TIS_REGS), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); @@ -260,22 +231,22 @@ int FWTPM_TIS_ClientConnect(FWTPM_TIS_CLIENT_CTX* client) } /* Open existing semaphores (server creates them) */ - semCmd = sem_open(semCmdName, 0); + semCmd = sem_open(FWTPM_TIS_SEM_CMD, 0); if (semCmd == SEM_FAILED) { #ifdef DEBUG_WOLFTPM printf("fwTPM HAL: sem_open(%s) failed: %d (%s)\n", - semCmdName, errno, strerror(errno)); + FWTPM_TIS_SEM_CMD, errno, strerror(errno)); #endif munmap(shm, sizeof(FWTPM_TIS_REGS)); close(fd); return TPM_RC_FAILURE; } - semRsp = sem_open(semRspName, 0); + semRsp = sem_open(FWTPM_TIS_SEM_RSP, 0); if (semRsp == SEM_FAILED) { #ifdef DEBUG_WOLFTPM printf("fwTPM HAL: sem_open(%s) failed: %d (%s)\n", - semRspName, errno, strerror(errno)); + FWTPM_TIS_SEM_RSP, errno, strerror(errno)); #endif sem_close(semCmd); munmap(shm, sizeof(FWTPM_TIS_REGS)); diff --git a/src/fwtpm/fwtpm_tis_shm.c b/src/fwtpm/fwtpm_tis_shm.c index e9564039..8f31858c 100644 --- a/src/fwtpm/fwtpm_tis_shm.c +++ b/src/fwtpm/fwtpm_tis_shm.c @@ -58,38 +58,11 @@ typedef struct { int shmFd; /* shm file descriptor */ sem_t* semCmd; /* command semaphore */ sem_t* semRsp; /* response semaphore */ - char semCmdName[FWTPM_TIS_SEM_NAME_SIZE]; - char semRspName[FWTPM_TIS_SEM_NAME_SIZE]; } FWTPM_TIS_SHM_CTX; /* Single server per process */ static FWTPM_TIS_SHM_CTX gTisShmCtx; -static void TisShmUnlinkOldEndpoint(const char* semCmdName, - const char* semRspName) -{ - (void)sem_unlink(semCmdName); - (void)sem_unlink(semRspName); -} - -/* Per-UID names keep concurrent users' wakeup semaphores apart. */ -static int TisShmMakeSemNames(uid_t ownerUid, char* semCmd, size_t semCmdSz, - char* semRsp, size_t semRspSz) -{ - int cmdLen; - int rspLen; - - cmdLen = XSNPRINTF(semCmd, semCmdSz, "%s-%lu", FWTPM_TIS_SEM_CMD, - (unsigned long)ownerUid); - rspLen = XSNPRINTF(semRsp, semRspSz, "%s-%lu", FWTPM_TIS_SEM_RSP, - (unsigned long)ownerUid); - if (cmdLen <= 0 || (size_t)cmdLen >= semCmdSz || - rspLen <= 0 || (size_t)rspLen >= semRspSz) { - return -1; - } - return 0; -} - /* --- HAL Callbacks --- */ static int TisShmInit(void* ctx, FWTPM_TIS_REGS** regs) @@ -113,8 +86,6 @@ static int TisShmInit(void* ctx, FWTPM_TIS_REGS** regs) shm->shmFd = -1; shm->semCmd = NULL; shm->semRsp = NULL; - shm->semCmdName[0] = '\0'; - shm->semRspName[0] = '\0'; /* Use a fresh inode so locks from a crashed server generation do not * prevent new clients from connecting to the replacement server. */ @@ -149,24 +120,6 @@ static int TisShmInit(void* ctx, FWTPM_TIS_REGS** regs) return -1; } - if (fstat(fd, &shmStat) != 0 || - shmStat.st_size != (off_t)sizeof(FWTPM_TIS_REGS) || - (shmStat.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) != - (S_IRUSR | S_IWUSR)) { - fprintf(stderr, "fwTPM TIS: invalid shm endpoint metadata\n"); - close(fd); - (void)unlink(FWTPM_TIS_SHM_PATH); - return -1; - } - if (TisShmMakeSemNames(shmStat.st_uid, - shm->semCmdName, sizeof(shm->semCmdName), - shm->semRspName, sizeof(shm->semRspName)) != 0) { - fprintf(stderr, "fwTPM TIS: failed to derive semaphore names\n"); - close(fd); - (void)unlink(FWTPM_TIS_SHM_PATH); - return -1; - } - shm->regs = (FWTPM_TIS_REGS*)mmap(NULL, sizeof(FWTPM_TIS_REGS), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (shm->regs == MAP_FAILED) { @@ -182,11 +135,12 @@ static int TisShmInit(void* ctx, FWTPM_TIS_REGS** regs) /* Remove stale names only after the data endpoint is ready, immediately * before O_EXCL recreates them. Early startup failures therefore leave a * still-running server generation's semaphore names intact. */ - TisShmUnlinkOldEndpoint(shm->semCmdName, shm->semRspName); - shm->semCmd = sem_open(shm->semCmdName, O_CREAT | O_EXCL, 0600, 0); + (void)sem_unlink(FWTPM_TIS_SEM_CMD); + (void)sem_unlink(FWTPM_TIS_SEM_RSP); + shm->semCmd = sem_open(FWTPM_TIS_SEM_CMD, O_CREAT | O_EXCL, 0600, 0); if (shm->semCmd == SEM_FAILED) { fprintf(stderr, "fwTPM TIS: sem_open(%s) failed: %d (%s)\n", - shm->semCmdName, errno, strerror(errno)); + FWTPM_TIS_SEM_CMD, errno, strerror(errno)); shm->semCmd = NULL; munmap(shm->regs, sizeof(FWTPM_TIS_REGS)); shm->regs = NULL; @@ -196,12 +150,12 @@ static int TisShmInit(void* ctx, FWTPM_TIS_REGS** regs) return -1; } - shm->semRsp = sem_open(shm->semRspName, O_CREAT | O_EXCL, 0600, 0); + shm->semRsp = sem_open(FWTPM_TIS_SEM_RSP, O_CREAT | O_EXCL, 0600, 0); if (shm->semRsp == SEM_FAILED) { fprintf(stderr, "fwTPM TIS: sem_open(%s) failed: %d (%s)\n", - shm->semRspName, errno, strerror(errno)); + FWTPM_TIS_SEM_RSP, errno, strerror(errno)); sem_close(shm->semCmd); - sem_unlink(shm->semCmdName); + sem_unlink(FWTPM_TIS_SEM_CMD); shm->semCmd = NULL; shm->semRsp = NULL; munmap(shm->regs, sizeof(FWTPM_TIS_REGS)); @@ -217,7 +171,7 @@ static int TisShmInit(void* ctx, FWTPM_TIS_REGS** regs) printf("fwTPM TIS: Shared memory at %s (%zu bytes)\n", FWTPM_TIS_SHM_PATH, sizeof(FWTPM_TIS_REGS)); printf("fwTPM TIS: Semaphores: cmd=%s, rsp=%s\n", - shm->semCmdName, shm->semRspName); + FWTPM_TIS_SEM_CMD, FWTPM_TIS_SEM_RSP); return 0; } @@ -262,16 +216,12 @@ static void TisShmCleanup(void* ctx) } if (shm->semRsp != NULL && shm->semRsp != SEM_FAILED) { sem_close(shm->semRsp); - if (shm->semRspName[0] != '\0') { - sem_unlink(shm->semRspName); - } + sem_unlink(FWTPM_TIS_SEM_RSP); shm->semRsp = NULL; } if (shm->semCmd != NULL && shm->semCmd != SEM_FAILED) { sem_close(shm->semCmd); - if (shm->semCmdName[0] != '\0') { - sem_unlink(shm->semCmdName); - } + sem_unlink(FWTPM_TIS_SEM_CMD); shm->semCmd = NULL; } if (shm->regs != NULL) { diff --git a/tests/fwtpm_hal_unit_tests.c b/tests/fwtpm_hal_unit_tests.c index 557af00d..08ce281e 100644 --- a/tests/fwtpm_hal_unit_tests.c +++ b/tests/fwtpm_hal_unit_tests.c @@ -41,8 +41,8 @@ static char gTestShmPath[96]; static char gTestAuxPath[96]; -static char gTestSemCmdBase[16]; -static char gTestSemRspBase[16]; +static char gTestSemCmd[16]; +static char gTestSemRsp[16]; static int gSwapAfterSemOpen; static int gSemOpenCount; static int gSwapFailed; @@ -52,16 +52,13 @@ static int CreateEndpointFile(const char* path, mode_t mode, static int ReplaceEndpoint(void); #define FWTPM_TIS_SHM_PATH gTestShmPath -#define FWTPM_TIS_SEM_CMD gTestSemCmdBase -#define FWTPM_TIS_SEM_RSP gTestSemRspBase +#define FWTPM_TIS_SEM_CMD gTestSemCmd +#define FWTPM_TIS_SEM_RSP gTestSemRsp #define WOLFTPM_INCLUDE_IO_FILE #include "../hal/tpm_io.h" #include -static char gTestSemCmd[FWTPM_TIS_SEM_NAME_SIZE]; -static char gTestSemRsp[FWTPM_TIS_SEM_NAME_SIZE]; - /* tpm_io_fwtpm.c normally gets this helper from libwolftpm. */ static void TestForceZero(void* mem, word32 len) { @@ -165,9 +162,7 @@ static int CreateEndpoint(mode_t mode, off_t sizeAdjust, UINT32 magic, int rc = -1; CleanupEndpoint(); - if (FWTPM_TIS_ClientMakeSemNames(geteuid(), gTestSemCmd, - sizeof(gTestSemCmd), gTestSemRsp, sizeof(gTestSemRsp)) != 0 || - CreateEndpointFile(gTestShmPath, mode, sizeAdjust, magic, + if (CreateEndpointFile(gTestShmPath, mode, sizeAdjust, magic, version) != 0) { goto exit; } @@ -426,40 +421,6 @@ static int TestMetadataValidation(void) return 0; } -static int TestCustomSemPrefixes(void) -{ - char semCmd[FWTPM_TIS_SEM_NAME_SIZE]; - char semRsp[FWTPM_TIS_SEM_NAME_SIZE]; - char otherCmd[FWTPM_TIS_SEM_NAME_SIZE]; - char otherRsp[FWTPM_TIS_SEM_NAME_SIZE]; - char expectedCmd[FWTPM_TIS_SEM_NAME_SIZE]; - char expectedRsp[FWTPM_TIS_SEM_NAME_SIZE]; - uid_t ownerUid = geteuid(); - - (void)snprintf(expectedCmd, sizeof(expectedCmd), "%s-%lu", - gTestSemCmdBase, (unsigned long)ownerUid); - (void)snprintf(expectedRsp, sizeof(expectedRsp), "%s-%lu", - gTestSemRspBase, (unsigned long)ownerUid); - if (FWTPM_TIS_ClientMakeSemNames(ownerUid, semCmd, sizeof(semCmd), - semRsp, sizeof(semRsp)) != 0 || - FWTPM_TIS_ClientMakeSemNames(ownerUid + 1U, otherCmd, - sizeof(otherCmd), otherRsp, sizeof(otherRsp)) != 0 || - XSTRCMP(semCmd, expectedCmd) != 0 || - XSTRCMP(semRsp, expectedRsp) != 0 || - XSTRCMP(semCmd, otherCmd) == 0 || - XSTRCMP(semRsp, otherRsp) == 0) { - printf("FAIL: caller semaphore prefixes lost UID namespacing\n"); - return 1; - } - if (FWTPM_TIS_ClientMakeSemNames(ownerUid, semCmd, 2U, semRsp, - sizeof(semRsp)) == 0) { - printf("FAIL: accepted a truncated semaphore name\n"); - return 1; - } - printf("PASS: namespaced caller-defined semaphore prefixes\n"); - return 0; -} - int main(void) { int failures = 0; @@ -470,9 +431,9 @@ int main(void) "/tmp/wolftpm-fwtpm-hal-%ld.shm", pid); (void)snprintf(gTestAuxPath, sizeof(gTestAuxPath), "/tmp/wolftpm-fwtpm-hal-%ld.aux", pid); - (void)snprintf(gTestSemCmdBase, sizeof(gTestSemCmdBase), + (void)snprintf(gTestSemCmd, sizeof(gTestSemCmd), "/c%x", semId); - (void)snprintf(gTestSemRspBase, sizeof(gTestSemRspBase), + (void)snprintf(gTestSemRsp, sizeof(gTestSemRsp), "/r%x", semId); failures += ExpectRejected("group-readable", 0640, 0, @@ -494,7 +455,6 @@ int main(void) failures += ExpectAccepted(); failures += ExpectDisconnectZeroized(); failures += TestMetadataValidation(); - failures += TestCustomSemPrefixes(); CleanupEndpoint(); printf("fwTPM HAL endpoint tests: %s\n", diff --git a/wolftpm/fwtpm/fwtpm_tis.h b/wolftpm/fwtpm/fwtpm_tis.h index b676b5b5..c7cf4b52 100644 --- a/wolftpm/fwtpm/fwtpm_tis.h +++ b/wolftpm/fwtpm/fwtpm_tis.h @@ -36,8 +36,7 @@ extern "C" { #endif -/* Shared-memory path and semaphore-name prefixes (POSIX transport defaults). - * Every semaphore prefix receives a per-UID suffix. */ +/* Shared memory and semaphore paths (POSIX transport defaults) */ #ifndef FWTPM_TIS_SHM_PATH #define FWTPM_TIS_SHM_PATH "/tmp/fwtpm.shm" #endif @@ -48,13 +47,9 @@ #define FWTPM_TIS_SEM_RSP "/fwtpm_rsp" #endif -/* Holds a leading slash, the 251 characters Linux permits after it, and NUL. - * Shorter platform limits are enforced by sem_open(). */ -#define FWTPM_TIS_SEM_NAME_SIZE 253 - /* Magic and version for shared memory validation */ #define FWTPM_TIS_MAGIC 0x57544953UL /* "WTIS" */ -#define FWTPM_TIS_VERSION 2 +#define FWTPM_TIS_VERSION 1 /* Publish/observe the magic sentinel with release/acquire ordering so a client * never sees FWTPM_TIS_MAGIC before the header it guards (wc_port.h ladder). */