mirror of https://github.com/wolfSSL/wolfTPM.git
999 lines
44 KiB
Markdown
999 lines
44 KiB
Markdown
# wolfTPM fwTPM (fTPM / swtpm) -- Firmware TPM 2.0
|
|
|
|
## 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:**
|
|
|
|
| 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, with a built-in append-only mode for write-once flash |
|
|
| `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) |
|
|
|
|
|
|
## Building
|
|
|
|
### Prerequisites
|
|
|
|
wolfSSL must be built with TPM support:
|
|
|
|
```sh
|
|
cd wolfssl
|
|
./configure --enable-wolftpm --enable-pkcallbacks
|
|
make
|
|
sudo make install
|
|
```
|
|
|
|
### Build fwTPM Server
|
|
|
|
**Socket transport (SWTPM protocol, default for development):**
|
|
|
|
```sh
|
|
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):**
|
|
|
|
```sh
|
|
./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):**
|
|
|
|
```sh
|
|
./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:**
|
|
|
|
```sh
|
|
./configure --enable-fwtpm --enable-swtpm --enable-debug
|
|
make
|
|
```
|
|
|
|
### Build Output
|
|
|
|
| Artifact | Description |
|
|
|----------|-------------|
|
|
| `src/fwtpm/fwtpm_server` | Standalone fwTPM server binary |
|
|
| `src/.libs/libwolftpm.*` | wolfTPM client library |
|
|
|
|
### Key Build Flags
|
|
|
|
| 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-fwtpm-nv-appendonly` | Append-only NV journal for write-once flash ports (off by default) |
|
|
| `--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` |
|
|
| `WOLFTPM_FWTPM_NV_APPEND_ONLY` | `--enable-fwtpm-nv-appendonly` (CMake `WOLFTPM_FWTPM_NV_APPEND_ONLY=yes`) |
|
|
| `WOLFTPM_FWTPM_TCG_TEST` | Manually (`CFLAGS=-DWOLFTPM_FWTPM_TCG_TEST`); off by default |
|
|
|
|
No vendor commands are registered by default. Define `WOLFTPM_FWTPM_TCG_TEST` to compile in the optional `TPM2_Vendor_TCG_Test` (`0x20000000`) echo command.
|
|
|
|
### Command-Code Enforcement
|
|
|
|
A valid command code carries only the 16-bit index plus, for vendor commands, the V bit (`CC_VEND`, bit 29); codes with any other reserved bit set, or codes not in the dispatch table, are rejected with `TPM_RC_COMMAND_CODE`. `TPM2_GetCapability(TPM_CAP_COMMANDS)` returns proper `TPMA_CC` values (index + handle attributes + V bit, ordered by command code).
|
|
|
|
|
|
## Usage
|
|
|
|
### Starting the Server
|
|
|
|
```sh
|
|
./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:**
|
|
|
|
```sh
|
|
# 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
|
|
```
|
|
|
|
In `--spdm-tcg` test mode the server also prints its generated responder
|
|
public key. This is a local test-harness convenience, not a provisioning or
|
|
trust-anchor channel for hardware responders.
|
|
|
|
### Connecting wolfTPM Clients
|
|
|
|
Any wolfTPM application built with `--enable-swtpm` connects to the fwTPM
|
|
server automatically via TCP:
|
|
|
|
```sh
|
|
# 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.
|
|
|
|
```c
|
|
/* 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
|
|
|
|
| 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 |
|
|
|
|
### Random Number Generation
|
|
|
|
| Command | Description |
|
|
|---------|-------------|
|
|
| `TPM2_GetRandom` | Generate random bytes (max 48 per call) |
|
|
| `TPM2_StirRandom` | Add entropy to RNG state |
|
|
|
|
### Capability
|
|
|
|
| Command | Description |
|
|
|---------|-------------|
|
|
| `TPM2_GetCapability` | Query TPM properties, algorithms, handles |
|
|
|
|
### Key Management
|
|
|
|
| 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 |
|
|
|
|
### Cryptographic Operations
|
|
|
|
| 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 |
|
|
|
|
### Hash Sequences
|
|
|
|
| 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 |
|
|
|
|
### Sealing
|
|
|
|
| Command | Description |
|
|
|---------|-------------|
|
|
| `TPM2_Unseal` | Unseal data from a sealed object |
|
|
|
|
### PCR (Platform Configuration Registers)
|
|
|
|
| Command | Description |
|
|
|---------|-------------|
|
|
| `TPM2_PCR_Read` | Read PCR values |
|
|
| `TPM2_PCR_Extend` | Extend a PCR with a digest |
|
|
| `TPM2_PCR_Reset` | Reset a resettable PCR |
|
|
|
|
### Clock
|
|
|
|
| Command | Description |
|
|
|---------|-------------|
|
|
| `TPM2_ReadClock` | Read TPM clock values |
|
|
| `TPM2_ClockSet` | Set TPM clock |
|
|
|
|
### Sessions and Authorization
|
|
|
|
| Command | Description |
|
|
|---------|-------------|
|
|
| `TPM2_StartAuthSession` | Create HMAC, policy, or trial session |
|
|
|
|
### Policy
|
|
|
|
| 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 |
|
|
|
|
### Dictionary Attack (DA) Protection
|
|
|
|
| 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.
|
|
|
|
### NV RAM
|
|
|
|
| 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 |
|
|
|
|
### Attestation and Credentials
|
|
|
|
| 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 |
|
|
|
|
|
|
## 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`):
|
|
|
|
| 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:**
|
|
|
|
```c
|
|
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`):
|
|
|
|
| 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:**
|
|
|
|
```c
|
|
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:
|
|
|
|
```c
|
|
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`).
|
|
|
|
| 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_NV_MAX_WRITE_ALIGN` | 64 | Max 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_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 |
|
|
|
|
### Stack/Heap Control
|
|
|
|
| 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.
|
|
|
|
### 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:**
|
|
|
|
| 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.
|
|
|
|
### 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.
|
|
|
|
| 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` or `HAVE_ALL_CURVES` | build-dependent | Enables P-521 when `MAX_ECC_KEY_BITS >= 521` provides 66-byte TPM ECC fields |
|
|
| `ECC_MIN_KEY_SZ` | wolfCrypt-defined | Excludes smaller curves from `ECC_Parameters` and `TPM_CAP_ECC_CURVES` |
|
|
| `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.
|
|
|
|
### TPM Feature Group Macros
|
|
|
|
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 |
|
|
| `FWTPM_NO_PARAM_ENC` | not defined | command/response parameter (XOR/AES-CFB) encryption support in sessions |
|
|
| `FWTPM_NO_KEY_MIGRATION` | not defined | `Import`, `Duplicate`, `Rewrap` |
|
|
| `FWTPM_NO_ECDH` | not defined | `ECDH_KeyGen`, `ECDH_ZGen`, `EC_Ephemeral`, `ZGen_2Phase`, `ECC_Parameters` (ECDSA sign/verify retained), plus the `ecEphemeral*` commit state in `FWTPM_CTX` |
|
|
| `FWTPM_NO_HASH_CMDS` | not defined | `Hash`, `HMAC`, `HMAC_Start`, `HashSequenceStart`, `SequenceUpdate`, `SequenceComplete`, `EventSequenceComplete`, and the `FWTPM_CTX` hash-sequence slots |
|
|
| `FWTPM_NO_CONTEXT` | not defined | `ContextSave`, `ContextLoad` (`FlushContext` retained), plus the per-boot context protection key and saved-context replay list in `FWTPM_CTX` |
|
|
| `FWTPM_NO_SYM_ENCRYPT` | not defined | `EncryptDecrypt`, `EncryptDecrypt2` |
|
|
| `FWTPM_NO_CLOCK` | not defined | `ReadClock`, `ClockSet`, `ClockRateAdjust` |
|
|
|
|
Removing a command group also removes it from the `TPM2_GetCapability(TPM_CAP_COMMANDS)` advertisement and the `TPM_PT_TOTAL_COMMANDS` count, since both are derived from the dispatch table. Note: when `WOLFTPM_MLDSA` is built, `SequenceUpdate` alone is retained under `FWTPM_NO_HASH_CMDS`, because ML-DSA verify sequences stream their message through it. `SequenceComplete` is not shared -- ML-DSA sequences finalize through `TPM2_SignSequenceComplete` / `TPM2_VerifySequenceComplete` -- so it is gated out with the rest of the hash commands rather than advertised as a command that can never succeed.
|
|
|
|
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](#dictionary-attack-da-protection).
|
|
|
|
**Minimal build example.** There is no umbrella macro - select the command
|
|
groups to drop explicitly, so each is a deliberate choice. For example, to build
|
|
a small ECC-only signing + NV fTPM (this set drops attestation; keep
|
|
`Sign`/`VerifySignature`, PCR, and NV):
|
|
|
|
```sh
|
|
./configure --enable-fwtpm --enable-swtpm \
|
|
CFLAGS="-DNO_RSA \
|
|
-DFWTPM_NO_POLICY -DFWTPM_NO_ATTESTATION -DFWTPM_NO_CREDENTIAL \
|
|
-DFWTPM_NO_DA -DFWTPM_NO_PARAM_ENC -DFWTPM_NO_KEY_MIGRATION \
|
|
-DFWTPM_NO_ECDH -DFWTPM_NO_HASH_CMDS -DFWTPM_NO_CONTEXT \
|
|
-DFWTPM_NO_SYM_ENCRYPT -DFWTPM_NO_CLOCK"
|
|
```
|
|
|
|
That set retains a core fTPM: `Startup`, `Shutdown`, `SelfTest`, `GetRandom`,
|
|
`GetCapability`, the `PCR_*` commands, `Create`/`CreatePrimary`/`Load`/
|
|
`ReadPublic`/`FlushContext`, `Sign`/`VerifySignature`, the `NV_*` commands, and
|
|
session support (`StartAuthSession`/`Unseal`). Add `-DFWTPM_NO_NV` to also drop
|
|
NV, or drop any `-DFWTPM_NO_*` above to keep that group. This ECC-only build is
|
|
small enough to run as a soft-core fTPM on a constrained FPGA (see the MicroBlaze
|
|
V example in the `wolftpm-examples` repo, which fits an ECC-only fTPM into
|
|
~192 KB of on-chip memory).
|
|
|
|
**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):
|
|
|
|
| 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.
|
|
|
|
### 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`):
|
|
|
|
| Field | Description |
|
|
|-------|-------------|
|
|
| `magic` / `version` | Validation header (`0x57544953` / `"WTIS"`, protocol version 2) |
|
|
| `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; clients require a regular, single-link, same-UID, exact-size `0600` endpoint |
|
|
| `FWTPM_TIS_SEM_CMD` | `/fwtpm_cmd` | Command semaphore name |
|
|
| `FWTPM_TIS_SEM_RSP` | `/fwtpm_rsp` | Response semaphore name |
|
|
|
|
Clients require an exact protocol version and shared-region-size match, so
|
|
rebuild the client library and `fwtpm_server` together when changing options
|
|
that affect `FWTPM_TIS_FIFO_SIZE`. The default paths are global, so one server
|
|
per host.
|
|
|
|
**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](../src/fwtpm/README.md) for the full CI test matrix
|
|
and test script usage. Quick reference:
|
|
|
|
```sh
|
|
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`)
|
|
|
|
| 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"`) |
|
|
|
|
### Command Processor (`fwtpm_command.h`)
|
|
|
|
| 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. |
|
|
|
|
### IO Transport (`fwtpm_io.h`)
|
|
|
|
| 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 |
|
|
|
|
### NV Storage (`fwtpm_nv.h`)
|
|
|
|
| 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 |
|
|
|
|
### TIS Server (`fwtpm_tis.h`)
|
|
|
|
| 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) |
|
|
|
|
### TIS Client (`fwtpm_tis.h`, requires `WOLFTPM_FWTPM_HAL`)
|
|
|
|
| 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 |
|
|
|
|
|
|
## 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
|
|
|
|
| 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 |
|
|
|
|
### Commands
|
|
|
|
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 |
|
|
|
|
### 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 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.
|
|
|
|
### Sign / Verify Sequences
|
|
|
|
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`
|
|
|
|
### Buffer Constants
|
|
|
|
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 |
|
|
|
|
### 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
|