Skip to content

Add TPM 2.0 v1.85 PQC (ML-DSA and ML-KEM) and fwTPM PQC Support #445

Merged
dgarske merged 51 commits intowolfSSL:masterfrom
aidangarske:v185-pq-support
Apr 29, 2026
Merged

Add TPM 2.0 v1.85 PQC (ML-DSA and ML-KEM) and fwTPM PQC Support #445
dgarske merged 51 commits intowolfSSL:masterfrom
aidangarske:v185-pq-support

Conversation

@aidangarske
Copy link
Copy Markdown
Member

@aidangarske aidangarske commented Dec 26, 2025

Description

This PR adds TPM 2.0 Library Specification v1.85 PQC support to wolfTPM, covering both client-side wrappers and an in-tree fwTPM server implementation that is the first open-source PQC-capable TPM 2.0 built from spec.

Implements ML-DSA (FIPS 204) for signing, ML-KEM (FIPS 203) for key encapsulation, and Hash-ML-DSA pre-hash variant. All three parameter sets per algorithm: ML-DSA-{44, 65, 87} and ML-KEM-{512, 768, 1024}.

Spec basis: TCG TPM 2.0 Library Specification v185 rc4, TCG Algorithm Registry v2.0 rc2, FIPS 203, FIPS 204.

New TPM 2.0 v1.85 Commands

8 new commands implemented end-to-end (client wrapper + marshaling + fwTPM server handler):

Command CC Spec Purpose
TPM2_Encapsulate 0x1A7 §14.10 KEM encap (ML-KEM)
TPM2_Decapsulate 0x1A8 §14.11 KEM decap (ML-KEM)
TPM2_SignSequenceStart 0x1AA §17.5 Begin multi-part sign
TPM2_VerifySequenceStart 0x1A9 §17.6 Begin multi-part verify
TPM2_SignSequenceComplete 0x1A4 §20.6 Finish sign (ML-DSA/Hash-ML-DSA)
TPM2_VerifySequenceComplete 0x1A3 §20.3 Finish verify; returns TPM_ST_MESSAGE_VERIFIED ticket
TPM2_SignDigest 0x1A6 §20.7 One-shot digest sign
TPM2_VerifyDigestSignature 0x1A5 §20.4 One-shot digest verify; returns TPM_ST_DIGEST_VERIFIED ticket

New Types, Enums, and Structures

  • Algorithm IDs: TPM_ALG_MLKEM (0x00A0), TPM_ALG_MLDSA (0x00A1), TPM_ALG_HASH_MLDSA (0x00A2)
  • Parameter sets: TPM_MLKEM_{512,768,1024}, TPM_MLDSA_{44,65,87}
  • Structure tags: TPM_ST_MESSAGE_VERIFIED (0x8026), TPM_ST_DIGEST_VERIFIED (0x8027)
  • Error codes: TPM_RC_EXT_MU (RC_FMT1+0x02B), TPM_RC_ONE_SHOT_SIGNATURE (RC_FMT1+0x02C), TPM_RC_SIGN_CONTEXT_KEY (RC_FMT1+0x02D)
  • TPM2B types: TPM2B_SIGNATURE_CTX, TPM2B_SIGNATURE_HINT, TPM2B_KEM_CIPHERTEXT, TPM2B_SHARED_SECRET, TPM2B_PUBLIC_KEY_MLDSA/MLKEM, TPM2B_PRIVATE_KEY_MLDSA/MLKEM
  • Parameter structures: TPMS_MLDSA_PARMS, TPMS_HASH_MLDSA_PARMS, TPMS_MLKEM_PARMS
  • Union extensions: TPMU_PUBLIC_PARMS, TPMU_PUBLIC_ID, TPMU_SENSITIVE_COMPOSITE, TPMU_SIGNATURE, TPMU_ENCRYPTED_SECRET (new mlkem arm per v185 Table 222), TPMU_TK_VERIFIED_META
  • Capability: TPMA_ML_PARAMETER_SET bitfield, TPM_PT_ML_PARAMETER_SETS property, wired into TPM_CAP_ALGS / TPM_CAP_COMMANDS / TPM_CAP_TPM_PROPERTIES
  • Ticket update: TPMT_TK_VERIFIED gains [tag]metadata (TPM_ALG_ID for digest-verified), digesthmac rename per v185
  • Constants: MAX_MLDSA_PUB_SIZE=2592, MAX_MLDSA_SIG_SIZE=4627, MAX_MLKEM_PUB_SIZE=1568, seed sizes

