fwTPM: add finer per-command-group gating macros

pull/574/head
David Garske 2026-08-12 13:53:16 -07:00
parent 55d1185498
commit 20dae36517
11 changed files with 841 additions and 73 deletions

View File

@ -149,6 +149,64 @@ jobs:
build_only: true
extra_cflags: -DFWTPM_NO_PARAM_ENC
# Build-only: finer per-command-group FWTPM_NO_* macros
- name: fwtpm-no-key-migration
os: ubuntu-latest
wolftpm_config: --enable-fwtpm --enable-swtpm
wolfssl_config: --enable-wolftpm --enable-pkcallbacks --enable-keygen
build_only: true
extra_cflags: -DFWTPM_NO_KEY_MIGRATION
- name: fwtpm-no-ecdh
os: ubuntu-latest
wolftpm_config: --enable-fwtpm --enable-swtpm
wolfssl_config: --enable-wolftpm --enable-pkcallbacks --enable-keygen
build_only: true
extra_cflags: -DFWTPM_NO_ECDH
- name: fwtpm-no-hash-cmds
os: ubuntu-latest
wolftpm_config: --enable-fwtpm --enable-swtpm
wolfssl_config: --enable-wolftpm --enable-pkcallbacks --enable-keygen
build_only: true
extra_cflags: -DFWTPM_NO_HASH_CMDS
- name: fwtpm-no-context
os: ubuntu-latest
wolftpm_config: --enable-fwtpm --enable-swtpm
wolfssl_config: --enable-wolftpm --enable-pkcallbacks --enable-keygen
build_only: true
extra_cflags: -DFWTPM_NO_CONTEXT
- name: fwtpm-no-sym-encrypt
os: ubuntu-latest
wolftpm_config: --enable-fwtpm --enable-swtpm
wolfssl_config: --enable-wolftpm --enable-pkcallbacks --enable-keygen
build_only: true
extra_cflags: -DFWTPM_NO_SYM_ENCRYPT
- name: fwtpm-no-clock
os: ubuntu-latest
wolftpm_config: --enable-fwtpm --enable-swtpm
wolfssl_config: --enable-wolftpm --enable-pkcallbacks --enable-keygen
build_only: true
extra_cflags: -DFWTPM_NO_CLOCK
# Build-only: fewer PCRs than the 24 standard ones. The locality
# tables are sized to a fixed 24 while IMPLEMENTATION_PCR sets the
# implemented count, so this leg proves the two boundaries compile
# apart. PLATFORM_PCR must drop with it or PCR_SELECT_MIN exceeds
# PCR_SELECT_MAX. Runtime coverage is in fwtpm-gated-runtime.
- name: fwtpm-reduced-pcr
os: ubuntu-latest
wolftpm_config: --enable-fwtpm --enable-swtpm
wolfssl_config: --enable-wolftpm --enable-pkcallbacks --enable-keygen
build_only: true
extra_cflags: -DIMPLEMENTATION_PCR=8 -DPLATFORM_PCR=8
# Note: the all-gates combinations are built AND run by the
# fwtpm-gated-runtime job below, so they are not duplicated here.
# Build-only: cross-algorithm + feature macro combinations
- name: fwtpm-no-rsa-no-policy
os: ubuntu-latest
@ -300,6 +358,89 @@ jobs:
tests/*.log
retention-days: 5
# ----------------------------------------------------------------
# Command-group gates: runtime behavior, not just compilation.
#
# `make check` cannot be used here - it also runs fwtpm_check.sh, which
# drives the examples and tpm2-tools against a server that no longer
# implements the gated commands. This job builds each gated configuration
# and runs only tests/fwtpm_unit.test, whose test_fwtpm_command_gates /
# test_fwtpm_total_commands cases assert that a gated command is rejected
# with TPM_RC_COMMAND_CODE, is absent from TPM_CAP_COMMANDS, and is not
# counted in TPM_PT_TOTAL_COMMANDS.
# ----------------------------------------------------------------
fwtpm-gated-runtime:
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
# Every command-group gate together on an ECC-only build - the
# minimal configuration documented in docs/FWTPM.md.
- name: all-gates-ecc-only
wolftpm_config: --enable-fwtpm --enable-swtpm
wolfssl_config: --enable-wolftpm --enable-pkcallbacks --enable-keygen --disable-rsa
extra_cflags: >-
-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
# Same gates with ML-DSA: SequenceUpdate must survive (the ML-DSA
# verify sequences stream through it) while SequenceComplete must
# not, and the ML-DSA sign/verify sequence tests must still pass.
- name: all-gates-mldsa
wolftpm_config: --enable-fwtpm --enable-swtpm --enable-v185 --enable-mldsa
wolfssl_config: --enable-wolftpm --enable-pkcallbacks --enable-keygen --enable-experimental --enable-dilithium --enable-mlkem
extra_cflags: >-
-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
# Fewer PCRs than the 24 standard ones: exercises the highest
# implemented PCR and the first unavailable one
# (test_fwtpm_pcr_bounds).
- name: reduced-pcr
wolftpm_config: --enable-fwtpm --enable-swtpm
wolfssl_config: --enable-wolftpm --enable-pkcallbacks --enable-keygen
extra_cflags: -DIMPLEMENTATION_PCR=8 -DPLATFORM_PCR=8
steps:
- name: Checkout wolfTPM
uses: actions/checkout@v4
- name: Setup wolfSSL
uses: ./.github/actions/setup-wolfssl
with:
configure-flags: ${{ matrix.wolfssl_config }}
cflags: -DWC_RSA_NO_PADDING ${{ matrix.extra_cflags }}
- name: Build wolfTPM (${{ matrix.name }})
run: |
./autogen.sh
./configure ${{ matrix.wolftpm_config }} \
CFLAGS="${{ matrix.extra_cflags }}"
make -j"$(nproc)" tests/fwtpm_unit.test
- name: Run fwTPM handler tests
run: ./tests/fwtpm_unit.test
# ----------------------------------------------------------------
# Dictionary Attack TPM_RC_RETRY (daUsed) end-to-end.
# Builds the server with -DFWTPM_DA_USED_RETRY so it returns TPM_RC_RETRY on

View File

@ -58,7 +58,7 @@ Features:
* TIS register-level transport over shared memory or SPI/I2C for bare-metal integration
* HAL abstractions for IO transport and NV storage portability
* File-based or custom NV storage via HAL callbacks
* Compile-time algorithm and feature selection (e.g., `NO_RSA`, `FWTPM_NO_NV`)
* Compile-time algorithm and per-command-group feature selection (e.g., `NO_RSA`, `FWTPM_NO_NV`, and independent per-command-group gates you pick and choose to shrink the fTPM footprint)
* `WOLFTPM_SMALL_STACK` support for constrained environments
See [docs/FWTPM.md](docs/FWTPM.md) for build instructions, configuration, and API reference.

View File

@ -664,22 +664,43 @@ to reduce code size on constrained targets.
| `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** (measured boot only):
**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_NV -DFWTPM_NO_ATTESTATION \
-DFWTPM_NO_POLICY -DFWTPM_NO_CREDENTIAL"
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"
```
This retains only: `Startup`, `Shutdown`, `SelfTest`, `GetRandom`, `GetCapability`,
`PCR_Read`, `PCR_Extend`, `PCR_Reset`, `Hash`, ECC keygen/sign, and session support.
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)

View File

@ -96,8 +96,25 @@ All tests below run in GitHub Actions CI. Run manually before PR submission.
|------|---------------|-------|-------|
| fwtpm-socket | `--enable-fwtpm --enable-swtpm --enable-debug` | | Primary test |
| fwtpm-tis | `--enable-fwtpm --enable-debug` | | TIS/SHM transport |
| fwtpm-asan | `--enable-fwtpm --enable-swtpm --enable-debug` | `-fsanitize=address` | Memory errors |
| fwtpm-ubsan | `--enable-fwtpm --enable-swtpm --enable-debug` | `-fsanitize=undefined` | UB detection |
| fwtpm-v185 | `--enable-fwtpm --enable-v185` | | PQC: wrapper + handler unit tests |
| fwtpm-macos-socket | `--enable-fwtpm --enable-swtpm --enable-debug` | | macOS runner |
ASan / UBSan / LeakSan coverage lives in `sanitizer.yml`, not this workflow.
### Runtime Tests, gated builds (`fwtpm-gated-runtime` job)
These configurations remove commands, so `make check` (which drives the
examples and tpm2-tools) does not apply. The job builds and runs only
`tests/fwtpm_unit.test`, whose `test_fwtpm_command_gates` /
`test_fwtpm_total_commands` / `test_fwtpm_pcr_bounds` cases assert that a gated
command is rejected with `TPM_RC_COMMAND_CODE`, is absent from
`TPM_CAP_COMMANDS`, and is not counted in `TPM_PT_TOTAL_COMMANDS`.
| Name | wolfTPM Config | wolfSSL Config | Extra CFLAGS |
|------|---------------|---------------|-------------|
| all-gates-ecc-only | `--enable-fwtpm --enable-swtpm` | `--disable-rsa` | the eleven command-group `-DFWTPM_NO_*` gates together (NV retained) |
| all-gates-mldsa | `--enable-fwtpm --enable-swtpm --enable-v185 --enable-mldsa` | `--enable-dilithium --enable-mlkem` | the same eleven gates; proves SequenceUpdate survives for ML-DSA while SequenceComplete does not |
| reduced-pcr | `--enable-fwtpm --enable-swtpm` | | `-DIMPLEMENTATION_PCR=8 -DPLATFORM_PCR=8` |
### Build-Only Tests
@ -105,6 +122,9 @@ All tests below run in GitHub Actions CI. Run manually before PR submission.
|------|---------------|---------------|-------------|
| fwtpm-no-rsa | `--enable-fwtpm --enable-swtpm` | `--disable-rsa` | |
| fwtpm-no-ecc | `--enable-fwtpm --enable-swtpm` | `--disable-ecc` | |
| fwtpm-no-sha384 | `--enable-fwtpm --enable-swtpm` | `--disable-sha384` | |
| fwtpm-no-sha1 | `--enable-fwtpm --enable-swtpm` | `--disable-sha` | `-DNO_SHA` |
| fwtpm-v185-build-only | `--enable-fwtpm --enable-v185` | | `-DDEBUG_WOLFTPM` |
| fwtpm-only | `--enable-fwtpm-only --enable-swtpm` | | No client library |
| fwtpm-minimal | `--enable-fwtpm --enable-swtpm` | | `-DFWTPM_NO_ATTESTATION -DFWTPM_NO_NV -DFWTPM_NO_POLICY -DFWTPM_NO_CREDENTIAL -DFWTPM_NO_DA -DFWTPM_NO_PARAM_ENC` |
| fwtpm-no-policy | `--enable-fwtpm --enable-swtpm` | | `-DFWTPM_NO_POLICY` |
@ -113,6 +133,13 @@ All tests below run in GitHub Actions CI. Run manually before PR submission.
| fwtpm-no-credential | `--enable-fwtpm --enable-swtpm` | | `-DFWTPM_NO_CREDENTIAL` |
| fwtpm-no-da | `--enable-fwtpm --enable-swtpm` | | `-DFWTPM_NO_DA` |
| fwtpm-no-param-enc | `--enable-fwtpm --enable-swtpm` | | `-DFWTPM_NO_PARAM_ENC` |
| fwtpm-no-key-migration | `--enable-fwtpm --enable-swtpm` | | `-DFWTPM_NO_KEY_MIGRATION` |
| fwtpm-no-ecdh | `--enable-fwtpm --enable-swtpm` | | `-DFWTPM_NO_ECDH` |
| fwtpm-no-hash-cmds | `--enable-fwtpm --enable-swtpm` | | `-DFWTPM_NO_HASH_CMDS` |
| fwtpm-no-context | `--enable-fwtpm --enable-swtpm` | | `-DFWTPM_NO_CONTEXT` |
| fwtpm-no-sym-encrypt | `--enable-fwtpm --enable-swtpm` | | `-DFWTPM_NO_SYM_ENCRYPT` |
| fwtpm-no-clock | `--enable-fwtpm --enable-swtpm` | | `-DFWTPM_NO_CLOCK` |
| fwtpm-reduced-pcr | `--enable-fwtpm --enable-swtpm` | | `-DIMPLEMENTATION_PCR=8 -DPLATFORM_PCR=8` |
| fwtpm-no-rsa-no-policy | `--enable-fwtpm --enable-swtpm` | `--disable-rsa` | `-DFWTPM_NO_POLICY` |
| fwtpm-no-ecc-no-nv | `--enable-fwtpm --enable-swtpm` | `--disable-ecc` | `-DFWTPM_NO_NV` |
| fwtpm-small-stack | `--enable-fwtpm --enable-swtpm` | | `-DWOLFTPM_SMALL_STACK` |
@ -140,7 +167,9 @@ control), spec version targeting, and HAL abstraction details.
Key options: `--enable-fwtpm`, `--enable-fwtpm-only`, `--enable-swtpm`,
`--enable-fwtpm-small-ctx`, `--enable-fuzz`. Feature disable macros:
`FWTPM_NO_ATTESTATION`, `FWTPM_NO_NV`, `FWTPM_NO_POLICY`, `FWTPM_NO_CREDENTIAL`,
`FWTPM_NO_DA`, `FWTPM_NO_PARAM_ENC`. Feature enable macros:
`FWTPM_NO_DA`, `FWTPM_NO_PARAM_ENC`, `FWTPM_NO_KEY_MIGRATION`, `FWTPM_NO_ECDH`,
`FWTPM_NO_HASH_CMDS`, `FWTPM_NO_CONTEXT`, `FWTPM_NO_SYM_ENCRYPT`,
`FWTPM_NO_CLOCK`. Feature enable macros:
`WOLFTPM_FWTPM_TCG_TEST` (optional `Vendor_TCG_Test` echo command, off by default).
Command codes with reserved bits set (only the 16-bit index plus the `CC_VEND` V bit are valid), or codes not in the dispatch table, are rejected with `TPM_RC_COMMAND_CODE`.
@ -186,16 +215,20 @@ CI exercises 7 build-only configure permutations + the two e2e modes on
The fwTPM implements 105 of 113 commands from the v1.38 baseline (93% coverage).
**Always enabled (47 commands):**
**Core set -- never gated (36 commands):**
Startup, Shutdown, SelfTest, IncrementalSelfTest, GetTestResult, GetRandom,
StirRandom, GetCapability, TestParms, PCR\_Read, PCR\_Extend, PCR\_Reset,
PCR\_Event, PCR\_Allocate, PCR\_SetAuthPolicy, PCR\_SetAuthValue,
ReadClock, ClockSet, ClockRateAdjust, CreatePrimary, FlushContext,
ContextSave, ContextLoad, ReadPublic, Clear, ClearControl, ChangeEPS, ChangePPS, HierarchyControl,
HierarchyChangeAuth, SetPrimaryPolicy, EvictControl, Create, ObjectChangeAuth,
Load, Sign, VerifySignature, Hash, HMAC, HMAC\_Start, HashSequenceStart,
SequenceUpdate, SequenceComplete, EventSequenceComplete, StartAuthSession,
Unseal, LoadExternal, Import, Duplicate, Rewrap, CreateLoaded
CreatePrimary, FlushContext, ReadPublic, Clear, ClearControl, ChangeEPS,
ChangePPS, HierarchyControl, HierarchyChangeAuth, SetPrimaryPolicy,
EvictControl, Create, ObjectChangeAuth, Load, Sign, VerifySignature,
StartAuthSession, Unseal, LoadExternal, CreateLoaded
These are present in every build. A default build (no `FWTPM_NO_*` macro set)
additionally enables every group listed under **Conditional on feature macros**
below; setting a gate macro removes that group's commands from the dispatch
table, from `TPM2_GetCapability(TPM_CAP_COMMANDS)`, and from the
`TPM_PT_TOTAL_COMMANDS` count.
**Optional vendor command (off by default, `WOLFTPM_FWTPM_TCG_TEST`):**
Vendor\_TCG\_Test
@ -221,6 +254,33 @@ EncryptDecrypt, EncryptDecrypt2
- `FWTPM_NO_PARAM_ENC`: Disables parameter encryption/decryption for command and
response parameters. Sessions still work for HMAC auth, but encrypted transport
is disabled. Reduces code size by removing AES-CFB and XOR param encryption.
- `FWTPM_NO_KEY_MIGRATION`: Import, Duplicate, Rewrap (3 commands). Shared key
helpers (used by Create/Load) are retained.
- `FWTPM_NO_ECDH`: ECDH\_KeyGen, ECDH\_ZGen, EC\_Ephemeral, ZGen\_2Phase,
ECC\_Parameters (5 commands). ECDSA sign/verify are retained. Also drops the
`ecEphemeral*` commit state from `FWTPM_CTX`.
- `FWTPM_NO_HASH_CMDS`: Hash, HMAC, HMAC\_Start, HashSequenceStart,
SequenceUpdate, SequenceComplete, EventSequenceComplete (7 commands). When
`WOLFTPM_MLDSA` is built, only SequenceUpdate is retained -- the MLDSA
verify sequences stream their message through it. SequenceComplete is not
shared: MLDSA sequences finalize through SignSequenceComplete /
VerifySequenceComplete, so advertising it in a gated build would expose a
command that can never succeed. Also drops the per-instance hash-sequence
slots (`hashSeq[FWTPM_MAX_HASH_SEQ]`) from `FWTPM_CTX`.
- `FWTPM_NO_CONTEXT`: ContextSave, ContextLoad (2 commands). FlushContext is
retained. Also drops the per-boot context protection key and the saved-context
replay list from `FWTPM_CTX`.
- `FWTPM_NO_SYM_ENCRYPT`: EncryptDecrypt, EncryptDecrypt2 (2 commands). Nests
inside `NO_AES`; AES itself is retained for session parameter encryption,
AES-GCM, and (unless `FWTPM_NO_CONTEXT` is also set) context protection.
- `FWTPM_NO_CLOCK`: ReadClock, ClockSet, ClockRateAdjust (3 commands). GetTime is
under `FWTPM_NO_ATTESTATION`, not this flag.
These gates are independent and there is intentionally no umbrella macro: pick
exactly the groups your fTPM does not need. Applying all of them plus the earlier
`FWTPM_NO_POLICY/ATTESTATION/CREDENTIAL/DA/PARAM_ENC` (keeping NV, or adding
`FWTPM_NO_NV` to drop it) leaves a core fTPM (Startup/GetCapability/GetRandom/
PCR/Create/Load/Sign/VerifySignature/NV/sessions) - see the MicroBlaze V example
in wolftpm-examples for a worked selection.
### Missing Commands -- TODO

View File

@ -32,6 +32,7 @@
#endif
#include <wolftpm/fwtpm/fwtpm.h>
#include <wolftpm/fwtpm/fwtpm_command.h>
#include <wolftpm/fwtpm/fwtpm_nv.h>
#include <string.h>
@ -90,6 +91,7 @@ int FWTPM_Init(FWTPM_CTX* ctx)
}
}
#ifndef FWTPM_NO_CONTEXT
/* Generate per-boot context protection key (volatile only) for
* ContextSave/ContextLoad HMAC + AES-CFB session blob protection. */
if (rc == 0) {
@ -99,6 +101,7 @@ int FWTPM_Init(FWTPM_CTX* ctx)
ctx->ctxProtectKeyValid = 1;
}
}
#endif
/* Initialize NV storage - loads existing state or creates fresh seeds */
#ifndef FWTPM_NO_NV
@ -148,6 +151,11 @@ int FWTPM_Cleanup(FWTPM_CTX* ctx)
rc = TPM_RC_SUCCESS;
#endif
/* Release transient objects, sessions, and hash/sign sequence slots first.
* ForceZero alone would drop the live wc_HashAlg / Hmac contexts they own,
* leaking their heap allocations under WOLFTPM_SMALL_STACK. */
FWTPM_ResetCommandClient(ctx);
wc_FreeRng(&ctx->rng);
wolfCrypt_Cleanup();

