Skip to content

Fixes for Firmware TPM issues and related session, clock and signature defects - #656

Open
dgarske wants to merge 11 commits into
wolfSSL:masterfrom
dgarske:fwtpm_fixes
Open

dgarske wants to merge 11 commits into
wolfSSL:masterfrom
dgarske:fwtpm_fixes

Conversation

@dgarske

@dgarske dgarske commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

This PR fixes the fwTPM issues #648-#654, plus related defects in the same code paths. Each fix is its own commit with a regression test:

Related fixes:

  • Apply command HMAC verification consistently to every session in the authorization area.
  • Hash and StirRandom parse their parameters after the authorization area and return session-framed responses.
  • TPM_CAP_HANDLES includes hash, HMAC, event, sign and verify sequence handles.
  • wolfTPM2_SignDigest and wolfTPM2_SignSequenceComplete normalize ECDSA scalars to the curve width, as wolfTPM2_SignHashScheme already does.

Notes:

  • New FWTPM_Clock_PowerOn() for hosts that power cycle within one FWTPM_CTX. Embedded hosts must call FWTPM_Clock_Update() at least every FWTPM_CLOCK_POLL_MS.
  • Older fwTPM firmware cannot read the extended orderly-shutdown NV marker, so a downgrade needs journal conversion.
  • MAX_CONTEXT_SIZE defaults to 4096 when MAX_RSA_KEY_BITS exceeds 2048 and to 8192 with PQC, since an object context now carries the private key. Define it to keep a fixed size.
  • New CI job runs tpm2-pkcs11 (tpm2_ptool and OpenSC pkcs11-tool) against fwtpm_server, directly and through tpm2-abrmd, for the classic and v1.85 builds.

Fixes #648, Fixes #649, Fixes #650, Fixes #651, Fixes #652, Fixes #653, Fixes #654

@AlexLaroche

Copy link
Copy Markdown

Thanks @dgarske, I tested #656 (0d35f1d) against master (d7f73b5) and it resolves #648–#653. #654 is mostly resolved: see the note below.

Reproductions. Both servers were built with --enable-fwtpm-only --enable-swtpm on wolfSSL v5.9.4-stable, with AddressSanitizer. Each case uses the ESYS program from its issue.

Issue master d7f73b5 #656
#648 Unseal through an encrypting session (plain, and salted+bound) 64 bytes, wrong 64 bytes, correct
#649 ECDSA r/s of 200 signatures each (P-256 child, P-521 child, P-256 primary) 1/0, 95/107, 2/1 short none short
#650 RSA_Decrypt OAEP-SHA1 (MGF1-SHA1) of an OpenSSL ciphertext TPM_RC_FAILURE decrypts, plaintext matches
#650 TPM_CAP_ALGS no SHA-1 SHA-1 and SHA3-256/384/512 listed
#651 TPM_CAP_TPM_PROPERTIES from TPM_PT_FIXED 19 properties 46, including TPM_PT_CONTEXT_HASH/_SYM/_SYM_SIZE (SHA-256, AES, 256)
#652 ContextSave, FlushContext, ContextLoad of a loaded key TPM_RC_HANDLE on load loads
#653 TestParms RSA keyBits 1000 0x087 0x1c4, the same as libtpms
#654 ECDSA of an all-zero digest TPM_RC_FAILURE TPM_RC_VALUE on parameter 1

End to end. I also ran the tpm2-pkcs11 stack you mention in the PR's notes. That was released tpm2-pkcs11 1.10.1 on tpm2-tss 4.2.0, driven by a C# PKCS#11 suite of 642 cases. It ran on both tpm2-pkcs11 store backends (esysdb and FAPI), and on both the classic and --enable-v185 fwTPM builds:

These results are without any of the local patches I had been applying to fwTPM for #648–#652, so I can drop them.

One note on #654. A clear TPM_RC_VALUE on the digest parameter is a real improvement, because a client no longer sees the TPM as failed. libtpms and the TCG reference implementation still sign e = 0, so a PKCS#11 C_Sign of an empty message with CKM_ECDSA works on those and fails on fwTPM. I'm fine with closing #654 on this. If wolfCrypt later accepts a zero hash, signing it would match the other TPMs.

@dgarske

dgarske commented Oct 28, 2026

Copy link
Copy Markdown
Member Author

Hi @AlexLaroche thank you for that feedback! I've pushed updates. Can you take another look? Thanks, David Garske, wolfSSL

@embhorn embhorn left a comment

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.

Skoll Multi-Scan Review

Modes: review + review-security

Overall recommendation: REQUEST_CHANGES
Findings: 6 total — 6 posted, 0 skipped
6 finding(s) posted as inline comments (see file-level comments below)