fwTPM Server PQC Support

All 8 new commands implemented in the in-tree fwTPM server (src/fwtpm/):

  • Deterministic primary keygen via KDFa from hierarchy primary seeds with documented labels ("MLDSA", "HASH_MLDSA", "MLKEM")
  • Seed-based private keys on the wire (32 bytes for ML-DSA Xi, 64 bytes for ML-KEM d‖z) per v185 Tables 206 / 210; expansion via FIPS 203/204 deterministic keygen
  • FwCmd_Create, FwCmd_CreatePrimary, FwCmd_CreateLoaded all gained MLDSA/HASH_MLDSA/MLKEM arms
  • Sequence-context persistence across SequenceStart/SequenceUpdate/SequenceComplete with Pure-MLDSA one-shot enforcement per §17.5 (returns TPM_RC_ONE_SHOT_SIGNATURE on SequenceUpdate)
  • Buffer constants (FWTPM_MAX_COMMAND_SIZE, FWTPM_MAX_PUB_BUF, FWTPM_MAX_DER_SIG_BUF) conditionally lifted under WOLFTPM_V185 so classical ABI stays stable when the flag is off
  • NV persistence of PQC public areas and sensitive seeds

Client-Side Wrappers and Examples

  • High-level wrappers in tpm2_wrap.c: wolfTPM2_Encapsulate, wolfTPM2_Decapsulate, wolfTPM2_SignSequenceStart/Update/Complete, wolfTPM2_VerifySequenceStart/Update/Complete, wolfTPM2_SignDigest, wolfTPM2_VerifyDigestSignature, wolfTPM2_GetKeyTemplate_{MLDSA, HASH_MLDSA, MLKEM}, wolfTPM2_EncryptSecret_MLKEM
  • New example examples/pqc/mldsa_sign.c-mldsa=44|65|87, full sign-sequence + verify-sequence round-trip against fwTPM
  • New example examples/pqc/mlkem_encap.c-mlkem=512|768|1024, encap/decap shared-secret round-trip
  • examples/keygen extended with -mldsa, -hash_mldsa, -mlkem options (keygen + keyload round-trip validated across all 18 parameter combinations in CI)

Testing

Unit tests (make check)

  • 88 fwtpm_unit.test cases covering: determinism (seed → same pub), negative-RC paths for every spec-mandated error code across all 8 new handlers, TPM_PT_ML_PARAMETER_SETS capability reporting, NV persistence round-trips, MLDSA-87 + MLKEM-1024 max-buffer pressure, Hash-ML-DSA sequences for all three parameter sets, sign-determinism KATs against pinned NIST vectors, slot exhaustion, long-message accumulation boundary
  • 31 unit.test wrapper cases including test_wolfTPM2_MLDSA_SignSequence, test_wolfTPM2_MLDSA_VerifySequence, test_wolfTPM2_MLKEM_Encapsulate, test_wolfTPM2_MLKEM_Decapsulate, test_wolfTPM2_MLKEM_RoundTrip, test_wolfTPM2_PQC_KeyTemplates, test_wolfTPM2_PQC_Sizes
  • End-to-end mssim harness (tests/pqc_mssim_e2e.sh) — launches fwtpm_server subprocess, points wolfTPM2 client wrappers at it, exercises MLDSA Hash-sequence sign/verify and MLKEM encap/decap
  • Dual-source KAT vectors — wolfSSL test vectors + NIST ACVP vectors (MLDSA-44 sigVer, MLKEM-512 encapDecap pinned)
  • 308 tpm2-tools cases (existing matrix, unchanged by PQC work)
  • 18-way PQC keygen + keyload matrix (examples/run_examples.sh)

Fuzz CI

.github/workflows/fuzz.yml extended with 2 new matrix entries (fuzz-full-pqc, fuzz-smoke-pqc) that exercise v1.85 command-code paths with PQC-aware dictionary + seed corpus. Classical fuzz entries unchanged.

Build

# wolfSSL with PQC + keygen

./configure --enable-wolftpm --enable-pkcallbacks --enable-keygen --enable-dilithium --enable-mlkem --enable-experimental --enable-harden CFLAGS="-DWC_RSA_NO_PADDING"
make && sudo make install

# wolfTPM with fwTPM + v1.85
./configure --enable-fwtpm --enable-pqc
make
make check

