wolfTPM fwTPM (fTPM / swtpm) -- Firmware TPM 2.0

June 29, 2026 · View on GitHub

Overview

The wolfTPM fwTPM (industry terms: fTPM, swtpm-compatible) is a portable firmware TPM 2.0 implementation built entirely on wolfCrypt cryptographic primitives. It provides a standards-compliant TPM 2.0 command processor as a standalone server process (fwtpm_server) implementing 105 of 113 commands from the TPM 2.0 v1.38 specification (93% coverage). The fwTPM can replace a hardware TPM for:

  • Embedded/IoT platforms without a discrete TPM chip (bare-metal via SPI/I2C TIS HAL)
  • Development and testing of TPM-dependent applications (drop-in for swtpm or MS TPM simulator)
  • CI/CD pipelines requiring TPM functionality (socket transport compatible with tpm2-tools)
  • Prototyping TPM workflows before hardware is available

Architecture

+---------------------+          +---------------------------+
| wolfTPM Client App  |          |  fwtpm_server             |
| (examples, tests)   |          |                           |
+----------+----------+          |  +---------------------+  |
           |                     |  | fwtpm_command.c      | |
     TCP (SWTPM protocol)        |  | (command processor)  | |
     or TIS shared memory        |  +----------+----------+  |
           |                     |             |             |
+----------v----------+          |  +----------v----------+  |
| Transport Layer     +--------->+  | wolfCrypt           |  |
| (socket or TIS HAL) |          |  | (RSA, ECC, SHA,     |  |
+---------------------+          |  |  HMAC, RNG, AES)    |  |
                                 |  +---------------------+  |
                                 |             |             |
                                 |  +----------v----------+  |
                                 |  | fwtpm_nv.c           | |
                                 |  | (persistent storage) | |
                                 |  +---------------------+  |
                                 +---------------------------+

Components:

FileRole
fwtpm_command.cTPM 2.0 command processor and dispatch table (~9500 lines)
fwtpm_io.cTransport layer -- SWTPM TCP socket protocol (default)
fwtpm_nv.cNV storage -- file-based (default); HAL-abstracted, with a built-in append-only mode for write-once flash
fwtpm_tis.cTIS register state machine (transport-agnostic)
fwtpm_tis_shm.cPOSIX shared memory + semaphore TIS transport
fwtpm_main.cServer entry point, CLI argument parsing
tpm2_util.cShared utilities (hash helpers, ForceZero, PrintBin)
tpm2_packet.cTPM packet marshaling/unmarshaling
tpm2_param_enc.cParameter encryption (XOR and AES session encryption)

Building

Prerequisites

wolfSSL must be built with TPM support:

cd wolfssl
./configure --enable-wolftpm --enable-pkcallbacks
make
sudo make install

Build fwTPM Server

Socket transport (SWTPM protocol, default for development):

cd wolftpm
./configure --enable-fwtpm --enable-swtpm
make

This produces src/fwtpm/fwtpm_server and builds the wolfTPM client library with WOLFTPM_SWTPM for socket-based communication.

TIS/shared-memory transport (for fwTPM HAL integration):

./configure --enable-fwtpm
make

When --enable-swtpm is omitted, the build uses TIS shared-memory transport (WOLFTPM_FWTPM_HAL, WOLFTPM_ADV_IO) and compiles fwtpm_tis.c into the server.

fwTPM server only (no client library or examples):

./configure --enable-fwtpm-only --enable-swtpm
make

This builds only the fwtpm_server binary, skipping libwolftpm, examples, and tests. Useful for embedded targets that only need the TPM server.

Debug build:

./configure --enable-fwtpm --enable-swtpm --enable-debug
make

Build Output

ArtifactDescription
src/fwtpm/fwtpm_serverStandalone fwTPM server binary
src/.libs/libwolftpm.*wolfTPM client library

Key Build Flags

Configure OptionEffect
--enable-fwtpmBuild fwtpm_server binary (alongside client library)
--enable-fwtpm-onlyBuild only fwtpm_server (no client library, examples, or tests)
--enable-swtpmUse SWTPM TCP socket transport (ports 2321/2322)
--enable-fwtpm-nv-appendonlyAppend-only NV journal for write-once flash ports (off by default)
--enable-debugEnable debug logging
Compile DefineSet By
WOLFTPM_FWTPMAutomatically set for fwtpm_server target only
WOLFTPM_SWTPM--enable-swtpm
WOLFTPM_FWTPM_HAL--enable-fwtpm without --enable-swtpm
WOLFTPM_FWTPM_TIS--enable-fwtpm without --enable-swtpm
WOLFTPM_ADV_IOSet with WOLFTPM_FWTPM_HAL
WOLFTPM_FWTPM_NV_APPEND_ONLY--enable-fwtpm-nv-appendonly (CMake WOLFTPM_FWTPM_NV_APPEND_ONLY=yes)

Usage

Starting the Server

./src/fwtpm/fwtpm_server [options]

Options:

OptionDescription
--help, -hShow usage information
--version, -vPrint version string
--port <port>Command port (default: 2321)
--platform-port <port>Platform port (default: 2322)

Example:

# Start with default ports
./src/fwtpm/fwtpm_server

# Start on custom ports
./src/fwtpm/fwtpm_server --port 2331 --platform-port 2332

The server prints its configuration on startup:

