Skip to content

Add new Firmware TPM (fwTPM)#474

Open
dgarske wants to merge 11 commits intowolfSSL:masterfrom
dgarske:fwtpm
Open

Add new Firmware TPM (fwTPM)#474
dgarske wants to merge 11 commits intowolfSSL:masterfrom
dgarske:fwtpm

Conversation

@dgarske
Copy link
Copy Markdown
Contributor

@dgarske dgarske commented Mar 21, 2026

Summary

  • Add firmware TPM 2.0 server (fwTPM) implementing TPM 2.0 spec v1.38 (105/113 commands, 93% coverage)
  • Spec-compliant primary key derivation: deterministic RSA (iterative KDFa prime generation), ECC (KDFa scalar, Q=d*G), KEYEDHASH/SYMCIPHER keys — same seed + template always produces the same key
  • ChangePPS / ChangeEPS hierarchy commands with seed regeneration and cache flush
  • NV storage with TLV journal format (flash-friendly, append-only)
  • Socket (mssim) and TIS/SHM transports for desktop, UART transport for embedded
  • STM32 Cortex-M33 bare-metal port with TrustZone (CMSE) support
  • Shared crypto refactoring: extract KDFa/KDFe, AES-CFB, HMAC/Hash to tpm2_crypto.c
  • Bounds-checked TPM2_Packet_ParseU16Buf variant for safer response parsing
  • Auth validation: reject oversized auth values instead of silent truncation
  • SIGTERM/SIGINT signal handler for graceful NV save on server kill

fwTPM Server

Core TPM 2.0 command processing in src/fwtpm/:

  • fwtpm_command.c — 105 command handlers with full auth, sessions, parameter encryption
  • fwtpm_nv.c — TLV journal NV storage (file-based default, HAL-abstracted for flash)
  • fwtpm_io.c — Socket transport (mssim + swtpm protocol auto-detection)
  • fwtpm_tis.c / fwtpm_tis_shm.c — TIS register interface via POSIX shared memory
  • fwtpm_crypto.c — Key generation, sign/verify, seed encrypt/decrypt helpers
  • Clock HAL with NV-persisted clockOffset

Build: ./configure --enable-fwtpm && make

Example: wolfSSL/wolftpm-examples#1

Primary Key Derivation (TPM 2.0 Part 1 Section 26)

  • RSA: iterative KDFa prime generation with labels "RSA p" / "RSA q"
  • ECC: KDFa scalar derivation, public point Q = d*G
  • KEYEDHASH/SYMCIPHER: KDFa with algorithm-specific labels
  • hashUnique = H(sensitiveCreate.data || unique) per Section 26.1
  • Primary cache retained as performance optimization, no longer required for correctness

UART Transport (--enable-swtpm=uart)

New transport option for wolfTPM client library to communicate with embedded fwTPM over serial:

  • ./configure --enable-swtpm=uart — uses termios serial I/O instead of TCP sockets
  • TPM2_SWTPM_HOST env var selects serial device at runtime
  • Same mssim protocol as socket transport (compatible with all wolfTPM examples)

Testing

  • 311 tpm2-tools compatibility tests (scripts/tpm2_tools_test.sh)
  • Full wolfTPM example suite (examples/run_examples.sh)
  • libFuzzer harness with corpus generator (tests/fuzz/)
  • m33mu Cortex-M33 emulator CI test (scripts/fwtpm_emu_test.sh)
  • ASan / UBSan clean
  • 20 CI matrix configurations (pedantic gcc/clang -Werror, sanitizers, feature-disable variants)

wolfSSL-Fenrir-bot

This comment was marked as resolved.