View File

@ -79,7 +79,9 @@ static UINT64 FwDaNowMs(FWTPM_CTX* ctx);
#ifdef WOLFTPM_MLDSA
static FWTPM_SignSeq* FwFindSignSeq(FWTPM_CTX* ctx, TPM_HANDLE handle);
#endif
#ifndef FWTPM_NO_HASH_CMDS
static FWTPM_HashSeq* FwFindHashSeq(FWTPM_CTX* ctx, TPM_HANDLE handle);
#endif
/* Command table accessors (fwCmdTable is defined near end of file) */
static int FwGetCmdCount(void);
@ -478,23 +480,31 @@ static void FwLookupEntityAuth(FWTPM_CTX* ctx, TPM_HANDLE handle,
*authValSz = objEnt->authValue.size;
}
else {
#ifndef FWTPM_NO_HASH_CMDS
FWTPM_HashSeq* seqEnt = FwFindHashSeq(ctx, handle);
#endif
#ifdef WOLFTPM_MLDSA
FWTPM_SignSeq* signEnt = NULL;
#endif
#ifndef FWTPM_NO_HASH_CMDS
if (seqEnt != NULL) {
*authVal = seqEnt->authValue.buffer;
*authValSz = seqEnt->authValue.size;
}
#endif
#ifdef WOLFTPM_MLDSA
else {
/* v1.85 sign/verify sequences also carry their own
* authValue. Without this lookup the password/HMAC
* verifier resolves these handles to authSz=0, which
* effectively bypasses the per-sequence auth set at
* SignSequenceStart / VerifySequenceStart. */
FWTPM_SignSeq* signEnt = FwFindSignSeq(ctx, handle);
if (signEnt != NULL) {
*authVal = signEnt->authValue.buffer;
*authValSz = signEnt->authValue.size;
}
/* v1.85 sign/verify sequences also carry their own authValue.
* Without this lookup the password/HMAC verifier resolves these
* handles to authSz=0, which effectively bypasses the
* per-sequence auth set at SignSequenceStart /
* VerifySequenceStart. */
#ifndef FWTPM_NO_HASH_CMDS
if (seqEnt == NULL)
#endif
signEnt = FwFindSignSeq(ctx, handle);
if (signEnt != NULL) {
*authVal = signEnt->authValue.buffer;
*authValSz = signEnt->authValue.size;
}
#endif
}
@ -710,7 +720,9 @@ static int FwComputeSessionHmac(FWTPM_Session* sess,
/* Forward declarations for helpers used by Startup */
static void FwFlushAllObjects(FWTPM_CTX* ctx);
static void FwFlushAllSessions(FWTPM_CTX* ctx);
#ifndef FWTPM_NO_HASH_CMDS
static void FwFreeHashSeq(FWTPM_HashSeq* seq);
#endif
#ifdef WOLFTPM_MLDSA
static void FwFreeSignSeq(FWTPM_SignSeq* seq);
#endif
@ -777,11 +789,13 @@ static TPM_RC FwCmd_Startup(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize,
* primary cache, and reset PCRs */
FwFlushAllObjects(ctx);
FwFlushAllSessions(ctx);
#ifndef FWTPM_NO_HASH_CMDS
for (i = 0; i < FWTPM_MAX_HASH_SEQ; i++) {
if (ctx->hashSeq[i].used) {
FwFreeHashSeq(&ctx->hashSeq[i]);
}
}
#endif
#ifdef WOLFTPM_MLDSA
for (i = 0; i < FWTPM_MAX_SIGN_SEQ; i++) {
if (ctx->signSeq[i].used) {
@ -799,9 +813,11 @@ static TPM_RC FwCmd_Startup(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize,
}
}
ctx->globalNvWriteLock = 0;
#ifndef FWTPM_NO_CONTEXT
/* Saved contexts are invalidated by TPM Reset */
ctx->contextLiveCount = 0;
#ifdef HAVE_ECC
#endif
#if defined(HAVE_ECC) && !defined(FWTPM_NO_ECDH)
ctx->ecEphemeralCounter = 0;
ctx->ecEphemeralKeySz = 0;
#endif
@ -2145,7 +2161,13 @@ static TPM_RC FwCmd_PCR_Read(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize,
* dump (loc2 reset of PCR 20-22 hardware-confirmed over SPI).
* reset: 0-15 never; 16,23 loc0-3; 17-19 loc4; 20-22 loc2-4.
* extend: 0-16,23 any; 17,18 loc2-4; 19 loc2-3; 20 loc1-3; 21,22 loc2. */
static const byte fwPcrResetLocality[IMPLEMENTATION_PCR] = {
/* The TCG PCR locality attributes are defined for the 24 standard PCRs. Size the
* tables to that fixed count (not IMPLEMENTATION_PCR) so a build implementing
* fewer PCRs (IMPLEMENTATION_PCR < 24, e.g. a minimal soft-core fTPM) neither
* drops initializers nor warns; FwPcrLocalityAllowed() only indexes valid PCRs. */
#define FW_PCR_LOCALITY_TABLE 24
static const byte fwPcrResetLocality[FW_PCR_LOCALITY_TABLE] = {
/* 0- 7 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 8-15 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 16 */ 0x0F, /* loc0-3 */
@ -2158,7 +2180,7 @@ static const byte fwPcrResetLocality[IMPLEMENTATION_PCR] = {
/* 23 */ 0x0F /* loc0-3 */
};
static const byte fwPcrExtendLocality[IMPLEMENTATION_PCR] = {
static const byte fwPcrExtendLocality[FW_PCR_LOCALITY_TABLE] = {
/* 0- 7 */ 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, /* any */
/* 8-15 */ 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, /* any */
/* 16 */ 0x1F, /* any */
@ -2177,7 +2199,8 @@ static int FwPcrLocalityAllowed(int pcrIndex, int locality, int isReset)
{
byte mask;
if (pcrIndex < 0 || pcrIndex >= IMPLEMENTATION_PCR) {
if (pcrIndex < 0 || pcrIndex >= IMPLEMENTATION_PCR ||
pcrIndex >= FW_PCR_LOCALITY_TABLE) {
return 0;
}
if (locality < 0 || locality > WOLFTPM_LOCALITY_MAX) {
@ -2705,6 +2728,7 @@ static TPM_RC FwCmd_PCR_SetAuthValue(FWTPM_CTX* ctx, TPM2_Packet* cmd,
return rc;
}
#ifndef FWTPM_NO_CLOCK
/* --- TPM2_ReadClock (CC 0x0181) --- */
static TPM_RC FwCmd_ReadClock(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize,
TPM2_Packet* rsp, UINT16 cmdTag)
@ -2824,6 +2848,7 @@ static TPM_RC FwCmd_ClockRateAdjust(FWTPM_CTX* ctx, TPM2_Packet* cmd,
return rc;
}
#endif /* !FWTPM_NO_CLOCK */
/* --- Object management helpers --- */
@ -2945,19 +2970,25 @@ static void FwFlushAllSessions(FWTPM_CTX* ctx)
void FWTPM_ResetCommandClient(FWTPM_CTX* ctx)
{
#if !defined(FWTPM_NO_HASH_CMDS) || defined(WOLFTPM_MLDSA)
int i;
#endif
if (ctx == NULL) {
return;
}
FwFlushAllObjects(ctx);
FwFlushAllSessions(ctx);
#ifndef FWTPM_NO_CONTEXT
/* Saved-context replay set belongs to the prior client */
ctx->contextLiveCount = 0;
#endif
#ifndef FWTPM_NO_HASH_CMDS
for (i = 0; i < FWTPM_MAX_HASH_SEQ; i++) {
if (ctx->hashSeq[i].used) {
FwFreeHashSeq(&ctx->hashSeq[i]);
}
}
#endif
#ifdef WOLFTPM_MLDSA
for (i = 0; i < FWTPM_MAX_SIGN_SEQ; i++) {
if (ctx->signSeq[i].used) {
@ -3523,6 +3554,7 @@ static TPM_RC FwCmd_FlushContext(FWTPM_CTX* ctx, TPM2_Packet* cmd,
* stored externally (e.g. a .ctx file) and later reloaded with ContextLoad.
* We use an opaque blob that stores the handle number; the object remains
* in its slot so ContextLoad can find it for the lifetime of the server. */
#ifndef FWTPM_NO_CONTEXT
#define FWTPM_CTX_MAGIC 0x4657544Du /* 'FWTM' */
#define FWTPM_CTX_VER 1u
static TPM_RC FwCmd_ContextSave(FWTPM_CTX* ctx, TPM2_Packet* cmd,
@ -3861,6 +3893,7 @@ static TPM_RC FwCmd_ContextLoad(FWTPM_CTX* ctx, TPM2_Packet* cmd,
return rc;
}
#endif /* !FWTPM_NO_CONTEXT */
/* --- TPM2_ReadPublic (CC 0x0173) --- */
static TPM_RC FwCmd_ReadPublic(FWTPM_CTX* ctx, TPM2_Packet* cmd,
@ -5482,6 +5515,7 @@ static TPM_RC FwCmd_LoadExternal(FWTPM_CTX* ctx, TPM2_Packet* cmd,
}
#ifndef FWTPM_NO_KEY_MIGRATION
/* --- TPM2_Import (CC 0x156) ---
* Import an externally created key (outer-wrapped) under a parent key.
* Response: [paramSz] | outPrivate */
@ -6598,6 +6632,7 @@ static TPM_RC FwCmd_Rewrap(FWTPM_CTX* ctx, TPM2_Packet* cmd,
FWTPM_FREE_BUF(encSeedBuf);
return rc;
}
#endif /* !FWTPM_NO_KEY_MIGRATION */
/* --- TPM2_CreateLoaded (CC 0x0191) ---
* Like Create but also loads the key into a transient slot.
@ -7536,6 +7571,7 @@ static TPM_RC FwCmd_RSA_Decrypt(FWTPM_CTX* ctx, TPM2_Packet* cmd,
/* Hash, HMAC, HashSequence, ECDH */
/* ================================================================== */
#ifndef FWTPM_NO_HASH_CMDS
/* --- TPM2_Hash (CC 0x017D) --- */
static TPM_RC FwCmd_Hash(FWTPM_CTX* ctx, TPM2_Packet* cmd,
int cmdSize, TPM2_Packet* rsp, UINT16 cmdTag)
@ -7964,7 +8000,14 @@ static TPM_RC FwCmd_HashSequenceStart(FWTPM_CTX* ctx, TPM2_Packet* cmd,
return rc;
}
#endif /* !FWTPM_NO_HASH_CMDS */
/* SequenceUpdate is shared with the MLDSA sign/verify sequences - keep it
* whenever MLDSA is built, even if the hash commands are gated.
* SequenceComplete is NOT shared: MLDSA sequences finalize through
* SignSequenceComplete / VerifySequenceComplete, so it stays under
* FWTPM_NO_HASH_CMDS alone. */
#if !defined(FWTPM_NO_HASH_CMDS) || defined(WOLFTPM_MLDSA)
/* --- TPM2_SequenceUpdate (CC 0x015C) --- */
static TPM_RC FwCmd_SequenceUpdate(FWTPM_CTX* ctx, TPM2_Packet* cmd,
int cmdSize, TPM2_Packet* rsp, UINT16 cmdTag)
@ -7973,7 +8016,9 @@ static TPM_RC FwCmd_SequenceUpdate(FWTPM_CTX* ctx, TPM2_Packet* cmd,
UINT32 seqHandle;
UINT16 dataSize = 0;
FWTPM_DECLARE_BUF(dataBuf, FWTPM_MAX_DATA_BUF);
#ifndef FWTPM_NO_HASH_CMDS
FWTPM_HashSeq* seq;
#endif
#ifdef WOLFTPM_MLDSA
FWTPM_SignSeq* signSeq = NULL;
#endif
@ -7987,20 +8032,26 @@ static TPM_RC FwCmd_SequenceUpdate(FWTPM_CTX* ctx, TPM2_Packet* cmd,
if (rc == 0) {
TPM2_Packet_ParseU32(cmd, &seqHandle);
#ifndef FWTPM_NO_HASH_CMDS
seq = FwFindHashSeq(ctx, seqHandle);
#endif
#ifdef WOLFTPM_MLDSA
if (seq == NULL) {
/* Not a hash sequence — check sign/verify sequence slots. */
/* Not a hash sequence - check sign/verify sequence slots. Per Part 3
* Sec.20.6.1, TPM_RC_ONE_SHOT_SIGNATURE is a Sign SequenceComplete-time
* RC ("sequenceHandle references a non-empty sequence"), not an
* Update-time RC. We accept the Update bytes here (accumulator below
* holds them) and let SignSequenceComplete fail with the spec-mandated
* RC if the key is one-shot and any bytes accumulated. */
#ifndef FWTPM_NO_HASH_CMDS
if (seq == NULL)
#endif
signSeq = FwFindSignSeq(ctx, seqHandle);
if (signSeq == NULL) {
rc = TPM_RC_HANDLE;
}
/* Per Part 3 Sec.20.6.1, TPM_RC_ONE_SHOT_SIGNATURE is a Sign
* SequenceComplete-time RC ("sequenceHandle references a
* non-empty sequence"), not an Update-time RC. We accept the
* Update bytes here (accumulator below holds them) and let
* SignSequenceComplete fail with the spec-mandated RC if the
* key is one-shot and any bytes accumulated. */
if (signSeq == NULL
#ifndef FWTPM_NO_HASH_CMDS
&& seq == NULL
#endif
) {
rc = TPM_RC_HANDLE;
}
#else
if (seq == NULL) {
@ -8083,8 +8134,11 @@ static TPM_RC FwCmd_SequenceUpdate(FWTPM_CTX* ctx, TPM2_Packet* cmd,
signSeq->msgBufSz += dataSize;
}
}
#ifndef FWTPM_NO_HASH_CMDS
else
#endif
#endif
#ifndef FWTPM_NO_HASH_CMDS
if (seq->isHmac) {
rc = wc_HmacUpdate(&seq->ctx.hmac, dataBuf, dataSize);
if (rc != 0) {
@ -8098,6 +8152,7 @@ static TPM_RC FwCmd_SequenceUpdate(FWTPM_CTX* ctx, TPM2_Packet* cmd,
rc = TPM_RC_FAILURE;
}
}
#endif
}
if (rc == 0) {
@ -8108,7 +8163,9 @@ static TPM_RC FwCmd_SequenceUpdate(FWTPM_CTX* ctx, TPM2_Packet* cmd,
FWTPM_FREE_BUF(dataBuf);
return rc;
}
#endif /* !FWTPM_NO_HASH_CMDS || WOLFTPM_MLDSA */
#ifndef FWTPM_NO_HASH_CMDS
/* --- TPM2_SequenceComplete (CC 0x013E) --- */
static TPM_RC FwCmd_SequenceComplete(FWTPM_CTX* ctx, TPM2_Packet* cmd,
int cmdSize, TPM2_Packet* rsp, UINT16 cmdTag)
@ -8124,9 +8181,6 @@ static TPM_RC FwCmd_SequenceComplete(FWTPM_CTX* ctx, TPM2_Packet* cmd,
TPMI_ALG_HASH hashAlg = TPM_ALG_NULL;
int paramSzPos, paramStart;
int trc;
#ifdef WOLFTPM_MLDSA
FWTPM_SignSeq* misRoutedSign = NULL;
#endif
FWTPM_ALLOC_BUF(dataBuf, FWTPM_MAX_DATA_BUF);
@ -8139,12 +8193,11 @@ static TPM_RC FwCmd_SequenceComplete(FWTPM_CTX* ctx, TPM2_Packet* cmd,
seq = FwFindHashSeq(ctx, seqHandle);
if (seq == NULL) {
/* A sign/verify sequence handle mis-routed here is rejected but
* NOT freed: this path runs before the auth area is parsed, so
* freeing would let an unauthenticated caller destroy any sequence
* by guessing its handle. FlushContext releases those slots. */
rc = TPM_RC_HANDLE;
#ifdef WOLFTPM_MLDSA
/* Free a sign/verify slot mis-routed here so it doesn't leak. */
misRoutedSign = FwFindSignSeq(ctx, seqHandle);
if (misRoutedSign != NULL) FwFreeSignSeq(misRoutedSign);
#endif
}
else {
hashAlg = seq->hashAlg;
@ -8384,8 +8437,10 @@ static TPM_RC FwCmd_EventSequenceComplete(FWTPM_CTX* ctx, TPM2_Packet* cmd,
FWTPM_FREE_BUF(dataBuf);
return rc;
}
#endif /* !FWTPM_NO_HASH_CMDS */
#ifdef HAVE_ECC
#ifndef FWTPM_NO_ECDH
/* --- TPM2_ECDH_KeyGen (CC 0x0163) --- */
static TPM_RC FwCmd_ECDH_KeyGen(FWTPM_CTX* ctx, TPM2_Packet* cmd,
int cmdSize, TPM2_Packet* rsp, UINT16 cmdTag)
@ -8680,6 +8735,7 @@ static TPM_RC FwCmd_ECDH_ZGen(FWTPM_CTX* ctx, TPM2_Packet* cmd,
FWTPM_FREE_VAR(inPoint);
return rc;
}
#endif /* !FWTPM_NO_ECDH */
#endif /* HAVE_ECC */
/* --- TPM2_StartAuthSession (CC 0x0176) --- */
@ -12392,6 +12448,7 @@ static TPM_RC FwCmd_DictionaryAttackParameters(FWTPM_CTX* ctx,
#endif /* !FWTPM_NO_DA */
#ifndef NO_AES
#ifndef FWTPM_NO_SYM_ENCRYPT
/* --- TPM2_EncryptDecrypt (CC 0x0164) and EncryptDecrypt2 (CC 0x0187) ---
* Symmetric encrypt/decrypt using a loaded SYMCIPHER key.
* EncryptDecrypt: keyHandle, decrypt, mode, ivIn, inData
@ -12684,6 +12741,7 @@ static TPM_RC FwCmd_EncryptDecrypt2(FWTPM_CTX* ctx, TPM2_Packet* cmd,
{
return FwEncryptDecryptCore(ctx, cmd, cmdSize, rsp, cmdTag, 1);
}
#endif /* !FWTPM_NO_SYM_ENCRYPT */
#endif /* !NO_AES */
/* ================================================================== */
@ -13818,6 +13876,7 @@ static TPM_RC FwCmd_ActivateCredential(FWTPM_CTX* ctx, TPM2_Packet* cmd,
/* ECC Parameters */
/* ================================================================== */
#ifndef FWTPM_NO_ECDH
/* Convert hex string to binary. Returns byte count, or -1 on error. */
static int FwHexToBin(const char* hex, byte* out, int outSz)
{
@ -14251,6 +14310,7 @@ static TPM_RC FwCmd_ZGen_2Phase(FWTPM_CTX* ctx, TPM2_Packet* cmd,
FWTPM_FREE_VAR(peerPub);
return rc;
}
#endif /* !FWTPM_NO_ECDH */
#endif /* HAVE_ECC */
#ifdef WOLFTPM_FWTPM_TCG_TEST
@ -16140,14 +16200,18 @@ static const FWTPM_CMD_ENTRY fwCmdTable[] = {
{ TPM_CC_PCR_Allocate, FwCmd_PCR_Allocate, 1, 1, 0, 0 },
{ TPM_CC_PCR_SetAuthPolicy, FwCmd_PCR_SetAuthPolicy, 1, 1, 0, FW_CMD_FLAG_ENC },
{ TPM_CC_PCR_SetAuthValue, FwCmd_PCR_SetAuthValue, 1, 1, 0, FW_CMD_FLAG_ENC },
#ifndef FWTPM_NO_CLOCK
{ TPM_CC_ReadClock, FwCmd_ReadClock, 0, 0, 0, 0 },
{ TPM_CC_ClockSet, FwCmd_ClockSet, 1, 1, 0, 0 },
{ TPM_CC_ClockRateAdjust, FwCmd_ClockRateAdjust, 1, 1, 0, 0 },
#endif /* !FWTPM_NO_CLOCK */
/* --- Key management (always enabled, algorithm checks inside) --- */
{ TPM_CC_CreatePrimary, FwCmd_CreatePrimary, 1, 1, 1, FW_CMD_FLAG_ENC | FW_CMD_FLAG_DEC },
{ TPM_CC_FlushContext, FwCmd_FlushContext, 1, 0, 0, 0 },
#ifndef FWTPM_NO_CONTEXT
{ TPM_CC_ContextSave, FwCmd_ContextSave, 1, 0, 0, 0 },
{ TPM_CC_ContextLoad, FwCmd_ContextLoad, 0, 0, 1, 0 },
#endif /* !FWTPM_NO_CONTEXT */
{ TPM_CC_ReadPublic, FwCmd_ReadPublic, 1, 0, 0, FW_CMD_FLAG_DEC },
{ TPM_CC_Clear, FwCmd_Clear, 1, 1, 0, 0 },
{ TPM_CC_ClearControl, FwCmd_ClearControl, 1, 1, 0, 0 },
@ -16167,19 +16231,32 @@ static const FWTPM_CMD_ENTRY fwCmdTable[] = {
{ TPM_CC_RSA_Decrypt, FwCmd_RSA_Decrypt, 1, 1, 0, FW_CMD_FLAG_ENC | FW_CMD_FLAG_DEC },
#endif
/* --- Hash/HMAC --- */
#ifndef FWTPM_NO_HASH_CMDS
{ TPM_CC_Hash, FwCmd_Hash, 0, 0, 0, FW_CMD_FLAG_ENC | FW_CMD_FLAG_DEC },
{ TPM_CC_HMAC, FwCmd_HMAC, 1, 1, 0, FW_CMD_FLAG_ENC | FW_CMD_FLAG_DEC },
{ TPM_CC_HMAC_Start, FwCmd_HMAC_Start, 1, 1, 1, FW_CMD_FLAG_ENC },
{ TPM_CC_HashSequenceStart, FwCmd_HashSequenceStart, 0, 0, 1, FW_CMD_FLAG_ENC },
#endif /* !FWTPM_NO_HASH_CMDS */
/* SequenceUpdate is shared with the MLDSA sign/verify sequences - keep it
* whenever MLDSA is built, even if the hash commands are gated. */
#if !defined(FWTPM_NO_HASH_CMDS) || defined(WOLFTPM_MLDSA)
{ TPM_CC_SequenceUpdate, FwCmd_SequenceUpdate, 1, 1, 0, FW_CMD_FLAG_ENC },
#endif
/* SequenceComplete only finalizes hash/HMAC sequences; MLDSA sequences use
* SignSequenceComplete / VerifySequenceComplete, so it is never advertised
* once the hash commands (and their sequence producers) are gated out. */
#ifndef FWTPM_NO_HASH_CMDS
{ TPM_CC_SequenceComplete, FwCmd_SequenceComplete, 1, 1, 0, FW_CMD_FLAG_ENC | FW_CMD_FLAG_DEC },
{ TPM_CC_EventSequenceComplete, FwCmd_EventSequenceComplete, 2, 2, 0, FW_CMD_FLAG_ENC },
#endif /* !FWTPM_NO_HASH_CMDS */
/* --- ECC --- */
#ifdef HAVE_ECC
#ifndef FWTPM_NO_ECDH
{ TPM_CC_ECDH_KeyGen, FwCmd_ECDH_KeyGen, 1, 0, 0, FW_CMD_FLAG_DEC },
{ TPM_CC_ECDH_ZGen, FwCmd_ECDH_ZGen, 1, 1, 0, FW_CMD_FLAG_ENC | FW_CMD_FLAG_DEC },
{ TPM_CC_EC_Ephemeral, FwCmd_EC_Ephemeral, 0, 0, 0, FW_CMD_FLAG_DEC },
{ TPM_CC_ZGen_2Phase, FwCmd_ZGen_2Phase, 1, 1, 0, FW_CMD_FLAG_ENC | FW_CMD_FLAG_DEC },
#endif /* !FWTPM_NO_ECDH */
#endif
/* --- Sessions --- */
{ TPM_CC_StartAuthSession, FwCmd_StartAuthSession, 2, 0, 1, 0 },
@ -16214,14 +16291,18 @@ static const FWTPM_CMD_ENTRY fwCmdTable[] = {
#endif /* !FWTPM_NO_POLICY */
/* --- Key import/export --- */
{ TPM_CC_LoadExternal, FwCmd_LoadExternal, 0, 0, 1, FW_CMD_FLAG_ENC | FW_CMD_FLAG_DEC },
#ifndef FWTPM_NO_KEY_MIGRATION
{ TPM_CC_Import, FwCmd_Import, 1, 1, 0, FW_CMD_FLAG_ENC | FW_CMD_FLAG_DEC },
{ TPM_CC_Duplicate, FwCmd_Duplicate, 2, 1, 0, FW_CMD_FLAG_DEC },
{ TPM_CC_Rewrap, FwCmd_Rewrap, 2, 1, 0, 0 },
#endif /* !FWTPM_NO_KEY_MIGRATION */
{ TPM_CC_CreateLoaded, FwCmd_CreateLoaded, 1, 1, 1, FW_CMD_FLAG_ENC | FW_CMD_FLAG_DEC },
/* --- Symmetric --- */
#ifndef NO_AES
#ifndef FWTPM_NO_SYM_ENCRYPT
{ TPM_CC_EncryptDecrypt, FwCmd_EncryptDecrypt, 1, 1, 0, FW_CMD_FLAG_ENC | FW_CMD_FLAG_DEC },
{ TPM_CC_EncryptDecrypt2, FwCmd_EncryptDecrypt2, 1, 1, 0, FW_CMD_FLAG_ENC | FW_CMD_FLAG_DEC },
#endif /* !FWTPM_NO_SYM_ENCRYPT */
#endif
/* --- NV RAM --- */
#ifndef FWTPM_NO_NV
@ -16241,7 +16322,9 @@ static const FWTPM_CMD_ENTRY fwCmdTable[] = {
#endif /* !FWTPM_NO_NV */
/* --- ECC Parameters --- */
#ifdef HAVE_ECC
#ifndef FWTPM_NO_ECDH
{ TPM_CC_ECC_Parameters, FwCmd_ECC_Parameters, 0, 0, 0, 0 },
#endif /* !FWTPM_NO_ECDH */
#endif
/* --- Attestation --- */
#ifndef FWTPM_NO_ATTESTATION

View File

@ -2296,6 +2296,7 @@ int FwUnwrapPrivate(FWTPM_Object* parent,
/* Context blob wrap/unwrap (ContextSave/ContextLoad) */
/* ================================================================== */
#ifndef FWTPM_NO_CONTEXT
/* Fold a 64-bit value into an HMAC as big-endian, used to bind the context
* sequence counter into the blob MAC for replay protection. */
static int FwHmacUpdateU64(Hmac* hmac, UINT64 v)
@ -2472,6 +2473,7 @@ int FwUnwrapContextBlob(FWTPM_CTX* ctx, UINT64 seq, byte ctxType,
FWTPM_FREE_VAR(hmac);
return rc;
}
#endif /* !FWTPM_NO_CONTEXT */
/* ================================================================== */
/* Seed encrypt/decrypt */

View File

@ -743,6 +743,7 @@ static void test_fwtpm_getcap_commands_tpma(void)
AssertIntEQ(TpmaCcRHandle(tpma), 0); /* no response handle */
AssertIntEQ(tpma & (UINT32)CC_VEND, 0);
#ifndef FWTPM_NO_HASH_CMDS
/* HashSequenceStart returns a sequence handle: expect rHandle == 1. */
cmdSz = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_GetCapability);
PutU32BE(gCmd + cmdSz, TPM_CAP_COMMANDS); cmdSz += 4;
@ -758,6 +759,7 @@ static void test_fwtpm_getcap_commands_tpma(void)
tpma = GetU32BE(gRsp + TPM2_HEADER_SIZE + 9);
AssertIntEQ(TpmaCcToCmdCode(tpma), (UINT32)TPM_CC_HashSequenceStart);
AssertIntEQ(TpmaCcRHandle(tpma), 1); /* returns a handle */
#endif /* !FWTPM_NO_HASH_CMDS */
/* StartAuthSession returns a session handle: expect rHandle == 1. */
cmdSz = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_GetCapability);
@ -970,26 +972,287 @@ static void test_fwtpm_getcap_paging(void)
fwtpm_pass("GetCapability paging convergence:", 0);
}
/* ================================================================== */
/* Command-group gates (FWTPM_NO_* macros) */
/* ================================================================== */
/* Whether this build advertises each gated command group. Kept as 0/1 macros
* so one table drives both the default build and every gated CI leg. */
#ifdef FWTPM_NO_KEY_MIGRATION
#define FW_GATED_KEY_MIGRATION 0
#else
#define FW_GATED_KEY_MIGRATION 1
#endif
#if defined(HAVE_ECC) && !defined(FWTPM_NO_ECDH)
#define FW_GATED_ECDH 1
#else
#define FW_GATED_ECDH 0
#endif
#ifdef FWTPM_NO_HASH_CMDS
#define FW_GATED_HASH_CMDS 0
#else
#define FW_GATED_HASH_CMDS 1
#endif
#ifdef FWTPM_NO_CONTEXT
#define FW_GATED_CONTEXT 0
#else
#define FW_GATED_CONTEXT 1
#endif
#if !defined(NO_AES) && !defined(FWTPM_NO_SYM_ENCRYPT)
#define FW_GATED_SYM_ENCRYPT 1
#else
#define FW_GATED_SYM_ENCRYPT 0
#endif
#ifdef FWTPM_NO_CLOCK
#define FW_GATED_CLOCK 0
#else
#define FW_GATED_CLOCK 1
#endif
#ifdef FWTPM_NO_POLICY
#define FW_GATED_POLICY 0
#else
#define FW_GATED_POLICY 1
#endif
#ifdef FWTPM_NO_NV
#define FW_GATED_NV 0
#else
#define FW_GATED_NV 1
#endif
#ifdef FWTPM_NO_ATTESTATION
#define FW_GATED_ATTESTATION 0
#else
#define FW_GATED_ATTESTATION 1
#endif
#ifdef FWTPM_NO_CREDENTIAL
#define FW_GATED_CREDENTIAL 0
#else
#define FW_GATED_CREDENTIAL 1
#endif
#ifdef FWTPM_NO_DA
#define FW_GATED_DA 0
#else
#define FW_GATED_DA 1
#endif
/* SequenceUpdate is the one sequence command shared with the ML-DSA verify
* sequences, so it survives FWTPM_NO_HASH_CMDS when ML-DSA is built.
* SequenceComplete is NOT shared - ML-DSA finalizes through
* SignSequenceComplete / VerifySequenceComplete - so it must disappear with
* the rest of the hash commands. */
#if !defined(FWTPM_NO_HASH_CMDS) || defined(WOLFTPM_MLDSA)
#define FW_GATED_SEQ_UPDATE 1
#else
#define FW_GATED_SEQ_UPDATE 0
#endif
typedef struct FwGateCase {
UINT32 cc;
int present; /* 1 = must be advertised and dispatchable */
const char* name;
} FwGateCase;
static const FwGateCase fwGateCases[] = {
{ TPM_CC_Import, FW_GATED_KEY_MIGRATION, "Import" },
{ TPM_CC_Duplicate, FW_GATED_KEY_MIGRATION, "Duplicate" },
{ TPM_CC_Rewrap, FW_GATED_KEY_MIGRATION, "Rewrap" },
{ TPM_CC_ECC_Parameters, FW_GATED_ECDH, "ECC_Parameters" },
{ TPM_CC_EC_Ephemeral, FW_GATED_ECDH, "EC_Ephemeral" },
{ TPM_CC_Hash, FW_GATED_HASH_CMDS, "Hash" },
{ TPM_CC_HashSequenceStart, FW_GATED_HASH_CMDS, "HashSequenceStart" },
{ TPM_CC_SequenceComplete, FW_GATED_HASH_CMDS, "SequenceComplete" },
{ TPM_CC_EventSequenceComplete, FW_GATED_HASH_CMDS,
"EventSequenceComplete" },
{ TPM_CC_SequenceUpdate, FW_GATED_SEQ_UPDATE, "SequenceUpdate" },
{ TPM_CC_ContextSave, FW_GATED_CONTEXT, "ContextSave" },
{ TPM_CC_ContextLoad, FW_GATED_CONTEXT, "ContextLoad" },
{ TPM_CC_EncryptDecrypt, FW_GATED_SYM_ENCRYPT, "EncryptDecrypt" },
{ TPM_CC_EncryptDecrypt2, FW_GATED_SYM_ENCRYPT, "EncryptDecrypt2" },
{ TPM_CC_ReadClock, FW_GATED_CLOCK, "ReadClock" },
{ TPM_CC_ClockSet, FW_GATED_CLOCK, "ClockSet" },
{ TPM_CC_ClockRateAdjust, FW_GATED_CLOCK, "ClockRateAdjust" },
{ TPM_CC_PolicyPCR, FW_GATED_POLICY, "PolicyPCR" },
{ TPM_CC_NV_ReadPublic, FW_GATED_NV, "NV_ReadPublic" },
{ TPM_CC_Quote, FW_GATED_ATTESTATION, "Quote" },
{ TPM_CC_MakeCredential, FW_GATED_CREDENTIAL, "MakeCredential" },
{ TPM_CC_DictionaryAttackLockReset, FW_GATED_DA,
"DictionaryAttackLockReset" },
/* Never gated - guards against a table/#ifdef slip removing a core
* command that every build must keep. */
{ TPM_CC_GetRandom, 1, "GetRandom" },
{ TPM_CC_PCR_Read, 1, "PCR_Read" },
{ TPM_CC_FlushContext, 1, "FlushContext" },
};
/* Enumerate TPM_CAP_COMMANDS in full and return the number of entries, writing
* each advertised command code into ccOut (up to ccMax). Asserts the list was
* not truncated so callers can treat the result as the complete set. */
static UINT32 GetAdvertisedCommands(FWTPM_CTX* ctx, UINT32* ccOut, UINT32 ccMax)
{
int rc, rspSize, cmdSz;
UINT32 count, i;
cmdSz = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_GetCapability);
PutU32BE(gCmd + cmdSz, TPM_CAP_COMMANDS); cmdSz += 4;
PutU32BE(gCmd + cmdSz, 0); cmdSz += 4;
PutU32BE(gCmd + cmdSz, 512); cmdSz += 4;
PutU32BE(gCmd + 2, (UINT32)cmdSz);
rspSize = 0;
rc = FWTPM_ProcessCommand(ctx, gCmd, cmdSz, gRsp, &rspSize, 0);
AssertIntEQ(rc, TPM_RC_SUCCESS);
AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS);
/* moreData must be NO: the whole list has to fit for the count to be
* comparable against TPM_PT_TOTAL_COMMANDS. */
AssertIntEQ(gRsp[TPM2_HEADER_SIZE], 0);
AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE + 1), (int)TPM_CAP_COMMANDS);
count = GetU32BE(gRsp + TPM2_HEADER_SIZE + 5);
AssertTrue(count <= ccMax);
for (i = 0; i < count; i++) {
ccOut[i] = TpmaCcToCmdCode(
GetU32BE(gRsp + TPM2_HEADER_SIZE + 9 + (int)(i * 4)));
}
return count;
}
static int CommandAdvertised(const UINT32* ccList, UINT32 count, UINT32 cc)
{
UINT32 i;
for (i = 0; i < count; i++) {
if (ccList[i] == cc) {
return 1;
}
}
return 0;
}
/* Every FWTPM_NO_* command group must disappear from both the dispatcher and
* the TPM_CAP_COMMANDS advertisement together. A command that is advertised
* but always fails (or dispatchable but unadvertised) is a gating bug. */
static void test_fwtpm_command_gates(void)
{
FWTPM_CTX ctx;
UINT32 advertised[512];
UINT32 count;
int rc, rspSize, cmdSz;
unsigned int i;
memset(&ctx, 0, sizeof(ctx));
AssertIntEQ(fwtpm_test_startup(&ctx), 0);
count = GetAdvertisedCommands(&ctx, advertised,
(UINT32)(sizeof(advertised) / sizeof(advertised[0])));
AssertIntGT(count, 0);
for (i = 0; i < sizeof(fwGateCases) / sizeof(fwGateCases[0]); i++) {
const FwGateCase* g = &fwGateCases[i];
/* (a) Capability advertisement matches the build. */
Assert(CommandAdvertised(advertised, count, g->cc) == g->present,
("%s advertised == %d", g->name, g->present),
("%s advertised == %d", g->name,
CommandAdvertised(advertised, count, g->cc)));
/* (b) Dispatch agrees: a gated command is rejected with
* TPM_RC_COMMAND_CODE; a live one fails for some other reason
* (missing handles/params), never as an unknown command. */
cmdSz = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, g->cc);
PutU32BE(gCmd + 2, (UINT32)cmdSz);
rspSize = 0;
rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0);
AssertIntEQ(rc, TPM_RC_SUCCESS);
if (g->present) {
AssertIntNE(GetRspRC(gRsp), TPM_RC_COMMAND_CODE);
}
else {
AssertIntEQ(GetRspRC(gRsp), TPM_RC_COMMAND_CODE);
}
}
FWTPM_Cleanup(&ctx);
fwtpm_pass("Command-group gates (dispatch + CAP_COMMANDS):", 0);
}
/* TPM_PT_TOTAL_COMMANDS is derived from the same dispatch table as
* TPM_CAP_COMMANDS, so gating a group must move both together. */
static void test_fwtpm_total_commands(void)
{
FWTPM_CTX ctx;
UINT32 advertised[512];
UINT32 count, props, i, total = 0;
int rc, rspSize, cmdSz, found = 0;
memset(&ctx, 0, sizeof(ctx));
AssertIntEQ(fwtpm_test_startup(&ctx), 0);
count = GetAdvertisedCommands(&ctx, advertised,
(UINT32)(sizeof(advertised) / sizeof(advertised[0])));
/* GetCapability(TPM_CAP_TPM_PROPERTIES, first=TPM_PT_TOTAL_COMMANDS) */
cmdSz = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_GetCapability);
PutU32BE(gCmd + cmdSz, TPM_CAP_TPM_PROPERTIES); cmdSz += 4;
PutU32BE(gCmd + cmdSz, TPM_PT_TOTAL_COMMANDS); cmdSz += 4;
PutU32BE(gCmd + cmdSz, 1); cmdSz += 4;
PutU32BE(gCmd + 2, (UINT32)cmdSz);
rspSize = 0;
rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0);
AssertIntEQ(rc, TPM_RC_SUCCESS);
AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS);
AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE + 1),
(int)TPM_CAP_TPM_PROPERTIES);
/* TPMS_TAGGED_PROPERTY list: count, then (property, value) pairs. */
props = GetU32BE(gRsp + TPM2_HEADER_SIZE + 5);
AssertIntGT(props, 0);
for (i = 0; i < props; i++) {
int off = TPM2_HEADER_SIZE + 9 + (int)(i * 8);
if (GetU32BE(gRsp + off) == (UINT32)TPM_PT_TOTAL_COMMANDS) {
total = GetU32BE(gRsp + off + 4);
found = 1;
break;
}
}
AssertIntEQ(found, 1);
AssertIntEQ(total, count);
FWTPM_Cleanup(&ctx);
fwtpm_pass("TPM_PT_TOTAL_COMMANDS matches CAP_COMMANDS:", 0);
}
/* ================================================================== */
/* 6. PCR Operations */
/* ================================================================== */
/* Build PCR_Read command for SHA-256 bank */
/* A PCR that is both readable and extendable from locality 0 in any build:
* PCR 16 (the debug PCR) when all 24 standard PCRs are implemented, otherwise
* the highest implemented PCR (0-15 also accept any locality for extend). */
#if IMPLEMENTATION_PCR >= 24
#define FW_TEST_PCR 16
#else
#define FW_TEST_PCR (IMPLEMENTATION_PCR - 1)
#endif
/* Build PCR_Read command for SHA-256 bank. The selection bitmap is sized from
* PCR_SELECT_MAX rather than a fixed 3 so the command stays well-formed in a
* build that implements fewer than 24 PCRs. */
static int BuildPcrReadCmd(byte* buf, UINT32 pcrIndex)
{
int pos;
int pos, i;
pos = BuildCmdHeader(buf, TPM_ST_NO_SESSIONS, 0, TPM_CC_PCR_Read);
/* TPML_PCR_SELECTION: count=1 */
PutU32BE(buf + pos, 1); pos += 4;
/* TPMS_PCR_SELECTION: hash=SHA256, sizeofSelect=3, pcrSelect[3] */
/* TPMS_PCR_SELECTION: hash=SHA256, sizeofSelect, pcrSelect[] */
PutU16BE(buf + pos, TPM_ALG_SHA256); pos += 2;
buf[pos++] = 3; /* sizeofSelect */
buf[pos++] = (byte)PCR_SELECT_MAX; /* sizeofSelect */
/* Set bit for pcrIndex in the bitmap */
buf[pos] = 0; buf[pos+1] = 0; buf[pos+2] = 0;
if (pcrIndex < 24) {
for (i = 0; i < PCR_SELECT_MAX; i++) {
buf[pos + i] = 0;
}
if (pcrIndex < (UINT32)IMPLEMENTATION_PCR) {
buf[pos + (pcrIndex / 8)] = (byte)(1 << (pcrIndex % 8));
}
pos += 3;
pos += PCR_SELECT_MAX;
PutU32BE(buf + 2, (UINT32)pos);
return pos;
}
@ -1027,7 +1290,7 @@ static void test_fwtpm_pcr_extend_and_read(void)
AssertIntEQ(rc, 0);
/* Read PCR 16 (resettable) before extend */
cmdSz = BuildPcrReadCmd(gCmd, 16);
cmdSz = BuildPcrReadCmd(gCmd, FW_TEST_PCR);
rspSize = 0;
rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0);
AssertIntEQ(rc, TPM_RC_SUCCESS);
@ -1050,8 +1313,8 @@ static void test_fwtpm_pcr_extend_and_read(void)
PutU16BE(gCmd + cmdSz, TPM_ST_SESSIONS); cmdSz += 2;
PutU32BE(gCmd + cmdSz, 0); cmdSz += 4; /* size placeholder */
PutU32BE(gCmd + cmdSz, TPM_CC_PCR_Extend); cmdSz += 4;
/* pcrHandle = PCR 16 */
PutU32BE(gCmd + cmdSz, 16); cmdSz += 4;
/* pcrHandle */
PutU32BE(gCmd + cmdSz, FW_TEST_PCR); cmdSz += 4;
/* Auth area: size(4) + sessionHandle(4) + nonce(2) + attrs(1) + hmac(2) */
PutU32BE(gCmd + cmdSz, 9); cmdSz += 4; /* authAreaSize */
PutU32BE(gCmd + cmdSz, TPM_RS_PW); cmdSz += 4; /* password session */
@ -1072,7 +1335,7 @@ static void test_fwtpm_pcr_extend_and_read(void)
AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS);
/* Read PCR 16 again - should be different from before */
cmdSz = BuildPcrReadCmd(gCmd, 16);
cmdSz = BuildPcrReadCmd(gCmd, FW_TEST_PCR);
rspSize = 0;
rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0);
AssertIntEQ(rc, TPM_RC_SUCCESS);
@ -1114,7 +1377,7 @@ static void test_fwtpm_pw_session_continue_set(void)
PutU16BE(gCmd + cmdSz, TPM_ST_SESSIONS); cmdSz += 2;
PutU32BE(gCmd + cmdSz, 0); cmdSz += 4;
PutU32BE(gCmd + cmdSz, TPM_CC_PCR_Extend); cmdSz += 4;
PutU32BE(gCmd + cmdSz, 16); cmdSz += 4;
PutU32BE(gCmd + cmdSz, FW_TEST_PCR); cmdSz += 4;
PutU32BE(gCmd + cmdSz, 9); cmdSz += 4;
PutU32BE(gCmd + cmdSz, TPM_RS_PW); cmdSz += 4;
PutU16BE(gCmd + cmdSz, 0); cmdSz += 2;
@ -1141,6 +1404,7 @@ static void test_fwtpm_pw_session_continue_set(void)
}
/* PCR_Event into DRTM PCR 17 must require locality 4 (Part 1 Sec.11.4.6). */
#if IMPLEMENTATION_PCR >= 24
static void test_fwtpm_pcr_event_drtm_locality_enforced(void)
{
FWTPM_CTX ctx;
@ -1177,6 +1441,7 @@ static void test_fwtpm_pcr_event_drtm_locality_enforced(void)
FWTPM_Cleanup(&ctx);
printf("Test fwTPM:\tPCR_Event DRTM locality enforced:\tPassed\n");
}
#endif /* IMPLEMENTATION_PCR >= 24 */
/* Per TPM 2.0 Part 3 Sec.22.3, PCR_Extend takes Auth Role USER on the
* PCR handle. When PCR_SetAuthValue has installed a non-empty
@ -1199,7 +1464,7 @@ static void test_fwtpm_pcr_extend_empty_pw_rejected_after_setauth(void)
PutU16BE(gCmd + cmdSz, TPM_ST_SESSIONS); cmdSz += 2;
PutU32BE(gCmd + cmdSz, 0); cmdSz += 4;
PutU32BE(gCmd + cmdSz, TPM_CC_PCR_SetAuthValue); cmdSz += 4;
PutU32BE(gCmd + cmdSz, 16); cmdSz += 4;
PutU32BE(gCmd + cmdSz, FW_TEST_PCR); cmdSz += 4;
PutU32BE(gCmd + cmdSz, 9); cmdSz += 4;
PutU32BE(gCmd + cmdSz, TPM_RS_PW); cmdSz += 4;
PutU16BE(gCmd + cmdSz, 0); cmdSz += 2;
@ -1219,7 +1484,7 @@ static void test_fwtpm_pcr_extend_empty_pw_rejected_after_setauth(void)
PutU16BE(gCmd + cmdSz, TPM_ST_SESSIONS); cmdSz += 2;
PutU32BE(gCmd + cmdSz, 0); cmdSz += 4;
PutU32BE(gCmd + cmdSz, TPM_CC_PCR_Extend); cmdSz += 4;
PutU32BE(gCmd + cmdSz, 16); cmdSz += 4;
PutU32BE(gCmd + cmdSz, FW_TEST_PCR); cmdSz += 4;
PutU32BE(gCmd + cmdSz, 9); cmdSz += 4;
PutU32BE(gCmd + cmdSz, TPM_RS_PW); cmdSz += 4;
PutU16BE(gCmd + cmdSz, 0); cmdSz += 2;
@ -1240,7 +1505,7 @@ static void test_fwtpm_pcr_extend_empty_pw_rejected_after_setauth(void)
PutU16BE(gCmd + cmdSz, TPM_ST_SESSIONS); cmdSz += 2;
PutU32BE(gCmd + cmdSz, 0); cmdSz += 4;
PutU32BE(gCmd + cmdSz, TPM_CC_PCR_SetAuthValue); cmdSz += 4;
PutU32BE(gCmd + cmdSz, 16); cmdSz += 4;
PutU32BE(gCmd + cmdSz, FW_TEST_PCR); cmdSz += 4;
PutU32BE(gCmd + cmdSz, 9 + pcrAuthSz); cmdSz += 4;
PutU32BE(gCmd + cmdSz, TPM_RS_PW); cmdSz += 4;
PutU16BE(gCmd + cmdSz, 0); cmdSz += 2;
@ -1262,6 +1527,7 @@ static void test_fwtpm_pcr_extend_empty_pw_rejected_after_setauth(void)
/* 7. ReadClock */
/* ================================================================== */
#ifndef FWTPM_NO_CLOCK
static void test_fwtpm_readclock(void)
{
FWTPM_CTX ctx;
@ -1284,6 +1550,7 @@ static void test_fwtpm_readclock(void)
FWTPM_Cleanup(&ctx);
fwtpm_pass("ReadClock:", 0);
}
#endif /* !FWTPM_NO_CLOCK */
/* ================================================================== */
/* 8. CreatePrimary (RSA and ECC) */
@ -4332,6 +4599,7 @@ static void test_fwtpm_sign_x509sign_returns_attributes(void)
/* End-to-end positive: TPM2_Hash produces a real HASHCHECK ticket that
* SignDigest must accept on a restricted key. Confirms ticket-validation
* actually verifies the HMAC (not just rejects everything). */
#ifndef FWTPM_NO_HASH_CMDS
static void test_fwtpm_signdigest_restricted_valid_ticket_succeeds(void)
{
FWTPM_CTX ctx;
@ -4406,6 +4674,7 @@ static void test_fwtpm_signdigest_restricted_valid_ticket_succeeds(void)
FWTPM_Cleanup(&ctx);
fwtpm_pass("SignDigest restricted+valid ticket (success):", 1);
}
#endif /* !FWTPM_NO_HASH_CMDS */
/* F-4: VerifyDigestSignature rejects sigHashAlg != key's hashAlg with
* TPM_RC_SCHEME per Part 3 Sec.20.4.1. Key is Hash-ML-DSA-65/SHA-256; wire
@ -5986,6 +6255,7 @@ test_fwtpm_signseqcomplete_hash_mldsa_genvalue_via_update_returns_value(void)
* the slot allocated lets a buggy or hostile client exhaust
* FWTPM_MAX_SIGN_SEQ slots by repeatedly issuing Start + wrong-key
* Complete, denying service to legitimate Sign sequences (CWE-772). */
#ifdef WOLFTPM_MLDSA
static void test_fwtpm_signseqcomplete_wrong_key_frees_slot(void)
{
FWTPM_CTX ctx;
@ -6071,6 +6341,7 @@ static void test_fwtpm_signseqcomplete_wrong_key_frees_slot(void)
FWTPM_Cleanup(&ctx);
fwtpm_pass("SignSeqComplete wrong key frees slot:", 1);
}
#endif /* WOLFTPM_MLDSA */
#ifdef WOLFTPM_V185
/* Extended CreatePrimary builder that overrides the default MLDSA/MLKEM
@ -6447,6 +6718,7 @@ static void test_fwtpm_mlkem1024_maxbuf(void)
* FWTPM_CTX holds FWTPM_MAX_SIGN_SEQ (4) slots for sign+verify sequences.
* Starting more than that must return TPM_RC_OBJECT_MEMORY from
* FwAllocSignSeq per Part 3 Sec.17.5. */
#ifdef WOLFTPM_MLDSA
static void test_fwtpm_signseq_slot_exhaustion(void)
{
FWTPM_CTX ctx;
@ -6491,6 +6763,7 @@ static void test_fwtpm_signseq_slot_exhaustion(void)
FWTPM_Cleanup(&ctx);
fwtpm_pass("SignSeq slot exhaustion:", 1);
}
#endif /* WOLFTPM_MLDSA */
/* ---- Long-message accumulation boundary for Pure-MLDSA verify seq ----
* msgBuf is FWTPM_MAX_DATA_BUF (1024) bytes. Accumulating across
@ -6899,6 +7172,7 @@ static void test_fwtpm_testparms_mldsa_supported_returns_success(void)
/* 9. Hash Sequence */
/* ================================================================== */
#ifndef FWTPM_NO_HASH_CMDS
static void test_fwtpm_hash(void)
{
FWTPM_CTX ctx;
@ -6942,6 +7216,7 @@ static void test_fwtpm_hash(void)
FWTPM_Cleanup(&ctx);
fwtpm_pass("Hash(SHA256, \"abc\"):", 0);
}
#endif /* !FWTPM_NO_HASH_CMDS */
/* ================================================================== */
/* 10. NULL pointer checks */
@ -8103,6 +8378,7 @@ static void test_fwtpm_sign_ecdaa_scheme(void)
/* ECDH key-agreement commands must reject a key without TPMA_OBJECT_decrypt
* per Part 3 Sec.14.3.3/14.7/21.3. A sign-only AIK would otherwise act as a
* CDH oracle over its private scalar. */
#ifndef FWTPM_NO_ECDH
static void test_fwtpm_ecdh_keygen_signkey_returns_attributes(void)
{
FWTPM_CTX ctx;
@ -8124,7 +8400,9 @@ static void test_fwtpm_ecdh_keygen_signkey_returns_attributes(void)
FWTPM_Cleanup(&ctx);
printf("Test fwTPM:\tECDH_KeyGen(sign key) rejected:\tPassed\n");
}
#endif /* !FWTPM_NO_ECDH */
#ifndef FWTPM_NO_ECDH
static void test_fwtpm_ecdh_zgen_signkey_returns_attributes(void)
{
FWTPM_CTX ctx;
@ -8154,7 +8432,9 @@ static void test_fwtpm_ecdh_zgen_signkey_returns_attributes(void)
FWTPM_Cleanup(&ctx);
printf("Test fwTPM:\tECDH_ZGen(sign key) rejected:\tPassed\n");
}
#endif /* !FWTPM_NO_ECDH */
#ifndef FWTPM_NO_ECDH
static void test_fwtpm_zgen_2phase_signkey_returns_attributes(void)
{
FWTPM_CTX ctx;
@ -8187,6 +8467,7 @@ static void test_fwtpm_zgen_2phase_signkey_returns_attributes(void)
FWTPM_Cleanup(&ctx);
printf("Test fwTPM:\tZGen_2Phase(sign key) rejected:\tPassed\n");
}
#endif /* !FWTPM_NO_ECDH */
/* Quote requires a restricted signing key per Part 3 Sec.18.4. Build the
* command once and run it against keys that violate each requirement. */
@ -8518,6 +8799,7 @@ static void test_fwtpm_test_parms(void)
memset(&ctx, 0, sizeof(ctx));
AssertIntEQ(fwtpm_test_startup(&ctx), 0);
#ifndef NO_RSA
/* TestParms: RSA-2048. TPMS_RSA_PARMS is symmetric, scheme, keyBits,
* exponent - in that order. */
pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_TestParms);
@ -8552,6 +8834,21 @@ static void test_fwtpm_test_parms(void)
rspSize = 0;
FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0);
AssertIntEQ(GetRspRC(gRsp), TPM_RC_SCHEME);
#else
/* An ECC-only build drops TPM_ALG_RSA from the TPMI_ALG_PUBLIC selectors
* TestParms accepts, so it must report the type as unsupported rather than
* silently validating parameters it cannot honor. */
pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_TestParms);
PutU16BE(gCmd + pos, TPM_ALG_RSA); pos += 2;
PutU16BE(gCmd + pos, TPM_ALG_NULL); pos += 2; /* symmetric */
PutU16BE(gCmd + pos, TPM_ALG_NULL); pos += 2; /* scheme */
PutU16BE(gCmd + pos, 2048); pos += 2; /* keyBits */
PutU32BE(gCmd + pos, 0); pos += 4; /* exponent */
PutU32BE(gCmd + 2, (UINT32)pos);
rspSize = 0;
FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0);
AssertIntEQ(GetRspRC(gRsp), TPM_RC_TYPE);
#endif /* !NO_RSA */
#ifdef HAVE_ECC
/* An unsupported ECC curve must be rejected. */
@ -8651,7 +8948,11 @@ static void test_fwtpm_test_parms(void)
#endif /* !NO_AES */
FWTPM_Cleanup(&ctx);
#ifndef NO_RSA
fwtpm_pass("TestParms(RSA-2048):", 0);
#else
fwtpm_pass("TestParms(ECC-only):", 0);
#endif
}
static void test_fwtpm_incremental_selftest(void)
@ -8775,6 +9076,51 @@ static TPM_RC SendPcrExtendLoc(FWTPM_CTX* ctx, int pcrIndex, int locality)
return GetRspRC(gRsp);
}
/* The PCR locality tables are sized to the 24 standard PCRs while the
* implemented PCR count is IMPLEMENTATION_PCR, so a build with fewer PCRs has
* two distinct boundaries. Exercise the highest implemented PCR (must work)
* and the first unavailable one (must be rejected, never silently aliased). */
static void test_fwtpm_pcr_bounds(void)
{
FWTPM_CTX ctx;
int rspSize, cmdSz;
memset(&ctx, 0, sizeof(ctx));
AssertIntEQ(fwtpm_test_startup(&ctx), 0);
/* TPM_PT_PCR_COUNT must report the implemented count. */
cmdSz = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_GetCapability);
PutU32BE(gCmd + cmdSz, TPM_CAP_TPM_PROPERTIES); cmdSz += 4;
PutU32BE(gCmd + cmdSz, TPM_PT_PCR_COUNT); cmdSz += 4;
PutU32BE(gCmd + cmdSz, 1); cmdSz += 4;
PutU32BE(gCmd + 2, (UINT32)cmdSz);
rspSize = 0;
AssertIntEQ(FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0),
TPM_RC_SUCCESS);
AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS);
AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE + 9), TPM_PT_PCR_COUNT);
AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE + 13), IMPLEMENTATION_PCR);
/* Highest implemented PCR: readable, and extendable from locality 0
* (PCR 0-16 and 23 accept any locality; a reduced-PCR build tops out
* inside that range). */
cmdSz = BuildPcrReadCmd(gCmd, (UINT32)(IMPLEMENTATION_PCR - 1));
rspSize = 0;
AssertIntEQ(FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0),
TPM_RC_SUCCESS);
AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS);
AssertIntEQ(SendPcrExtendLoc(&ctx, IMPLEMENTATION_PCR - 1, 0),
TPM_RC_SUCCESS);
/* First unavailable PCR: rejected, not wrapped onto a valid index. */
AssertIntEQ(SendPcrExtendLoc(&ctx, IMPLEMENTATION_PCR, 0), TPM_RC_VALUE);
AssertIntEQ(SendPcrResetLoc(&ctx, IMPLEMENTATION_PCR, 0), TPM_RC_VALUE);
FWTPM_Cleanup(&ctx);
fwtpm_pass("PCR bounds (highest implemented / first absent):", 0);
}
#if IMPLEMENTATION_PCR >= 24
static void test_fwtpm_pcr_reset(void)
{
FWTPM_CTX ctx;
@ -8788,6 +9134,7 @@ static void test_fwtpm_pcr_reset(void)
FWTPM_Cleanup(&ctx);
fwtpm_pass("PCR_Reset(16):", 0);
}
#endif /* IMPLEMENTATION_PCR >= 24 */
/* Per the TCG PC Client TPM Profile per-PCR reset locality map:
* PCR 16, 23 reset from localities 0-3 (not 4);
@ -8795,6 +9142,7 @@ static void test_fwtpm_pcr_reset(void)
* PCR 20-22 reset from localities 2-4;
* PCR 0-15 are never user-resettable.
* Verify both the reject (wrong locality) and allow (correct locality) paths. */
#if IMPLEMENTATION_PCR >= 24
static void test_fwtpm_pcr_reset_locality_enforced(void)
{
FWTPM_CTX ctx;
@ -8827,9 +9175,11 @@ static void test_fwtpm_pcr_reset_locality_enforced(void)
FWTPM_Cleanup(&ctx);
fwtpm_pass("PCR_Reset locality map enforced:", 0);
}
#endif /* IMPLEMENTATION_PCR >= 24 */
/* Per-PCR extend locality map: 0-16,23 any; 17,18 loc2-4; 19 loc2-3;
* 20 loc1-3; 21,22 loc2. Verify reject and allow paths. */
#if IMPLEMENTATION_PCR >= 24
static void test_fwtpm_pcr_extend_locality_enforced(void)
{
FWTPM_CTX ctx;
@ -8861,7 +9211,9 @@ static void test_fwtpm_pcr_extend_locality_enforced(void)
FWTPM_Cleanup(&ctx);
fwtpm_pass("PCR_Extend locality map enforced:", 0);
}
#endif /* IMPLEMENTATION_PCR >= 24 */
#if IMPLEMENTATION_PCR >= 24
/* TPM_CAP_PCR_PROPERTIES must report a well-formed TPML_TAGGED_PCR_PROPERTY
* whose RESET_Lx / EXTEND_Lx / DRTM_RESET bitmaps match the enforcement table. */
static int PcrSelHas(const byte* sel, int selSz, int pcr)
@ -8959,7 +9311,9 @@ static void test_fwtpm_pcr_properties_capability(void)
FWTPM_Cleanup(&ctx);
fwtpm_pass("PCR_PROPERTIES capability map:", 0);
}
#endif /* IMPLEMENTATION_PCR >= 24 */
#if IMPLEMENTATION_PCR >= 24
static void test_fwtpm_pcr_event(void)
{
FWTPM_CTX ctx;
@ -8984,6 +9338,7 @@ static void test_fwtpm_pcr_event(void)
FWTPM_Cleanup(&ctx);
fwtpm_pass("PCR_Event(16):", 0);
}
#endif /* IMPLEMENTATION_PCR >= 24 */
static void test_fwtpm_hierarchy_change_auth(void)
{
@ -9159,6 +9514,7 @@ static void test_fwtpm_sessions_trial_in_auth_slot_rejected(void)
/* A command with two @auth handles that supplies only one auth entry must be
* rejected with TPM_RC_AUTH_MISSING. */
#ifndef FWTPM_NO_HASH_CMDS
static void test_fwtpm_sessions_short_authcount_rejected(void)
{
FWTPM_CTX ctx;
@ -9182,6 +9538,7 @@ static void test_fwtpm_sessions_short_authcount_rejected(void)
FWTPM_Cleanup(&ctx);
fwtpm_pass("SESSIONS two-auth one-entry (AUTH_MISSING):", 0);
}
#endif /* !FWTPM_NO_HASH_CMDS */
/* A SESSIONS command that ends before its authorizationSize field is malformed
* and must be reported as TPM_RC_COMMAND_SIZE, not an auth error. */
@ -10148,6 +10505,7 @@ static void test_fwtpm_loadexternal_symcipher_bad_keysize_rejected(void)
/* Rewrap must re-encrypt under a storage parent. A TPM_RH_NULL newParent
* would serialize the unwrapped TPMT_SENSITIVE in the clear, so it must be
* rejected per Part 3 Sec.23.4.2. */
#ifndef FWTPM_NO_KEY_MIGRATION
static void test_fwtpm_rewrap_null_newparent_rejected(void)
{
FWTPM_CTX ctx;
@ -10176,11 +10534,13 @@ static void test_fwtpm_rewrap_null_newparent_rejected(void)
FWTPM_Cleanup(&ctx);
printf("Test fwTPM:\tRewrap(NULL newParent) rejected:\tPassed\n");
}
#endif /* !FWTPM_NO_KEY_MIGRATION */
/* ================================================================== */
/* Group D: Hash/HMAC Sequences */
/* ================================================================== */
#ifndef FWTPM_NO_HASH_CMDS
static void test_fwtpm_hash_sequence(void)
{
FWTPM_CTX ctx;
@ -10231,8 +10591,9 @@ static void test_fwtpm_hash_sequence(void)
FWTPM_Cleanup(&ctx);
fwtpm_pass("HashSequence (Start/Upd/Comp):", 0);
}
#endif /* !FWTPM_NO_HASH_CMDS */
#ifdef HAVE_ECC
#if defined(HAVE_ECC) && !defined(FWTPM_NO_ECDH)
static void test_fwtpm_ecc_parameters(void)
{
FWTPM_CTX ctx;
@ -10254,6 +10615,7 @@ static void test_fwtpm_ecc_parameters(void)
}
#endif
#ifndef FWTPM_NO_CONTEXT
static void test_fwtpm_context_save(void)
{
FWTPM_CTX ctx;
@ -10281,9 +10643,11 @@ static void test_fwtpm_context_save(void)
FWTPM_Cleanup(&ctx);
fwtpm_pass("ContextSave:", 0);
}
#endif /* !FWTPM_NO_CONTEXT */
/* A saved context must load at most once; replaying the same blob is
* rejected to prevent resurrecting a satisfied policy session. */
#ifndef FWTPM_NO_CONTEXT
static void test_fwtpm_context_load_replay_rejected(void)
{
FWTPM_CTX ctx;
@ -10356,9 +10720,11 @@ static void test_fwtpm_context_load_replay_rejected(void)
FWTPM_Cleanup(&ctx);
printf("Test fwTPM:\tContextLoad object reload + session replay:\tPassed\n");
}
#endif /* !FWTPM_NO_CONTEXT */
/* A hand-forged object context blob (unauthenticated plaintext format) must be
* rejected on load with TPM_RC_INTEGRITY. */
#ifndef FWTPM_NO_CONTEXT
static void test_fwtpm_contextload_forged_object_blob_rejected(void)
{
FWTPM_CTX ctx;
@ -10398,10 +10764,12 @@ static void test_fwtpm_contextload_forged_object_blob_rejected(void)
FWTPM_Cleanup(&ctx);
printf("Test fwTPM:\tContextLoad forged object blob rejected:\tPassed\n");
}
#endif /* !FWTPM_NO_CONTEXT */
/* A ContextLoad whose blobSz claims more bytes than the command carries must
* be rejected before any blob is parsed, so a short command cannot leave the
* wrapped-blob buffer partly uninitialized. */
#ifndef FWTPM_NO_CONTEXT
static void test_fwtpm_contextload_short_blob_rejected(void)
{
FWTPM_CTX ctx;
@ -10438,9 +10806,11 @@ static void test_fwtpm_contextload_short_blob_rejected(void)
FWTPM_Cleanup(&ctx);
printf("Test fwTPM:\tContextLoad short blob rejected:\tPassed\n");
}
#endif /* !FWTPM_NO_CONTEXT */
/* A context saved by the TPM must load back successfully and yield a usable
* handle. Guards that the object blob wrapping is self-consistent. */
#ifndef FWTPM_NO_CONTEXT
static void test_fwtpm_context_object_roundtrip(void)
{
FWTPM_CTX ctx;
@ -10485,9 +10855,11 @@ static void test_fwtpm_context_object_roundtrip(void)
FWTPM_Cleanup(&ctx);
printf("Test fwTPM:\tContextLoad object roundtrip:\tPassed\n");
}
#endif /* !FWTPM_NO_CONTEXT */
/* The context type is bound into the blob MAC, so a blob wrapped for one
* domain must not verify when unwrapped as the other. */
#ifndef FWTPM_NO_CONTEXT
static void test_fwtpm_context_blob_domain_separation(void)
{
FWTPM_CTX ctx;
@ -10515,9 +10887,11 @@ static void test_fwtpm_context_blob_domain_separation(void)
FWTPM_Cleanup(&ctx);
printf("Test fwTPM:\tContext blob domain separation:\tPassed\n");
}
#endif /* !FWTPM_NO_CONTEXT */
/* When the command client changes, transient objects must be flushed so a
* replacement client cannot enumerate and use the previous client's handles. */
#ifndef FWTPM_NO_CONTEXT
static void test_fwtpm_reset_command_client_flushes_transient(void)
{
FWTPM_CTX ctx;
@ -10551,6 +10925,7 @@ static void test_fwtpm_reset_command_client_flushes_transient(void)
FWTPM_Cleanup(&ctx);
printf("Test fwTPM:\tResetCommandClient flushes transient:\tPassed\n");
}
#endif /* !FWTPM_NO_CONTEXT */
static void test_fwtpm_evict_control(void)
{
@ -10746,6 +11121,7 @@ static void test_fwtpm_evict_control_persistent_object_rejected(void)
fwtpm_pass("EvictControl persistent object reject (HANDLE):", 0);
}
#ifndef FWTPM_NO_CLOCK
static void test_fwtpm_clock_set(void)
{
FWTPM_CTX ctx;
@ -10784,6 +11160,7 @@ static void test_fwtpm_clock_set(void)
FWTPM_Cleanup(&ctx);
fwtpm_pass("ClockSet/ClockRateAdjust:", 0);
}
#endif /* !FWTPM_NO_CLOCK */
/* ================================================================== */
/* HAL registration tests */
@ -11404,22 +11781,33 @@ int fwtpm_unit_tests(int argc, char *argv[])
test_fwtpm_getcap_properties();
test_fwtpm_getcap_pcrs();
test_fwtpm_getcap_paging();
test_fwtpm_total_commands();
/* Command-group gates (FWTPM_NO_* macros) */
test_fwtpm_command_gates();
/* PCR operations */
test_fwtpm_pcr_read();
test_fwtpm_pcr_bounds();
test_fwtpm_pcr_extend_and_read();
test_fwtpm_pw_session_continue_set();
#if IMPLEMENTATION_PCR >= 24
test_fwtpm_pcr_event_drtm_locality_enforced();
#endif /* IMPLEMENTATION_PCR >= 24 */
test_fwtpm_pcr_extend_empty_pw_rejected_after_setauth();
#if IMPLEMENTATION_PCR >= 24
test_fwtpm_pcr_reset();
test_fwtpm_pcr_reset_locality_enforced();
test_fwtpm_pcr_extend_locality_enforced();
test_fwtpm_pcr_properties_capability();
test_fwtpm_pcr_event();
#endif /* IMPLEMENTATION_PCR >= 24 */
/* Clock */
#ifndef FWTPM_NO_CLOCK
test_fwtpm_readclock();
test_fwtpm_clock_set();
#endif /* !FWTPM_NO_CLOCK */
/* HAL registration (clock + NV). The NV HAL test uses a mock
* backend and must not leave the default file in a half-written
@ -11484,7 +11872,9 @@ int fwtpm_unit_tests(int argc, char *argv[])
test_fwtpm_sequenceupdate_neg();
test_fwtpm_signdigest_restricted_null_ticket_returns_ticket();
test_fwtpm_signdigest_x509sign_returns_attributes();
#ifndef FWTPM_NO_HASH_CMDS
test_fwtpm_signdigest_restricted_valid_ticket_succeeds();
#endif /* !FWTPM_NO_HASH_CMDS */
test_fwtpm_verifydigest_sig_hashalg_mismatch_returns_scheme();
test_fwtpm_create_primary_mldsa_extmu_returns_ext_mu();
test_fwtpm_testparms_mldsa_extmu_returns_ext_mu();
@ -11510,9 +11900,13 @@ int fwtpm_unit_tests(int argc, char *argv[])
test_fwtpm_verifyseqcomplete_hash_mldsa_ticket_tag_digest();
test_fwtpm_verifyseqcomplete_hash_mldsa_ticket_binds_message();
test_fwtpm_signseqcomplete_hash_mldsa_genvalue_via_update_returns_value();
#ifdef WOLFTPM_MLDSA
test_fwtpm_signseqcomplete_wrong_key_frees_slot();
#endif /* WOLFTPM_MLDSA */
test_fwtpm_pqc_nv_persistence();
#ifdef WOLFTPM_MLDSA
test_fwtpm_signseq_slot_exhaustion();
#endif /* WOLFTPM_MLDSA */
test_fwtpm_signseq_longmsg_boundary();
test_fwtpm_mldsa87_maxbuf();
test_fwtpm_response_buffer_capacity();
@ -11521,11 +11915,14 @@ int fwtpm_unit_tests(int argc, char *argv[])
#endif
test_fwtpm_read_public();
test_fwtpm_loadexternal_symcipher_bad_keysize_rejected();
#ifndef FWTPM_NO_KEY_MIGRATION
test_fwtpm_rewrap_null_newparent_rejected();
#endif /* !FWTPM_NO_KEY_MIGRATION */
test_fwtpm_evict_control();
test_fwtpm_evict_control_cross_hierarchy_rejected();
test_fwtpm_evict_control_bad_persistent_handle_rejected();
test_fwtpm_evict_control_persistent_object_rejected();
#ifndef FWTPM_NO_CONTEXT
test_fwtpm_context_save();
test_fwtpm_context_load_replay_rejected();
test_fwtpm_contextload_forged_object_blob_rejected();
@ -11533,11 +11930,14 @@ int fwtpm_unit_tests(int argc, char *argv[])
test_fwtpm_context_object_roundtrip();
test_fwtpm_context_blob_domain_separation();
test_fwtpm_reset_command_client_flushes_transient();
#endif /* !FWTPM_NO_CONTEXT */
/* Crypto */
#ifndef FWTPM_NO_HASH_CMDS
test_fwtpm_hash();
test_fwtpm_hash_sequence();
#ifdef HAVE_ECC
#endif /* !FWTPM_NO_HASH_CMDS */
#if defined(HAVE_ECC) && !defined(FWTPM_NO_ECDH)
test_fwtpm_ecc_parameters();
#endif
@ -11610,9 +12010,11 @@ int fwtpm_unit_tests(int argc, char *argv[])
test_fwtpm_quote_ecdaa_scheme();
test_fwtpm_sign_ecdaa_scheme();
test_fwtpm_certify_creation_ecdaa_scheme();
#ifndef FWTPM_NO_ECDH
test_fwtpm_ecdh_keygen_signkey_returns_attributes();
test_fwtpm_ecdh_zgen_signkey_returns_attributes();
test_fwtpm_zgen_2phase_signkey_returns_attributes();
#endif /* !FWTPM_NO_ECDH */
test_fwtpm_quote_decrypt_key_returns_key();
test_fwtpm_quote_unrestricted_sign_returns_attributes();
test_fwtpm_sign_scheme_downgrade_rejected();
@ -11634,7 +12036,9 @@ int fwtpm_unit_tests(int argc, char *argv[])
test_fwtpm_clear_no_sessions_returns_auth_missing();
test_fwtpm_sessions_empty_autharea_rejected();
test_fwtpm_sessions_trial_in_auth_slot_rejected();
#ifndef FWTPM_NO_HASH_CMDS
test_fwtpm_sessions_short_authcount_rejected();
#endif /* !FWTPM_NO_HASH_CMDS */
test_fwtpm_sessions_missing_authsize_command_size();
test_fwtpm_clear();

View File

@ -440,7 +440,10 @@ static void test_TPM2_PCRSel(void)
{
int rc = 0;
TPML_PCR_SELECTION pcr;
byte pcrArray[PCR_SELECT_MAX];
/* This array holds PCR indexes, not a select bitmap, so it is sized by the
* number of indexes the test uses. PCR_SELECT_MAX is a byte count and is
* only 1 in a reduced-PCR build (IMPLEMENTATION_PCR <= 8). */
byte pcrArray[3];
word32 pcrArraySz;
XMEMSET(&pcr, 0, sizeof(pcr));

View File

@ -110,6 +110,40 @@
#define FWTPM_MAX_RANDOM_BYTES 48
#endif
/* Command feature-group toggles - opt-in macros that compile out fwTPM command
* groups a minimal build does not need. All default OFF (the full command set is
* built). Removing a group drops its handlers AND its TPM2_GetCapability
* advertisement (the command list is derived from the dispatch table).
* FWTPM_NO_POLICY - policy session commands (TPM2_Policy*)
* FWTPM_NO_ATTESTATION - Quote/Certify/CertifyCreation/GetTime/NV_Certify
* FWTPM_NO_CREDENTIAL - MakeCredential/ActivateCredential
* FWTPM_NO_DA - dictionary-attack lockout protection (see below)
* FWTPM_NO_PARAM_ENC - command/response parameter encryption
* FWTPM_NO_NV - all NV_* commands
* FWTPM_NO_KEY_MIGRATION - Import/Duplicate/Rewrap
* FWTPM_NO_ECDH - ECDH_KeyGen/ECDH_ZGen/EC_Ephemeral/ZGen_2Phase/
* ECC_Parameters (ECDSA sign/verify are retained)
* FWTPM_NO_HASH_CMDS - Hash, HMAC, and the hash/HMAC sequence commands
* FWTPM_NO_CONTEXT - ContextSave/ContextLoad (FlushContext retained)
* FWTPM_NO_SYM_ENCRYPT - EncryptDecrypt/EncryptDecrypt2
* FWTPM_NO_CLOCK - ReadClock/ClockSet/ClockRateAdjust
*
* These flags are independent; select exactly the command groups your fTPM does
* not need. There is intentionally no single "minimal" umbrella macro - dropping
* a command group removes real TPM functionality, so each choice must be made
* deliberately. For a worked example that picks a set for a constrained target,
* see the MicroBlaze V build in the wolftpm-examples repository.
*
* Gates that own per-instance state also drop it from FWTPM_CTX below, so the
* RAM saving is real and not just code size:
* FWTPM_NO_HASH_CMDS - hashSeq[FWTPM_MAX_HASH_SEQ]. The FWTPM_MAX_HASH_SEQ
* macro itself stays defined: MLDSA sequence handles are
* numbered above the hash-sequence range, so removing it
* would renumber them between gated and ungated builds.
* FWTPM_NO_CONTEXT - ctxProtectKey/ctxProtectKeyValid, contextSeqCounter,
* contextLive[]/contextLiveCount
* FWTPM_NO_ECDH - ecEphemeralCounter/Key/KeySz/Curve */
/* Dictionary Attack (DA) feature toggles:
* FWTPM_NO_DA - compile out all DA lockout protection.
* FWTPM_DA_USED_RETRY - emulate real-TPM behavior where the first use of a
@ -486,7 +520,10 @@ typedef struct FWTPM_HashSeq {
#endif
} FWTPM_HashSeq;
#ifdef WOLFTPM_V185
/* Guarded on WOLFTPM_MLDSA, not WOLFTPM_V185: a lean WOLFTPM_PQC build defines
* WOLFTPM_MLDSA on its own, and every consumer of these slots tests
* WOLFTPM_MLDSA. */
#ifdef WOLFTPM_MLDSA
/* ML-DSA sign/verify sequence slot (v1.85 Part 3 Sec.17.5, Sec.17.6). Pure ML-DSA
* is one-shot the message arrives via the `buffer` parameter of
* TPM2_SignSequenceComplete and TPM2_SequenceUpdate is rejected with
@ -531,7 +568,7 @@ typedef struct FWTPM_SignSeq {
#ifndef FWTPM_MAX_SIGN_SEQ
#define FWTPM_MAX_SIGN_SEQ 4
#endif
#endif /* WOLFTPM_V185 */
#endif /* WOLFTPM_MLDSA */
/* Auth session slot */
typedef struct FWTPM_Session {
@ -708,8 +745,10 @@ typedef struct FWTPM_CTX {
FWTPM_NvIndex nvIndices[FWTPM_MAX_NV_INDICES];
/* Hash sequence slots */
#ifndef FWTPM_NO_HASH_CMDS
FWTPM_HashSeq hashSeq[FWTPM_MAX_HASH_SEQ];
#ifdef WOLFTPM_V185
#endif
#ifdef WOLFTPM_MLDSA
FWTPM_SignSeq signSeq[FWTPM_MAX_SIGN_SEQ];
#endif
@ -738,11 +777,13 @@ typedef struct FWTPM_CTX {
TPM2B_DIGEST lockoutPolicy;
TPMI_ALG_HASH lockoutPolicyAlg;
#ifndef FWTPM_NO_CONTEXT
/* Per-boot context protection key (volatile only, never persisted).
* Used by ContextSave/ContextLoad for HMAC + AES-CFB protection of
* session context blobs per TPM 2.0 Part 1 Sec.30. */
byte ctxProtectKey[AES_256_KEY_SIZE];
int ctxProtectKeyValid;
#endif
/* TIS transport state (when not using sockets) */
#ifdef WOLFTPM_FWTPM_TIS
@ -769,18 +810,21 @@ typedef struct FWTPM_CTX {
word32 nvGranuleFill; /* bytes buffered (0..writeAlign) */
#endif
#ifndef FWTPM_NO_CONTEXT
/* ContextSave sequence counter (monotonic, reset on init) */
UINT64 contextSeqCounter;
/* Live (saved-but-not-yet-loaded) context sequences. A context loads at
* most once and saved contexts may load in any order. */
UINT64 contextLive[FWTPM_MAX_OBJECTS + FWTPM_MAX_SESSIONS];
int contextLiveCount;
#endif
/* Set once TPM2_SelfTest has completed successfully */
int selfTestRun;
#ifdef HAVE_ECC
/* EC_Ephemeral commit counter and key storage (volatile) */
#if defined(HAVE_ECC) && !defined(FWTPM_NO_ECDH)
/* EC_Ephemeral commit counter and key storage (volatile). Only the ECDH
* command group (EC_Ephemeral / ZGen_2Phase) uses this state. */
UINT16 ecEphemeralCounter;
byte ecEphemeralKey[FWTPM_MAX_PRIVKEY_DER];
int ecEphemeralKeySz;

View File

@ -255,6 +255,7 @@ int FwUnwrapPrivate(FWTPM_Object* parent,
/* --- Context blob wrap/unwrap (ContextSave/Load) --- */
#ifndef FWTPM_NO_CONTEXT
/* Domain-separation tag bound into the blob MAC so an object blob cannot be
* verified through the session path or vice versa. */
#define FWTPM_CTX_TYPE_SESSION 0x01
@ -266,6 +267,7 @@ int FwWrapContextBlob(FWTPM_CTX* ctx, UINT64 seq, byte ctxType,
int FwUnwrapContextBlob(FWTPM_CTX* ctx, UINT64 seq, byte ctxType,
const byte* in, int inSz,
byte* out, int outBufSz, int* outSz);
#endif /* !FWTPM_NO_CONTEXT */
/* --- Seed encrypt/decrypt --- */