wolfTPM fwTPM Server v0.1.0
  Command port:  2321
  Platform port: 2322
  Manufacturer:  WOLF
  Model:         fwTPM

Connecting wolfTPM Clients

Any wolfTPM application built with --enable-swtpm connects to the fwTPM server automatically via TCP:

# In one terminal: start the server
./src/fwtpm/fwtpm_server

# In another terminal: run wolfTPM examples
./examples/wrap/wrap_test
./examples/keygen/keygen keyblob.bin -rsa -t
./examples/attestation/make_credential

NV Persistence

The server stores persistent state (hierarchy seeds, auth values, PCR state, NV indices) in fwtpm_nv.bin (configurable via FWTPM_NV_FILE). On first start, seeds are randomly generated and saved. Subsequent starts reload existing state.

NV Storage HAL (embedded ports)

NV access is abstracted behind FWTPM_NV_HAL (read/write/erase/ctx/ maxSize/get_integrity_key). The default backend is the file above; an embedded port supplies its own HAL and registers it before FWTPM_Init().

The journal is log-structured. On a byte-addressable backend (the file default) it writes TLV entries at byte-granular offsets, rewrites the header in place, and rewrites a trailing integrity MAC after every append. Internal flash and NOR are write-once and program-granularity aligned, so they cannot service in-place rewrites. Build with --enable-fwtpm-nv-appendonly (-DWOLFTPM_FWTPM_NV_APPEND_ONLY) and set appendOnly and writeAlign on the HAL; the journal then runs in append-only mode: the header is written only at compaction, writePos is derived by scanning on load, and each commit is sealed with an appended MAC-checkpoint entry padded up to writeAlign. The existing read/write/erase HAL is the integration point - no separate adapter.

/* Native flash HAL. In append-only mode the journal only ever calls write()
 * with writeAlign-aligned, forward, into-erased bytes, so write() is a simple
 * flash program; erase() erases the region (sector loop); read() reads raw. */
FWTPM_NV_HAL hal;
XMEMSET(&hal, 0, sizeof(hal));
hal.read = myRead; hal.write = myProgram; hal.erase = myErase;
hal.ctx = myCtx; hal.maxSize = NV_SIZE;
hal.appendOnly = 1;
hal.writeAlign = PROG_SIZE;            /* flash word size, e.g. 16 (STM32H5) */
hal.get_integrity_key = myDeviceSecret;/* recommended on flash */
FWTPM_NV_SetHAL(&ctx, &hal);           /* before FWTPM_Init() */

In append-only mode the journal buffers a pending program granule internally and flushes full, aligned granules through write(), so a programmed cell is never rewritten and a whole sector is erased only on compaction. The header sector is therefore not erased on every append, and a torn final commit (e.g. on power loss) is ignored on the next load while all previously committed state survives. A get_integrity_key callback is strongly recommended on flash so the MAC checkpoints authenticate the journal and reject a torn or tampered tail. writeAlign <= 1 selects no buffering, so byte-writable NV (EEPROM/FRAM) works with a plain write(). Compaction still erases the whole region before rewriting, so a power loss during compaction itself remains a vulnerable window (a future two-region ping-pong layout would close it). See src/fwtpm/ports/README.md for the full porting guide.

Supported TPM 2.0 Commands

Startup / Self-Test

CommandDescription
TPM2_StartupInitialize TPM (SU_CLEAR or SU_STATE)
TPM2_ShutdownSave state and prepare for power-off
TPM2_SelfTestExecute full self-test
TPM2_IncrementalSelfTestIncremental algorithm self-test
TPM2_GetTestResultReturn self-test result

Random Number Generation

CommandDescription
TPM2_GetRandomGenerate random bytes (max 48 per call)
TPM2_StirRandomAdd entropy to RNG state

Capability

CommandDescription
TPM2_GetCapabilityQuery TPM properties, algorithms, handles

Key Management

CommandDescription
TPM2_CreatePrimaryCreate primary key under a hierarchy
TPM2_CreateCreate child key under a parent
TPM2_CreateLoadedCreate and load key in one command
TPM2_LoadLoad key from private/public parts
TPM2_LoadExternalLoad external (software) key
TPM2_ImportImport externally wrapped key
TPM2_DuplicateExport key for transfer (inner/outer wrapping)
TPM2_RewrapRe-wrap key under new parent (placeholder)
TPM2_FlushContextUnload a transient object or session
TPM2_ContextSaveSave object/session context
TPM2_ContextLoadRestore saved context
TPM2_ReadPublicRead public area of a loaded key
TPM2_ObjectChangeAuthChange authorization of a key
TPM2_EvictControlMake transient key persistent (or remove)
TPM2_HierarchyControlEnable or disable a hierarchy
TPM2_HierarchyChangeAuthChange hierarchy authorization value
TPM2_ClearClear hierarchy (Owner or Platform)
TPM2_ChangePPSReplace platform primary seed
TPM2_ChangeEPSReplace endorsement primary seed

Cryptographic Operations

CommandDescription
TPM2_SignSign digest with loaded key
TPM2_VerifySignatureVerify signature against loaded key
TPM2_RSA_EncryptRSA encryption (OAEP, PKCS1)
TPM2_RSA_DecryptRSA decryption
TPM2_EncryptDecryptSymmetric encrypt/decrypt
TPM2_EncryptDecrypt2Symmetric encrypt/decrypt (alternate)
TPM2_HashSingle-shot hash computation
TPM2_HMACSingle-shot HMAC computation
TPM2_ECDH_KeyGenGenerate ephemeral ECC key pair
TPM2_ECDH_ZGenCompute ECDH shared secret
TPM2_ECC_ParametersGet ECC curve parameters
TPM2_TestParmsValidate algorithm parameter support