@dgarske dgarske force-pushed the fwtpm branch 3 times, most recently from 9c0208f to eae465e Compare March 21, 2026 23:51
@dgarske dgarske changed the title Add fwTPM firmware TPM 2.0 server with STM32 port Add fwTPM firmware TPM 2.0 server Mar 21, 2026
@dgarske dgarske changed the title Add fwTPM firmware TPM 2.0 server Add new Firmware TPM (fwTPM) Mar 22, 2026
@dgarske dgarske force-pushed the fwtpm branch 2 times, most recently from b186ecc to f529484 Compare March 23, 2026 21:28
@dgarske dgarske assigned wolfSSL-Bot and unassigned dgarske and aidangarske Mar 24, 2026
wolfSSL-Fenrir-bot

This comment was marked as resolved.

…_examples.sh and

  tpm2_tools_test.sh to make test for fwtpm as well
wolfSSL-Fenrir-bot

This comment was marked as resolved.

This comment was marked as resolved.

@aidangarske aidangarske requested a review from danielinux March 25, 2026 20:51
Comment on lines +339 to +342
pkt.pos = 2; /* skip size we already read */
TPM2_Packet_ParsePublic(&pkt, &pub2b);
XMEMCPY(pub, &pub2b.publicArea, sizeof(TPMT_PUBLIC));
*pos += pubSz;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This calls TPM2_Packet_ParsePublic which parses the size again. I think restoring might corrupt saved records. Please verify with a test

/* Check if journal has space */
if (ctx->nvWritePos + entrySize > hal->maxSize) {
/* Compact and retry */
rc = FWTPM_NV_Save(ctx);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cyclic recursion seems possible between FWTPM_NV_Save and FwNvAppendEntry if compacting is not sufficient the functions will keep calling each other

FwFlushAllSessions(ctx);
}

FWTPM_NV_Save(ctx);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return value is discarded here, this functions will return success regardless of FWTPM_NV_Save result. Review error handling in this function.

Copilot AI review requested due to automatic review settings April 3, 2026 17:42

This comment was marked as resolved.

This comment was marked as resolved.

wolfSSL-Fenrir-bot

This comment was marked as resolved.

- Fixed LE/BE endianness mismatch in FwNvMarshalPublic/FwNvUnmarshalPublic
- Added nvCompacting guard to prevent infinite recursion between FwNvAppendEntry and FWTPM_NV_Save
- Check FWTPM_NV_Save return value in FwCmd_Shutdown and other callers
- Reinitialize hash context per iteration in TPM2_KDFe_ex for multi-block derivation
- Align packet->pos to inSensSize boundary in ParseSensitiveCreate
- Clean up wolfCrypt resources if FWTPM_NV_Init fails in FWTPM_Init
- Reset g_initialized on fuzzer reinit failure
- Fix configure.ac so --enable-swtpm=uart doesn't force TIS mode
- Update README command count from 103/91% to 105/93%
@aidangarske aidangarske removed their request for review April 3, 2026 18:55
@aidangarske aidangarske self-assigned this Apr 3, 2026
@aidangarske aidangarske requested a review from danielinux April 3, 2026 19:21
@dgarske dgarske assigned danielinux and wolfSSL-Bot and unassigned aidangarske and dgarske Apr 5, 2026
- Fixed memory leak in FwComputeRpHash — initialized rc = 0, added FWTPM_FREE_VAR(hashCtx) before both early returns to prevent heap leak under
  WOLFTPM_SMALL_STACK
- Removed static from eccUniqueBuf in FwCmd_CreatePrimary — eliminates thread-safety risk and stale data persistence across calls (132-byte buffer is
  fine on stack)