Files

  • wolftpm/tpm2.h, wolftpm/tpm2_types.h, wolftpm/tpm2_wrap.h — PQC types, constants, wrapper declarations
  • src/tpm2.c, src/tpm2_packet.c, src/tpm2_wrap.c — marshaling + wrappers
  • src/fwtpm/fwtpm_command.c, src/fwtpm/fwtpm_crypto.c — fwTPM server PQC handlers + crypto helpers
  • examples/pqc/mldsa_sign.c, examples/pqc/mlkem_encap.c — end-to-end demos
  • examples/keygen/keygen.c — PQC options
  • tests/unit_tests.c, tests/fwtpm_unit_tests.c, tests/pqc_mssim_e2e.sh, tests/pqc_kat_vectors.h — test coverage
  • README.md, docs/FWTPM.md, examples/pqc/README.md — usage docs
  • .github/workflows/fuzz.yml, tests/fuzz/gen_corpus.py, tests/fuzz/tpm2.dict — fuzz matrix extensions

@aidangarske aidangarske self-assigned this Dec 26, 2025
@dgarske dgarske self-requested a review December 29, 2025 17:44
@dgarske dgarske self-assigned this Jan 2, 2026
Comment thread configure.ac
Comment thread wolftpm/tpm2.h Outdated
@dgarske dgarske removed their assignment Jan 28, 2026
@dgarske dgarske removed their request for review January 28, 2026 20:59
Copy link
Copy Markdown

@tdjCisco tdjCisco left a comment

Choose a reason for hiding this comment

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

Won't the command/response buffers need updating

Copy link
Copy Markdown

@tdjCisco tdjCisco left a comment

Choose a reason for hiding this comment

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

Looks like bus encryption with PQC key is missing

Copy link
Copy Markdown

@tdjCisco tdjCisco left a comment

Choose a reason for hiding this comment

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

I think updates to TPMU_PUBLIC_PARMS, new TPMS_MLDSA_PARMS and TPMS_MLKEM_PARMS, TPMU_PUBLIC_ID, TPMU_SENSITIVE_COMPOSITE, TPM2_Packet_AppendPublicParms, TPM2_Packet_AppendPublicArea, wolfTPM2_GetKeyTemplate_MLKEM and MLDSA are needed.

@dgarske dgarske self-assigned this Mar 5, 2026
Copilot AI review requested due to automatic review settings March 11, 2026 19:20
@aidangarske aidangarske review requested due to automatic review settings March 11, 2026 19:22
Copilot AI review requested due to automatic review settings March 19, 2026 19:44

This comment was marked as resolved.

wolfSSL-Fenrir-bot

This comment was marked as resolved.

Copilot AI review requested due to automatic review settings March 19, 2026 22:01

This comment was marked as resolved.

