diff --git a/.github/workflows/fwtpm-test.yml b/.github/workflows/fwtpm-test.yml index c05ce5ef..b02e1af6 100644 --- a/.github/workflows/fwtpm-test.yml +++ b/.github/workflows/fwtpm-test.yml @@ -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 diff --git a/README.md b/README.md index 5ec2dca9..3dc9af9f 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/docs/FWTPM.md b/docs/FWTPM.md index 46696371..a63b0620 100644 --- a/docs/FWTPM.md +++ b/docs/FWTPM.md @@ -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) diff --git a/src/fwtpm/README.md b/src/fwtpm/README.md index 75840250..cfbb39ef 100644 --- a/src/fwtpm/README.md +++ b/src/fwtpm/README.md @@ -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 diff --git a/src/fwtpm/fwtpm.c b/src/fwtpm/fwtpm.c index 285f3454..6653ddd2 100644 --- a/src/fwtpm/fwtpm.c +++ b/src/fwtpm/fwtpm.c @@ -32,6 +32,7 @@ #endif #include +#include #include #include @@ -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(); diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index ca480b31..1096921b 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -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 diff --git a/src/fwtpm/fwtpm_crypto.c b/src/fwtpm/fwtpm_crypto.c index 6df367fb..16cd2655 100644 --- a/src/fwtpm/fwtpm_crypto.c +++ b/src/fwtpm/fwtpm_crypto.c @@ -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 */ diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index 1f2cd270..eec948d7 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -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. */ @@ -8775,6 +9056,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 +9114,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 +9122,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 +9155,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 +9191,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 +9291,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 +9318,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 +9494,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 +9518,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 +10485,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 +10514,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 +10571,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 +10595,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 +10623,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 +10700,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 +10744,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 +10786,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 +10835,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 +10867,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 +10905,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 +11101,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 +11140,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 +11761,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 +11852,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 +11880,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 +11895,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 +11910,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 +11990,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 +12016,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(); diff --git a/wolftpm/fwtpm/fwtpm.h b/wolftpm/fwtpm/fwtpm.h index f2d99c35..b5680113 100644 --- a/wolftpm/fwtpm/fwtpm.h +++ b/wolftpm/fwtpm/fwtpm.h @@ -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; diff --git a/wolftpm/fwtpm/fwtpm_crypto.h b/wolftpm/fwtpm/fwtpm_crypto.h index a3b0e422..84758042 100644 --- a/wolftpm/fwtpm/fwtpm_crypto.h +++ b/wolftpm/fwtpm/fwtpm_crypto.h @@ -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 --- */