- Initialized rc = TPM_RC_SUCCESS in FWTPM_ProcessCommand — prevents use of uninitialized variable
- Added XMEMSET(cmdAuths, 0, sizeof(cmdAuths)) in FWTPM_ProcessCommand — zeroes auth struct array to prevent use of garbage data on partial parse
- Added auth-parse error check — if auth area parsing fails (e.g. TPM_RC_AUTHSIZE), returns error response instead of falling through to command dispatch
- Moved pwSz/avSz into inner scope — declared where actually used instead of function scope
- Added TPM2_ForceZero(&bindAuth, ...) in FwCmd_StartAuthSession — zeroizes hierarchy auth secrets before scope exit
- Fixed NV handle mask consistency — changed == NV_INDEX_FIRST to == (NV_INDEX_FIRST & 0xFF000000) to match style elsewhere
- Added wc_HmacInit() in FwWrapPrivate — call wc_HmacInit before wc_HmacSetKey, matching pattern used in FwComputeSessionHmac and FwDeriveWrapKey
- Added wc_HmacInit() in FwUnwrapPrivate — same fix for the unwrap path
- Fixed UART (void)port suppression — removed erroneous cast that hid the fact port is used later
- Added NULL check before atoi(port) — prevents undefined behavior if port is NULL
- Restored idx >= inputSz bounds check in TPM2_ASN_DecodeX509Cert — defense-in-depth against OOB read on malformed certificates
- Restored *sigSz < 3 minimum in TPM2_ASN_RsaUnpadPkcsv15 — valid PKCS#1 v1.5 needs at least 0x00|0x01|0xFF
- Fixed summary alignment — consistent column spacing for fwTPM output lines
@aidangarske aidangarske requested review from Copilot and removed request for Copilot April 6, 2026 19:13
Copilot AI review requested due to automatic review settings April 6, 2026 22:12
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 60 out of 62 changed files in this pull request and generated 10 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 191 to 195
if (rc >= 0) {
if (len <= 0 || idx >= (word32)inputSz) {
if (idx >= (word32)inputSz) {
rc = TPM_RC_VALUE;
}
}
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In TPM2_ASN_DecodeX509Cert(), the new check only guards idx >= inputSz but no longer rejects a zero/negative len returned by TPM2_ASN_GetHeader() for the version wrapper. If len is 0, the subsequent input[idx] check may end up validating the next field's tag as the version INTEGER and mis-parse the certificate structure. Consider restoring the len <= 0 validation (and/or validating idx + len <= inputSz) before continuing.

Copilot uses AI. Check for mistakes.
Comment on lines +264 to +268
TPM2_Packet_ParseU16(packet, &wireSize);
copySz = wireSize;
if (copySz > maxBufSz) {
copySz = maxBufSz;
}
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TPM2_Packet_ParseU16Buf() never checks that wireSize fits within the remaining packet (packet->size - packet->pos). Since TPM2_Packet_ParseBytes() always advances packet->pos by the requested size (even when truncating the copy), a malformed response can push pos beyond packet->size and make later parsing silently incorrect. Consider clamping the skip/copy to remaining bytes and/or returning an error when wireSize exceeds what’s left in the packet.

Copilot uses AI. Check for mistakes.
Comment on lines +66 to +70
/* Open existing shared memory file */
fd = open(FWTPM_TIS_SHM_PATH, O_RDWR);
if (fd < 0) {
#ifdef DEBUG_WOLFTPM
printf("fwTPM HAL: open(%s) failed: %d (%s)\n",
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWTPM_TIS_ClientConnect() opens the shared-memory backing file in /tmp without hardening flags or verification. Since the server creates this path with O_NOFOLLOW and 0600 permissions, consider using O_NOFOLLOW (and ideally O_CLOEXEC) on the client too, and/or fstat() validation (regular file, expected mode/owner) to reduce symlink/hijack risk.

Copilot uses AI. Check for mistakes.
Comment on lines +182 to +185
uses: actions/checkout@master

- name: Checkout wolfSSL
uses: actions/checkout@master
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This workflow uses actions/checkout@master, which is a moving ref and less reproducible than a version tag (e.g. @v4) or a pinned SHA. Since other workflows in this PR already use actions/checkout@v4, consider switching these steps to @v4 (or pinning to a commit) to reduce supply-chain risk and improve determinism.

Suggested change
uses: actions/checkout@master
- name: Checkout wolfSSL
uses: actions/checkout@master
uses: actions/checkout@v4
- name: Checkout wolfSSL
uses: actions/checkout@v4

Copilot uses AI. Check for mistakes.
Comment on lines +310 to +314
if test "x$ENABLED_FWTPM_ONLY" = "xyes"
then
ENABLED_FWTPM=yes
ENABLED_EXAMPLES=no
ENABLED_WRAPPER=no
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--enable-fwtpm-only sets ENABLED_FWTPM=yes after the --enable-fwtpm validation block, so the wolfCrypt requirement check won’t run for fwtpm-only builds. This can turn a configuration error (e.g. --enable-fwtpm-only --disable-wolfcrypt) into a later compile-time failure instead of a clear configure-time error. Consider sharing/moving the fwTPM validation so it applies to both options.

Copilot uses AI. Check for mistakes.
Comment on lines +319 to +323
int TPM2_AesCfbEncrypt(
const byte* key, int keySz,
const byte* iv,
byte* data, word32 dataSz)
{
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TPM2_AesCfbEncrypt()/TPM2_AesCfbDecrypt() are exported APIs but don’t validate inputs (e.g., key/data can be NULL, and keySz can be an unsupported size). These pointers/sizes are passed directly into wc_AesSetKey/wc_AesCfb* and can lead to NULL deref or unexpected behavior. Consider returning BAD_FUNC_ARG when required pointers are NULL (or when dataSz>0 with data==NULL) and validating keySz is 16/24/32.

Copilot uses AI. Check for mistakes.
Comment on lines +373 to +377
int TPM2_HmacCompute(
TPMI_ALG_HASH hashAlg,
const byte* key, word32 keySz,
const byte* data, word32 dataSz,
const byte* data2, word32 data2Sz,
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TPM2_HmacCompute() does not validate required pointers before calling wolfCrypt (e.g., digest is passed to wc_HmacFinal unconditionally, and data is passed to wc_HmacUpdate even when dataSz > 0). As a public API this can crash on NULL inputs. Consider adding BAD_FUNC_ARG checks for NULL digest, and for (data==NULL && dataSz>0), plus similar validation for expected in TPM2_HmacVerify().

Copilot uses AI. Check for mistakes.
Comment on lines +442 to +446
int TPM2_HashCompute(
TPMI_ALG_HASH hashAlg,
const byte* data, word32 dataSz,
byte* digest, word32* digestSz)
{
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TPM2_HashCompute() calls wc_Hash(..., digest, ...) without validating digest (or data when dataSz > 0). Since this is an exported API, a NULL digest/data can cause a crash. Consider returning BAD_FUNC_ARG when required pointers are NULL (and when dataSz>0 with data==NULL).

Copilot uses AI. Check for mistakes.
Comment on lines +162 to +166
WOLFTPM_API int TPM2_AesCfbEncrypt(
const byte* key, int keySz,
const byte* iv,
byte* data, word32 dataSz);

Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tpm2_crypto.h declares these AES/HMAC helpers unconditionally, but the corresponding implementations are compiled out when wolfCrypt or the primitive is disabled (e.g. WOLFTPM2_NO_WOLFCRYPT or NO_AES). That can leave callers with compile-time prototypes but missing link-time symbols. Consider either guarding these declarations with the same feature macros as the .c file, or providing stub implementations that return NOT_COMPILED_IN when disabled.

Copilot uses AI. Check for mistakes.
Comment on lines +208 to +212
WOLFTPM_API int TPM2_HmacCompute(
TPMI_ALG_HASH hashAlg,
const byte* key, word32 keySz,
const byte* data, word32 dataSz,
const byte* data2, word32 data2Sz,
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, TPM2_HmacCompute/TPM2_HmacVerify are declared unconditionally here, but their implementations are compiled out when NO_HMAC (or WOLFTPM2_NO_WOLFCRYPT) is set. This can cause link errors in feature-disabled builds (including unit tests that compile against these prototypes). Consider guarding these declarations with #if !defined(WOLFTPM2_NO_WOLFCRYPT) && !defined(NO_HMAC) or adding stub implementations that return NOT_COMPILED_IN.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants