FwCmd_PolicyAuthorize gated the entire HMAC verification block on
ticketDigestSz > 0 (same pattern as the prior FwCmd_PolicyTicket fix).
An attacker could submit ticketTag=TPM_ST_VERIFIED with
ticketDigestSz=0, pass the tag check, skip FwComputeTicketHmac and
TPM2_ConstantCompare entirely, and fall through to the policyDigest
extension using attacker-supplied approvedPolicy and keySignName.
This is the root of the F-4742 chain: combined with an empty-HMAC
policy session, the forged policyDigest matches the entity's authPolicy
in FWTPM_ProcessCommand and grants access to any PolicyAuthorize-
protected object whose userWithAuth is clear, without possession of
any signing key. Cutting the chain at PolicyAuthorize blocks the whole
sequence. The empty-HMAC behavior on policy sessions is intentional
per the existing in-code comment (spec-conformant when neither
PolicyAuthValue nor PolicyPassword has been called) and is not
modified here.
Reject ticketDigestSz==0 with TPM_RC_TICKET after the tag check, and
drop the redundant ticketDigestSz > 0 guard from the HMAC block so
verification runs unconditionally for every accepted ticket.
Add a negative test that issues TPM2_PolicyAuthorize with
TPM_ST_VERIFIED and digest size 0 against a policy session and asserts
TPM_RC_TICKET.
FwCmd_LoadExternal's SYMCIPHER copy path gated the XMEMCPY into
privKeyDer only on qSz > FWTPM_MAX_DER_SIG_BUF, duplicating the outer
parse check rather than testing the destination size. On v1.85 builds
with any ML-DSA parameter set enabled FWTPM_MAX_DER_SIG_BUF grows to
2548–4755 while FWTPM_MAX_PRIVKEY_DER stays at 1280 (or 256 in NO_RSA
builds), so any qSz between FWTPM_MAX_PRIVKEY_DER + 1 and
FWTPM_MAX_DER_SIG_BUF wrote up to 3475 attacker-controlled bytes past
the destination buffer.
Replace the dead guard with an explicit valid-AES-key-size check per
TPM 2.0 Part 2 Sec.11.1.9 — accept only 16, 24, or 32 bytes for a
SYMCIPHER sensitive area and return TPM_RC_SIZE otherwise. Every
allowed length is well within FWTPM_MAX_PRIVKEY_DER on every supported
build, so the destination overflow is no longer reachable.
Add a negative test that submits SYMCIPHER LoadExternal with qSz=33
(passes the FWTPM_MAX_DER_SIG_BUF gate on non-v1.85 builds, exceeds
both the AES set and the destination on v1.85 builds) and asserts
TPM_RC_SIZE.
FwCmd_PolicyTicket gated the entire HMAC verification block on
ticketDigestSz > 0. A caller could submit ticketTag=TPM_ST_AUTH_SIGNED
with ticketDigestSz=0, pass the tag check, skip FwComputeTicketHmac and
TPM2_ConstantCompare entirely, and fall through to FwPolicyExtend with
attacker-supplied authName/policyRef as if a valid PolicySigned or
PolicySecret ticket had been presented. The forged extension then
satisfied any PolicySigned or PolicySecret clause for an arbitrary
named entity without possession of any signing key.
Reject ticketDigestSz==0 with TPM_RC_TICKET immediately after the tag
check, and drop the redundant ticketDigestSz > 0 guard from the HMAC
block so verification runs unconditionally for every accepted ticket.
Add a negative test that issues TPM2_PolicyTicket with ticketTag
TPM_ST_AUTH_SIGNED and digest size 0 against a policy session and
asserts TPM_RC_TICKET.
FWTPM_ProcessCommand parsed the auth area only under
if (cmdTag == TPM_ST_SESSIONS), so a NO_SESSIONS command with
authHandleCnt > 0 left cmdAuthCnt at 0 and bypassed every downstream
policy, password, and HMAC enforcement loop. An 18-byte unauthenticated
TPM2_Clear wiped owner and endorsement state, reseeded both hierarchies,
and reset PCRs.
Add a centralized gate immediately after the command-table lookup that
returns TPM_RC_AUTH_MISSING when the tag is TPM_ST_NO_SESSIONS but the
handler declares authHandleCnt > 0. Matches the spec rule that any auth
role requires TPM_ST_SESSIONS, and closes the same gap that already had
per-handler guards on a few callers.
Add a negative test that issues TPM2_Clear with TPM_ST_NO_SESSIONS and
asserts TPM_RC_AUTH_MISSING is returned without any state mutation.
Bring TPM 2.0 v1.85 fwTPM implementation to full TCG compliance for the
sign/verify, KEM, and key-exchange paths. This unblocks interop with the
TCG simulator and removes four scope gaps surfaced by Skoll TCG review.
TCG Phase B fixes (4 of 4):
- TCG-HIGH-3: TPM2_VerifySequenceComplete now binds the streamed message
bytes in the TPMT_TK_VERIFIED ticket per Part 2 Sec.10.6.5 Eq (5).
Hash-ML-DSA SequenceUpdate mirrors bytes into seq->msgBuf alongside
the hash accumulator, so the ticket builder has the material at
Complete time. Pure ML-DSA already had this; both paths now share one
ticket-data construction.
- TCG-HIGH-2: ECC DHKEM Encapsulate/Decapsulate per RFC 9180 Sec.4.1 +
Part 2 Sec.12.2.3.5. Added FwEncapsulateEcdhDhkem/FwDecapsulateEcdhDhkem
helpers in fwtpm_crypto.c implementing LabeledExtract/LabeledExpand over
HKDF. Curve-hash pairings: P-256/SHA256 (kem_id 0x0010), P-384/SHA384
(0x0011), P-521/SHA512 (0x0012). Wire ciphertext is SEC1-uncompressed
ephemeral public key. Added TPM_ALG_HKDF=0x001F to wolftpm/tpm2.h.
- TCG-HIGH-4: ML-KEM Labeled KEM in FwEncryptSeed/FwDecryptSeed per
Part 1 Sec.47.4 Eq.66:
seed = KDFa(nameAlg, K, label, ciphertext, publicKey, bits)
This unlocks ML-KEM as TPM2_StartAuthSession salt key, TPM2_Import
newParent, TPM2_MakeCredential issuer, etc.
- TCG-HIGH-1: Classical RSA/ECC sign+verify in v1.85 sequence/digest
commands. Extended FwCmd_SignDigest, FwCmd_VerifyDigestSignature,
FwCmd_SignSequenceStart/Complete, FwCmd_VerifySequenceStart/Complete,
and the SequenceUpdate dispatch to accept TPM_ALG_RSA/TPM_ALG_ECC keys.
Reuses existing FwSignDigestAndAppend + FwVerifySignatureCore helpers;
signature scheme/hashAlg resolved from the key's metadata.
Earlier review-cycle fixes also included in this commit:
- src/tpm2.c: TPM2_VerifySequenceComplete/VerifyDigestSignature use
TPM2_Packet_ParseU16Buf for atomic clamp+skip of the validation digest
field. TPM2_Encapsulate CmdInfo flags switched to CMD_FLAG_DEC2 (the
protected value is the first response param, not a command param).
- src/tpm2_wrap.c: wolfTPM2_VerifySequenceComplete moved per-key-type
sigSz validation ahead of the internal SequenceUpdate to avoid leaking
the sequence handle on BUFFER_E (CWE-772). Added ECC arm. Added defensive
ForceZero in wolfTPM2_EncryptSecret_MLKEM on the failure path so partial
KDFa output cannot leak via callers that ignore rc.
- src/tpm2_packet.c: comment fix MAX_MLDSA_KEY_BYTES -> MAX_MLDSA_PRIV_SEED_SIZE.
- wolftpm/tpm2.h: TPM_RC_PARMS moved out of WOLFTPM_V185 guard (it has
been part of TCG Part 2 since v1.16, not v1.85 — Windows CI fix).
- tests/unit_tests.c: align tab-formatted Test TPM Wrapper output lines
to the same column as the rest.
Tests:
- 6 new fwtpm_unit tests (Hash-MLDSA ticket binds message, ECC DHKEM
roundtrip, ML-KEM seed roundtrip, ECDSA SignDigest+VerifyDigestSignature
roundtrip, ECDSA SignSequence+VerifySequence roundtrip).
- 1 existing negative test updated: VerifyDigestSignature with RSASSA on
ML-DSA key now returns TPM_RC_KEY (key-type mismatch) instead of
TPM_RC_SCHEME (which previously meant scheme unsupported).
Validation: tests/fwtpm_unit.test 117/0; tests/fwtpm_check.sh 2/0/1 (incl.
308/0 tpm2-tools compat against fwtpm_server); make check FAIL: 0.
Build / portability:
- Drop #pragma message in fwtpm_crypto.c (MSVC-incompatible)
- Replace non-ASCII section sign with Sec. across all sources/docs
Configure:
- Add --enable-pqc alias for --enable-v185 (same WOLFTPM_V185 macro)
- Auto-detect: when --enable-fwtpm + wolfCrypt has dilithium.h+mlkem.h
and neither flag is set, configure auto-enables PQC; --disable-pqc
opts out
- Both flags probe the wolfSSL PQC headers and fail at configure time
with a clear hint when missing
Spec / security hardening:
- VerifySequenceComplete now emits TPM_ST_DIGEST_VERIFIED (with hashAlg
metadata) for Hash-ML-DSA tickets, MESSAGE_VERIFIED for Pure ML-DSA
(was mis-tagging digests as messages, breaking PolicyTicket consumers)
- Sign/VerifySequenceComplete: free the slot on TPM_RC_SIGN_CONTEXT_KEY
too, so wrong-key Complete cannot exhaust FWTPM_MAX_SIGN_SEQ slots
(CWE-772 DoS)
- TestParms PQC arms return TPM_RC_PARMS (spec-correct) instead of
TPM_RC_VALUE; reject MLDSA/MLKEM parameter sets not actually compiled
in; parse TPMS_MLKEM_PARMS.symmetric via TPM2_Packet_ParseSymmetric
- GetCapability TPMA_ML_PARAMETER_SETS gates each MLDSA/MLKEM bit on the
per-set wolfCrypt availability macro (subset builds advertise truth)
- TPM2_VerifySignature client parser now defensive: only consume the
v1.85 metaAlg when tag==DIGEST_VERIFIED && hierarchy!=RH_NULL
- VerifyDigestSignature: hard-fail on keyName overflow instead of
silently emitting a ticket missing the name binding
- TPM_GENERATED_VALUE prefix check guarded with rc==0
- Drop dead (void)cmdSize casts in Sign/VerifySequenceStart
- wolfTPM2_EncryptSecret_MLKEM: track wc_InitRng_ex/wc_MlKemKey_Init
success flags so Free is only called on initialized state
- UBSan-v185 sanitizer cflags: explicitly disable signed-integer-overflow
and shift checks (matches the comment about wolfSSL Hash_df 440<<24)
Embedded RAM:
- FWTPM_NV_PUBAREA_EST derives from FWTPM_MAX_MLDSA_PUB_SIZE /
FWTPM_MAX_MLKEM_PUB_SIZE auto-shrink macros (subset builds save NV)
- tpm2_types.h MAX_MLDSA_*/MAX_MLKEM_* stay at worst-case (ABI floor
for TPM2B wire buffers) with comment
Tests:
- Negative test for Hash-MLDSA VerifySeqComplete ticket tag
- Negative test exposing sign-seq slot leak on TPM_RC_SIGN_CONTEXT_KEY
- Roundtrip test for wolfTPM2_SignDigest + VerifyDigestSignature
Documentation:
- README, FWTPM.md, fwtpm/README.md, examples/pqc/README.md mention
both --enable-pqc and --enable-v185 + auto-detect
- README wolfSSL line: --enable-pkcallbacks + WC_RSA_NO_PADDING
- fwtpm/README.md: drop FWTPM_SPEC_* labels (macros never existed),
remove v1.85 Additions table (all 8 commands implemented), update
coverage table to 137/113/24 (82%); note remaining gaps are
inherited v1.59/v1.84 commands, not PQC
- fwtpm_nv.h:52: clarify 2592 vs 2720 math (PQC pub key + header slack)
Closes 13 findings across two reviews of the v1.85 PQC paths.
Tickets (TPMT_TK_VERIFIED / TPMT_TK_HASHCHECK / TPMT_TK_CREATION):
- FwAppendTicket binds tag (always) and metadata (DIGEST_VERIFIED only)
into the HMAC per Part 2 §10.6.5 Eq (5). Streamed via chunked
wc_HmacUpdate, no temp buffer. All 5 callers updated; the hand-rolled
VerifyDigestSignature path collapses into FwAppendTicket.
- FWTPM_Object gains a hierarchy field, captured at every load/create
site (CreatePrimary, Load, LoadExternal, CreateLoaded). Replaces
hardcoded TPM_RH_OWNER in VerifySignature, VerifySequenceComplete,
VerifyDigestSignature, ContextSave, and Create's creation ticket.
- VerifySequenceComplete snapshots the verified digest before
wc_HashFinal so Hash-ML-DSA tickets bind (digest || keyName) rather
than just keyName — pre-fix, two distinct verified digests on the
same key produced byte-identical tickets (universal reuse).
Authorization:
- Sign/VerifySequenceStart split TPM_RC_KEY (non-signing key, e.g.
ML-KEM) from TPM_RC_SCHEME (signing key, scheme unsupported) using
TPMA_OBJECT_sign per Part 3 §17.5.1 / §17.6.1.
- SignDigest restricted-key path validates TPMT_TK_HASHCHECK HMAC
per Part 3 §20.7.1 instead of blanket-rejecting; x509sign keeps
the TPM_RC_ATTRIBUTES short-circuit.
- Decapsulate, SignDigest, SignSequenceComplete reject NO_SESSIONS
with TPM_RC_AUTH_MISSING (Auth Role: USER, Tables 62/124/126).
Restricted-key TPM_GENERATED_VALUE check:
- FWTPM_SignSeq.firstBytes[4] populated by SequenceUpdate covers the
Hash-ML-DSA path where bytes are otherwise consumed by hashCtx;
topped-up from the Complete trailing buffer for Pure-MLDSA one-shot.
Closes the Update-then-empty-Complete bypass.
Client-side (src/tpm2.c):
- TPM2_VerifySequenceComplete defensively dispatches on validation.tag
for TPMU_TK_VERIFIED_META, mirroring TPM2_VerifyDigestSignature.
Other:
- TPM2_Packet_AppendSensitive caps mldsa/mlkem .size to buffer length.
- pqc_mssim_e2e.c zeroizes ss1/ss2 on cleanup.
- Untrack examples/pqc/pqc_mssim_e2e (libtool wrapper with hardcoded
/home/aidangarske path; .gitignore already covered it).
- #pragma message at WOLFTPM_V185 build-time flagging that the PQC
primary-key KDFa labels are interpretation pending TCG Part 4 v1.85;
suppressible via -DWOLFTPM_V185_LABELS_ACK.
Tests: 11 new fixtures in tests/fwtpm_unit_tests.c, 4 existing tests
updated to assert new spec-mandated RCs. fwtpm_unit.test reports 105
passing, zero failures.
Closes 13 spec-conformance findings flagged by two TCG compliance reviews
of the v1.85 PQC handlers. Each fix maps to a specific Part 2 / Part 3
section; all are exercised by negative test fixtures in
tests/fwtpm_unit_tests.c that bite-verify each fix in isolation.
Spec-RC corrections (one-line each):
- Drop TPMA_ML_PARAMETER_SET_extMu from TPM_PT_ML_PARAMETER_SETS — Part 2
§12.2.3.6 (no μ-direct sign API in wolfCrypt yet).
- Sign* handlers return TPM_RC_SCHEME (not TPM_RC_KEY) for valid keys
with unsupported scheme — Part 3 §17.5.1 / §20.7.1.
- SignDigest / VerifyDigestSignature return TPM_RC_ATTRIBUTES (not
TPM_RC_EXT_MU) when key's allowExternalMu=NO — EXT_MU is reserved for
capability errors, ATTRIBUTES for key-attribute errors.
Validation additions:
- SignDigest rejects restricted and x509sign keys at entry
(TPM_RC_ATTRIBUTES, Part 3 §20.7.1).
- VerifyDigestSignature enforces sigHashAlg == key.hashAlg
(TPM_RC_SCHEME, Part 3 §20.4.1) and digest size == hashAlg digest size
(TPM_RC_SIZE).
- CreatePrimary / Create / CreateLoaded / TestParms reject MLDSA with
allowExternalMu=YES at object-creation time (TPM_RC_EXT_MU, Part 2
§12.2.3.6) instead of letting the request succeed and fail later.
- TestParms validates ML-DSA / Hash-ML-DSA / ML-KEM parameterSet ranges.
- SignSequenceComplete rejects restricted keys signing messages whose
first 4 bytes are TPM_GENERATED_VALUE (0xFF544347) (TPM_RC_VALUE,
Part 3 §20.6.1).
- SignSequenceComplete rejects x509sign keys (TPM_RC_ATTRIBUTES).
- TPM_RC_ONE_SHOT_SIGNATURE moved from SequenceUpdate to
SignSequenceComplete (Part 3 §20.6.1: it's a Complete-time RC about
non-empty sequence, not an Update-time RC).
Structural fixes:
- TPMA_OBJECT_x509sign (bit 19, Part 2 §8.3.3 v1.85) added to the enum
in wolftpm/tpm2.h and enforced in the two sign-side handlers.
- TPM_PT_ML_PARAMETER_SETS bits gated on wolfCrypt build symbols
(WOLFSSL_HAVE_MLKEM / KYBER_*, WOLFSSL_WC_DILITHIUM / HAVE_DILITHIUM)
so the capability matches what the build actually delivers.
- wolfTPM2_EncryptSecret_MLKEM applies KDFa(SECRET, ct, pub) over the
ML-KEM shared secret per Part 1 §47.4 Eq 66 (Labeled-KEM); previous
code emitted raw K as the salt.
- VerifySequenceComplete and VerifyDigestSignature emit hierarchy-bound
TPMT_TK_VERIFIED tickets (Part 2 §10.6.5) instead of the
TPM_RH_NULL + empty-HMAC stub.
Tests (tests/fwtpm_unit_tests.c, 8 new + 4 updated, all under fwtpm_unit):
- 8 new spec-bite negatives, one per finding where a single-RC test is
meaningful. Each was bite-verified by reverting its source fix and
confirming the test fails with a different RC.
- 4 existing negatives updated to assert the new spec-mandated RCs and
reshaped where the rejection point moved (SequenceUpdate → Complete).
CI:
- New .github/workflows/pqc-examples.yml: builds + smoke-runs the v1.85
examples + invokes tests/check_doc_constants.sh on each PR.
- tests/check_doc_constants.sh greps every FWTPM_* size/seed/digest
constant from wolftpm/fwtpm/fwtpm.h and asserts each is mentioned in
docs/FWTPM.md (catches doc drift like the v1.85
FWTPM_MAX_COMMAND_SIZE 4096→8192 bump). docs/FWTPM.md gains 6 missing
entries (CMD_AUTHS, SENSITIVE_SIZE, SIGN_SEQ, SYM_KEY_SIZE,
HMAC_KEY_SIZE, HMAC_DIGEST_SIZE) so the check passes clean.
- fuzz.yml / fwtpm-test.yml / make-test-swtpm.yml / sanitizer.yml each
gain a v1.85 matrix entry running the same checks under
--enable-v185 + wolfSSL --enable-dilithium --enable-mlkem
--enable-experimental.
The harness was Phase-1 scaffolding: driver does JSON well-formedness
only; 6 of 8 fixtures carry TBD_PHASE_* placeholders; no byte-level
spec assertion. Its unique value (catching client and server both
misread the spec the same wa) requires a second v1.85 implementation
to compare against — none exists today, and wolfTPM's wire correctness
is already covered end-to-end by:
- tests/fwtpm_unit.test (88 cases, in-process)
- tests/unit.test via mssim (31 wrapper cases)
- tests/pqc_mssim_e2e.sh (dedicated PQC round-trip)
- examples/run_examples.sh (18-way keygen+keyload matrix)
- scripts/tpm2_tools_test.sh (308 tpm2-tools compatibility cases)
Rebuild from scratch if/when a second v1.85 TPM ships for interop.
FwCmd_CreateLoaded previously handled only RSA/ECC/KEYEDHASH/SYMCIPHER;
any caller issuing TPM2_CreateLoaded with an MLDSA or MLKEM template
hit the default case and got TPM_RC_TYPE. The switch now mirrors the
same three PQC arms that FwCmd_Create landed earlier (src/fwtpm/
fwtpm_command.c lines 3649-3689):
- TPM_ALG_MLDSA / TPM_ALG_HASH_MLDSA: draw a 32-byte Xi seed via
wc_RNG_GenerateBlock, expand through FwGenerateMldsaKey, write the
expanded public key to inPublic.unique.mldsa. The wire-format private
portion is the 32-byte seed.
- TPM_ALG_MLKEM: same pattern with a 64-byte d||z seed and
FwGenerateMlkemKey.
Both arms guarded on WOLFTPM_V185 to keep the non-PQC build unchanged.
Test coverage:
- new test_fwtpm_create_loaded_mldsa creates an RSA SRK then
CreateLoaded an MLDSA-65 child under it, asserts TPM_RC_SUCCESS and
a non-zero transient handle, flushes both.
- new test_fwtpm_create_loaded_mlkem is the MLKEM-768 mirror.
- new BuildCreateLoadedCmd helper reuses BuildCreatePrimaryCmd's
TPMT_PUBLIC emitter and rewrites command code + parent handle,
stripping the trailing outsideInfo + creationPCR fields that
CreatePrimary has but CreateLoaded does not (spec Part 3 §30.2).
- Note: server's FwCmd_CreateLoaded accepts only loaded-object parents,
not hierarchy handles; tests CreatePrimary first to get a usable
parent. Hierarchy-as-parent is a separate v1.38 spec feature tracked
for follow-up.
Per TCG TPM 2.0 Library v1.85 Part 1 §24 (p.316) and Part 2 Table 222,
v1.85 adds ML-KEM as a valid key-exchange type for encryptedSalt
(TPM2_StartAuthSession), inSymSeed (Duplicate/Import/Rewrap), and
credentialBlob (ActivateCredential/MakeCredential). The caller
encapsulates under the TPM's ML-KEM public key: the 32-byte shared
secret becomes the session salt, the ciphertext goes on the wire.
Changes:
- wolftpm/tpm2.h: add mlkem[MAX_MLKEM_CT_SIZE] arm to
TPMU_ENCRYPTED_SECRET union (gated on WOLFTPM_V185). Without this the
union sized at MAX_RSA_KEY_BYTES (512) could not hold an ML-KEM-768
ciphertext (1088 bytes) let alone ML-KEM-1024 (1568 bytes).
- src/tpm2_wrap.c: new static wolfTPM2_EncryptSecret_MLKEM helper that
inits an MlKemKey from tpmKey->pub.unique.mlkem, calls
wc_MlKemKey_Encapsulate, writes shared secret to data->buffer and
ciphertext to secret->secret. Dispatch switch in wolfTPM2_EncryptSecret
gains a TPM_ALG_MLKEM case. ML-DSA intentionally omitted — signing
keys have no encrypt operation (spec Table 222 has no mldsa arm).
- tests/unit_tests.c: test_wolfTPM2_EncryptSecret now creates a real
MLKEM-768 primary and asserts data.size == 32 and secret.size == 1088.
Widen printf format strings in tests/unit_tests.c so every Passed /
Failed / Skipped line lands at the same column regardless of test
name length. Pattern is inline: printf(Test TPM Wrapper: %-40s ...).
No new header or helper; fwtpm_unit_tests.c is untouched and keeps its
local fwtpm_pass column-alignment helper.
Bug fixes carried alongside:
- test_wolfTPM2_MLKEM_Encapsulate now writes ct to a caller buffer,
test_wolfTPM2_PQC threads it into Decapsulate. Previously Decap was
being called with an all-zero 2048-byte buffer which the server
correctly rejected with TPM_RC_SIZE.
- test_wolfTPM2_PQC now creates a real MLDSA-65 primary key via
wolfTPM2_CreatePrimaryKey and a real MLKEM-768 primary before running
the sequence / encap tests. Pure-MLDSA Sign uses one-shot at Complete
(no SequenceUpdate) per Part 3 §17.5; Verify still uses Update per
§20.3.
- Removed test_wolfTPM2_MLDSA_SignDigest + test_wolfTPM2_MLDSA_Verify-
DigestSignature: those always hit the skip path because Pure-MLDSA
external-μ sign needs wolfCrypt's mu-direct API (static
dilithium_sign_with_seed_mu, not WOLFSSL_API). Tracked as task #95.
Hash-ML-DSA paths still cover SignDigest / VerifyDigestSignature
wire format via the examples/pqc/pqc_mssim_e2e.c E2E test.
Cross-process PQC validation: new examples/pqc/pqc_mssim_e2e exercises
wolfTPM2_* client wrappers against a running fwtpm_server over the
mssim (SWTPM) socket transport. Two round-trips in one binary:
- MLKEM-768 Encap/Decap: asserts ciphertext = 1088 bytes and the two
derived shared secrets are byte-identical.
- HashMLDSA-65 SignDigest/Verify: asserts signature = 3309 bytes and
the validation ticket carries TPM_ST_DIGEST_VERIFIED.
tests/pqc_mssim_e2e.sh spawns fwtpm_server, waits for TCP readiness,
runs the client, and cleans up. Proves client marshaling + mssim
framing + fwtpm_server unmarshaling + PQC handler dispatch agree over
a real socket between two separately-compiled processes — orthogonal
to the in-process fwtpm_unit.test suite.
Infrastructure:
- configure.ac: new AM_CONDITIONAL BUILD_V185 so the example only
builds when --enable-v185 is passed (matches the pattern of
BUILD_SWTPM, BUILD_DEVTPM, etc.).
- examples/include.am, examples/pqc/include.am: register the example.
Output polish: replaced 85 inconsistent calls with a
fwtpm_pass(name, is_pqc) helper that produces aligned
columns. PQC tests are tagged [PQC] so they're visually distinct
from the classical suite at a glance. All 86 existing tests still pass.
Five new tests exercising corner conditions not previously covered:
- SignSeq slot exhaustion: fills all FWTPM_MAX_SIGN_SEQ (4) slots,
asserts the 5th start returns TPM_RC_OBJECT_MEMORY.
- SignSeq long-message boundary: accumulates exactly
FWTPM_MAX_DATA_BUF (1024) bytes across SequenceUpdate calls on a
Pure-MLDSA verify sequence; one extra byte overflows with
TPM_RC_MEMORY.
- MLDSA-87 max-buffer round-trip: full Sign sequence at the largest
parameter set, asserting the 4627-byte signature encodes cleanly
through the response path. Pressures FWTPM_MAX_DER_SIG_BUF (4736)
and FWTPM_MAX_COMMAND_SIZE (8192).
- MLKEM-1024 max-buffer round-trip: CreatePrimary + Encapsulate at
the largest KEM parameter set; asserts ct size = 1568 per Table 204.
- Hash-ML-DSA sequence round-trip at MLDSA-44, MLDSA-65, MLDSA-87:
SignSequenceStart -> chunked SequenceUpdate -> SignSequenceComplete,
verifying the hash-accumulator path works for all three parameter
sets and the signature response carries the correct alg/hash/size
triple.
Also adds BuildCreatePrimaryCmdParam helper so tests can vary the
parameter set without duplicating the full template.
An MLDSA-65 persistent key must survive a full FWTPM_Init / Cleanup
cycle with only the NV backing file as handoff. The new test exercises
FWTPM_NV_Save / Load end-to-end for a PQC object and verifies the
FWTPM_NV_PUBAREA_EST (2720-byte) lift is wide enough for the serialized
MLDSA public area. Compares outPublic bytes across the restart via
XMEMCMP.
Also strips plan-phase labels from in-source comments — those belong
in commit messages, not code.
tests/fwtpm_unit_tests.c:
- Extend BuildCreatePrimaryCmd to emit MLKEM-768, MLDSA-65, and
Hash-MLDSA-65/SHA-256 TPMT_PUBLIC templates per Part 2 Table 232.
- test_fwtpm_create_primary_mlkem: sanity-check MLKEM keygen path.
- test_fwtpm_create_primary_mldsa: sanity-check MLDSA keygen path.
- test_fwtpm_mlkem_roundtrip: CreatePrimary MLKEM-768 → Encapsulate →
Decapsulate, asserts both shared secrets are 32 bytes and identical.
Proves Phase 3 KDFa derivation + Phase 4 encap/decap crypto + wire
format for TPM2B_SHARED_SECRET and TPM2B_KEM_CIPHERTEXT.
- test_fwtpm_mldsa_digest_roundtrip: CreatePrimary Hash-MLDSA-65 →
SignDigest → VerifyDigestSignature, asserts TPM_ST_DIGEST_VERIFIED
ticket. Proves Phase 5 digest handlers and Bug M-4 metadata format.
- test_fwtpm_mldsa_sequence_roundtrip: CreatePrimary Pure MLDSA-65 →
SignSequenceStart → SignSequenceComplete → VerifySequenceStart →
SequenceUpdate → VerifySequenceComplete, asserts TPM_ST_MESSAGE_
VERIFIED. Proves Phase 5 sign path, Phase 5b one-shot semantics and
message accumulator, and Bug M-1 Pure-MLDSA wire format.
Wired into main(); all five tests pass on --enable-v185.
src/fwtpm/fwtpm_crypto.c, wolftpm/fwtpm/fwtpm_crypto.h:
- Fix: FwSignMldsaMessage and FwSignMldsaHash now take a WC_RNG*.
wolfCrypt wc_dilithium_sign_ctx_msg / sign_ctx_hash require a
non-NULL RNG to source the 32-byte value for hedged signing
(FIPS 204 Algorithm 2 step 7). The Phase 5 code passed NULL and
returned BAD_FUNC_ARG at runtime. Both hedged and deterministic
signing are FIPS 204 compliant; wolfCrypt's non-_with_seed API
chose hedged, matching normal TPM side-channel practice.
src/fwtpm/fwtpm_command.c:
- Pass &ctx->rng to the two updated sign helpers from FwCmd_SignDigest
and FwCmd_SignSequenceComplete.
- Add TPM2B_MLDSA_SIGNATURE type with proper 4627-byte buffer for ML-DSA-87
signatures instead of reusing TPM2B_MAX_BUFFER (1024 bytes)
- Add bounds checking and byte skipping for MLDSA/MLKEM public key parsing
in TPM2_Packet_ParsePublic to prevent buffer overflow
- Add bounds checking for ML-DSA signature parsing in
TPM2_Packet_ParseSignature with proper wire size tracking
- Add bounds checking to Encapsulate/Decapsulate response parsing
(sharedSecret and ciphertext buffers)
- Add negative size validation for contextSz, digestSz, dataSz parameters
in wrapper functions: wolfTPM2_SignSequenceStart, wolfTPM2_SignSequenceComplete,
wolfTPM2_VerifySequenceStart, wolfTPM2_VerifySequenceComplete,
wolfTPM2_SignDigest, wolfTPM2_VerifyDigestSignature
- Fix misleading MAX_SIGNATURE_CTX_SIZE comment - this is for domain
separation context (255 bytes), not signature size
- Change TPMT_PUBLIC size check from assertion to warning for embedded
systems compatibility
- configure.ac: switch wolfCrypt link probe to AC_LINK_IFELSE with real
headers so CC=g++ builds don't fail on name-mangling; gate fwTPM/swTPM
autodetect on ENABLED_WOLFCRYPT=yes (fwTPM requires wolfCrypt)
- wolftpm/fwtpm/fwtpm.h: hoist FWTPM_NV_HAL_S and FWTPM_CLOCK_HAL_S out
of the FWTPM_CTX nested scope so C++ consumers can reference them
- src/fwtpm/fwtpm_command.c, fwtpm_crypto.c: add explicit TPMI_ALG_HASH
and enum wc_HashType casts to silence strict C++ warnings; drop stale
KEYEDHASH hashAlg initializer
- src/tpm2_wrap.c: move rc = TPM_RC_SUCCESS into the correct #else branch
so Linux/winapi paths propagate TPM2_Startup errors
- examples/endorsement/get_ek_certs.c: drop redundant indexType default
- examples/endorsement/verify_ek_cert.c: initialize curveName on the
WOLFTPM2_NO_WOLFCRYPT/no-ECC path
- examples/native/native_test.c: stop masking TPM2_ZGen_2Phase and
TPM2_EC_Ephemeral 'command unavailable' as success
- tests/fwtpm_unit_tests.c: use cmdSz (not hardcoded 14) on FlushContext
- CI: add no-examples and st33ktpm2-i2c compile-only matrix entries to
make-test-swtpm; add release-checks workflow (CC=g++ + scan-build)
Use unshare --user --net to isolate fwtpm_check.sh TCP ports in CI, eliminating flaky port conflicts on shared runners. Add macOS CI with socket transport tests. Add Windows socket portability (Winsock2) and build-only CI via CMake.
- Restore userWithAuth attribute in seal example (intentional for
password-based unsealing)
- Restore chained if (rc == TPM_RC_SUCCESS) pattern and the
release-build authSz mismatch check in TPM2_CommandProcess
- Restore TPM2_Packet_AppendSymmetric call in TPM2_Duplicate
- Update ParamEncDec_Dispatch test to use fwtpm raw-pointer
TPM2_ParamEnc_AESCFB signature
Add portable firmware TPM 2.0 implementation (fwTPM) built on wolfCrypt.
Implements 105/113 TPM 2.0 v1.38 commands (93%) as a standalone server
with socket and TIS transports, NV storage, and full CI/fuzz coverage.
- src/tpm2_wrap.c: wolfTPM2_Init auto-SPDM path now calls vendor-specific
wolfTPM2_SpdmConnectNuvoton/Nations (which sets TIS I/O, mode, and the
host ephemeral key pair for mutual auth). Previous refactor to the
generic wolfTPM2_SpdmConnect dropped that setup, so FINISH failed with
WOLFSPDM_E_PEER_ERROR whenever the TPM was in SPDM-only-locked mode.
- src/tpm2_spdm.c: wolfSPDM_ParseVendorDefined returns payload dataLen
(>=0) on success per spdm_tcg.c:72 convention. wolfTPM2_SPDM_SecuredExchange
was treating any non-zero return as an error, so the first TPM command
over SPDM (10-byte Startup response) bubbled up as rc=0xa.
- tests/unit_tests.c: Add test_tpm_alg_supported() helper that queries
TPM_CAP_ALGS and skip test_wolfTPM2_EccSignVerifyDig iterations when the
hash isn't supported. Nuvoton NPCT75x lacks SHA512 and returns
TPM_RC_SIZE parameter 1 (not TPM_RC_HASH), so the prior post-hoc skip
couldn't catch it.
- .github/workflows/hw-spdm-test.yml: New workflow targeting a self-hosted
Raspberry Pi runner. Runs examples/spdm/spdm_test.sh against real Nuvoton
hardware. Fork-PR gated. Health check soft-fails with a warning if the
Pi is unhealthy so merging isn't blocked on a flaky runner. Nations
matrix entry scaffolded (commented) for when the NS350 is wired.
- scripts/hw-runner-health-check.sh: Pre-flight that verifies /dev/spidev,
/dev/gpiochip, and gpiod are present+accessible before the build.
F-2959 - https://fenrir.wolfssl.com/finding/2959 - Add unit test for wolfTPM2_SetAuthHandleName policyAuth branch verifying auth placed at authDigestSz offset
* Updated copyright
* Fixed minor cast warning.
* Fixed minor scan-build warnings.
* Cleanup print public and fix possible print of uninitialized field.
* Added key generation example with parameter encryption
* Fixes and cleanups for KDFa
* Added KDFa unit test (passes)
* Added AES CFB support
* Fix for nonceTPM
* Added support for encrypted RSA salt and salted-unbounded session
* Removed unsalted-unbounded specific code for authValueKDF from KDFa
* Add innerWrap support
* Add missing wolfcrypt header for AES CFB
* Fixes for casting when calling KDFa for AES CFB parameter encryption
* Add outerWrap support
Signed-off-by: Dimitar Tomov <dimi@wolfssl.com>