Copilot AI review requested due to automatic review settings March 21, 2026 21:35
@aidangarske aidangarske review requested due to automatic review settings March 21, 2026 21:42
Copilot AI review requested due to automatic review settings April 6, 2026 18:45
@aidangarske aidangarske review requested due to automatic review settings April 6, 2026 18:45
aidangarske and others added 25 commits April 29, 2026 10:27
  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.
  examples/keygen/keygen:
  - New -mldsa[=44|65|87], -hash_mldsa[=44|65|87], -mlkem[=512|768|1024]
    options alongside existing -rsa/-ecc/-sym/-keyedhash. Dispatches to
    wolfTPM2_GetKeyTemplate_{MLDSA,HASH_MLDSA,MLKEM}, then CreateKey under
    the SRK parent. AIK template path correctly rejects PQC (AIKs are
    RSA/ECC only per TCG).
  - Param-set parser defaults: MLDSA-65, MLKEM-768, SHA-256 pre-hash for
    Hash-ML-DSA.

  examples/pqc/mlkem_encap (new):
  - CreatePrimary MLKEM (512/768/1024) then Encapsulate + Decapsulate,
    asserting the two shared secrets match byte-for-byte. Companion to
    pqc_mssim_e2e but focused on the KEM wrappers alone.

  examples/run_examples.sh:
  - Detects WOLFTPM_V185 from config.h, runs keygen+keyload round-trip
    for all 9 PQC variants (same pattern used by RSA/ECC blocks above).
    All 9 pass against fwtpm_server.
   Main README:
   - New ## Post-Quantum Cryptography (v1.85) section between fwTPM and
     TPM 2.0 Overview. Covers supported algorithms (ML-DSA-44/65/87,
     Hash-ML-DSA, ML-KEM-512/768/1024), exact wolfSSL + wolfTPM build
     config (--enable-dilithium --enable-mlkem --enable-experimental ...
     for wolfSSL; --enable-fwtpm --enable-v185 for wolfTPM), and a
     make check pointer.
   - Existing feature-list bullet at line 41 now points to the new
     section instead of directly to docs/FWTPM.md.

   examples/pqc/README.md:
   - Rewrite around three audience splits: (1) build steps, (2) run
     everything with make check, (3) per-example details.
   - New sections for mlkem_encap and the -mldsa/-hash_mldsa/-mlkem
     options on examples/keygen/keygen.
   - Drop stale --enable-swtpm reference (wrong flag; caused reviewer
     confusion).
   - Point users at the existing tests/fwtpm_check.sh and
     tests/pqc_mssim_e2e.sh for targeted reruns without the full classical
     suite.

   Documentation split (no duplication):
   - Top-level README - build + I just want to run it
   - examples/pqc/README.md per-example usage
   - docs/FWTPM.md#tpm-20-v185-post-quantum-support -> server internals
     (commands, primary-key derivation, buffer constants, spec
     interpretation decisions)
  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.
  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.
  tests/fuzz/tpm2.dict gains:
  - 8 new command codes (Encapsulate 0x1A7, Decapsulate 0x1A8,
    SignSequenceStart 0x1AA, SignSequenceComplete 0x1A4,
    VerifySequenceStart 0x1A9, VerifySequenceComplete 0x1A3,
    SignDigest 0x1A6, VerifyDigestSignature 0x1A5)
  - 3 PQC algorithm IDs (MLKEM 0x00A0, MLDSA 0x00A1, HASH_MLDSA 0x00A2)
  - 6 parameter-set values (MLKEM 512/768/1024, MLDSA 44/65/87)
  - 3 new response codes (RC_EXT_MU, RC_ONE_SHOT_SIGNATURE,
    RC_SIGN_CONTEXT_KEY)

  tests/fuzz/gen_corpus.py gains 10 PQC seed files:
  - pqc_encapsulate_mlkem, pqc_decapsulate_mlkem
  - pqc_signseqstart_mldsa, pqc_signseqcomplete_mldsa
  - pqc_verifyseqstart_mldsa, pqc_verifyseqcomplete_mldsa
  - pqc_signdigest_mldsa, pqc_verifydigestsig_mldsa
  - pqc_createprimary_mlkem, pqc_createprimary_mldsa

  These give libFuzzer starting shapes for the 8 new command paths rather
  than relying on coverage-guided discovery from scratch.

  .github/workflows/fuzz.yml matrix expanded:
  - Existing 2 entries (fuzz-full, fuzz-smoke) classical-only, unchanged
    flags, zero regression risk
  - New fuzz-full-pqc (10-min weekly) and fuzz-smoke-pqc (60s every PR)
    build wolfSSL with --enable-dilithium --enable-mlkem --enable-
    experimental --enable-harden and wolfTPM with --enable-v185, use
    -max_len=8192 to accommodate MLDSA-87 sigs (4627 bytes).
  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.
   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.
  Fixes 5 v1.85 PR CI/build issues:

  1. src/tpm2_wrap.c: add #include <wolfssl/wolfcrypt/mlkem.h> inside the
     v185 MLKEM guard. Builds with --disable-fwtpm against wolfSSL with
     --enable-mlkem failed because the MLKEM symbol declarations were
     only pulled in transitively by src/fwtpm/fwtpm_crypto.c.

  2. src/fwtpm/fwtpm_command.c: switch FWTPM_ALLOC_BUF(privKeyDer) to
     FWTPM_CALLOC_BUF in 4 sites (Create, Load, LoadExternal, Import,
     CreateLoaded). MSan-v185 flagged uninit-value reads in SocketSend
     originating from FwCmd_Create's keyedHash branch — when caller
     supplies undersized inSensitive material, FwComputeUniqueHash hashed
     beyond what was written. Zero-initialising the buffer eliminates the
     class of issue.

  3. examples/keygen/keygen.c: pass allowExternalMu=NO for MLDSA. The
     v1.85 EXT_MU enforcement now correctly rejects allowExternalMu=YES
     at object creation per Part 2 §12.2.3.6.

  4. .github/workflows/make-test-swtpm.yml: convert v185-pqc-swtpm lane
     to build-only. swtpm has no v1.85 PQC, so unit.test PQC blocks fail
     on TPM_RC_SIZE; runtime PQC coverage stays in the fwtpm-v185 lane.

  5. .github/workflows/sanitizer.yml: UBSan-v185 now uses the same
     sanitizer flags as the classical UBSan lane (drops ).
     Pre-existing wolfSSL UB at misc.c:117 (440<<24 in Hash_df) only
     surfaces under -fsanitize=integer.
  Code quality / defensive fixes:
  - TPM2_Encap/Decap: drop bare scope braces, hoist wireSize locals
  - FwCmd_SequenceUpdate: clarify Pure ML-DSA sign accumulation comment
  - FwAllocSignSeq: _Static_assert transient slot range stays valid
  - keygen: drop unused hashMldsaHash local, pass TPM_ALG_SHA256 directly
  - FwCmd_Encapsulate: skip auth area when cmdTag == TPM_ST_SESSIONS
  - writeKeyBlob: restore no-op TPM_RC_SUCCESS in NO_FILESYSTEM build
  - FwCmd_SignDigest restricted-key ticket compare: TPM2_ConstantCompare
  - FwCmd_VerifySequenceComplete: hard-fail if ticket data binding lost
    (no silent fallback that emits a weakened ticket)
  - wolfTPM2_VerifySequenceComplete: validate sigSz before SequenceUpdate
    so BUFFER_E does not leak the TPM-side sequence handle
  - FwCmd_VerifySequenceComplete: heap-allocate ~1KB ticketData via
    FWTPM_DECLARE_BUF / FWTPM_ALLOC_BUF (matches sibling buffers)

  v1.85 capability + scope:
  - GetCapability: report TPM_PT_FIRMWARE_SVN/MAX_SVN = 0
  - Allow Pure ML-DSA streaming via SequenceUpdate per FIPS 204 (SHAKE256
    absorbing is incremental); SignSequenceComplete concatenates msgBuf
    with the trailing complete-time buffer and signs the full message
  - Document v1.85 scope: Encap/Decap is ML-KEM only; Sign/VerifySequence
    and SignDigest/VerifyDigestSignature are ML-DSA / Hash-ML-DSA only
    (classical schemes still go via TPM2_Sign / TPM2_VerifySignature)

  TCG ticket wire-format fixes (security):
  - TPMT_TK_HASHCHECK: SignDigest now validates tag = TPM_ST_HASHCHECK
    unconditionally (TPM_RC_TAG); wolfTPM2_SignDigest wrapper synthesizes
    the NULL Hashcheck instead of sending tag=0/hierarchy=0 from XMEMSET
  - NULL Verified Tickets: FwAppendTicket no longer appends metadata
    bytes when hierarchy == TPM_RH_NULL; client parser conditions
    metaAlg consumption on hierarchy != TPM_RH_NULL (Part 2 §10.6.5)

  Embedded RAM auto-shrink (v1.85):
  - New FWTPM_MAX_MLDSA_{SIG,PUB}_SIZE / FWTPM_MAX_MLKEM_{CT,PUB}_SIZE
    resolve to the largest enabled parameter set via wolfCrypt's
    WOLFSSL_NO_ML_DSA_{44,65,87} / WOLFSSL_NO_KYBER{512,768,1024} gates
  - FWTPM_MAX_DER_SIG_BUF, FWTPM_MAX_PUB_BUF, FWTPM_MAX_KEM_CT_BUF
    derive from those (no per-board override needed)
  - FWTPM_MAX_COMMAND_SIZE / FWTPM_TIS_FIFO_SIZE only lift to 8192 when
    MLDSA-65 or MLDSA-87 is enabled; MLDSA-44-only and MLKEM-only
    v1.85 builds stay at 4096
  - docs/FWTPM.md: per-build size table + override + small-stack notes

  Test coverage:
  - examples/run_examples.sh: invoke pqc/mldsa_sign and pqc/mlkem_encap
    inside the v1.85 block
  - tests/fwtpm_unit_tests.c:
    * SignDigest with malformed HASHCHECK tag rejected (TPM_RC_TAG)
    * FwAppendTicket NULL DIGEST_VERIFIED emits no metadata
    * SignSeqComplete Pure-MLDSA streaming (FIPS 204 §6) — replaces
      obsolete one-shot rejection assertion
  - tests/unit_tests.c:
    * Hash-ML-DSA SignSeqUpdate streaming end-to-end + arg validation
    * TPMT_SIGNATURE round-trip for ML-DSA / Hash-ML-DSA arms
    * TPM2B_PUBLIC round-trip for ML-DSA / Hash-ML-DSA / ML-KEM arms
  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)
…support

  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.
@dgarske dgarske merged commit fbbf6fe into wolfSSL:master Apr 29, 2026
257 of 269 checks passed
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.

5 participants