Hash Sequences

CommandDescription
TPM2_HashSequenceStartStart a hash sequence
TPM2_HMAC_StartStart an HMAC sequence
TPM2_SequenceUpdateAdd data to a hash/HMAC sequence
TPM2_SequenceCompleteFinalize hash/HMAC sequence and get result
TPM2_EventSequenceCompleteFinalize hash sequence and extend PCR

Sealing

CommandDescription
TPM2_UnsealUnseal data from a sealed object

PCR (Platform Configuration Registers)

CommandDescription
TPM2_PCR_ReadRead PCR values
TPM2_PCR_ExtendExtend a PCR with a digest
TPM2_PCR_ResetReset a resettable PCR

Clock

CommandDescription
TPM2_ReadClockRead TPM clock values
TPM2_ClockSetSet TPM clock

Sessions and Authorization

CommandDescription
TPM2_StartAuthSessionCreate HMAC, policy, or trial session

Policy

CommandDescription
TPM2_PolicyGetDigestGet current policy session digest
TPM2_PolicyRestartReset policy session digest
TPM2_PolicyPCRBind policy to PCR values
TPM2_PolicyPasswordInclude password in policy
TPM2_PolicyAuthValueInclude auth value in policy
TPM2_PolicyCommandCodeRestrict policy to specific command
TPM2_PolicyORLogical OR of policy branches
TPM2_PolicySecretAuthorization with secret
TPM2_PolicyAuthorizeApprove policy with signing key
TPM2_PolicyNVPolicy based on NV index comparison
TPM2_PolicyLocalityRestrict policy to specific locality
TPM2_PolicySignedAuthorize policy with external signing key

Dictionary Attack (DA) Protection

CommandDescription
TPM2_DictionaryAttackParametersSet maxTries, recoveryTime, lockoutRecovery
TPM2_DictionaryAttackLockResetReset the failed-tries counter (lockoutAuth)

fwTPM follows the TPM 2.0 spec (Part 1 Sec.19.8). A failed authorization of a DA-protected entity increments failedTries; once it reaches maxTries the TPM returns TPM_RC_LOCKOUT. failedTries is persisted in NV on every failure, so a power cycle cannot reset it. When a clock HAL is registered (FWTPM_Clock_SetHAL) it self-heals one try per recoveryTime seconds, and a non-orderly shutdown adds a one-try penalty; on clockless builds neither applies (recovery is via DictionaryAttackLockReset/Clear only) so routine unclean power-off cannot accumulate into lockout. A failed lockoutAuth locks the lockout hierarchy: that lock persists across reboot and clears after lockoutRecovery seconds, except when lockoutRecovery is 0 (reboot-only recovery). Because the clock HAL reports milliseconds since boot, this timer measures continuous post-boot uptime, not wall-clock time across reboots — a device that reboots more often than lockoutRecovery extends its effective recovery window. The lock only blocks commands authorized via lockoutAuth (DictionaryAttackLockReset, DictionaryAttackParameters, lockout-authorized Clear); the platform hierarchy is always an escape hatch — TPM2_ClearControl(platformAuth, clearDisable=NO) then TPM2_Clear(platformAuth) recovers even when disableClear was set. Startup/Shutdown are never DA-gated, so a reboot in lockout can always recover. Entities marked noDA (TPMA_OBJECT_noDA on objects, TPMA_NV_NO_DA on NV indices) never feed the counter and stay usable during lockout. TPM2_GetCapability(TPM_CAP_TPM_PROPERTIES) reports TPM_PT_MAX_AUTH_FAIL, TPM_PT_LOCKOUT_INTERVAL, TPM_PT_LOCKOUT_RECOVERY, TPM_PT_LOCKOUT_COUNTER, and the inLockout bit of TPM_PT_PERMANENT.

Durable accounting writes the NV FLAGS entry on each DA-protected failure (and on the first DA-protected auth use per boot). This is bounded per boot — the lockout gate stops counting once locked — but on flash-backed targets it adds wear and makes failed-auth latency NV-bound; size the NV backend accordingly.

The first use of a DA-protected (non-noDA) authorization after startup makes a real TPM persist a daUsed flag to NV and return TPM_RC_RETRY ("resubmit the identical command") while it writes. Build with FWTPM_DA_USED_RETRY to emulate this so clients exercise their resubmit/retry handling. It is off by default; DA accounting and persistence are active regardless. Compile out all DA logic with FWTPM_NO_DA.

Coverage: DA/noDA/lockout/self-heal/persistence unit tests in tests/fwtpm_unit_tests.c, the examples/management/da_check end-to-end example (add -lockout for the destructive lockout/recovery path), and the tests/fwtpm_da_retry.sh harness that exercises the TPM_RC_RETRY path against a FWTPM_DA_USED_RETRY build.

NV RAM