Posted findings

  • [High] [review] Legacy persistent-object children can no longer be context-saved — src/fwtpm/fwtpm_command.c:5844-5848
  • [High] [review] Any signing failure on a zero digest becomes a parameter error — src/fwtpm/fwtpm_crypto.c:3990-3995
  • [High] [review] RSA SHA-3 schemes silently use MGF1-SHA256 — src/fwtpm/fwtpm_crypto.c:94-107
  • [Medium] [review-security] Fresh TPM reports Clock as unsafe for its first update interval — src/fwtpm/fwtpm.c:41-47
  • [Medium] [review-security] OAEP now selects MGF1 from the scheme hash — src/fwtpm/fwtpm_crypto.c:85-88
  • [Medium] [review-security] SHA-3 RSA falls back to MGF1-SHA256 when its selector is unavailable — src/fwtpm/fwtpm_crypto.c:94-108

Review generated by Skoll

Comment thread src/fwtpm/fwtpm_command.c
obj->privKeySize > (int)sizeof(obj->privKey)) {
return TPM_RC_FAILURE;
}
rc = FwComputeProofValue(ctx, obj->hierarchy, TPM_ALG_SHA256,

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.

Legacy persistent-object children can no longer be context-saved

A legacy owner-range persistent object can load with hierarchy == 0: the NV loader deliberately retains that value, and Load copies it to its child. The new object snapshot requires FwComputeProofValue(ctx, 0, ...), but FwGetHierarchySeed has no case for zero, so ContextSave now fails for that otherwise usable child. The previous handle-based object context path could save it. The new round-trip tests use only objects with a known hierarchy.

Suggested fix: A fix needs a design decision for authenticating legacy hierarchy-zero contexts while preserving their conservative hierarchy checks; consult the fwTPM legacy NV provenance contract.

Comment thread src/fwtpm/fwtpm_crypto.c
derSig, &derSigSz, &ctx->rng, eccKey);
if (wcRc != 0)
rc = TPM_RC_FAILURE;
if (wcRc != 0) {

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.

Any signing failure on a zero digest becomes a parameter error

The new error mapping ignores wcRc. If wc_ecc_sign_hash fails because its RNG or signing operation fails while the digest is zero, fwTPM reports TPM_RC_VALUE on the digest parameter. That hides an operational failure and tells the caller to change valid input. The added test checks only whether a normal zero-digest signing probe succeeds; it does not exercise a signing failure with zero input.

Suggested fix: Use wolfCrypt’s documented wc_ecc_sign_hash return-code contract to distinguish zero-digest rejection from RNG and signing failures before returning TPM_RC_VALUE.

Comment thread src/fwtpm/fwtpm_crypto.c
#ifdef WOLFSSL_SHA384
case TPM_ALG_SHA384: return WC_MGF1SHA384;
#endif
#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_256) && \

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.

RSA SHA-3 schemes silently use MGF1-SHA256

The PR enables SHA-3 RSA hash selection, but when wolfCrypt has SHA-3 hashing without the matching SHA-3 MGF1 identifier, these cases are omitted and the default returns WC_MGF1SHA256. OAEP and PSS then use different hash and MGF1 algorithms. TestParms accepts the scheme because it checks only hash availability, and the new SHA-3 interoperability tests are compiled out under the same missing-MGF condition. The checked wolfCrypt headers have this configuration.

Suggested fix: Reject SHA-3 OAEP and PSS schemes when the corresponding MGF1 selector is unavailable, including in TestParms, and test that configuration.
Basis: wolfCrypt rsa.h, “Mask Generation Function Identifiers,” defines the available MGF1 selectors independently of SHA-3 hash availability.

Comment thread src/fwtpm/fwtpm.c

/* Clock resumes from its NV checkpoint. An orderly shutdown can preserve an
* unsafe Clock. */
static void FwClockRestoreSafe(FWTPM_CTX* 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.

Fresh TPM reports Clock as unsafe for its first update interval · Logic

On fresh NV creation, FWTPM_NV_Save sets clockSafe to 1, but no orderly marker is written. FWTPM_Init then calls FwClockRestoreSafe, whose new conjunction clears clockSafe because nvOrderly is 0. ReadClock, attestations, and PolicyCounterTimer now expose that false value; before the PR they reported safe as 1. Without another NV save, Clock_Update does not restore safety until roughly 70 minutes have elapsed. Fresh-state startup is therefore treated like recovery from an unclean shutdown.

Suggested fix: Carry a fresh-NV indicator through initialization and preserve clockSafe for that case, while continuing to clear it when loading a journal after an unclean shutdown.
Basis: TCG TPM 2.0 Part 2, Clock Safe defines Safe as YES when Clock is guaranteed to exceed values previously reported for the current Owner.

Comment thread src/fwtpm/fwtpm_crypto.c
int FwGetMgfType(UINT16 hashAlg)
{
switch (hashAlg) {
#ifndef NO_SHA

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.

OAEP now selects MGF1 from the scheme hash · Regression

Before this PR, SHA-1 fell through to MGF1-SHA256. The new SHA-1 case makes RSA_Encrypt and RSA_Decrypt use MGF1-SHA1 whenever OAEP specifies SHA-1. Those callers pass the OAEP scheme hash to FwGetMgfType, but TPM OAEP uses the RSA key's Name hash for MGF1. With a SHA-256 Name hash and SHA-1 OAEP scheme, this change produces ciphertext a conforming TPM cannot decrypt and rejects ciphertext it produced. The new interoperability test uses the same incorrect hash pairing.

Suggested fix: Pass the RSA key's nameAlg to FwGetMgfType in the OAEP encrypt and decrypt paths, retain the scheme hash for OAEP's label digest, and test a key whose Name hash differs from its OAEP scheme hash.
Basis: TCG TPM 2.0 Part 1, RSAES-OAEP specifies that OAEP's mask-generation function uses the RSA key's Name algorithm.

Comment thread src/fwtpm/fwtpm_crypto.c
#ifdef WOLFSSL_SHA384
case TPM_ALG_SHA384: return WC_MGF1SHA384;
#endif
#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_256) && \

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.

SHA-3 RSA falls back to MGF1-SHA256 when its selector is unavailable · Logic

The PR newly accepts SHA-3 in FwGetRsaHashOid, but each SHA-3 branch in FwGetMgfType is conditional on a separate WC_MGF1SHA3_* definition. In a wolfCrypt build with SHA-3 hashing but without the matching MGF selector, an RSA-PSS request uses a SHA-3 digest with MGF1-SHA256 through the default branch. The SHA-3 interoperability tests are guarded by the same selector definitions, so they miss this configuration. Before the PR, FwGetRsaHashOid did not accept SHA-3 for RSA.

Suggested fix: Return an unsupported result when a requested SHA-3 MGF1 selector is unavailable, and reject the affected RSA operation instead of silently selecting SHA-256.
Basis: TCG TPM 2.0 Part 2, RSA-PSS scheme requires the signature hash to be used for the masking KDF.

@embhorn embhorn assigned dgarske and unassigned embhorn Oct 29, 2026
@AlexLaroche

Copy link
Copy Markdown

Hi @AlexLaroche thank you for that feedback! I've pushed updates. Can you take another look? Thanks, David Garske, wolfSSL

Thanks @dgarske! I retested #656 at ad2e978, again with no local patches. Everything still holds, and #654 is now fully resolved.

Reproductions. As before: fwtpm_server built with --enable-fwtpm-only --enable-swtpm on wolfSSL v5.9.4-stable, with AddressSanitizer, and each issue's ESYS program. No sanitizer reports.

Issue ad2e978
#648 Unseal through an encrypting session (plain, and salted+bound) 64 bytes, correct
#649 ECDSA r/s of 200 signatures each (P-256 child, P-521 child, P-256 primary) none short
#650 RSA_Decrypt OAEP-SHA1 (MGF1-SHA1) of an OpenSSL ciphertext decrypts, plaintext matches
#650 TPM_CAP_ALGS SHA-1 and SHA3-256/384/512 listed
#651 TPM_CAP_TPM_PROPERTIES from TPM_PT_FIXED 46 properties
#652 ContextSave, FlushContext, ContextLoad of a loaded key loads
#653 TestParms RSA keyBits 1000 0x1c4, the same as libtpms
#654 ECDSA of an all-zero digest, wolfSSL defaults TPM_RC_VALUE on parameter 1
#654 ECDSA of an all-zero digest, wolfSSL with -DWC_ALLOW_ECC_ZERO_HASH signs; OpenSSL verifies the signature against the key's public point

Thanks for the WC_ALLOW_ECC_ZERO_HASH path. With it, a PKCS#11 C_Sign of an empty message with CKM_ECDSA works on fwTPM as it does on libtpms and the TCG reference implementation.

End to end. The C# PKCS#11 suite (642 cases) ran through tpm2-pkcs11, now at master a5156c5, on tpm2-tss 4.2.0. It covered both store backends (esysdb and FAPI) and both the classic and --enable-v185 builds, connected directly and behind tpm2-abrmd. All 8 configurations pass, with 0 failures. That includes the --enable-v185 build behind tpm2-abrmd, which I hadn't run last time.

One small note on the new digest-size check in TPM2_Sign (fwtpm_command.c, "Part 3 Sec.20.2.1"). It returns a bare TPM_RC_SIZE, while the TCG reference implementation returns TPM_RCS_SIZE + RC_Sign_digest, which names parameter 1 (Sign.c). The reference also checks the size only when no validation ticket is supplied. I found this by reading the code, not by running it. It doesn't affect anything above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment