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
+---------------------+ +---------------------------+
| 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:
| File | Role |
|---|---|
fwtpm_command.c |
TPM 2.0 command processor and dispatch table (~9500 lines) |
fwtpm_io.c |
Transport layer -- SWTPM TCP socket protocol (default) |
fwtpm_nv.c |
NV storage -- file-based (default), HAL-abstracted for embedded |
fwtpm_tis.c |
TIS register state machine (transport-agnostic) |
fwtpm_tis_shm.c |
POSIX shared memory + semaphore TIS transport |
fwtpm_main.c |
Server entry point, CLI argument parsing |
tpm2_util.c |
Shared utilities (hash helpers, ForceZero, PrintBin) |
tpm2_packet.c |
TPM packet marshaling/unmarshaling |
tpm2_param_enc.c |
Parameter encryption (XOR and AES session encryption) |
wolfSSL must be built with TPM support:
cd wolfssl
./configure --enable-wolftpm --enable-pkcallbacks
make
sudo make installSocket transport (SWTPM protocol, default for development):
cd wolftpm
./configure --enable-fwtpm --enable-swtpm
makeThis 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
makeWhen --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
makeThis 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| Artifact | Description |
|---|---|
src/fwtpm/fwtpm_server |
Standalone fwTPM server binary |
src/.libs/libwolftpm.* |
wolfTPM client library |
| Configure Option | Effect |
|---|---|
--enable-fwtpm |
Build fwtpm_server binary (alongside client library) |
--enable-fwtpm-only |
Build only fwtpm_server (no client library, examples, or tests) |
--enable-swtpm |
Use SWTPM TCP socket transport (ports 2321/2322) |
--enable-debug |
Enable debug logging |
| Compile Define | Set By |
|---|---|
WOLFTPM_FWTPM |
Automatically 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_IO |
Set with WOLFTPM_FWTPM_HAL |
./src/fwtpm/fwtpm_server [options]Options:
| Option | Description |
|---|---|
--help, -h |
Show usage information |
--version, -v |
Print 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 2332The server prints its configuration on startup:
wolfTPM fwTPM Server v0.1.0
Command port: 2321
Platform port: 2322
Manufacturer: WOLF
Model: fwTPM
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_credentialThe 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.
| Command | Description |
|---|---|
TPM2_Startup |
Initialize TPM (SU_CLEAR or SU_STATE) |
TPM2_Shutdown |
Save state and prepare for power-off |
TPM2_SelfTest |
Execute full self-test |
TPM2_IncrementalSelfTest |
Incremental algorithm self-test |
TPM2_GetTestResult |
Return self-test result |
| Command | Description |
|---|---|
TPM2_GetRandom |
Generate random bytes (max 48 per call) |
TPM2_StirRandom |
Add entropy to RNG state |
| Command | Description |
|---|---|
TPM2_GetCapability |
Query TPM properties, algorithms, handles |
| Command | Description |
|---|---|
TPM2_CreatePrimary |
Create primary key under a hierarchy |
TPM2_Create |
Create child key under a parent |
TPM2_CreateLoaded |
Create and load key in one command |
TPM2_Load |
Load key from private/public parts |
TPM2_LoadExternal |
Load external (software) key |
TPM2_Import |
Import externally wrapped key |
TPM2_Duplicate |
Export key for transfer (inner/outer wrapping) |
TPM2_Rewrap |
Re-wrap key under new parent (placeholder) |
TPM2_FlushContext |
Unload a transient object or session |
TPM2_ContextSave |
Save object/session context |
TPM2_ContextLoad |
Restore saved context |
TPM2_ReadPublic |
Read public area of a loaded key |
TPM2_ObjectChangeAuth |
Change authorization of a key |
TPM2_EvictControl |
Make transient key persistent (or remove) |
TPM2_HierarchyControl |
Enable or disable a hierarchy |
TPM2_HierarchyChangeAuth |
Change hierarchy authorization value |
TPM2_Clear |
Clear hierarchy (Owner or Platform) |
TPM2_ChangePPS |
Replace platform primary seed |
TPM2_ChangeEPS |
Replace endorsement primary seed |
| Command | Description |
|---|---|
TPM2_Sign |
Sign digest with loaded key |
TPM2_VerifySignature |
Verify signature against loaded key |
TPM2_RSA_Encrypt |
RSA encryption (OAEP, PKCS1) |
TPM2_RSA_Decrypt |
RSA decryption |
TPM2_EncryptDecrypt |
Symmetric encrypt/decrypt |
TPM2_EncryptDecrypt2 |
Symmetric encrypt/decrypt (alternate) |
TPM2_Hash |
Single-shot hash computation |
TPM2_HMAC |
Single-shot HMAC computation |
TPM2_ECDH_KeyGen |
Generate ephemeral ECC key pair |
TPM2_ECDH_ZGen |
Compute ECDH shared secret |
TPM2_ECC_Parameters |
Get ECC curve parameters |
TPM2_TestParms |
Validate algorithm parameter support |
| Command | Description |
|---|---|
TPM2_HashSequenceStart |
Start a hash sequence |
TPM2_HMAC_Start |
Start an HMAC sequence |
TPM2_SequenceUpdate |
Add data to a hash/HMAC sequence |
TPM2_SequenceComplete |
Finalize hash/HMAC sequence and get result |
TPM2_EventSequenceComplete |
Finalize hash sequence and extend PCR |
| Command | Description |
|---|---|
TPM2_Unseal |
Unseal data from a sealed object |
| Command | Description |
|---|---|
TPM2_PCR_Read |
Read PCR values |
TPM2_PCR_Extend |
Extend a PCR with a digest |
TPM2_PCR_Reset |
Reset a resettable PCR |
| Command | Description |
|---|---|
TPM2_ReadClock |
Read TPM clock values |
TPM2_ClockSet |
Set TPM clock |
| Command | Description |
|---|---|
TPM2_StartAuthSession |
Create HMAC, policy, or trial session |
| Command | Description |
|---|---|
TPM2_PolicyGetDigest |
Get current policy session digest |
TPM2_PolicyRestart |
Reset policy session digest |
TPM2_PolicyPCR |
Bind policy to PCR values |
TPM2_PolicyPassword |
Include password in policy |
TPM2_PolicyAuthValue |
Include auth value in policy |
TPM2_PolicyCommandCode |
Restrict policy to specific command |
TPM2_PolicyOR |
Logical OR of policy branches |
TPM2_PolicySecret |
Authorization with secret |
TPM2_PolicyAuthorize |
Approve policy with signing key |
TPM2_PolicyNV |
Policy based on NV index comparison |
TPM2_PolicyLocality |
Restrict policy to specific locality |
TPM2_PolicySigned |
Authorize policy with external signing key |
| Command | Description |
|---|---|
TPM2_DictionaryAttackParameters |
Set maxTries, recoveryTime, lockoutRecovery |
TPM2_DictionaryAttackLockReset |
Reset 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.
| Command | Description |
|---|---|
TPM2_NV_DefineSpace |
Create an NV index |
TPM2_NV_UndefineSpace |
Delete an NV index |
TPM2_NV_ReadPublic |
Read NV index public metadata |
TPM2_NV_Write |
Write data to NV index |
TPM2_NV_Read |
Read data from NV index |
TPM2_NV_Extend |
Extend NV index (hash-extend) |
TPM2_NV_Increment |
Increment NV counter |
TPM2_NV_WriteLock |
Lock NV index for writes |
TPM2_NV_ReadLock |
Lock NV index for reads |
TPM2_NV_SetBits |
OR bits into NV bit field index |
TPM2_NV_ChangeAuth |
Change NV index authorization value |
TPM2_NV_Certify |
Certify NV index contents |
| Command | Description |
|---|---|
TPM2_Quote |
Generate signed PCR quote |
TPM2_Certify |
Certify a loaded key |
TPM2_CertifyCreation |
Prove key was created by this TPM |
TPM2_GetTime |
Signed attestation of TPM clock |
TPM2_MakeCredential |
Create credential blob for a key |
TPM2_ActivateCredential |
Unwrap credential blob |
The fwTPM provides two hardware abstraction layers (HALs) for porting to embedded targets without modifying core logic.
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):
| Callback | Signature | Description |
|---|---|---|
send |
int (*)(void* ctx, const void* buf, int sz) |
Send data to client |
recv |
int (*)(void* ctx, void* buf, int sz) |
Receive data from client |
wait |
int (*)(void* ctx) |
Wait for data/connections. Returns bitmask: 0x01=command data, 0x02=platform data, 0x04=new command connection, 0x08=new platform connection |
accept |
int (*)(void* ctx, int type) |
Accept new connection (type: 0=command, 1=platform) |
close_conn |
void (*)(void* ctx, int type) |
Close connection (type: 0=command, 1=platform) |
ctx |
void* |
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);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):
| Callback | Signature | Description |
|---|---|---|
read |
int (*)(void* ctx, word32 offset, byte* buf, word32 size) |
Read from NV at offset |
write |
int (*)(void* ctx, word32 offset, const byte* buf, word32 size) |
Write to NV at offset |
ctx |
void* |
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);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);All macros are compile-time overridable (e.g., -DFWTPM_MAX_OBJECTS=8).
| Macro | Default | Description |
|---|---|---|
FWTPM_MAX_COMMAND_SIZE |
4096 | Maximum command/response buffer size (bytes) |
FWTPM_MAX_RANDOM_BYTES |
48 | Maximum bytes per GetRandom call |
FWTPM_MAX_OBJECTS |
16 | Maximum concurrently loaded transient objects |
FWTPM_MAX_PERSISTENT |
8 | Maximum persistent objects (via EvictControl) |
FWTPM_MAX_PRIVKEY_DER |
2048 | Maximum DER-encoded private key size (bytes) |
FWTPM_MAX_HASH_SEQ |
4 | Maximum concurrent hash/HMAC sequences |
FWTPM_MAX_PRIMARY_CACHE |
16 | Cached primary keys per hierarchy+template |
FWTPM_MAX_SESSIONS |
8 | Maximum concurrent auth sessions |
FWTPM_MAX_NV_INDICES |
16 | Maximum NV RAM index slots |
FWTPM_MAX_NV_DATA |
2048 | Maximum data per NV index (bytes) |
FWTPM_DA_DEFAULT_MAX_TRIES |
32 | DA failed-auth count before lockout |
FWTPM_DA_DEFAULT_RECOVERY |
600 | DA self-heal interval (seconds per try) |
FWTPM_DA_DEFAULT_LOCKOUT_RECOVERY |
86400 | lockoutAuth recovery time (seconds) |
FWTPM_DA_MAX_TRIES_LIMIT |
0xFFFF | Upper clamp for a replayed maxTries/failedTries |
FWTPM_MAX_DATA_BUF |
1024 | Internal buffer for HMAC, hash, general data |
FWTPM_MAX_PUB_BUF |
512 | Internal buffer for public area, signatures |
FWTPM_MAX_DER_SIG_BUF |
256 | Internal buffer for DER signatures, ECC points |
FWTPM_MAX_ATTEST_BUF |
1024 | Internal buffer for attestation marshaling |
FWTPM_MAX_CMD_AUTHS |
3 | Maximum authorization sessions per command (TPM-spec hard cap) |
FWTPM_MAX_SENSITIVE_SIZE |
FWTPM_MAX_PRIVKEY_DER + 128 |
Maximum marshaled sensitive area (private key + auth + nonce headroom) |
FWTPM_MAX_SIGN_SEQ |
4 | Maximum concurrent v1.85 PQC sign/verify sequences |
FWTPM_MAX_SYM_KEY_SIZE |
32 | Symmetric key buffer (sized for AES-256) |
FWTPM_MAX_HMAC_KEY_SIZE |
64 | HMAC key buffer (sized for SHA-512 block) |
FWTPM_MAX_HMAC_DIGEST_SIZE |
64 | HMAC output buffer (sized for SHA-512) |
FWTPM_CMD_PORT |
2321 | Default TCP command port |
FWTPM_PLAT_PORT |
2322 | Default TCP platform port |
FWTPM_NV_FILE |
"fwtpm_nv.bin" |
Default NV storage file path |
FWTPM_PCR_BANKS |
2 | Number of PCR banks (SHA-256 + SHA-384) |
FWTPM_TIS_BURST_COUNT |
64 | TIS FIFO burst count (bytes per transfer) |
FWTPM_TIS_FIFO_SIZE |
4096 | TIS command/response FIFO size |
| Macro | Effect |
|---|---|
WOLFTPM_SMALL_STACK |
Use heap allocation for large stack objects |
WOLFTPM2_NO_HEAP |
Forbid 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.
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:
| Macro | Classical | MLDSA-44 + MLKEM-512 | MLDSA-65 + MLKEM-768 | MLDSA-87 + MLKEM-1024 |
|---|---|---|---|---|
FWTPM_TIS_FIFO_SIZE |
4096 | 4096 | 8192 | 8192 |
FWTPM_MAX_COMMAND_SIZE |
4096 | 4096 | 8192 | 8192 |
FWTPM_MAX_PUB_BUF |
512 | 1440 | 2080 | 2720 |
FWTPM_MAX_DER_SIG_BUF |
256 | 2548 | 3437 | 4755 |
FWTPM_MAX_KEM_CT_BUF |
n/a | 832 | 1152 | 1632 |
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.
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.
| Macro | Default | Effect |
|---|---|---|
NO_RSA |
not defined | Excludes RSA keygen, sign, verify, RSA_Encrypt, RSA_Decrypt |
HAVE_ECC |
defined | Enables ECC keygen, sign, verify, ECDH_KeyGen, ECDH_ZGen, ECC_Parameters |
HAVE_ECC384 |
defined | Enables P-384 curve support |
HAVE_ECC521 |
defined | Enables P-521 curve support |
NO_AES |
not defined | Excludes EncryptDecrypt, EncryptDecrypt2, AES parameter encryption |
WOLFSSL_SHA384 |
defined | Enables 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.
These fwTPM-specific macros disable entire groups of TPM 2.0 functionality to reduce code size on constrained targets.
| Macro | Default | Commands Excluded |
|---|---|---|
FWTPM_NO_ATTESTATION |
not defined | Quote, Certify, CertifyCreation, GetTime, NV_Certify |
FWTPM_NO_NV |
not defined | NV_DefineSpace, NV_UndefineSpace, NV_ReadPublic, NV_Write, NV_Read, NV_Extend, NV_Increment, NV_WriteLock, NV_ReadLock, NV_Certify |
FWTPM_NO_POLICY |
not defined | PolicyGetDigest, PolicyRestart, PolicyPCR, PolicyPassword, PolicyAuthValue, PolicyCommandCode, PolicyOR, PolicySecret, PolicyAuthorize, PolicyNV |
FWTPM_NO_CREDENTIAL |
not defined | MakeCredential, ActivateCredential |
FWTPM_NO_DA |
not defined | DictionaryAttackParameters, 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_NValso removesNV_Certify(even ifFWTPM_NO_ATTESTATIONis not set)NO_RSAimplies no RSA attestation signatures (ECC-only attestation still works withHAVE_ECC)
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):
| Signal | Value | Description |
|---|---|---|
SIGNAL_POWER_ON |
1 | Power on the TPM |
SIGNAL_POWER_OFF |
2 | Power off the TPM |
SIGNAL_PHYS_PRES_ON |
3 | Assert physical presence |
SIGNAL_PHYS_PRES_OFF |
4 | Deassert physical presence |
SIGNAL_HASH_START |
5 | Start measured boot hash |
SIGNAL_HASH_DATA |
6 | Provide measured boot data |
SIGNAL_HASH_END |
9 | End measured boot hash |
SEND_COMMAND |
8 | Send TPM command (command port) |
SIGNAL_NV_ON |
11 | NV storage available |
SIGNAL_CANCEL_ON |
13 | Cancel current command |
SIGNAL_CANCEL_OFF |
14 | Clear cancel |
SIGNAL_RESET |
17 | Reset TPM |
SESSION_END |
20 | End TCP session |
STOP |
21 | Stop server |
wolfTPM clients connect using the standard SWTPM interface, compatible with
tpm2-tools and other SWTPM-aware software.
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):
| Field | Description |
|---|---|
magic / version |
Validation header (0x57544953 / "WTIS") |
reg_addr, reg_len, reg_is_write, reg_data |
Register access request |
TIS register shadow: access, sts, int_enable, int_status, intf_caps, did_vid, rid |
Emulated TIS registers |
cmd_buf[4096], cmd_len, fifo_write_pos |
Command FIFO |
rsp_buf[4096], rsp_len, fifo_read_pos |
Response FIFO |
Paths (compile-time configurable):
| Define | Default | Description |
|---|---|---|
FWTPM_TIS_SHM_PATH |
/tmp/fwtpm.shm |
Shared memory file |
FWTPM_TIS_SEM_CMD |
/fwtpm_cmd |
Command semaphore name |
FWTPM_TIS_SEM_RSP |
/fwtpm_rsp |
Response semaphore name |
Server-side API:
FWTPM_TIS_Init()-- Create shared memory and semaphoresFWTPM_TIS_Cleanup()-- Remove shared memory and semaphoresFWTPM_TIS_ServerLoop()-- Process TIS register accesses, dispatch commands
Client-side API (enabled by WOLFTPM_FWTPM_HAL):
FWTPM_TIS_ClientConnect()-- Attach to existing shared memoryFWTPM_TIS_ClientDisconnect()-- Detach from shared memory
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.
| Function | Description |
|---|---|
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") |
| Function | Description |
|---|---|
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. |
| Function | Description |
|---|---|
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 |
| Function | Description |
|---|---|
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 |
| Function | Description |
|---|---|
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) |
| Function | Description |
|---|---|
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 |
- First boot:
FWTPM_NV_Initfinds no NV file, generates random hierarchy seeds, saves initial state. TPM2_Startup(SU_CLEAR): Flushes transient objects and sessions, resets PCRs. Required before any other TPM command.- Normal operation: Commands are processed via
FWTPM_ProcessCommand. TPM2_Shutdown: Saves NV state but does NOT clear the "started" flag. The TPM remains logically powered on.- Server restart (process exit and relaunch) constitutes a power cycle.
Only after a power cycle can
TPM2_Startupbe called again.
Calling TPM2_Startup on an already-started TPM returns TPM_RC_INITIALIZE.
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).
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.
| Alg | Parameter Sets | Use |
|---|---|---|
TPM_ALG_MLKEM (0x00A0) |
MLKEM-512 / 768 / 1024 | Key encapsulation (decrypt-only keys) |
TPM_ALG_MLDSA (0x00A1) |
MLDSA-44 / 65 / 87 | Pure ML-DSA message signing |
TPM_ALG_HASH_MLDSA (0x00A2) |
MLDSA-44 / 65 / 87 | Pre-hashed ML-DSA signing |
The eight v1.85 PQC commands in src/fwtpm/fwtpm_command.c:
| Command | CC | Purpose |
|---|---|---|
TPM2_Encapsulate |
0x000001A7 |
ML-KEM encapsulation, returns sharedSecret + ciphertext |
TPM2_Decapsulate |
0x000001A8 |
ML-KEM decapsulation from ciphertext (requires USER auth) |
TPM2_SignSequenceStart |
0x000001AA |
Begin ML-DSA sign sequence |
TPM2_SignSequenceComplete |
0x000001A4 |
Finalize sign sequence with message buffer |
TPM2_VerifySequenceStart |
0x000001A9 |
Begin ML-DSA verify sequence |
TPM2_VerifySequenceComplete |
0x000001A3 |
Finalize verify sequence, returns TPMT_TK_VERIFIED |
TPM2_SignDigest |
0x000001A6 |
One-shot digest sign (Hash-ML-DSA or ext-μ ML-DSA) |
TPM2_VerifyDigestSignature |
0x000001A5 |
Verify digest signature |
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 Xi→wc_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.
Pure ML-DSA is one-shot — TPM2_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-DSA →
TPM2B_SIGNATURE_MLDSA:sigAlg + size + bytes - Hash-ML-DSA →
TPMS_SIGNATURE_HASH_MLDSA:sigAlg + hashAlg + size + bytes
Under WOLFTPM_V185, buffers are lifted to accommodate ML-DSA-87 signatures
(4627 bytes) and public keys (2592 bytes):
| Symbol | v1.38 | v1.85 |
|---|---|---|
FWTPM_MAX_COMMAND_SIZE |
4096 | 8192 |
FWTPM_MAX_PUB_BUF |
512 | 2720 |
FWTPM_MAX_DER_SIG_BUF |
256 | 4736 |
FWTPM_MAX_KEM_CT_BUF |
— | 1600 |
FWTPM_TIS_FIFO_SIZE |
4096 | 8192 |
FWTPM_NV_PUBAREA_EST |
600 | 2720 |
Three v1.85 features are deferred with documented reasons:
- 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 themlkemarm ofTPMU_ENCRYPTED_SECRET. Part 4 v185 (which would normatively specify this) is not yet published. Current behavior:TPM2_StartAuthSessionreturnsTPM_RC_KEYfor ML-KEM tpmKey; revisit when Part 4 v185 lands. - 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_SCHEMEfor ext-μ paths,TPM_RC_EXT_MUfor Pure ML-DSA keys withoutallowExternalMu. See DEC-0006. - ECC KEM arm of Encapsulate/Decapsulate — Part 2 Sec.10.3.13 Table 100 has
both
mlkemandecdharms, but the table note explicitly allows implementations to modify the union based on supported algorithms. Current fwTPM supports themlkemarm only.
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