CommandDescription
TPM2_NV_DefineSpaceCreate an NV index
TPM2_NV_UndefineSpaceDelete an NV index
TPM2_NV_ReadPublicRead NV index public metadata
TPM2_NV_WriteWrite data to NV index
TPM2_NV_ReadRead data from NV index
TPM2_NV_ExtendExtend NV index (hash-extend)
TPM2_NV_IncrementIncrement NV counter
TPM2_NV_WriteLockLock NV index for writes
TPM2_NV_ReadLockLock NV index for reads
TPM2_NV_SetBitsOR bits into NV bit field index
TPM2_NV_ChangeAuthChange NV index authorization value
TPM2_NV_CertifyCertify NV index contents

Attestation and Credentials

CommandDescription
TPM2_QuoteGenerate signed PCR quote
TPM2_CertifyCertify a loaded key
TPM2_CertifyCreationProve key was created by this TPM
TPM2_GetTimeSigned attestation of TPM clock
TPM2_MakeCredentialCreate credential blob for a key
TPM2_ActivateCredentialUnwrap credential blob

HAL Abstraction

The fwTPM provides two hardware abstraction layers (HALs) for porting to embedded targets without modifying core logic.

IO HAL (Transport)

The IO HAL abstracts the transport between the fwTPM server and its clients. The default implementation uses TCP sockets (SWTPM protocol). For embedded targets, replace with SPI, I2C, UART, or shared memory callbacks.

Callback structure (defined in FWTPM_IO_HAL in fwtpm.h):

CallbackSignatureDescription
sendint (*)(void* ctx, const void* buf, int sz)Send data to client
recvint (*)(void* ctx, void* buf, int sz)Receive data from client
waitint (*)(void* ctx)Wait for data/connections. Returns bitmask: 0x01=command data, 0x02=platform data, 0x04=new command connection, 0x08=new platform connection
acceptint (*)(void* ctx, int type)Accept new connection (type: 0=command, 1=platform)
close_connvoid (*)(void* ctx, int type)Close connection (type: 0=command, 1=platform)
ctxvoid*User context pointer

Registration:

FWTPM_IO_HAL myHal;
myHal.send = my_send;
myHal.recv = my_recv;
myHal.wait = my_wait;
myHal.accept = my_accept;
myHal.close_conn = my_close;
myHal.ctx = &myTransportCtx;

FWTPM_IO_SetHAL(&ctx, &myHal);

NV HAL (Persistent Storage)

The NV HAL abstracts persistent storage. The default implementation uses a local file (fwtpm_nv.bin). For embedded targets, replace with flash, EEPROM, or other non-volatile storage callbacks.

Callback structure (defined in FWTPM_NV_HAL in fwtpm.h):

CallbackSignatureDescription
readint (*)(void* ctx, word32 offset, byte* buf, word32 size)Read from NV at offset
writeint (*)(void* ctx, word32 offset, const byte* buf, word32 size)Write to NV at offset
ctxvoid*User context pointer

Registration:

FWTPM_NV_HAL myNvHal;
myNvHal.read = my_flash_read;
myNvHal.write = my_flash_write;
myNvHal.ctx = &myFlashCtx;

FWTPM_NV_SetHAL(&ctx, &myNvHal);

Porting Example

For a bare-metal embedded target with SPI transport and SPI flash NV:

FWTPM_CTX ctx;
FWTPM_Init(&ctx);

/* Set custom IO transport */
FWTPM_IO_HAL ioHal = {
    .send = spi_slave_send,
    .recv = spi_slave_recv,
    .wait = spi_slave_poll,
    .accept = NULL,         /* not connection-oriented */
    .close_conn = NULL,
    .ctx = &spiHandle
};
FWTPM_IO_SetHAL(&ctx, &ioHal);

/* Set custom NV storage */
FWTPM_NV_HAL nvHal = {
    .read = spi_flash_read,
    .write = spi_flash_write,
    .ctx = &flashHandle
};
FWTPM_NV_SetHAL(&ctx, &nvHal);

/* Initialize IO and run */
FWTPM_IO_Init(&ctx);
FWTPM_IO_ServerLoop(&ctx);  /* blocks */

FWTPM_IO_Cleanup(&ctx);
FWTPM_Cleanup(&ctx);

Configuration Macros

All macros are compile-time overridable (e.g., -DFWTPM_MAX_OBJECTS=8).

MacroDefaultDescription
FWTPM_MAX_COMMAND_SIZE4096Maximum command/response buffer size (bytes)
FWTPM_MAX_RANDOM_BYTES48Maximum bytes per GetRandom call
FWTPM_MAX_OBJECTS16Maximum concurrently loaded transient objects
FWTPM_MAX_PERSISTENT8Maximum persistent objects (via EvictControl)
FWTPM_MAX_PRIVKEY_DER2048Maximum DER-encoded private key size (bytes)
FWTPM_MAX_HASH_SEQ4Maximum concurrent hash/HMAC sequences
FWTPM_MAX_PRIMARY_CACHE16Cached primary keys per hierarchy+template
FWTPM_MAX_SESSIONS8Maximum concurrent auth sessions
FWTPM_MAX_NV_INDICES16Maximum NV RAM index slots
FWTPM_MAX_NV_DATA2048Maximum data per NV index (bytes)
FWTPM_DA_DEFAULT_MAX_TRIES32DA failed-auth count before lockout
FWTPM_DA_DEFAULT_RECOVERY600DA self-heal interval (seconds per try)
FWTPM_DA_DEFAULT_LOCKOUT_RECOVERY86400lockoutAuth recovery time (seconds)
FWTPM_DA_MAX_TRIES_LIMIT0xFFFFUpper clamp for a replayed maxTries/failedTries
FWTPM_MAX_DATA_BUF1024Internal buffer for HMAC, hash, general data
FWTPM_MAX_PUB_BUF512Internal buffer for public area, signatures
FWTPM_MAX_DER_SIG_BUF256Internal buffer for DER signatures, ECC points
FWTPM_MAX_ATTEST_BUF1024Internal buffer for attestation marshaling
FWTPM_MAX_CMD_AUTHS3Maximum authorization sessions per command (TPM-spec hard cap)
FWTPM_MAX_SENSITIVE_SIZEFWTPM_MAX_PRIVKEY_DER + 128Maximum marshaled sensitive area (private key + auth + nonce headroom)
FWTPM_MAX_SIGN_SEQ4Maximum concurrent v1.85 PQC sign/verify sequences
FWTPM_MAX_SYM_KEY_SIZE32Symmetric key buffer (sized for AES-256)
FWTPM_MAX_HMAC_KEY_SIZE64HMAC key buffer (sized for SHA-512 block)
FWTPM_MAX_HMAC_DIGEST_SIZE64HMAC output buffer (sized for SHA-512)
FWTPM_CMD_PORT2321Default TCP command port
FWTPM_PLAT_PORT2322Default TCP platform port
FWTPM_NV_FILE"fwtpm_nv.bin"Default NV storage file path
FWTPM_NV_MAX_WRITE_ALIGN64Max append-only program granule in bytes (upper bound on a HAL's writeAlign); sizes the pending-granule buffer when WOLFTPM_FWTPM_NV_APPEND_ONLY is set
FWTPM_PCR_BANKS2Number of PCR banks (SHA-256 + SHA-384)
FWTPM_TIS_BURST_COUNT64TIS FIFO burst count (bytes per transfer)
FWTPM_TIS_FIFO_SIZE4096TIS command/response FIFO size

Stack/Heap Control

MacroEffect
WOLFTPM_SMALL_STACKUse heap allocation for large stack objects
WOLFTPM2_NO_HEAPForbid heap allocation (all stack)

Note: WOLFTPM_SMALL_STACK and WOLFTPM2_NO_HEAP are mutually exclusive and will produce a compile error if both are defined.

v1.85 Embedded RAM Impact

Enabling --enable-pqc (or --enable-v185) lifts several internal buffers to accommodate PQC key/signature sizes. The defaults auto-shrink at compile time based on which ML-DSA / ML-KEM parameter sets wolfCrypt was actually built with (WOLFSSL_NO_ML_DSA_44/65/87, WOLFSSL_NO_KYBER512/768/1024) — boards that only enable the smaller params get smaller buffers automatically, no per-board override required.

Buffer sizes by enabled parameter set:

MacroClassicalMLDSA-44 + MLKEM-512MLDSA-65 + MLKEM-768MLDSA-87 + MLKEM-1024
FWTPM_TIS_FIFO_SIZE4096409681928192
FWTPM_MAX_COMMAND_SIZE4096409681928192
FWTPM_MAX_PUB_BUF512144020802720
FWTPM_MAX_DER_SIG_BUF256254834374755
FWTPM_MAX_KEM_CT_BUFn/a83211521632

Sizing logic lives in wolftpm/fwtpm/fwtpm.h (constants FWTPM_MAX_MLDSA_SIG_SIZE, FWTPM_MAX_MLDSA_PUB_SIZE, FWTPM_MAX_MLKEM_CT_SIZE, FWTPM_MAX_MLKEM_PUB_SIZE) and wolftpm/fwtpm/fwtpm_tis.h (FIFO size). The MLDSA constants come from wolfCrypt's WC_MLDSA_{44,65,87}_*_SIZE macros; the MLKEM constants are FIPS 203 spec values (wolfCrypt's WC_ML_KEM_*_SIZE macros aren't preprocessor-evaluable).

The 8192 lifts on FIFO/command buffers only kick in when MLDSA-65 or MLDSA-87 is enabled (their signatures don't fit a 4096 response with TPM headers). MLDSA-44-only and MLKEM-only v1.85 builds stay at 4096.

Per-deployment override: every macro above is still #ifndef-guarded, so a board can override individually on the compile line if the auto default is wrong for its workload (e.g. -DFWTPM_TIS_FIFO_SIZE=2048).

Heap-vs-stack: building with WOLFTPM_SMALL_STACK moves the large per-call buffers off the stack into XMALLOC/XFREE regions. The PQC paths already use FWTPM_DECLARE_BUF / FWTPM_ALLOC_BUF which respect this flag, so no source changes are required. WOLFTPM2_NO_HEAP is supported but pays full stack cost — pair it with the smallest PQC parameter set you can.

Algorithm Feature Macros

These macros use wolfCrypt's existing compile-time options to control which cryptographic algorithms are available in fwtpm_server. If an algorithm is disabled, the corresponding TPM commands are excluded from the build.

MacroDefaultEffect
NO_RSAnot definedExcludes RSA keygen, sign, verify, RSA_Encrypt, RSA_Decrypt
HAVE_ECCdefinedEnables ECC keygen, sign, verify, ECDH_KeyGen, ECDH_ZGen, ECC_Parameters
HAVE_ECC384definedEnables P-384 curve support
HAVE_ECC521definedEnables P-521 curve support
NO_AESnot definedExcludes EncryptDecrypt, EncryptDecrypt2, AES parameter encryption
WOLFSSL_SHA384definedEnables SHA-384 PCR bank

When an algorithm is disabled, commands that exclusively use that algorithm are removed from the dispatch table at compile time. Commands that support multiple algorithms (e.g., CreatePrimary, Sign) remain available but return TPM_RC_ASYMMETRIC for the disabled algorithm type.

TPM Feature Group Macros

These fwTPM-specific macros disable entire groups of TPM 2.0 functionality to reduce code size on constrained targets.

MacroDefaultCommands Excluded
FWTPM_NO_ATTESTATIONnot definedQuote, Certify, CertifyCreation, GetTime, NV_Certify
FWTPM_NO_NVnot definedNV_DefineSpace, NV_UndefineSpace, NV_ReadPublic, NV_Write, NV_Read, NV_Extend, NV_Increment, NV_WriteLock, NV_ReadLock, NV_Certify
FWTPM_NO_POLICYnot definedPolicyGetDigest, PolicyRestart, PolicyPCR, PolicyPassword, PolicyAuthValue, PolicyCommandCode, PolicyOR, PolicySecret, PolicyAuthorize, PolicyNV
FWTPM_NO_CREDENTIALnot definedMakeCredential, ActivateCredential
FWTPM_NO_DAnot definedDictionaryAttackParameters, DictionaryAttackLockReset, and all lockout accounting

The FWTPM_DA_USED_RETRY macro (off by default) does not remove commands; it makes the server return TPM_RC_RETRY on the first DA-protected auth use after startup, emulating a real TPM persisting daUsed. See Dictionary Attack (DA) Protection.

Minimal build example (measured boot only):

./configure --enable-fwtpm --enable-swtpm \
    CFLAGS="-DNO_RSA -DFWTPM_NO_NV -DFWTPM_NO_ATTESTATION \
            -DFWTPM_NO_POLICY -DFWTPM_NO_CREDENTIAL"

This retains only: Startup, Shutdown, SelfTest, GetRandom, GetCapability, PCR_Read, PCR_Extend, PCR_Reset, Hash, ECC keygen/sign, and session support.

Dependencies:

  • FWTPM_NO_NV also removes NV_Certify (even if FWTPM_NO_ATTESTATION is not set)
  • NO_RSA implies no RSA attestation signatures (ECC-only attestation still works with HAVE_ECC)

Transport Modes

Socket / SWTPM (Default)

Built with --enable-fwtpm --enable-swtpm. The server listens on two TCP ports using the SWTPM wire protocol:

  • Command port (default 2321): TPM command/response traffic
  • Platform port (default 2322): Platform signals (power on/off, NV on, cancel, reset, session end, stop)

SWTPM TCP protocol commands (platform port):

SignalValueDescription
SIGNAL_POWER_ON1Power on the TPM
SIGNAL_POWER_OFF2Power off the TPM
SIGNAL_PHYS_PRES_ON3Assert physical presence
SIGNAL_PHYS_PRES_OFF4Deassert physical presence
SIGNAL_HASH_START5Start measured boot hash
SIGNAL_HASH_DATA6Provide measured boot data
SIGNAL_HASH_END9End measured boot hash
SEND_COMMAND8Send TPM command (command port)
SIGNAL_NV_ON11NV storage available
SIGNAL_CANCEL_ON13Cancel current command
SIGNAL_CANCEL_OFF14Clear cancel
SIGNAL_RESET17Reset TPM
SESSION_END20End TCP session
STOP21Stop server

wolfTPM clients connect using the standard SWTPM interface, compatible with tpm2-tools and other SWTPM-aware software.

TIS / Shared Memory

Built with --enable-fwtpm (without --enable-swtpm). Uses POSIX shared memory and named semaphores to emulate TIS (TPM Interface Specification) register-level access. This mode simulates an SPI-attached TPM.

Shared memory layout (FWTPM_TIS_SHM):

FieldDescription
magic / versionValidation header (0x57544953 / "WTIS")
reg_addr, reg_len, reg_is_write, reg_dataRegister access request
TIS register shadow: access, sts, int_enable, int_status, intf_caps, did_vid, ridEmulated TIS registers
cmd_buf[4096], cmd_len, fifo_write_posCommand FIFO
rsp_buf[4096], rsp_len, fifo_read_posResponse FIFO

Paths (compile-time configurable):

DefineDefaultDescription
FWTPM_TIS_SHM_PATH/tmp/fwtpm.shmShared memory file
FWTPM_TIS_SEM_CMD/fwtpm_cmdCommand semaphore name
FWTPM_TIS_SEM_RSP/fwtpm_rspResponse semaphore name

Server-side API:

  • FWTPM_TIS_Init() -- Create shared memory and semaphores
  • FWTPM_TIS_Cleanup() -- Remove shared memory and semaphores
  • FWTPM_TIS_ServerLoop() -- Process TIS register accesses, dispatch commands

Client-side API (enabled by WOLFTPM_FWTPM_HAL):

  • FWTPM_TIS_ClientConnect() -- Attach to existing shared memory
  • FWTPM_TIS_ClientDisconnect() -- Detach from shared memory

Testing

See src/fwtpm/README.md for the full CI test matrix and test script usage. Quick reference:

make check                  # Build + unit.test + run_examples.sh + tpm2-tools
scripts/tpm2_tools_test.sh  # tpm2-tools only (311 tests)

make check runs tests/fwtpm_check.sh, which starts and stops fwtpm_server automatically -- do not start it manually.

API Reference

Core (fwtpm.h)

FunctionDescription
int FWTPM_Init(FWTPM_CTX* ctx)Initialize fwTPM context, RNG, load NV state
int FWTPM_Cleanup(FWTPM_CTX* ctx)Save NV, free resources, zero sensitive data
const char* FWTPM_GetVersionString(void)Return version string (e.g., "0.1.0")

Command Processor (fwtpm_command.h)

FunctionDescription
int FWTPM_ProcessCommand(FWTPM_CTX* ctx, const byte* cmdBuf, int cmdSize, byte* rspBuf, int* rspSize, int locality)Process a raw TPM command packet and produce a response. Returns TPM_RC_SUCCESS on successful processing; the response buffer may contain a TPM error RC.

IO Transport (fwtpm_io.h)

FunctionDescription
int FWTPM_IO_SetHAL(FWTPM_CTX* ctx, FWTPM_IO_HAL* hal)Register custom IO transport callbacks
int FWTPM_IO_Init(FWTPM_CTX* ctx)Initialize transport (sockets or custom HAL)
void FWTPM_IO_Cleanup(FWTPM_CTX* ctx)Close transport and release resources
int FWTPM_IO_ServerLoop(FWTPM_CTX* ctx)Main server loop -- blocks until ctx->running is cleared

NV Storage (fwtpm_nv.h)

FunctionDescription
int FWTPM_NV_Init(FWTPM_CTX* ctx)Load NV state from storage or create new (generates seeds)
int FWTPM_NV_Save(FWTPM_CTX* ctx)Save current TPM state to NV storage
int FWTPM_NV_SetHAL(FWTPM_CTX* ctx, FWTPM_NV_HAL* hal)Register custom NV storage callbacks

TIS Server (fwtpm_tis.h)

FunctionDescription
int FWTPM_TIS_Init(FWTPM_CTX* ctx)Create shared memory region and semaphores
void FWTPM_TIS_Cleanup(FWTPM_CTX* ctx)Unlink shared memory and semaphores
int FWTPM_TIS_ServerLoop(FWTPM_CTX* ctx)Process TIS register accesses (blocks)

TIS Client (fwtpm_tis.h, requires WOLFTPM_FWTPM_HAL)

FunctionDescription
int FWTPM_TIS_ClientConnect(FWTPM_TIS_CLIENT_CTX* client)Attach to fwTPM shared memory
void FWTPM_TIS_ClientDisconnect(FWTPM_TIS_CLIENT_CTX* client)Detach from shared memory

Startup / Shutdown Lifecycle

  1. First boot: FWTPM_NV_Init finds no NV file, generates random hierarchy seeds, saves initial state.
  2. TPM2_Startup(SU_CLEAR): Flushes transient objects and sessions, resets PCRs. Required before any other TPM command.
  3. Normal operation: Commands are processed via FWTPM_ProcessCommand.
  4. TPM2_Shutdown: Saves NV state but does NOT clear the "started" flag. The TPM remains logically powered on.
  5. Server restart (process exit and relaunch) constitutes a power cycle. Only after a power cycle can TPM2_Startup be called again.

Calling TPM2_Startup on an already-started TPM returns TPM_RC_INITIALIZE.

Primary Key Derivation

Primary keys are deterministically derived from the hierarchy seed per TPM 2.0 Part 1 Section 26. The same seed + same template always produces the same key:

  • RSA: Primes p, q derived via iterative KDFa with labels "RSA p" / "RSA q", primality testing, then CRT computation
  • ECC: Private scalar d derived via KDFa(nameAlg, seed, "ECC", hashUnique, counter), public point Q = d*G
  • KEYEDHASH/SYMCIPHER: Key bytes derived via KDFa(nameAlg, seed, label, hashUnique)
  • hashUnique: H(sensitiveCreate.data || inPublic.unique) per Section 26.1

A primary key cache (SHA-256 of template, FWTPM_MAX_PRIMARY_CACHE slots) avoids re-deriving expensive RSA keys on repeated CreatePrimary calls.

Hierarchy seeds are managed by ChangePPS (platform) and ChangeEPS (endorsement). Clear regenerates owner and endorsement seeds. The null seed is re-randomized on every Startup(CLEAR).

TPM 2.0 v1.85 Post-Quantum Support

Enabled with --enable-pqc (alias --enable-v185) at configure time, or auto-detected when --enable-fwtpm is built against a wolfCrypt that has both ML-DSA and ML-KEM available. Both flags set the internal WOLFTPM_V185 macro that gates the implementation. Pass --disable-pqc to opt out when auto-detect would otherwise enable it. Implements the post-quantum additions from TCG TPM 2.0 Library Specification v1.85 using wolfCrypt's FIPS 203 / FIPS 204 modules.

Algorithms

AlgParameter SetsUse
TPM_ALG_MLKEM (0x00A0)MLKEM-512 / 768 / 1024Key encapsulation (decrypt-only keys)
TPM_ALG_MLDSA (0x00A1)MLDSA-44 / 65 / 87Pure ML-DSA message signing
TPM_ALG_HASH_MLDSA (0x00A2)MLDSA-44 / 65 / 87Pre-hashed ML-DSA signing

Commands

The eight v1.85 PQC commands in src/fwtpm/fwtpm_command.c:

CommandCCPurpose
TPM2_Encapsulate0x000001A7ML-KEM encapsulation, returns sharedSecret + ciphertext
TPM2_Decapsulate0x000001A8ML-KEM decapsulation from ciphertext (requires USER auth)
TPM2_SignSequenceStart0x000001AABegin ML-DSA sign sequence
TPM2_SignSequenceComplete0x000001A4Finalize sign sequence with message buffer
TPM2_VerifySequenceStart0x000001A9Begin ML-DSA verify sequence
TPM2_VerifySequenceComplete0x000001A3Finalize verify sequence, returns TPMT_TK_VERIFIED
TPM2_SignDigest0x000001A6One-shot digest sign (Hash-ML-DSA or ext-μ ML-DSA)
TPM2_VerifyDigestSignature0x000001A5Verify digest signature

Primary Key Derivation

PQC primary keys follow the same deterministic derivation model as RSA/ECC: hierarchy seed + template → KDFa-derived seed → FIPS 203/204 key expansion.

  • ML-DSA: KDFa(nameAlg, seed, "MLDSA", hashUnique) → 32-byte Xiwc_MlDsaKey_MakeKeyFromSeed → (pub, expanded-priv). The wire format stores only the 32-byte Xi per TCG Part 2 Table 210.
  • Hash-ML-DSA: label is "HASH_MLDSA"; same seed size and expansion.
  • ML-KEM: KDFa(nameAlg, seed, "MLKEM", hashUnique) → 64-byte (d‖z)wc_MlKemKey_MakeKeyWithRandom → (ek, dk). Wire format stores only 64-byte seed per TCG Part 2 Table 206.

These label strings are an interpretation — TCG Part 4 v185 (which would normatively specify them) is unpublished, so they are subject to change if rc5 / Part 4 v185 prescribe different labels.

Sign / Verify Sequences

Pure ML-DSA is one-shotTPM2_SequenceUpdate on a Pure ML-DSA sign sequence returns TPM_RC_ONE_SHOT_SIGNATURE; the message must arrive via the buffer parameter of TPM2_SignSequenceComplete. Verify sequences accumulate the message via TPM2_SequenceUpdate since TPM2_VerifySequenceComplete has no buffer parameter.

Hash-ML-DSA sequences (both sign and verify) use wolfCrypt's wc_HashAlg context to stream the message into the key's hash algorithm; TPM2_SignSequenceComplete finalizes the hash and calls wc_MlDsaKey_SignCtxHash.

Signature wire formats differ per spec Part 2 Table 217:

  • Pure ML-DSATPM2B_SIGNATURE_MLDSA: sigAlg + size + bytes
  • Hash-ML-DSATPMS_SIGNATURE_HASH_MLDSA: sigAlg + hashAlg + size + bytes

Buffer Constants

Under WOLFTPM_V185, buffers are lifted to accommodate ML-DSA-87 signatures (4627 bytes) and public keys (2592 bytes):

Symbolv1.38v1.85
FWTPM_MAX_COMMAND_SIZE40968192
FWTPM_MAX_PUB_BUF5122720
FWTPM_MAX_DER_SIG_BUF2564736
FWTPM_MAX_KEM_CT_BUF1600
FWTPM_TIS_FIFO_SIZE40968192
FWTPM_NV_PUBAREA_EST6002720

Deferred / Out of Scope

Three v1.85 features are deferred with documented reasons:

  1. ML-KEM-salted sessions — Part 3 Sec.11.1 (TPM2_StartAuthSession) does not describe an ML-KEM bullet alongside RSA-OAEP and ECDH paths, even though Part 2 Sec.11.4.2 Table 222 defines the mlkem arm of TPMU_ENCRYPTED_SECRET. Part 4 v185 (which would normatively specify this) is not yet published. Current behavior: TPM2_StartAuthSession returns TPM_RC_KEY for ML-KEM tpmKey; revisit when Part 4 v185 lands.
  2. External-μ ML-DSA signing — wolfCrypt has no μ-direct sign API. Part 2 Sec.12.2.3.7 text says "512-byte external Mu" but FIPS 204 Algorithm 7 Line 6 produces 64 bytes (SHAKE256 output). Pending wolfCrypt API addition and TCG errata confirmation. Current behavior: TPM_RC_SCHEME for ext-μ paths, TPM_RC_EXT_MU for Pure ML-DSA keys without allowExternalMu. See DEC-0006.
  3. ECC KEM arm of Encapsulate/Decapsulate — Part 2 Sec.10.3.13 Table 100 has both mlkem and ecdh arms, but the table note explicitly allows implementations to modify the union based on supported algorithms. Current fwTPM supports the mlkem arm only.

Test Coverage

tests/fwtpm_unit_tests.c includes ten PQC tests exercising the full path:

  • CreatePrimary for MLKEM-768 and MLDSA-65
  • Full Encap/Decap round-trip (shared secret byte match)
  • Hash-ML-DSA SignDigest / VerifyDigestSignature round-trip
  • Pure ML-DSA sign sequence + verify sequence round-trip
  • Dual-source KAT tests (NIST ACVP + wolfSSL internal vectors) for MLDSA-44 verify, MLDSA-44 keygen determinism, MLKEM-512 encapsulation with pinned randomness, and MLKEM-512 keygen determinism
  • LoadExternal of a NIST ACVP MLDSA-44 public key through the fwTPM handler