From 2b8e41caf14522d0b465f629645720b57f0b9314 Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 13 Aug 2026 10:41:24 -0700 Subject: [PATCH] Add caller-supplied policy authorization for TPM firmware upgrade Lets a deployment gate the TPM firmware-update start command behind its own platform hierarchy policy instead of the vendor default. Previously wolfTPM always managed that authorization internally: on Infineon it installed and satisfied a PolicyCommandCode(TPM_CC_FieldUpgradeStartVendor) policy on the platform primary policy, and on ST33 it used password authorization with an empty platform password. Both assume default platform auth, which a hardened deployment will not have. New _ex entry points take an already-satisfied session: wolfTPM2_FirmwareUpgradeHash_ex() wolfTPM2_FirmwareUpgrade_ex() wolfTPM2_FirmwareUpgradeRecover_ex() Passing NULL for startSession keeps the existing library-managed behavior, so the original functions are unchanged wrappers and existing code is unaffected. With a session supplied, Infineon no longer overwrites the platform primary policy (the caller provisions it) and ST33 uses the session in place of TPM_RS_PW. Supporting wrappers: wolfTPM2_PolicyOR() satisfy a session with a compound OR wolfTPM2_PolicyCommandCodeMake() offline PolicyCommandCode digest wolfTPM2_IsAlgSupported() report whether the TPM implements an alg PolicyOR is hash-agnostic (each branch carries its own size), so SHA2-256 through SHA2-512 policy branches all work. It requires at least two branches per TPM 2.0 Part 3 Sec.23.6 and validates each branch size against the digest buffer. PolicyCommandCodeMake takes digestSz as in/out - input is the output buffer capacity and BUFFER_E is returned when it is too small, checked before the buffer is written. IsAlgSupported reports through an out-parameter and returns TPM_RC, so a capability-query failure cannot be misread as "supported"; it sets the out-parameter to 0 on every error path. Session contract: the vendor FieldUpgradeStart commands are hand-marshalled with an authorization area carrying only the session handle - empty nonceCaller, zero attributes, empty HMAC. That is correct only for an unsalted, unbound policy session with no auth value, so the _ex path validates the caller session up front and rejects anything needing a computed session HMAC or parameter encryption (PolicyAuthValue/PolicyPassword, attached auth value, bind, salt, encrypt/decrypt/audit attributes, or a non-policy handle). Validation runs before any TPM traffic. On a successful start the TPM consumes the session and the library sets handle.hndl to TPM_RH_NULL; this is now documented, including that it is not zero and that wolfTPM2_UnloadHandle no-ops on it. Examples: ifx_fw_update and st33_fw_update gain --policy, --policyor and --policytest, backed by a shared examples/firmware/firmware_policy.c. The --policytest mode is a non-destructive self-test that checks the TPM's running policy digest against an offline computation at SHA2-256/384/512, skipping any hash the TPM does not implement or the local wolfCrypt build was not compiled with (a build mismatch is a skip, not a failure). The provisioned PolicyOR carries a PolicyCommandCode(TPM_CC_SetPrimaryPolicy) branch so the policy can authorize its own removal, and cleanup is gated on a FirmwarePolicyCtx that records what was actually provisioned, so an early failure cannot clear a policy the deployment installed itself. A failed rollback is reported but never overwrites the upgrade error that explains the run, and is skipped once the start has succeeded (the TPM has reset into upgrade mode and will not service SetPrimaryPolicy). Because TPM 2.0 offers no way to read a hierarchy authPolicy back, the example cannot detect or restore one it replaces, so provisioning prints an explicit warning naming that. The policy modes are also refused where the session could never be used: any Infineon operational mode other than normal (recovery and finalize skip the start entirely), and on ST33 when the TPM is already in firmware-upgrade mode - previously the flags were silently ignored there, which is the authorization downgrade they exist to prevent. Rollback normally uses platform password authorization, which an installed authPolicy does not disable (TPM 2.0 Part 1 Sec.19.7); this was confirmed on ST33KTPM2X, SLB9670 and SLB9673 parts. The firmware examples now require wolfCrypt in their feature guards. They drive wolfTPM2_FirmwareUpgrade_ex, which hashes the manifest with SHA-384 and only exists with wolfCrypt, so --enable-firmware with --disable-wolfcrypt previously failed to compile. Also converts examples/nvram/extend.c to the new PolicyCommandCodeMake capacity contract. Tests: argument validation and known-answer vectors for the new wrappers, short-buffer and canary coverage for PolicyCommandCodeMake, caller-session rejection for every unsupported session shape plus an accepted session, simulator-backed success paths for PolicyOR and IsAlgSupported, and a regression test that a platform authPolicy remains clearable with password authorization while a non-matching policy session is refused. --- CMakeLists.txt | 17 +- examples/firmware/README.md | 94 +++- examples/firmware/firmware_policy.c | 456 +++++++++++++++++++ examples/firmware/firmware_policy.h | 99 +++++ examples/firmware/ifx_fw_update.c | 183 +++++++- examples/firmware/include.am | 12 +- examples/firmware/st33_fw_update.c | 166 ++++++- examples/nvram/extend.c | 21 +- src/tpm2_wrap.c | 390 +++++++++++++--- tests/unit_tests.c | 659 ++++++++++++++++++++++++++++ wolftpm/tpm2_wrap.h | 222 ++++++++++ 11 files changed, 2206 insertions(+), 113 deletions(-) create mode 100644 examples/firmware/firmware_policy.c create mode 100644 examples/firmware/firmware_policy.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a3469ad17..3f014bf17 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -480,8 +480,13 @@ if (WOLFTPM_EXAMPLES AND BUILD_WOLFTPM_LIB) endif() function(add_tpm_example name src) + # Optional additional sources may be passed after 'src' (ARGN) + set(_example_srcs examples/${src}) + foreach(_extra ${ARGN}) + list(APPEND _example_srcs examples/${_extra}) + endforeach() add_executable(${name} - examples/${src} + ${_example_srcs} ) target_link_libraries(${name} PRIVATE wolftpm tpm_test_lib wolftpm_wolfssl_dep) if(WIN32) @@ -705,7 +710,15 @@ if (WOLFTPM_EXAMPLES AND BUILD_WOLFTPM_LIB) add_tpm_example(secure_rot boot/secure_rot.c) add_tpm_example(csr csr/csr.c) add_tpm_example(get_ek_certs endorsement/get_ek_certs.c) - add_tpm_example(ifx_fw_update firmware/ifx_fw_update.c) + # firmware_policy.c is entirely inside a WOLFTPM_FIRMWARE_UPGRADE guard, so + # compiling it with firmware upgrade disabled yields an empty translation + # unit (which ISO C forbids and -Wpedantic rejects). + if(WOLFTPM_FIRMWARE) + add_tpm_example(ifx_fw_update firmware/ifx_fw_update.c + firmware/firmware_policy.c) + else() + add_tpm_example(ifx_fw_update firmware/ifx_fw_update.c) + endif() add_tpm_example(gpio_config gpio/gpio_config.c) add_tpm_example(gpio_read gpio/gpio_read.c) add_tpm_example(gpio_set gpio/gpio_set.c) diff --git a/examples/firmware/README.md b/examples/firmware/README.md index 66d0c60d6..2c861caed 100644 --- a/examples/firmware/README.md +++ b/examples/firmware/README.md @@ -54,7 +54,13 @@ The TPM has a vendor capability for getting the key group id. This is populated Infineon Firmware Update Usage: ./ifx_fw_update (get info) ./ifx_fw_update --abandon (cancel) - ./ifx_fw_update + ./ifx_fw_update --policytest (safe policy auth self-test) + ./ifx_fw_update [policy opts] + ./ifx_fw_update (default auth) +Policy options (caller-supplied authorization): + --policy provision+satisfy a PolicyCommandCode + --policyor provision+satisfy a PolicyOR (multi-branch) + --sha256|--sha384|--sha512 policy hash (default SHA-256) # Run without arguments to display the current firmware information including key group id and operational mode ./ifx_fw_update @@ -131,7 +137,13 @@ The `st33_fw_update` tool automatically detects the firmware format. ST33 Firmware Update Usage: ./st33_fw_update (get info) ./st33_fw_update --abandon (cancel) - ./st33_fw_update + ./st33_fw_update --policytest (safe policy auth self-test) + ./st33_fw_update [policy opts] + ./st33_fw_update (default password auth) +Policy options (caller-supplied authorization): + --policy provision+satisfy a PolicyCommandCode + --policyor provision+satisfy a PolicyOR (multi-branch) + --sha256|--sha384|--sha512 policy hash (default SHA-256) Firmware format is auto-detected from TPM firmware version: - Firmware < 512: Non-LMS format (177 byte manifest) @@ -199,3 +211,81 @@ Success: Please reset or power cycle TPM ``` **Note**: Firmware files cannot be made public and must be obtained separately from STMicroelectronics. + +## Policy-Based Authorization (Advanced) + +By default wolfTPM manages the platform-hierarchy authorization for the firmware-update *start* command internally: on Infineon it installs and satisfies a `PolicyCommandCode(TPM_CC_FieldUpgradeStartVendor)` policy on the platform primary policy, and on ST33 it uses password authorization (`TPM_RS_PW`) with an empty platform password. This assumes the platform hierarchy has default/empty authorization. + +Deployments that gate firmware upgrade behind their own platform policy (for example a signed-policy check, a PCR state, or a multi-branch `PolicyOR`) can supply an already-satisfied authorization session using `wolfTPM2_FirmwareUpgradeHash_ex()`. When a session is supplied: + +- **Infineon**: the library does **not** overwrite your platform primary policy. You provision the platform `authPolicy` yourself (via `TPM2_SetPrimaryPolicy` with `authHandle = TPM_RH_PLATFORM`, using SHA2-256 or SHA2-512) and pass a session that satisfies it. Note this applies to the *library*: the `--policy`/`--policyor` example modes are themselves such a caller, and their helper (`examples/firmware/firmware_policy.c`) does overwrite the platform `authPolicy` with a digest it generates. Do not run those modes on a system whose platform hierarchy already carries a policy you need. +- **ST33**: the supplied session replaces the default `TPM_RS_PW` password authorization. + +**Supported session contract**: the vendor `FieldUpgradeStart` command is sent with an authorization area carrying only the session handle - empty `nonceCaller`, zero session attributes and an empty HMAC. The supplied session must therefore be an unsalted, unbound `TPM_SE_POLICY` session with no auth value and no parameter encryption. Policies satisfied with `wolfTPM2_PolicyAuthValue()` or `wolfTPM2_PolicyPassword()` are **not** supported, because the session HMAC they require is not serialized on this path; such a session is rejected with `BAD_FUNC_ARG` before anything is sent to the TPM. `PolicyPCR`, `PolicySigned`, `PolicySecret`, `PolicyAuthorize`, `PolicyCommandCode` and `PolicyOR` branches are all fine. + +Both SHA2-256 (non-PQC) and SHA2-512 (PQC) policy digests are supported, because the session hash is chosen with `wolfTPM2_StartSession_ex(..., authHash)` and `wolfTPM2_PolicyOR()` carries per-branch digest sizes. + +Example: satisfy a multi-branch `PolicyOR` (up to 8 branches, SHA2-512 shown) and start the upgrade under it: + +```c +WOLFTPM2_SESSION session; +TPML_DIGEST orList; +uint8_t manifest_hash[TPM_SHA512_DIGEST_SIZE]; +int rc; + +/* zero both structs - orList must not carry uninitialized branch sizes */ +XMEMSET(&session, 0, sizeof(session)); +XMEMSET(&orList, 0, sizeof(orList)); + +/* start a policy session using the desired policy hash (SHA2-512 for PQC) */ +rc = wolfTPM2_StartSession_ex(&dev, &session, NULL, NULL, + TPM_SE_POLICY, TPM_ALG_NULL, TPM_ALG_SHA512); +if (rc != TPM_RC_SUCCESS) goto cleanup; + +/* Satisfy one branch (PCR, PolicySigned, PolicyAuthorize, PolicyCommandCode, + * ...), then OR against the full branch list the platform authPolicy encodes. + * Set count and each digests[i].size/buffer for every branch you populate. + * PolicyOR requires at least 2 branches. */ +orList.count = 2; +/* orList.digests[0].size = ...; XMEMCPY(orList.digests[0].buffer, ...); */ +/* orList.digests[1].size = ...; XMEMCPY(orList.digests[1].buffer, ...); */ +rc = wolfTPM2_PolicyOR(&dev, &session, &orList); +if (rc != TPM_RC_SUCCESS) goto cleanup; + +/* hash the manifest with the matching algorithm, then start the upgrade under + * the caller-satisfied session (NULL would use the library-default auth) */ +rc = wc_Sha512Hash(manifest, manifest_sz, manifest_hash); +if (rc != 0) goto cleanup; +rc = wolfTPM2_FirmwareUpgradeHash_ex(&dev, TPM_ALG_SHA512, + manifest_hash, (uint32_t)sizeof(manifest_hash), + manifest, manifest_sz, fwDataCb, fwCbCtx, &session); + +cleanup: +/* On a successful FieldUpgradeStart the TPM consumes the session and the + * library sets session.handle.hndl to TPM_RH_NULL (0x40000007) - it is NOT + * zeroed, so do not test for == 0 to detect consumption. Calling + * wolfTPM2_UnloadHandle is always safe: it is a no-op on TPM_RH_NULL, so this + * only releases a session that is still loaded. */ +if (session.handle.hndl != 0) + wolfTPM2_UnloadHandle(&dev, &session.handle); +``` + +Passing `NULL` for the final `startSession` argument makes `wolfTPM2_FirmwareUpgradeHash_ex()` behave exactly like `wolfTPM2_FirmwareUpgradeHash()` (library-managed authorization), so existing code is unaffected. + +### Destructive: provisioning replaces any existing platform policy + +`--policy`/`--policyor` call `TPM2_SetPrimaryPolicy` on the platform hierarchy with a digest the example generates. TPM 2.0 provides **no way to read a hierarchy's `authPolicy` back** - there is no read command, and `TPMA_PERMANENT` reports only `authValue` state - so the example cannot detect an existing policy, cannot preserve it, and cannot restore it. Cleanup **removes** the policy rather than restoring whatever was there before. + +If your platform hierarchy is gated by a policy you need to keep, do not run these modes. The example prints this warning at provisioning time. `--policytest` is unaffected: it is non-destructive and never calls `TPM2_SetPrimaryPolicy`. + +The modes also require the normal operational mode. In recovery and finalize modes the library skips `FieldUpgradeStart` entirely, so a caller-supplied session would never be used; the example refuses rather than installing a policy nothing will exercise. On ST33, if the TPM is already in firmware-upgrade mode the policy flags are likewise rejected, since the start command has already run. + +### Rollback of the example-provisioned policy + +The example `--policy`/`--policyor` modes provision the platform hierarchy `authPolicy` via `TPM2_SetPrimaryPolicy` before the upgrade. On failure the example clears it again so a later default-auth run is not locked out; on success the required TPM reset clears it. + +- Rollback normally uses platform **password** authorization. Per TPM 2.0 Part 1 Sec.19.7 a hierarchy is authorized by *either* its `authValue` *or* its `authPolicy`, so installing an `authPolicy` does not disable the password path. With the default empty `platformAuth` the clear always succeeds. +- `--policyor` additionally provisions a `PolicyCommandCode(TPM_CC_SetPrimaryPolicy)` branch alongside the firmware-start branch, so the policy can authorize its own removal. If the password path fails (a deployment that set a non-default `platformAuth`), the example retries the clear under that branch. +- `--policy` provisions a single `PolicyCommandCode(FieldUpgradeStart)` branch and therefore has no policy-based rollback path. It relies entirely on `platformAuth` still being usable. +- Rollback is attempted only when the example actually installed the policy, so an early failure (a missing firmware file, for example) never clears a policy the deployment provisioned itself. +- A failed rollback is reported explicitly and becomes the exit status. If a run is interrupted before cleanup, or the clear fails, the platform hierarchy still requires the policy until the TPM is reset/power-cycled. diff --git a/examples/firmware/firmware_policy.c b/examples/firmware/firmware_policy.c new file mode 100644 index 000000000..ad1f9c4db --- /dev/null +++ b/examples/firmware/firmware_policy.c @@ -0,0 +1,456 @@ +/* firmware_policy.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +/* These helpers are built on the wolfTPM2 wrapper API and use wolfCrypt hashing */ +#if defined(WOLFTPM_FIRMWARE_UPGRADE) && !defined(WOLFTPM2_NO_WRAPPER) && \ + !defined(WOLFTPM2_NO_WOLFCRYPT) + +#include + +/* Print a digest as hex. Unlike TPM2_PrintBin (a no-op unless DEBUG_WOLFTPM), + * this is always available so the self-test failure report is usable in a + * stock build. */ +static void firmware_print_hex(const byte* buf, word32 len) +{ + word32 j; + for (j = 0; j < len; j++) { + printf("%02x", buf[j]); + } + printf("\n"); +} + +/* Report whether the LOCAL wolfCrypt build implements this hash. + * wolfTPM2_IsAlgSupported only reports what the TPM implements, and + * TPM2_GetHashDigestSize is a static table lookup that answers 64 for SHA2-512 + * regardless of build flags. Without this check a TPM that implements a hash + * wolfCrypt was not built with would take the offline digest path and fail in + * wc_HashInit, turning a build-configuration mismatch into a self-test + * failure. wc_HashGetDigestSize returns negative for an unavailable hash. */ +static int firmware_hash_local_supported(TPMI_ALG_HASH hashAlg) +{ + enum wc_HashType hashType; + int rc; + + rc = TPM2_GetHashType(hashAlg); + if (rc <= 0) { + return 0; + } + hashType = (enum wc_HashType)rc; + return (wc_HashGetDigestSize(hashType) > 0) ? 1 : 0; +} + +/* Exercise wolfTPM2_PolicyOR at the requested hash and verify the TPM's + * running policy digest matches an offline computation. Non-destructive. + * The skip decision is reported through *skipped rather than a magic return + * value, so the return code namespace holds only real errors and cannot + * collide with a TPM rc. Returns 0 on match (or when skipped), TPM_RC_POLICY + * on digest mismatch, or a TPM rc / BAD_FUNC_ARG on other errors. */ +static int firmware_policy_selftest(WOLFTPM2_DEV* dev, TPMI_ALG_HASH hashAlg, + const char* name, int* skipped) +{ + int rc; + int isSupported = 0; + WOLFTPM2_SESSION sess; + TPML_DIGEST orList; + word32 hsz = (word32)TPM2_GetHashDigestSize(hashAlg); + byte branchA[TPM_MAX_DIGEST_SIZE]; + byte branchB[TPM_MAX_DIGEST_SIZE]; + byte concat[2 * TPM_MAX_DIGEST_SIZE]; + byte expected[TPM_MAX_DIGEST_SIZE]; + byte got[TPM_MAX_DIGEST_SIZE]; + word32 aSz = 0, bSz = 0, expSz = 0, gotSz = 0; + + XMEMSET(&sess, 0, sizeof(sess)); + XMEMSET(&orList, 0, sizeof(orList)); + + if (skipped == NULL) { + return BAD_FUNC_ARG; + } + *skipped = 0; + if (hsz == 0 || hsz > TPM_MAX_DIGEST_SIZE) { + return BAD_FUNC_ARG; + } + + /* Skip cleanly if the local wolfCrypt build cannot hash with this + * algorithm - the offline digest below needs it, and a build mismatch is + * not a self-test failure. */ + if (!firmware_hash_local_supported(hashAlg)) { + printf(" %s: skipped (not built into this wolfCrypt)\n", name); + *skipped = 1; + return 0; + } + + /* Skip cleanly if the TPM does not implement this hash. A query failure is + * reported and returned, distinct from "not implemented". */ + rc = wolfTPM2_IsAlgSupported(dev, hashAlg, &isSupported); + if (rc != TPM_RC_SUCCESS) { + printf(" %s: capability query failed 0x%x: %s\n", + name, rc, TPM2_GetRCString(rc)); + return rc; + } + if (!isSupported) { + printf(" %s: skipped (not implemented by this TPM)\n", name); + *skipped = 1; + return 0; + } + + /* Offline: two distinct PolicyCommandCode branch digests. aSz/bSz are + * in/out - supply the buffer capacity, get back the digest size. */ + aSz = (word32)sizeof(branchA); + rc = wolfTPM2_PolicyCommandCodeMake(hashAlg, branchA, &aSz, TPM_CC_NV_Read); + if (rc == 0) { + bSz = (word32)sizeof(branchB); + rc = wolfTPM2_PolicyCommandCodeMake(hashAlg, branchB, &bSz, + TPM_CC_Unseal); + } + /* Offline PolicyOR digest = H(zeros || TPM_CC_PolicyOR || A || B) */ + if (rc == 0) { + XMEMCPY(concat, branchA, aSz); + XMEMCPY(&concat[aSz], branchB, bSz); + XMEMSET(expected, 0, sizeof(expected)); + expSz = hsz; + rc = wolfTPM2_PolicyHash(hashAlg, expected, &expSz, + TPM_CC_PolicyOR, concat, aSz + bSz); + } + + /* On-TPM: start a policy session using the requested hash algorithm */ + if (rc == 0) { + rc = wolfTPM2_StartSession_ex(dev, &sess, NULL, NULL, + TPM_SE_POLICY, TPM_ALG_NULL, hashAlg); + if (rc != 0) { + printf(" %s: StartSession failed 0x%x: %s\n", + name, rc, TPM2_GetRCString(rc)); + return rc; + } + } + /* Satisfy branch A, then OR against {A,B} with the new wrapper */ + if (rc == 0) { + rc = wolfTPM2_PolicyCommandCode(dev, &sess, TPM_CC_NV_Read); + } + if (rc == 0) { + orList.count = 2; + orList.digests[0].size = (UINT16)aSz; + XMEMCPY(orList.digests[0].buffer, branchA, aSz); + orList.digests[1].size = (UINT16)bSz; + XMEMCPY(orList.digests[1].buffer, branchB, bSz); + rc = wolfTPM2_PolicyOR(dev, &sess, &orList); + } + if (rc == 0) { + gotSz = (word32)sizeof(got); + rc = wolfTPM2_GetPolicyDigest(dev, sess.handle.hndl, got, &gotSz); + } + + if (rc == 0) { + if (gotSz == expSz && XMEMCMP(got, expected, expSz) == 0) { + printf(" %s PolicyOR: PASS (%u byte digest matches)\n", + name, expSz); + } + else { + printf(" %s PolicyOR: FAIL (digest mismatch)\n", name); + printf(" expected: "); + firmware_print_hex(expected, expSz); + printf(" got: "); + firmware_print_hex(got, gotSz); + /* a defined rc, so the caller can propagate it meaningfully */ + rc = TPM_RC_POLICY; + } + } + else { + printf(" %s PolicyOR: ERROR 0x%x: %s\n", + name, rc, TPM2_GetRCString(rc)); + } + + if (sess.handle.hndl != 0) { + wolfTPM2_UnloadHandle(dev, &sess.handle); + } + return rc; +} + +int firmware_policy_selftest_all(WOLFTPM2_DEV* dev) +{ + int i, rc, skipped; + int firstFail = 0; + struct { TPMI_ALG_HASH alg; const char* name; } hashes[3]; + + hashes[0].alg = TPM_ALG_SHA256; hashes[0].name = "SHA2-256"; + hashes[1].alg = TPM_ALG_SHA384; hashes[1].name = "SHA2-384"; + hashes[2].alg = TPM_ALG_SHA512; hashes[2].name = "SHA2-512"; + + printf("Firmware policy authorization self-test " + "(no firmware changes):\n"); + for (i = 0; i < 3; i++) { + skipped = 0; + rc = firmware_policy_selftest(dev, hashes[i].alg, hashes[i].name, + &skipped); + /* Keep the first real failure rc so the caller (and the process exit + * status) can report why, rather than a generic -1. A skip is not a + * failure. */ + if (rc != 0 && !skipped && firstFail == 0) { + firstFail = rc; + } + } + return firstFail; +} + +/* Build the PolicyOR branch list this example provisions. Branch A authorizes + * the vendor firmware start; branch B authorizes TPM2_SetPrimaryPolicy so the + * policy can be rolled back under itself. */ +static void firmware_policy_or_list(const FirmwarePolicyCtx* ctx, + TPML_DIGEST* orList) +{ + XMEMSET(orList, 0, sizeof(*orList)); + orList->count = 2; + orList->digests[0].size = (UINT16)ctx->branchSz; + XMEMCPY(orList->digests[0].buffer, ctx->branchFu, ctx->branchSz); + orList->digests[1].size = (UINT16)ctx->branchSz; + XMEMCPY(orList->digests[1].buffer, ctx->branchSpp, ctx->branchSz); +} + +/* Clear the platform authPolicy under a policy session that satisfies the + * SetPrimaryPolicy branch. Only usable when a PolicyOR was provisioned. */ +static int firmware_policy_clear_by_policy(WOLFTPM2_DEV* dev, + FirmwarePolicyCtx* ctx) +{ + int rc; + int restoreAuth = 0; + WOLFTPM2_SESSION sess; + TPML_DIGEST orList; + + XMEMSET(&sess, 0, sizeof(sess)); + + rc = wolfTPM2_StartSession_ex(dev, &sess, NULL, NULL, + TPM_SE_POLICY, TPM_ALG_NULL, ctx->hashAlg); + if (rc == 0) { + rc = wolfTPM2_PolicyCommandCode(dev, &sess, TPM_CC_SetPrimaryPolicy); + } + if (rc == 0) { + firmware_policy_or_list(ctx, &orList); + rc = wolfTPM2_PolicyOR(dev, &sess, &orList); + } + if (rc == 0) { + /* route the next command's authorization through this policy session */ + rc = wolfTPM2_SetAuthSession(dev, 0, &sess, 0); + if (rc == 0) { + restoreAuth = 1; + } + } + if (rc == 0) { + rc = wolfTPM2_SetPrimaryPolicy(dev, TPM_RH_PLATFORM, TPM_ALG_NULL, + NULL, 0); + if (rc == TPM_RC_SUCCESS) { + /* The session was set up with continueSession CLEAR, so the TPM + * flushed it when the command completed. Mark it released (as the + * library does for a consumed firmware-start session) so the + * cleanup below does not send a FlushContext the TPM will + * reject. */ + sess.handle.hndl = TPM_RH_NULL; + } + } + + /* restore the default password authorization on session slot 0 */ + if (restoreAuth) { + wolfTPM2_SetAuthPassword(dev, 0, NULL); + } + if (sess.handle.hndl != 0) { + wolfTPM2_UnloadHandle(dev, &sess.handle); + } + return rc; +} + +int firmware_policy_clear(WOLFTPM2_DEV* dev, FirmwarePolicyCtx* ctx) +{ + int rc; + + if (dev == NULL || ctx == NULL) { + return BAD_FUNC_ARG; + } + /* Never touch a policy this example did not install. Without this an early + * failure (a bad firmware file, for example) would clear a platform policy + * the deployment provisioned itself. */ + if (!ctx->provisioned) { + return 0; + } + + /* Default path: platform password authorization. Installing an authPolicy + * does not disable the authValue path (TPM 2.0 Part 1 Sec.19.7), so this + * succeeds whenever platformAuth is still the default empty password. */ + rc = wolfTPM2_SetPrimaryPolicy(dev, TPM_RH_PLATFORM, TPM_ALG_NULL, NULL, 0); + + /* Fallback: authorize the clear with the provisioned SetPrimaryPolicy + * branch, for a deployment that set a non-default platformAuth. */ + if (rc != TPM_RC_SUCCESS && ctx->useOr) { + printf("Clearing platform policy with default auth failed 0x%x: %s\n", + rc, TPM2_GetRCString(rc)); + printf(" retrying under the SetPrimaryPolicy branch\n"); + rc = firmware_policy_clear_by_policy(dev, ctx); + } + + if (rc == TPM_RC_SUCCESS) { + ctx->provisioned = 0; + printf("Cleared platform policy (restored default auth)\n"); + } + else { + printf("ERROR: could not clear the platform policy 0x%x: %s\n", + rc, TPM2_GetRCString(rc)); + printf(" The platform hierarchy still requires the provisioned " + "policy.\n"); + printf(" Reset or power cycle the TPM to restore default " + "authorization.\n"); + } + return rc; +} + +int firmware_policy_session_setup(WOLFTPM2_DEV* dev, FirmwarePolicyCtx* ctx, + TPMI_ALG_HASH hashAlg, int useOr, TPM_CC fuStartCC, + WOLFTPM2_SESSION* session) +{ + int rc; + int isSupported = 0; + TPML_DIGEST orList; + word32 hsz = (word32)TPM2_GetHashDigestSize(hashAlg); + byte concat[2 * TPM_MAX_DIGEST_SIZE]; + byte platformPolicy[TPM_MAX_DIGEST_SIZE]; + word32 aSz, bSz = 0, polSz = 0; + + if (dev == NULL || ctx == NULL || session == NULL) { + return BAD_FUNC_ARG; + } + if (hsz == 0 || hsz > TPM_MAX_DIGEST_SIZE) { + return BAD_FUNC_ARG; + } + XMEMSET(ctx, 0, sizeof(*ctx)); + XMEMSET(session, 0, sizeof(*session)); + XMEMSET(&orList, 0, sizeof(orList)); + ctx->hashAlg = hashAlg; + ctx->useOr = useOr; + + /* Fail early (before provisioning) if this policy hash is unusable, either + * because the local wolfCrypt build lacks it or the TPM does not implement + * it. Unlike the self-test these are hard errors: the caller explicitly + * asked for this hash. */ + if (!firmware_hash_local_supported(hashAlg)) { + printf("Policy hash %s not built into this wolfCrypt\n", + TPM2_GetAlgName(hashAlg)); + return BAD_FUNC_ARG; + } + rc = wolfTPM2_IsAlgSupported(dev, hashAlg, &isSupported); + if (rc != TPM_RC_SUCCESS) { + printf("Capability query failed 0x%x: %s\n", + rc, TPM2_GetRCString(rc)); + return rc; + } + if (!isSupported) { + printf("Policy hash %s not implemented by this TPM\n", + TPM2_GetAlgName(hashAlg)); + return BAD_FUNC_ARG; + } + + printf("Provisioning platform policy (%s, %s)\n", + useOr ? "PolicyOR" : "PolicyCommandCode", + TPM2_GetAlgName(hashAlg)); + /* TPM 2.0 provides no way to read a hierarchy's authPolicy back (there is + * no read command, and TPMA_PERMANENT only reports authValue state), so + * this cannot detect or preserve an existing one. Say so plainly. */ + printf(" WARNING: this REPLACES any authPolicy already installed\n"); + printf(" platform hierarchy. An existing policy cannot be read back or\n"); + printf(" restored - cleanup REMOVES the policy rather than restoring\n"); + printf(" the previous digest. Do not use on a system whose platform\n"); + printf(" hierarchy is gated by a policy you need to keep.\n"); + + /* Branch A: PolicyCommandCode(FieldUpgradeStart) - required to start FU */ + aSz = (word32)sizeof(ctx->branchFu); + rc = wolfTPM2_PolicyCommandCodeMake(hashAlg, ctx->branchFu, &aSz, + fuStartCC); + + /* Compute the platform authPolicy digest */ + if (rc == 0) { + ctx->branchSz = aSz; + if (useOr) { + /* Branch B: PolicyCommandCode(SetPrimaryPolicy). This is the + * rollback branch - it lets the provisioned policy authorize its + * own removal, so cleanup works even if platformAuth is not the + * default empty password. */ + bSz = (word32)sizeof(ctx->branchSpp); + rc = wolfTPM2_PolicyCommandCodeMake(hashAlg, ctx->branchSpp, &bSz, + TPM_CC_SetPrimaryPolicy); + if (rc == 0) { + XMEMCPY(concat, ctx->branchFu, aSz); + XMEMCPY(&concat[aSz], ctx->branchSpp, bSz); + XMEMSET(platformPolicy, 0, sizeof(platformPolicy)); + polSz = hsz; + rc = wolfTPM2_PolicyHash(hashAlg, platformPolicy, &polSz, + TPM_CC_PolicyOR, concat, aSz + bSz); + } + } + else { + XMEMCPY(platformPolicy, ctx->branchFu, aSz); + polSz = aSz; + } + } + + /* Provision the platform primary policy (empty platformAuth) */ + if (rc == 0) { + rc = wolfTPM2_SetPrimaryPolicy(dev, TPM_RH_PLATFORM, hashAlg, + platformPolicy, polSz); + if (rc != 0) { + printf(" SetPrimaryPolicy failed 0x%x: %s\n", + rc, TPM2_GetRCString(rc)); + } + else { + ctx->provisioned = 1; + } + } + + /* Start a policy session and satisfy the platform policy */ + if (rc == 0) { + rc = wolfTPM2_StartSession_ex(dev, session, NULL, NULL, + TPM_SE_POLICY, TPM_ALG_NULL, hashAlg); + if (rc != 0) { + printf(" StartSession failed 0x%x: %s\n", + rc, TPM2_GetRCString(rc)); + } + } + if (rc == 0) { + rc = wolfTPM2_PolicyCommandCode(dev, session, fuStartCC); + } + if (rc == 0 && useOr) { + firmware_policy_or_list(ctx, &orList); + rc = wolfTPM2_PolicyOR(dev, session, &orList); + } + + if (rc != 0) { + if (session->handle.hndl != 0) { + wolfTPM2_UnloadHandle(dev, &session->handle); + } + /* Restore default platform auth so a later run is not locked out (the + * platform policy is otherwise cleared only on TPM reset). */ + (void)firmware_policy_clear(dev, ctx); + } + return rc; +} + +#endif /* WOLFTPM_FIRMWARE_UPGRADE && !NO_WRAPPER && !NO_WOLFCRYPT */ diff --git a/examples/firmware/firmware_policy.h b/examples/firmware/firmware_policy.h new file mode 100644 index 000000000..21c31b840 --- /dev/null +++ b/examples/firmware/firmware_policy.h @@ -0,0 +1,99 @@ +/* firmware_policy.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* Shared caller-supplied policy authorization helpers for the firmware update + * examples (ifx_fw_update and st33_fw_update). The vendor difference is carried + * by the fuStartCC parameter, so there is no per-vendor logic here. */ + +#ifndef WOLFTPM_EXAMPLE_FIRMWARE_POLICY_H +#define WOLFTPM_EXAMPLE_FIRMWARE_POLICY_H + +#include + +/* These helpers are built on the wolfTPM2 wrapper API and use wolfCrypt hashing */ +#if defined(WOLFTPM_FIRMWARE_UPGRADE) && !defined(WOLFTPM2_NO_WRAPPER) && \ + !defined(WOLFTPM2_NO_WOLFCRYPT) + +#ifdef __cplusplus +extern "C" { +#endif + +/* Single definition of the "policy helpers are available" condition, so the + * two firmware examples cannot drift. Tested as WOLFTPM_HAVE_FW_POLICY. */ +#define WOLFTPM_HAVE_FW_POLICY + +/* Records what firmware_policy_session_setup actually provisioned, so cleanup + * only touches a policy this example installed and can reproduce the branch + * digests needed to authorize the rollback. Treat as opaque; zero before + * use. */ +typedef struct FirmwarePolicyCtx { + TPMI_ALG_HASH hashAlg; /* policy session / authPolicy hash */ + int useOr; /* 1 = platform policy is a PolicyOR */ + int provisioned; /* 1 = we installed the platform authPolicy */ + word32 branchSz; /* size of each branch digest below */ + /* PolicyOR branch A: PolicyCommandCode(fuStartCC) */ + byte branchFu[TPM_MAX_DIGEST_SIZE]; + /* PolicyOR branch B: PolicyCommandCode(TPM_CC_SetPrimaryPolicy) */ + byte branchSpp[TPM_MAX_DIGEST_SIZE]; +} FirmwarePolicyCtx; + +/* Non-destructive self-test: exercises wolfTPM2_PolicyOR at SHA-256/384/512 and + * checks the TPM's running policy digest against an offline computation. A hash + * the TPM does not implement is reported and skipped. Returns 0 on overall + * success (all supported hashes matched), -1 if any supported hash failed. */ +int firmware_policy_selftest_all(WOLFTPM2_DEV* dev); + +/* Provision the platform authPolicy and return a session that satisfies it, so + * the firmware-start command can be authorized by a caller-controlled policy + * instead of the vendor default. When useOr is set the platform policy is a + * PolicyOR of PolicyCommandCode(fuStartCC) and + * PolicyCommandCode(TPM_CC_SetPrimaryPolicy); otherwise it is a single + * PolicyCommandCode(fuStartCC) branch. fuStartCC is the vendor FieldUpgrade + * start command code. ctx records what was provisioned and must be passed to + * firmware_policy_clear. On success *session is started and satisfied (the + * caller passes it to wolfTPM2_FirmwareUpgrade_ex and must UnloadHandle it). + * On failure any provisioned platform policy is cleared so a later default-auth + * run is not locked out. */ +int firmware_policy_session_setup(WOLFTPM2_DEV* dev, FirmwarePolicyCtx* ctx, + TPMI_ALG_HASH hashAlg, int useOr, TPM_CC fuStartCC, + WOLFTPM2_SESSION* session); + +/* Clear a platform authPolicy this example provisioned, restoring default auth + * so a later default-auth run is not locked out. Does nothing (and returns 0) + * when ctx reports nothing was provisioned, so an unconditional call on an + * error path cannot wipe a policy the deployment installed itself. + * + * Rollback normally uses platform password authorization: per TPM 2.0 Part 1 + * Sec.19.7 a hierarchy is authorized by either its authValue or its authPolicy, + * so installing an authPolicy does not disable the password path. If the + * password path fails (a deployment that set a non-default platformAuth) and a + * PolicyOR was provisioned, the SetPrimaryPolicy branch is used to authorize + * the clear under policy instead. Returns 0 on success, otherwise the TPM rc; + * a non-zero return means the platform hierarchy still requires the policy + * until the TPM is reset or power cycled. */ +int firmware_policy_clear(WOLFTPM2_DEV* dev, FirmwarePolicyCtx* ctx); + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFTPM_FIRMWARE_UPGRADE && !NO_WRAPPER && !NO_WOLFCRYPT */ +#endif /* WOLFTPM_EXAMPLE_FIRMWARE_POLICY_H */ diff --git a/examples/firmware/ifx_fw_update.c b/examples/firmware/ifx_fw_update.c index 9934e229b..33b5d4fb3 100644 --- a/examples/firmware/ifx_fw_update.c +++ b/examples/firmware/ifx_fw_update.c @@ -28,13 +28,30 @@ #include +/* wolfTPM2_FirmwareUpgrade_ex hashes the manifest with SHA-384, so it is only + * built with wolfCrypt. This tool has no other way to drive an upgrade. */ #if defined(WOLFTPM_FIRMWARE_UPGRADE) && \ - (defined(WOLFTPM_SLB9672) || defined(WOLFTPM_SLB9673)) + (defined(WOLFTPM_SLB9672) || defined(WOLFTPM_SLB9673)) && \ + !defined(WOLFTPM2_NO_WOLFCRYPT) #include +#include #include #include +/* WOLFTPM_HAVE_FW_POLICY comes from firmware_policy.h, which owns the + * condition so the two firmware examples cannot drift. */ + +/* Caller-supplied policy authorization modes */ +#define IFX_POLICY_NONE 0 /* library-managed authorization */ +#define IFX_POLICY_CMDCODE 1 /* --policy: single PolicyCommandCode */ +#define IFX_POLICY_OR 2 /* --policyor: multi-branch PolicyOR */ + +/* Infineon operational modes (subset used here) */ +#define IFX_OPMODE_NORMAL 0x00 /* normal; FieldUpgradeStart reached */ +#define IFX_OPMODE_RECOVERY 0x02 /* recovery; start is skipped */ +#define IFX_OPMODE_FINALIZE 0x03 /* update done, finalize only */ + /******************************************************************************/ /* --- BEGIN TPM2.0 Firmware Update tool -- */ /******************************************************************************/ @@ -44,7 +61,14 @@ static void usage(void) printf("Infineon Firmware Update Usage:\n"); printf("\t./ifx_fw_update (get info)\n"); printf("\t./ifx_fw_update --abandon (cancel)\n"); - printf("\t./ifx_fw_update \n"); + printf("\t./ifx_fw_update --policytest (safe policy auth self-test)\n"); + printf("\t./ifx_fw_update [policy opts] \n"); + printf("\t./ifx_fw_update " + "(default auth)\n"); + printf("Policy options (caller-supplied authorization):\n"); + printf("\t--policy provision+satisfy a PolicyCommandCode\n"); + printf("\t--policyor provision+satisfy a PolicyOR (multi-branch)\n"); + printf("\t--sha256|--sha384|--sha512 policy hash (default SHA-256)\n"); } typedef struct { @@ -115,24 +139,67 @@ int TPM2_IFX_Firmware_Update(void* userCtx, int argc, char *argv[]) const char* firmware_file = NULL; fw_info_t fwinfo; int abandon = 0, recovery = 0; + int i; +#ifdef WOLFTPM_HAVE_FW_POLICY + int policytest = 0; + int policyMode = IFX_POLICY_NONE; + TPMI_ALG_HASH policyHash = TPM_ALG_SHA256; + WOLFTPM2_SESSION policySession; + FirmwarePolicyCtx policyCtx; + int clearRc; +#endif XMEMSET(&fwinfo, 0, sizeof(fwinfo)); +#ifdef WOLFTPM_HAVE_FW_POLICY + XMEMSET(&policySession, 0, sizeof(policySession)); + XMEMSET(&policyCtx, 0, sizeof(policyCtx)); +#endif - if (argc >= 2) { - if (XSTRCMP(argv[1], "-?") == 0 || - XSTRCMP(argv[1], "-h") == 0 || - XSTRCMP(argv[1], "--help") == 0) { + for (i = 1; i < argc; i++) { + if (XSTRCMP(argv[i], "-?") == 0 || + XSTRCMP(argv[i], "-h") == 0 || + XSTRCMP(argv[i], "--help") == 0) { usage(); return 0; } - if (XSTRCMP(argv[1], "--abandon") == 0) { + else if (XSTRCMP(argv[i], "--abandon") == 0) { abandon = 1; } +#ifdef WOLFTPM_HAVE_FW_POLICY + else if (XSTRCMP(argv[i], "--policytest") == 0) { + policytest = 1; + } + else if (XSTRCMP(argv[i], "--policy") == 0) { + policyMode = IFX_POLICY_CMDCODE; + } + else if (XSTRCMP(argv[i], "--policyor") == 0) { + policyMode = IFX_POLICY_OR; + } + else if (XSTRCMP(argv[i], "--sha256") == 0) { + policyHash = TPM_ALG_SHA256; + } + else if (XSTRCMP(argv[i], "--sha384") == 0) { + policyHash = TPM_ALG_SHA384; + } + else if (XSTRCMP(argv[i], "--sha512") == 0) { + policyHash = TPM_ALG_SHA512; + } +#endif /* WOLFTPM_HAVE_FW_POLICY */ + else if (argv[i][0] == '-') { + printf("Unrecognized option: %s\n", argv[i]); + usage(); + return BAD_FUNC_ARG; + } + else if (manifest_file == NULL) { + manifest_file = argv[i]; + } + else if (firmware_file == NULL) { + firmware_file = argv[i]; + } else { - manifest_file = argv[1]; - if (argc >= 3) { - firmware_file = argv[2]; - } + printf("Unexpected extra argument: %s\n", argv[i]); + usage(); + return BAD_FUNC_ARG; } } @@ -148,6 +215,16 @@ int TPM2_IFX_Firmware_Update(void* userCtx, int argc, char *argv[]) goto exit; } +#ifdef WOLFTPM_HAVE_FW_POLICY + if (policytest) { + /* Non-destructive validation of caller-supplied policy authorization. + * Does not touch firmware upgrade state. */ + rc = firmware_policy_selftest_all(&dev); + wolfTPM2_Cleanup(&dev); + return rc; + } +#endif + rc = wolfTPM2_GetCapabilities(&dev, &caps); if (rc != TPM_RC_SUCCESS) { goto exit; @@ -156,7 +233,7 @@ int TPM2_IFX_Firmware_Update(void* userCtx, int argc, char *argv[]) if (caps.keyGroupId == 0) { printf("Error getting key group id from TPM!\n"); } - if (caps.opMode == 0x02 || (caps.opMode & 0x80)) { + if (caps.opMode == IFX_OPMODE_RECOVERY || (caps.opMode & 0x80)) { /* if opmode == 2 or 0x8x then we need to use recovery mode */ recovery = 1; } @@ -187,18 +264,53 @@ int TPM2_IFX_Firmware_Update(void* userCtx, int argc, char *argv[]) rc = loadFile(firmware_file, &fwinfo.firmware_buf, &fwinfo.firmware_bufSz); } +#ifdef WOLFTPM_HAVE_FW_POLICY + /* When a policy mode is requested, provision the platform authPolicy and + * build a session that satisfies it, then drive the upgrade under that + * caller-supplied session instead of the library-managed authorization. + * + * Only the normal operational mode actually sends FieldUpgradeStart, which + * is the one command the session authorizes. In recovery and finalize + * modes the library skips the start entirely, so provisioning a policy + * there would install a platform authPolicy that is never exercised - and + * on a successful run nothing would clear it. Refuse instead of silently + * changing hierarchy state the user did not ask to change. */ + if (rc == 0 && policyMode != IFX_POLICY_NONE) { + if (caps.opMode != IFX_OPMODE_NORMAL) { + printf("Policy authorization requires the normal operational " + "mode.\n"); + printf(" Current opMode 0x%x does not send FieldUpgradeStart, " + "so a\n", caps.opMode); + printf(" caller-supplied session would never be used.\n"); + rc = BAD_FUNC_ARG; + } + else { + rc = firmware_policy_session_setup(&dev, &policyCtx, policyHash, + (policyMode == IFX_POLICY_OR), TPM_CC_FieldUpgradeStartVendor, + &policySession); + } + } +#endif if (rc == 0) { + WOLFTPM2_SESSION* startSess = NULL; + #ifdef WOLFTPM_HAVE_FW_POLICY + if (policyMode != IFX_POLICY_NONE) { + startSess = &policySession; + } + #endif if (recovery) { - printf("Firmware Update (recovery mode):\n"); - rc = wolfTPM2_FirmwareUpgradeRecover(&dev, + printf("Firmware Update (recovery mode%s):\n", + startSess ? ", caller policy" : ""); + rc = wolfTPM2_FirmwareUpgradeRecover_ex(&dev, fwinfo.manifest_buf, (uint32_t)fwinfo.manifest_bufSz, - TPM2_IFX_FwData_Cb, &fwinfo); + TPM2_IFX_FwData_Cb, &fwinfo, startSess); } else { - printf("Firmware Update (normal mode):\n"); - rc = wolfTPM2_FirmwareUpgrade(&dev, + printf("Firmware Update (normal mode%s):\n", + startSess ? ", caller policy" : ""); + rc = wolfTPM2_FirmwareUpgrade_ex(&dev, fwinfo.manifest_buf, (uint32_t)fwinfo.manifest_bufSz, - TPM2_IFX_FwData_Cb, &fwinfo); + TPM2_IFX_FwData_Cb, &fwinfo, startSess); } } if (rc == 0) { @@ -212,6 +324,33 @@ int TPM2_IFX_Firmware_Update(void* userCtx, int argc, char *argv[]) rc, TPM2_GetRCString(rc)); } +#ifdef WOLFTPM_HAVE_FW_POLICY + /* On a successful start the TPM consumes the session and the library sets + * handle.hndl to TPM_RH_NULL (0x40000007), which is non-zero - so this + * guard does not distinguish consumed from live. It does not need to: + * wolfTPM2_UnloadHandle is a no-op on TPM_RH_NULL, so the call only ever + * flushes a session that is still loaded. */ + if (policySession.handle.hndl != 0) { + wolfTPM2_UnloadHandle(&dev, &policySession.handle); + } + /* Clear the platform policy if the upgrade did not complete. Gated on + * policyCtx.provisioned inside the helper, so an early failure cannot wipe + * a policy this example never installed. + * + * Skip the attempt once the start has succeeded: the TPM consumed the + * session (handle set to TPM_RH_NULL) and reset into firmware-upgrade + * mode, where it will not service TPM2_SetPrimaryPolicy - and the + * mandatory TPM reset clears the policy anyway. */ + if (rc != 0 && policySession.handle.hndl != TPM_RH_NULL) { + clearRc = firmware_policy_clear(&dev, &policyCtx); + /* Report a failed rollback, but never overwrite the upgrade error that + * explains why this run failed - that rc is the process exit status + * and the only machine-readable diagnostic a script sees. */ + if (clearRc != 0 && rc == 0) { + rc = clearRc; + } + } +#endif XFREE(fwinfo.firmware_buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); XFREE(fwinfo.manifest_buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); wolfTPM2_Cleanup(&dev); @@ -222,7 +361,8 @@ int TPM2_IFX_Firmware_Update(void* userCtx, int argc, char *argv[]) /******************************************************************************/ /* --- END TPM2.0 Firmware Update tool -- */ /******************************************************************************/ -#endif /* WOLFTPM_FIRMWARE_UPGRADE && (WOLFTPM_SLB9672 || WOLFTPM_SLB9673) */ +#endif /* WOLFTPM_FIRMWARE_UPGRADE && (WOLFTPM_SLB9672 || WOLFTPM_SLB9673) && + * !WOLFTPM2_NO_WOLFCRYPT */ #ifndef NO_MAIN_DRIVER int main(int argc, char *argv[]) @@ -230,7 +370,8 @@ int main(int argc, char *argv[]) int rc = -1; #if defined(WOLFTPM_FIRMWARE_UPGRADE) && \ - (defined(WOLFTPM_SLB9672) || defined(WOLFTPM_SLB9673)) + (defined(WOLFTPM_SLB9672) || defined(WOLFTPM_SLB9673)) && \ + !defined(WOLFTPM2_NO_WOLFCRYPT) rc = TPM2_IFX_Firmware_Update(NULL, argc, argv); #else printf("Support for firmware upgrade not compiled in!\n" @@ -238,6 +379,8 @@ int main(int argc, char *argv[]) printf("This tool is for the Infineon SLB9672 or SLB9673 TPMs only\n" "\t--enable-infineon=slb9672 (WOLFTPM_SLB9672)\n" "\t--enable-infineon=slb9673 --enable-i2c (WOLFTPM_SLB9673)\n"); + printf("Firmware upgrade also requires wolfCrypt " + "(not WOLFTPM2_NO_WOLFCRYPT)\n"); (void)argc; (void)argv; #endif diff --git a/examples/firmware/include.am b/examples/firmware/include.am index 4c355b856..1c01f4373 100644 --- a/examples/firmware/include.am +++ b/examples/firmware/include.am @@ -7,13 +7,20 @@ EXTRA_DIST += examples/firmware/Makefile # Host side tool for extracting the firmware manifest and data EXTRA_DIST += examples/firmware/ifx_fw_extract.c +# Shared caller-supplied policy authorization helpers for the firmware examples +EXTRA_DIST += examples/firmware/firmware_policy.c +EXTRA_DIST += examples/firmware/firmware_policy.h + if BUILD_EXAMPLES if BUILD_FIRMWARE +noinst_HEADERS += examples/firmware/firmware_policy.h + if BUILD_INFINEON noinst_PROGRAMS += examples/firmware/ifx_fw_update noinst_HEADERS += examples/firmware/ifx_fw_update.h examples_firmware_ifx_fw_update_SOURCES = examples/firmware/ifx_fw_update.c \ + examples/firmware/firmware_policy.c \ examples/tpm_test_keys.c examples_firmware_ifx_fw_update_LDADD = src/libwolftpm.la $(LIB_STATIC_ADD) examples_firmware_ifx_fw_update_DEPENDENCIES = src/libwolftpm.la @@ -22,6 +29,7 @@ endif if BUILD_ST33 noinst_PROGRAMS += examples/firmware/st33_fw_update examples_firmware_st33_fw_update_SOURCES = examples/firmware/st33_fw_update.c \ + examples/firmware/firmware_policy.c \ examples/tpm_test_keys.c examples_firmware_st33_fw_update_LDADD = src/libwolftpm.la $(LIB_STATIC_ADD) examples_firmware_st33_fw_update_DEPENDENCIES = src/libwolftpm.la @@ -31,7 +39,9 @@ endif endif example_firmwaredir = $(exampledir)/firmware -dist_example_firmware_DATA = examples/firmware/ifx_fw_update.c \ +dist_example_firmware_DATA = examples/firmware/firmware_policy.c \ + examples/firmware/firmware_policy.h \ + examples/firmware/ifx_fw_update.c \ examples/firmware/st33_fw_update.c DISTCLEANFILES+= examples/firmware/.libs/ifx_fw_update \ diff --git a/examples/firmware/st33_fw_update.c b/examples/firmware/st33_fw_update.c index 2120df3db..76eff46f6 100644 --- a/examples/firmware/st33_fw_update.c +++ b/examples/firmware/st33_fw_update.c @@ -28,12 +28,24 @@ #include +/* wolfTPM2_FirmwareUpgrade_ex hashes the manifest with SHA-384, so it is only + * built with wolfCrypt. This tool has no other way to drive an upgrade. */ #if defined(WOLFTPM_FIRMWARE_UPGRADE) && \ - (defined(WOLFTPM_ST33) || defined(WOLFTPM_AUTODETECT)) + (defined(WOLFTPM_ST33) || defined(WOLFTPM_AUTODETECT)) && \ + !defined(WOLFTPM2_NO_WOLFCRYPT) +#include #include #include +/* WOLFTPM_HAVE_FW_POLICY comes from firmware_policy.h, which owns the + * condition so the two firmware examples cannot drift. */ + +/* Caller-supplied policy authorization modes */ +#define ST33_POLICY_NONE 0 /* default password (TPM_RS_PW) auth */ +#define ST33_POLICY_CMDCODE 1 /* --policy: single PolicyCommandCode */ +#define ST33_POLICY_OR 2 /* --policyor: multi-branch PolicyOR */ + /******************************************************************************/ /* --- BEGIN ST33 TPM2.0 Firmware Update tool -- */ /******************************************************************************/ @@ -47,7 +59,13 @@ static void usage(void) printf("ST33 Firmware Update Usage:\n"); printf("\t./st33_fw_update (get info)\n"); printf("\t./st33_fw_update --abandon (cancel)\n"); - printf("\t./st33_fw_update \n"); + printf("\t./st33_fw_update --policytest (safe policy auth self-test)\n"); + printf("\t./st33_fw_update [policy opts] \n"); + printf("\t./st33_fw_update (default password auth)\n"); + printf("Policy options (caller-supplied authorization):\n"); + printf("\t--policy provision+satisfy a PolicyCommandCode\n"); + printf("\t--policyor provision+satisfy a PolicyOR (multi-branch)\n"); + printf("\t--sha256|--sha384|--sha512 policy hash (default SHA-256)\n"); printf("\nFirmware format is auto-detected from the TPM firmware version.\n"); printf("Just provide the correct .fi file for your TPM and it will be handled automatically.\n"); } @@ -185,22 +203,65 @@ int TPM2_ST33_Firmware_Update(void* userCtx, int argc, char *argv[]) fw_info_t fwinfo; int abandon = 0; size_t blob0_size; + int i; +#ifdef WOLFTPM_HAVE_FW_POLICY + int policytest = 0; + int policyMode = ST33_POLICY_NONE; + TPMI_ALG_HASH policyHash = TPM_ALG_SHA256; + WOLFTPM2_SESSION policySession; + FirmwarePolicyCtx policyCtx; + int clearRc; +#endif XMEMSET(&fwinfo, 0, sizeof(fwinfo)); XMEMSET(&caps, 0, sizeof(caps)); +#ifdef WOLFTPM_HAVE_FW_POLICY + XMEMSET(&policySession, 0, sizeof(policySession)); + XMEMSET(&policyCtx, 0, sizeof(policyCtx)); +#endif - if (argc >= 2) { - if (XSTRCMP(argv[1], "-?") == 0 || - XSTRCMP(argv[1], "-h") == 0 || - XSTRCMP(argv[1], "--help") == 0) { + for (i = 1; i < argc; i++) { + if (XSTRCMP(argv[i], "-?") == 0 || + XSTRCMP(argv[i], "-h") == 0 || + XSTRCMP(argv[i], "--help") == 0) { usage(); return 0; } - if (XSTRCMP(argv[1], "--abandon") == 0) { + else if (XSTRCMP(argv[i], "--abandon") == 0) { abandon = 1; } +#ifdef WOLFTPM_HAVE_FW_POLICY + else if (XSTRCMP(argv[i], "--policytest") == 0) { + policytest = 1; + } + else if (XSTRCMP(argv[i], "--policy") == 0) { + policyMode = ST33_POLICY_CMDCODE; + } + else if (XSTRCMP(argv[i], "--policyor") == 0) { + policyMode = ST33_POLICY_OR; + } + else if (XSTRCMP(argv[i], "--sha256") == 0) { + policyHash = TPM_ALG_SHA256; + } + else if (XSTRCMP(argv[i], "--sha384") == 0) { + policyHash = TPM_ALG_SHA384; + } + else if (XSTRCMP(argv[i], "--sha512") == 0) { + policyHash = TPM_ALG_SHA512; + } +#endif /* WOLFTPM_HAVE_FW_POLICY */ + else if (argv[i][0] == '-') { + printf("Unrecognized option: %s\n", argv[i]); + usage(); + return BAD_FUNC_ARG; + } + else if (fi_file == NULL) { + fi_file = argv[i]; + } else { - fi_file = argv[1]; + printf("Unexpected extra argument: %s\n", argv[i]); + usage(); + return BAD_FUNC_ARG; } } @@ -231,6 +292,23 @@ int TPM2_ST33_Firmware_Update(void* userCtx, int argc, char *argv[]) return rc; } if (fi_file != NULL) { + #ifdef WOLFTPM_HAVE_FW_POLICY + /* FieldUpgradeStart already ran, so there is no command left for a + * caller-supplied session to authorize. Continuing here would + * silently downgrade to the default authorization - exactly what + * these flags exist to make explicit - so refuse instead. */ + if (policyMode != ST33_POLICY_NONE || policytest) { + printf("Cannot apply policy authorization: the TPM is already " + "in\n"); + printf(" firmware upgrade mode, so FieldUpgradeStart has " + "already run.\n"); + printf(" Re-run without --policy/--policyor/--policytest to " + "continue,\n"); + printf(" or --abandon and power cycle to start over.\n"); + rc = BAD_FUNC_ARG; + goto exit; + } + #endif /* Continue firmware update - TPM already in upgrade mode */ printf("Continuing firmware update...\n"); fwinfo.in_upgrade_mode = 1; @@ -245,6 +323,18 @@ int TPM2_ST33_Firmware_Update(void* userCtx, int argc, char *argv[]) goto exit; } +#ifdef WOLFTPM_HAVE_FW_POLICY + if (policytest) { + /* Non-destructive validation of caller-supplied policy authorization. + * Runs SHA2-256/384/512 PolicyOR digest checks (a hash the TPM does + * not support is reported and skipped). Does not touch firmware + * upgrade state. */ + rc = firmware_policy_selftest_all(&dev); + wolfTPM2_Cleanup(&dev); + return rc; + } +#endif + rc = wolfTPM2_GetCapabilities(&dev, &caps); if (rc != TPM_RC_SUCCESS) { printf("wolfTPM2_GetCapabilities failed 0x%x: %s\n", @@ -351,10 +441,27 @@ int TPM2_ST33_Firmware_Update(void* userCtx, int argc, char *argv[]) rc = TPM2_ST33_SendFirmwareData(&fwinfo); } else { - /* Normal mode - use unified API which auto-detects format from manifest size */ - rc = wolfTPM2_FirmwareUpgrade(&dev, - fwinfo.manifest_buf, (uint32_t)fwinfo.manifest_bufSz, - TPM2_ST33_FwData_Cb, &fwinfo); + WOLFTPM2_SESSION* startSess = NULL; + #ifdef WOLFTPM_HAVE_FW_POLICY + /* When a policy mode is requested, provision the platform authPolicy + * and build a session that satisfies it, then drive the upgrade under + * that caller-supplied session instead of the default password auth. */ + if (policyMode != ST33_POLICY_NONE) { + rc = firmware_policy_session_setup(&dev, &policyCtx, policyHash, + (policyMode == ST33_POLICY_OR), + TPM_CC_FieldUpgradeStartVendor_ST33, &policySession); + if (rc == 0) { + printf("Using caller-supplied policy session\n"); + startSess = &policySession; + } + } + #endif + /* Normal mode - unified API auto-detects format from manifest size */ + if (rc == 0) { + rc = wolfTPM2_FirmwareUpgrade_ex(&dev, + fwinfo.manifest_buf, (uint32_t)fwinfo.manifest_bufSz, + TPM2_ST33_FwData_Cb, &fwinfo, startSess); + } } if (rc == 0) { printf("\nFirmware update completed successfully.\n"); @@ -377,6 +484,33 @@ int TPM2_ST33_Firmware_Update(void* userCtx, int argc, char *argv[]) rc, TPM2_GetRCString(rc)); } +#ifdef WOLFTPM_HAVE_FW_POLICY + /* On a successful start the TPM consumes the session and the library sets + * handle.hndl to TPM_RH_NULL (0x40000007), which is non-zero - so this + * guard does not distinguish consumed from live. It does not need to: + * wolfTPM2_UnloadHandle is a no-op on TPM_RH_NULL, so the call only ever + * flushes a session that is still loaded. */ + if (policySession.handle.hndl != 0) { + wolfTPM2_UnloadHandle(&dev, &policySession.handle); + } + /* Clear the platform policy if the upgrade did not complete. Gated on + * policyCtx.provisioned inside the helper, so an early failure cannot wipe + * a policy this example never installed. + * + * Skip the attempt once the start has succeeded: the TPM consumed the + * session (handle set to TPM_RH_NULL) and reset into firmware-upgrade + * mode, where it will not service TPM2_SetPrimaryPolicy - and the + * mandatory TPM reset clears the policy anyway. */ + if (rc != 0 && policySession.handle.hndl != TPM_RH_NULL) { + clearRc = firmware_policy_clear(&dev, &policyCtx); + /* Report a failed rollback, but never overwrite the upgrade error that + * explains why this run failed - that rc is the process exit status + * and the only machine-readable diagnostic a script sees. */ + if (clearRc != 0 && rc == 0) { + rc = clearRc; + } + } +#endif /* Only free the main fi_buf - manifest_buf and firmware_buf point into it */ XFREE(fwinfo.fi_buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); wolfTPM2_Cleanup(&dev); @@ -387,7 +521,8 @@ int TPM2_ST33_Firmware_Update(void* userCtx, int argc, char *argv[]) /******************************************************************************/ /* --- END ST33 TPM2.0 Firmware Update tool -- */ /******************************************************************************/ -#endif /* WOLFTPM_FIRMWARE_UPGRADE && (WOLFTPM_ST33 || WOLFTPM_AUTODETECT) */ +#endif /* WOLFTPM_FIRMWARE_UPGRADE && (WOLFTPM_ST33 || WOLFTPM_AUTODETECT) && + * !WOLFTPM2_NO_WOLFCRYPT */ #ifndef NO_MAIN_DRIVER int main(int argc, char *argv[]) @@ -395,13 +530,16 @@ int main(int argc, char *argv[]) int rc = -1; #if defined(WOLFTPM_FIRMWARE_UPGRADE) && \ - (defined(WOLFTPM_ST33) || defined(WOLFTPM_AUTODETECT)) + (defined(WOLFTPM_ST33) || defined(WOLFTPM_AUTODETECT)) && \ + !defined(WOLFTPM2_NO_WOLFCRYPT) rc = TPM2_ST33_Firmware_Update(NULL, argc, argv); #else printf("Support for ST33 firmware upgrade not compiled in!\n" "See --enable-firmware or WOLFTPM_FIRMWARE_UPGRADE\n"); printf("This tool is for the STMicroelectronics ST33KTPM TPMs only\n" "\t--enable-st33 (WOLFTPM_ST33)\n"); + printf("Firmware upgrade also requires wolfCrypt " + "(not WOLFTPM2_NO_WOLFCRYPT)\n"); (void)argc; (void)argv; #endif diff --git a/examples/nvram/extend.c b/examples/nvram/extend.c index 31fba14fd..51f24232d 100644 --- a/examples/nvram/extend.c +++ b/examples/nvram/extend.c @@ -50,14 +50,6 @@ static void usage(void) printf("* -aes/xor: Use Parameter Encryption\n");; } -static int BuildPolicyCommandCode(TPMI_ALG_HASH hashAlg, - byte* digest, word32* digestSz, TPM_CC cc) -{ - word32 val = cpu_to_be32(cc); - return wolfTPM2_PolicyHash(hashAlg, digest, digestSz, - TPM_CC_PolicyCommandCode, (byte*)&val, sizeof(val)); -} - static int PolicyOrApply(WOLFTPM2_DEV* dev, WOLFTPM2_SESSION* policySession, byte** hashList, word32 hashListSz, word32 digestSz) { @@ -161,7 +153,10 @@ int TPM2_NVRAM_Extend_Example(void* userCtx, int argc, char *argv[]) /* Policy A: TPM2_PolicyCommandCode -> TPM_CC_NV_Read */ /* 47ce3032d8bad1f3089cb0c09088de43501491d460402b90cd1b7fc0b68ca92f */ policy[0] = &policyDigest[policyDigestSz]; - rc = BuildPolicyCommandCode(hashAlg, policy[0], &nvSize, TPM_CC_NV_Read); + /* nvSize is in/out: supply the room left in policyDigest as the capacity */ + nvSize = (word32)sizeof(policyDigest) - policyDigestSz; + rc = wolfTPM2_PolicyCommandCodeMake(hashAlg, policy[0], &nvSize, + TPM_CC_NV_Read); if (rc != TPM_RC_SUCCESS) { printf("Building PolicyA failed!\n"); goto exit; @@ -173,7 +168,9 @@ int TPM2_NVRAM_Extend_Example(void* userCtx, int argc, char *argv[]) /* Policy B: TPM2_PolicyCommandCode -> TPM_CC_NV_Extend */ /* b6a2e7142ee56fd978047488483daa5b42b8dc4cc7ddcceddfb91793cf1ff1b7 */ policy[1] = &policyDigest[policyDigestSz]; - rc = BuildPolicyCommandCode(hashAlg, policy[1], &nvSize, TPM_CC_NV_Extend); + nvSize = (word32)sizeof(policyDigest) - policyDigestSz; + rc = wolfTPM2_PolicyCommandCodeMake(hashAlg, policy[1], &nvSize, + TPM_CC_NV_Extend); if (rc != TPM_RC_SUCCESS) { printf("Building PolicyB failed!\n"); goto exit; @@ -185,7 +182,9 @@ int TPM2_NVRAM_Extend_Example(void* userCtx, int argc, char *argv[]) /* Policy C: TPM2_PolicyCommandCode -> TPM_CC_PolicyNV */ /* 203e4bd5d0448c9615cc13fa18e8d39222441cc40204d99a77262068dbd55a43 */ policy[2] = &policyDigest[policyDigestSz]; - rc = BuildPolicyCommandCode(hashAlg, policy[2], &nvSize, TPM_CC_PolicyNV); + nvSize = (word32)sizeof(policyDigest) - policyDigestSz; + rc = wolfTPM2_PolicyCommandCodeMake(hashAlg, policy[2], &nvSize, + TPM_CC_PolicyNV); if (rc != TPM_RC_SUCCESS) { printf("Building PolicyC failed!\n"); goto exit; diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index 1dd1a26ba..34a3e85af 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -1024,6 +1024,52 @@ int wolfTPM2_GetCapabilities(WOLFTPM2_DEV* dev, WOLFTPM2_CAPS* cap) return wolfTPM2_GetCapabilities_NoDev(cap); } +/* Report whether the TPM implements the given algorithm. + * Returns TPM_RC_SUCCESS with *isSupported set to 1 (supported) or 0 (not + * supported); on any failure a non-zero rc is returned and *isSupported is set + * to 0 so a caller that ignores the rc fails closed. + * Queries TPM_CAP_ALGS: the TPM returns algorithms with ID >= property, so a + * match at index 0 for a single-property query means it is implemented. */ +int wolfTPM2_IsAlgSupported(WOLFTPM2_DEV* dev, TPM_ALG_ID alg, int* isSupported) +{ + int rc; + GetCapability_In in; + GetCapability_Out out; + TPML_ALG_PROPERTY* algs; + + if (isSupported == NULL) { + return BAD_FUNC_ARG; + } + /* fail closed: never leave the out-param indicating "supported" on error */ + *isSupported = 0; + if (dev == NULL) { + return BAD_FUNC_ARG; + } + XMEMSET(&in, 0, sizeof(in)); + XMEMSET(&out, 0, sizeof(out)); + in.capability = TPM_CAP_ALGS; + in.property = alg; + in.propertyCount = 1; + rc = TPM2_GetCapability(&in, &out); + if (rc != TPM_RC_SUCCESS) { + return rc; /* query failure, distinct from "not supported" */ + } + /* capabilityData.data is a union - confirm the TPM answered with the + * capability we asked for before reading the algorithm member, so a + * non-conforming response cannot be reinterpreted as an algorithm + * property. */ + if (out.capabilityData.capability != TPM_CAP_ALGS) { + return TPM_RC_VALUE; + } + /* The TPM returns algorithms with ID >= property; a match at index 0 + * means the requested algorithm is implemented. */ + algs = &out.capabilityData.data.algorithms; + if (algs->count >= 1 && algs->algProperties[0].alg == alg) { + *isSupported = 1; + } + return TPM_RC_SUCCESS; +} + int wolfTPM2_GetHandles(TPM_HANDLE handle, TPML_HANDLE* handles) { int rc; @@ -10706,6 +10752,71 @@ int wolfTPM2_PolicyCommandCode(WOLFTPM2_DEV* dev, WOLFTPM2_SESSION* tpmSession, return TPM2_PolicyCommandCode(&policyCC); } +/* Satisfy a policy session with a compound OR of pre-computed policy digests. + * The digest list is hash-agnostic (each branch carries its own size), so it + * works for SHA2-256 as well as SHA2-512 policy branches. */ +int wolfTPM2_PolicyOR(WOLFTPM2_DEV* dev, WOLFTPM2_SESSION* tpmSession, + const TPML_DIGEST* pHashList) +{ + PolicyOR_In policyOR; + word32 i; + + if (dev == NULL || tpmSession == NULL || pHashList == NULL) { + return BAD_FUNC_ARG; + } + /* TPM 2.0 Part 3 Sec.23.6 requires at least two digests, so reject a + * one-branch list here rather than letting the TPM return TPM_RC_VALUE. */ + if (pHashList->count < 2 || + pHashList->count > (word32)(sizeof(pHashList->digests) / + sizeof(pHashList->digests[0]))) { + return BAD_FUNC_ARG; + } + /* Validate each branch digest size against its buffer so TPM2_PolicyOR + * cannot marshal past the fixed digest buffer (out-of-bounds read). */ + for (i = 0; i < pHashList->count; i++) { + if (pHashList->digests[i].size > + (UINT16)sizeof(pHashList->digests[i].buffer)) { + return BAD_FUNC_ARG; + } + } + + XMEMSET(&policyOR, 0, sizeof(policyOR)); + policyOR.policySession = tpmSession->handle.hndl; + XMEMCPY(&policyOR.pHashList, pHashList, sizeof(policyOR.pHashList)); + return TPM2_PolicyOR(&policyOR); +} + +/* Set (or clear) the authPolicy for a hierarchy (owner/endorsement/platform/ + * lockout). Pass authPolicy=NULL/authPolicySz=0 with hashAlg=TPM_ALG_NULL to + * clear an existing policy. */ +int wolfTPM2_SetPrimaryPolicy(WOLFTPM2_DEV* dev, + TPMI_RH_HIERARCHY_AUTH authHandle, TPM_ALG_ID hashAlg, + const byte* authPolicy, word32 authPolicySz) +{ + SetPrimaryPolicy_In in; + + if (dev == NULL) { + return BAD_FUNC_ARG; + } + if (authPolicySz > (word32)sizeof(in.authPolicy.buffer)) { + return BAD_FUNC_ARG; + } + /* Reject NULL policy with a non-zero size: setting size 0 here would clear + * the policy, silently downgrading "set" to "remove all policy" */ + if (authPolicy == NULL && authPolicySz > 0) { + return BAD_FUNC_ARG; + } + + XMEMSET(&in, 0, sizeof(in)); + in.authHandle = authHandle; + in.hashAlg = hashAlg; + if (authPolicy != NULL && authPolicySz > 0) { + in.authPolicy.size = (UINT16)authPolicySz; + XMEMCPY(in.authPolicy.buffer, authPolicy, authPolicySz); + } + return TPM2_SetPrimaryPolicy(&in); +} + #ifndef WOLFTPM2_NO_WOLFCRYPT /* Authorize a policy based on external key for a verified policy digiest signature */ int wolfTPM2_PolicyAuthorize(WOLFTPM2_DEV* dev, TPM_HANDLE sessionHandle, @@ -10874,6 +10985,41 @@ int wolfTPM2_PolicyHash(TPM_ALG_ID hashAlg, return rc; } +/* Assemble a PolicyCommandCode digest for a fresh policy session */ +/* policyDigest = hash(zeroDigest || TPM_CC_PolicyCommandCode || cc) */ +/* digestSz is in/out: input is the digest buffer capacity, output is the + * hash size actually written */ +int wolfTPM2_PolicyCommandCodeMake(TPM_ALG_ID hashAlg, + byte* digest, word32* digestSz, TPM_CC cc) +{ + int hashSz; + byte val[4]; /* command code big-endian, matching the TPM wire format */ + + if (digest == NULL || digestSz == NULL) { + return BAD_FUNC_ARG; + } + hashSz = TPM2_GetHashDigestSize(hashAlg); + if (hashSz <= 0) { + return BAD_FUNC_ARG; + } + /* The whole hash is written to digest, so the caller must supply at least + * that much room. Checked before any write, since *digestSz (the capacity) + * is overwritten with the output size below. */ + if (*digestSz < (word32)hashSz) { + return BUFFER_E; + } + /* fresh policy session starts from a zero digest of the hash size */ + XMEMSET(digest, 0, hashSz); + *digestSz = (word32)hashSz; + + val[0] = (byte)((cc >> 24) & 0xFF); + val[1] = (byte)((cc >> 16) & 0xFF); + val[2] = (byte)((cc >> 8) & 0xFF); + val[3] = (byte)(cc & 0xFF); + return wolfTPM2_PolicyHash(hashAlg, digest, digestSz, + TPM_CC_PolicyCommandCode, val, sizeof(val)); +} + /* Assemble a PCR policy */ /* policyDigestnew = hash(policyDigestOld || TPM_CC_PolicyPCR || PCRS || * pcrDigest) */ @@ -11092,6 +11238,44 @@ int wolfTPM2_SetIdentityAuth(WOLFTPM2_DEV* dev, WOLFTPM2_HANDLE* handle, #ifdef WOLFTPM_FIRMWARE_UPGRADE +/* Validate a caller-supplied firmware-start authorization session. + * + * The vendor FieldUpgradeStart commands are hand-marshalled (see + * TPM2_IFX_FieldUpgradeStart / TPM2_ST33_FieldUpgradeStart in tpm2.c): they + * emit a TPMS_AUTH_COMMAND carrying only the session handle, with an empty + * nonceCaller, zero sessionAttributes and an empty HMAC. That wire format is + * only correct for an unsalted, unbound policy session with no auth value. Any + * session that would require a computed session HMAC or parameter encryption + * is rejected here instead of failing on the wire with TPM_RC_AUTH_FAIL. */ +static int tpm2_firmware_check_session(WOLFTPM2_SESSION* startSession) +{ + if (startSession == NULL) { + return TPM_RC_SUCCESS; /* library-managed authorization */ + } + /* must be a policy session; an HMAC session always needs a session HMAC */ + if (!TPM2_IS_POLICY_SESSION(startSession->handle.hndl)) { + return BAD_FUNC_ARG; + } + /* PolicyAuthValue / PolicyPassword require the entity auth value to be + * carried in the auth area, which this command does not serialize */ + if (startSession->handle.policyAuth || startSession->handle.policyPass) { + return BAD_FUNC_ARG; + } + /* an attached auth value, a bound session or a salted session all imply a + * non-empty session HMAC */ + if (startSession->handle.auth.size > 0 || startSession->bind != NULL || + startSession->salt.size > 0) { + return BAD_FUNC_ARG; + } + /* parameter encryption and audit are not applied on this raw path */ + if (startSession->sessionAttributes & + (TPMA_SESSION_encrypt | TPMA_SESSION_decrypt | + TPMA_SESSION_audit)) { + return BAD_FUNC_ARG; + } + return TPM_RC_SUCCESS; +} + #if defined(WOLFTPM_SLB9672) || defined(WOLFTPM_SLB9673) /* Maximum size of firmware chunks */ @@ -11101,11 +11285,12 @@ int wolfTPM2_SetIdentityAuth(WOLFTPM2_DEV* dev, WOLFTPM2_HANDLE* handle, static int tpm2_ifx_firmware_enable_policy(WOLFTPM2_DEV* dev) { int rc; - SetPrimaryPolicy_In policy; + byte policyDigest[TPM_MAX_DIGEST_SIZE]; + word32 policySz = (word32)sizeof(policyDigest); WOLFTPM2_SESSION tpmSession; XMEMSET(&tpmSession, 0, sizeof(tpmSession)); - XMEMSET(&policy, 0, sizeof(policy)); + XMEMSET(policyDigest, 0, sizeof(policyDigest)); rc = wolfTPM2_StartSession(dev, &tpmSession, NULL, NULL, TPM_SE_POLICY, TPM_ALG_NULL); @@ -11113,17 +11298,15 @@ static int tpm2_ifx_firmware_enable_policy(WOLFTPM2_DEV* dev) rc = wolfTPM2_PolicyCommandCode(dev, &tpmSession, TPM_CC_FieldUpgradeStartVendor); if (rc == TPM_RC_SUCCESS) { - word32 policySz = (word32)sizeof(policy.authPolicy.buffer); + policySz = (word32)sizeof(policyDigest); rc = wolfTPM2_GetPolicyDigest(dev, tpmSession.handle.hndl, - policy.authPolicy.buffer, &policySz); - policy.authPolicy.size = policySz; + policyDigest, &policySz); } wolfTPM2_UnloadHandle(dev, &tpmSession.handle); } if (rc == TPM_RC_SUCCESS) { - policy.authHandle = TPM_RH_PLATFORM; - policy.hashAlg = TPM_ALG_SHA256; - rc = TPM2_SetPrimaryPolicy(&policy); + rc = wolfTPM2_SetPrimaryPolicy(dev, TPM_RH_PLATFORM, TPM_ALG_SHA256, + policyDigest, policySz); } #ifdef DEBUG_WOLFTPM @@ -11136,10 +11319,13 @@ static int tpm2_ifx_firmware_enable_policy(WOLFTPM2_DEV* dev) } static int tpm2_ifx_firmware_start(WOLFTPM2_DEV* dev, TPM_ALG_ID hashAlg, - uint8_t* manifest_hash, uint32_t manifest_hash_sz) + uint8_t* manifest_hash, uint32_t manifest_hash_sz, + WOLFTPM2_SESSION* startSession) { int rc; WOLFTPM2_SESSION tpmSession; + TPM_HANDLE sessionHandle = TPM_RH_NULL; + int ownSession = 0; if (dev == NULL || manifest_hash == NULL || manifest_hash_sz == 0 || manifest_hash_sz > TPM_SHA512_DIGEST_SIZE) { @@ -11151,44 +11337,65 @@ static int tpm2_ifx_firmware_start(WOLFTPM2_DEV* dev, TPM_ALG_ID hashAlg, XMEMSET(&tpmSession, 0, sizeof(tpmSession)); - rc = wolfTPM2_StartSession(dev, &tpmSession, NULL, NULL, - TPM_SE_POLICY, TPM_ALG_NULL); - if (rc == TPM_RC_SUCCESS) { - rc = wolfTPM2_PolicyCommandCode(dev, &tpmSession, - TPM_CC_FieldUpgradeStartVendor); - if (rc == TPM_RC_SUCCESS) { - /* build command for manifest header */ - uint16_t val16; - /* max cmd: type (1) + data sz (2) + hash alg (2) + max digest (64) */ - uint8_t cmd[1 + 2 + 2 + TPM_SHA512_DIGEST_SIZE]; - cmd[0] = 0x01; /* type */ - val16 = be16_to_cpu(manifest_hash_sz + 2); - XMEMCPY(&cmd[1], &val16, sizeof(val16)); /* data size */ - val16 = be16_to_cpu(hashAlg); - XMEMCPY(&cmd[3], &val16, sizeof(val16)); /* hash algorithm */ - XMEMCPY(&cmd[5], manifest_hash, manifest_hash_sz); - - rc = TPM2_IFX_FieldUpgradeStart(tpmSession.handle.hndl, - cmd, 1 + 2 + 2 + manifest_hash_sz); - } + if (startSession != NULL) { + /* Caller has already satisfied the platform policy on this session */ + sessionHandle = startSession->handle.hndl; + rc = TPM_RC_SUCCESS; + } + else { + /* Default: internal policy session asserting the firmware start + * command code, matching the policy installed on the platform + * hierarchy by tpm2_ifx_firmware_enable_policy */ + rc = wolfTPM2_StartSession(dev, &tpmSession, NULL, NULL, + TPM_SE_POLICY, TPM_ALG_NULL); if (rc == TPM_RC_SUCCESS) { - /* delay to give the TPM time to switch modes */ - XSLEEP_MS(300); - /* it is not required to release session handle, - * since TPM reset into firmware upgrade mode */ - - #if !defined(WOLFTPM_LINUX_DEV) && !defined(WOLFTPM_SWTPM) && \ - !defined(WOLFTPM_WINAPI) - /* Do chip startup and request locality again */ - #ifdef WOLFTPM_LINUX_DEV_AUTODETECT - if (dev->ctx.fd < 0) /* Only needed for SPI path */ - #endif - rc = TPM2_ChipStartup(&dev->ctx, 10); - #endif + ownSession = 1; + sessionHandle = tpmSession.handle.hndl; + rc = wolfTPM2_PolicyCommandCode(dev, &tpmSession, + TPM_CC_FieldUpgradeStartVendor); } - else { - wolfTPM2_UnloadHandle(dev, &tpmSession.handle); + } + + if (rc == TPM_RC_SUCCESS) { + /* build command for manifest header */ + uint16_t val16; + /* max cmd: type (1) + data sz (2) + hash alg (2) + max digest (64) */ + uint8_t cmd[1 + 2 + 2 + TPM_SHA512_DIGEST_SIZE]; + cmd[0] = 0x01; /* type */ + val16 = be16_to_cpu(manifest_hash_sz + 2); + XMEMCPY(&cmd[1], &val16, sizeof(val16)); /* data size */ + val16 = be16_to_cpu(hashAlg); + XMEMCPY(&cmd[3], &val16, sizeof(val16)); /* hash algorithm */ + XMEMCPY(&cmd[5], manifest_hash, manifest_hash_sz); + + rc = TPM2_IFX_FieldUpgradeStart(sessionHandle, + cmd, 1 + 2 + 2 + manifest_hash_sz); + } + + if (rc == TPM_RC_SUCCESS) { + /* The TPM consumed the session entering firmware upgrade mode; mark a + * caller-supplied session as released so the caller does not flush it */ + if (startSession != NULL) { + startSession->handle.hndl = TPM_RH_NULL; } + + /* delay to give the TPM time to switch modes */ + XSLEEP_MS(300); + /* it is not required to release session handle, + * since TPM reset into firmware upgrade mode */ + + #if !defined(WOLFTPM_LINUX_DEV) && !defined(WOLFTPM_SWTPM) && \ + !defined(WOLFTPM_WINAPI) + /* Do chip startup and request locality again */ + #ifdef WOLFTPM_LINUX_DEV_AUTODETECT + if (dev->ctx.fd < 0) /* Only needed for SPI path */ + #endif + rc = TPM2_ChipStartup(&dev->ctx, 10); + #endif + } + else if (ownSession) { + /* only release a session we started ourselves */ + wolfTPM2_UnloadHandle(dev, &tpmSession.handle); } #ifdef DEBUG_WOLFTPM if (rc != TPM_RC_SUCCESS) { @@ -11345,7 +11552,7 @@ static int tpm2_ifx_firmware_final(WOLFTPM2_DEV* dev) static int tpm2_st33_firmware_upgrade_hash(WOLFTPM2_DEV* dev, TPM_ALG_ID hashAlg, uint8_t* manifest_hash, uint32_t manifest_hash_sz, uint8_t* manifest, uint32_t manifest_sz, - wolfTPM2FwDataCb cb, void* cb_ctx); + wolfTPM2FwDataCb cb, void* cb_ctx, WOLFTPM2_SESSION* startSession); static int tpm2_st33_firmware_cancel(WOLFTPM2_DEV* dev); #endif @@ -11353,10 +11560,28 @@ int wolfTPM2_FirmwareUpgradeHash(WOLFTPM2_DEV* dev, TPM_ALG_ID hashAlg, uint8_t* manifest_hash, uint32_t manifest_hash_sz, uint8_t* manifest, uint32_t manifest_sz, wolfTPM2FwDataCb cb, void* cb_ctx) +{ + /* Default behavior: library-managed platform authorization */ + return wolfTPM2_FirmwareUpgradeHash_ex(dev, hashAlg, + manifest_hash, manifest_hash_sz, manifest, manifest_sz, + cb, cb_ctx, NULL); +} + +int wolfTPM2_FirmwareUpgradeHash_ex(WOLFTPM2_DEV* dev, TPM_ALG_ID hashAlg, + uint8_t* manifest_hash, uint32_t manifest_hash_sz, + uint8_t* manifest, uint32_t manifest_sz, + wolfTPM2FwDataCb cb, void* cb_ctx, WOLFTPM2_SESSION* startSession) { int rc; WOLFTPM2_CAPS caps; + /* Check the caller session before any TPM traffic, so an unsupported + * session is reported without leaving the TPM in a partial state */ + rc = tpm2_firmware_check_session(startSession); + if (rc != TPM_RC_SUCCESS) { + return rc; + } + /* Get capabilities to determine manufacturer */ rc = wolfTPM2_GetCapabilities(dev, &caps); if (rc != TPM_RC_SUCCESS) { @@ -11370,7 +11595,7 @@ int wolfTPM2_FirmwareUpgradeHash(WOLFTPM2_DEV* dev, TPM_ALG_ID hashAlg, return tpm2_st33_firmware_upgrade_hash(dev, hashAlg, manifest_hash, manifest_hash_sz, manifest, manifest_sz, - cb, cb_ctx); + cb, cb_ctx, startSession); } #endif @@ -11386,10 +11611,18 @@ int wolfTPM2_FirmwareUpgradeHash(WOLFTPM2_DEV* dev, TPM_ALG_ID hashAlg, return tpm2_ifx_firmware_final(dev); } if (caps.opMode == 0x00) { - rc = tpm2_ifx_firmware_enable_policy(dev); + /* Ensure rc is assigned in this scope regardless of the branch + * below (the caller-session path does not call enable_policy). */ + rc = TPM_RC_SUCCESS; + /* When the caller supplies a session it must already satisfy the + * platform authPolicy, so do not overwrite the platform primary + * policy - only manage it for the library-default path */ + if (startSession == NULL) { + rc = tpm2_ifx_firmware_enable_policy(dev); + } if (rc == TPM_RC_SUCCESS) { rc = tpm2_ifx_firmware_start(dev, hashAlg, - manifest_hash, manifest_hash_sz); + manifest_hash, manifest_hash_sz, startSession); } } if (rc == TPM_RC_SUCCESS) { @@ -11419,9 +11652,9 @@ int wolfTPM2_FirmwareUpgradeHash(WOLFTPM2_DEV* dev, TPM_ALG_ID hashAlg, } #ifndef WOLFTPM2_NO_WOLFCRYPT -int wolfTPM2_FirmwareUpgrade(WOLFTPM2_DEV* dev, +int wolfTPM2_FirmwareUpgrade_ex(WOLFTPM2_DEV* dev, uint8_t* manifest, uint32_t manifest_sz, - wolfTPM2FwDataCb cb, void* cb_ctx) + wolfTPM2FwDataCb cb, void* cb_ctx, WOLFTPM2_SESSION* startSession) { #ifdef WOLFSSL_SHA384 int rc; @@ -11430,31 +11663,49 @@ int wolfTPM2_FirmwareUpgrade(WOLFTPM2_DEV* dev, /* hash the manifest */ rc = wc_Sha384Hash(manifest, manifest_sz, manifest_hash); if (rc == 0) { - rc = wolfTPM2_FirmwareUpgradeHash(dev, TPM_ALG_SHA384, + rc = wolfTPM2_FirmwareUpgradeHash_ex(dev, TPM_ALG_SHA384, manifest_hash, (uint32_t)sizeof(manifest_hash), - manifest, manifest_sz, cb, cb_ctx); + manifest, manifest_sz, cb, cb_ctx, startSession); } return rc; #else (void)dev; (void)manifest; (void)manifest_sz; - (void)cb; (void)cb_ctx; + (void)cb; (void)cb_ctx; (void)startSession; return NOT_COMPILED_IN; #endif } -#endif -int wolfTPM2_FirmwareUpgradeRecover(WOLFTPM2_DEV* dev, +int wolfTPM2_FirmwareUpgrade(WOLFTPM2_DEV* dev, uint8_t* manifest, uint32_t manifest_sz, wolfTPM2FwDataCb cb, void* cb_ctx) +{ + /* Default behavior: library-managed platform authorization */ + return wolfTPM2_FirmwareUpgrade_ex(dev, manifest, manifest_sz, + cb, cb_ctx, NULL); +} +#endif + +int wolfTPM2_FirmwareUpgradeRecover_ex(WOLFTPM2_DEV* dev, + uint8_t* manifest, uint32_t manifest_sz, + wolfTPM2FwDataCb cb, void* cb_ctx, WOLFTPM2_SESSION* startSession) { uint8_t manifest_hash[TPM_SHA384_DIGEST_SIZE]; /* recovery mode manifest hash is all 0x3C */ XMEMSET(manifest_hash, 0x3C, sizeof(manifest_hash)); - return wolfTPM2_FirmwareUpgradeHash(dev, TPM_ALG_SHA384, + return wolfTPM2_FirmwareUpgradeHash_ex(dev, TPM_ALG_SHA384, manifest_hash, (uint32_t)sizeof(manifest_hash), - manifest, manifest_sz, cb, cb_ctx); + manifest, manifest_sz, cb, cb_ctx, startSession); +} + +int wolfTPM2_FirmwareUpgradeRecover(WOLFTPM2_DEV* dev, + uint8_t* manifest, uint32_t manifest_sz, + wolfTPM2FwDataCb cb, void* cb_ctx) +{ + /* Default behavior: library-managed platform authorization */ + return wolfTPM2_FirmwareUpgradeRecover_ex(dev, manifest, manifest_sz, + cb, cb_ctx, NULL); } /* terminate a firmware update */ @@ -11528,19 +11779,31 @@ int wolfTPM2_FirmwareUpgradeCancel(WOLFTPM2_DEV* dev) * 300ms delay: ST reference implementation uses this delay to allow * TPM to switch modes after FieldUpgradeStart command */ static int tpm2_st33_firmware_start_common(WOLFTPM2_DEV* dev, - uint8_t* manifest, uint32_t manifest_sz, int is_lms) + uint8_t* manifest, uint32_t manifest_sz, int is_lms, + WOLFTPM2_SESSION* startSession) { int rc; + TPM_HANDLE sessionHandle; (void)dev; - /* ST33 uses password auth (TPM_RS_PW) for FieldUpgradeStart. - * This matches the ST reference implementation behavior. + /* By default ST33 uses password auth (TPM_RS_PW) for FieldUpgradeStart, + * matching the ST reference implementation behavior. When the caller + * supplies a session (for example a policy session that satisfies a + * custom platform authPolicy), use it instead. * For LMS format, the manifest (blob0) already contains the embedded * LMS signature. Send the full manifest directly. */ - rc = TPM2_ST33_FieldUpgradeStart(TPM_RS_PW, manifest, manifest_sz); + sessionHandle = (startSession != NULL) ? + startSession->handle.hndl : (TPM_HANDLE)TPM_RS_PW; + rc = TPM2_ST33_FieldUpgradeStart(sessionHandle, manifest, manifest_sz); if (rc == TPM_RC_SUCCESS) { + /* The TPM consumed the session entering firmware upgrade mode; mark a + * caller-supplied session as released so the caller does not flush it */ + if (startSession != NULL) { + startSession->handle.hndl = TPM_RH_NULL; + } + /* 300ms delay: ST reference implementation uses this delay to allow * TPM to switch modes after FieldUpgradeStart command */ XSLEEP_MS(300); @@ -11697,7 +11960,7 @@ static int tpm2_st33_firmware_data(WOLFTPM2_DEV* dev, static int tpm2_st33_firmware_upgrade_hash(WOLFTPM2_DEV* dev, TPM_ALG_ID hashAlg, uint8_t* manifest_hash, uint32_t manifest_hash_sz, uint8_t* manifest, uint32_t manifest_sz, - wolfTPM2FwDataCb cb, void* cb_ctx) + wolfTPM2FwDataCb cb, void* cb_ctx, WOLFTPM2_SESSION* startSession) { int rc; WOLFTPM2_CAPS caps; @@ -11771,7 +12034,8 @@ static int tpm2_st33_firmware_upgrade_hash(WOLFTPM2_DEV* dev, TPM_ALG_ID hashAlg } /* Send manifest - the common function handles both LMS and non-LMS */ - rc = tpm2_st33_firmware_start_common(dev, manifest, manifest_sz, is_lms); + rc = tpm2_st33_firmware_start_common(dev, manifest, manifest_sz, is_lms, + startSession); if (rc == TPM_RC_SUCCESS) { rc = tpm2_st33_firmware_data(dev, cb, cb_ctx); diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 0a18a44c3..564e7f819 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -282,6 +282,7 @@ static void test_wolfTPM2_ReadPublicKey(void) static void test_wolfTPM2_ST33_FirmwareUpgrade(void) { int rc; + int rcEx; WOLFTPM2_DEV dev; WOLFTPM2_CAPS caps; #if !defined(WOLFTPM2_NO_WOLFCRYPT) && defined(WOLFSSL_SHA384) @@ -323,10 +324,30 @@ static void test_wolfTPM2_ST33_FirmwareUpgrade(void) rc = wolfTPM2_FirmwareUpgradeRecover(NULL, NULL, 0, NULL, NULL); AssertIntNE(rc, 0); + /* _ex variants with caller session - NULL dev */ + rc = wolfTPM2_FirmwareUpgradeHash_ex(NULL, TPM_ALG_SHA384, NULL, 0, NULL, + 0, NULL, NULL, NULL); + AssertIntNE(rc, 0); + rc = wolfTPM2_FirmwareUpgradeRecover_ex(NULL, NULL, 0, NULL, NULL, NULL); + AssertIntNE(rc, 0); + + /* startSession == NULL delegates to the legacy call (same rc). Use a NULL + * dev so this never reaches the TPM (a live dev under autodetect could + * otherwise push an Infineon part into firmware-upgrade mode). */ + rc = wolfTPM2_FirmwareUpgradeHash(NULL, TPM_ALG_SHA384, + NULL, 0, NULL, 0, NULL, NULL); + rcEx = wolfTPM2_FirmwareUpgradeHash_ex(NULL, TPM_ALG_SHA384, + NULL, 0, NULL, 0, NULL, NULL, NULL); + AssertIntEQ(rc, rcEx); + #if !defined(WOLFTPM2_NO_WOLFCRYPT) && defined(WOLFSSL_SHA384) /* wolfTPM2_FirmwareUpgrade - NULL dev */ rc = wolfTPM2_FirmwareUpgrade(NULL, NULL, 0, NULL, NULL); AssertIntNE(rc, 0); + + /* wolfTPM2_FirmwareUpgrade_ex - NULL dev */ + rc = wolfTPM2_FirmwareUpgrade_ex(NULL, NULL, 0, NULL, NULL, NULL); + AssertIntNE(rc, 0); #endif /* !WOLFTPM2_NO_WOLFCRYPT && WOLFSSL_SHA384 */ /* ===== Test NULL/invalid parameter combinations ===== */ @@ -376,6 +397,632 @@ static void test_wolfTPM2_ST33_FirmwareUpgrade(void) #endif /* WOLFTPM_ST33 || WOLFTPM_AUTODETECT */ #endif /* WOLFTPM_FIRMWARE_UPGRADE */ +#ifdef WOLFTPM_FIRMWARE_UPGRADE +/* The vendor FieldUpgradeStart commands serialize an authorization area that + * carries only the session handle - empty nonceCaller, zero attributes, empty + * HMAC. A caller session that would need a computed session HMAC or parameter + * encryption must therefore be rejected by wolfTPM2_FirmwareUpgradeHash_ex + * before any command is sent, rather than failing on the wire. + * + * These cases run against the simulator only (and only when it reports an + * unknown manufacturer) so a real TPM is never pushed toward firmware-upgrade + * mode by the accepted-session case. */ +static void test_wolfTPM2_FirmwareUpgrade_ex_session(void) +{ +#if defined(WOLFTPM_SWTPM) + int rc; + WOLFTPM2_DEV dev; + WOLFTPM2_CAPS caps; + WOLFTPM2_SESSION sess; + TPM2B_AUTH bindAuth; + uint8_t hash[TPM_SHA384_DIGEST_SIZE]; + + XMEMSET(&dev, 0, sizeof(dev)); + XMEMSET(&caps, 0, sizeof(caps)); + XMEMSET(&bindAuth, 0, sizeof(bindAuth)); + XMEMSET(hash, 0, sizeof(hash)); + + rc = wolfTPM2_Init(&dev, TPM2_IoCb, NULL); + AssertIntEQ(rc, 0); + rc = wolfTPM2_GetCapabilities(&dev, &caps); + AssertIntEQ(rc, 0); + if (caps.mfg != TPM_MFG_UNKNOWN) { + /* not the simulator - do not exercise firmware upgrade paths */ + wolfTPM2_Cleanup(&dev); + printf("Test FW Upgr _ex: %-40s Skipped (real TPM)\n", + "Session Validation:"); + return; + } + + /* A clean, unsalted, unbound policy session passes validation and reaches + * the manufacturer dispatch, which rejects the simulator with + * TPM_RC_COMMAND_CODE. This proves the checks below are real rejections + * and not just the generic argument handling. */ + XMEMSET(&sess, 0, sizeof(sess)); + sess.handle.hndl = POLICY_SESSION_FIRST; + rc = wolfTPM2_FirmwareUpgradeHash_ex(&dev, TPM_ALG_SHA384, + hash, (uint32_t)sizeof(hash), NULL, 0, NULL, NULL, &sess); + AssertIntEQ(rc, TPM_RC_COMMAND_CODE); + + /* An HMAC (non-policy) session handle always needs a session HMAC */ + XMEMSET(&sess, 0, sizeof(sess)); + sess.handle.hndl = HMAC_SESSION_FIRST; + AssertIntEQ(wolfTPM2_FirmwareUpgradeHash_ex(&dev, TPM_ALG_SHA384, + hash, (uint32_t)sizeof(hash), NULL, 0, NULL, NULL, &sess), + BAD_FUNC_ARG); + + /* wolfTPM2_PolicyAuthValue marks the session; the auth value it needs is + * not serialized by the vendor command */ + XMEMSET(&sess, 0, sizeof(sess)); + sess.handle.hndl = POLICY_SESSION_FIRST; + sess.handle.policyAuth = 1; + AssertIntEQ(wolfTPM2_FirmwareUpgradeHash_ex(&dev, TPM_ALG_SHA384, + hash, (uint32_t)sizeof(hash), NULL, 0, NULL, NULL, &sess), + BAD_FUNC_ARG); + + /* wolfTPM2_PolicyPassword likewise */ + XMEMSET(&sess, 0, sizeof(sess)); + sess.handle.hndl = POLICY_SESSION_FIRST; + sess.handle.policyPass = 1; + AssertIntEQ(wolfTPM2_FirmwareUpgradeHash_ex(&dev, TPM_ALG_SHA384, + hash, (uint32_t)sizeof(hash), NULL, 0, NULL, NULL, &sess), + BAD_FUNC_ARG); + + /* an attached auth value implies a non-empty session HMAC */ + XMEMSET(&sess, 0, sizeof(sess)); + sess.handle.hndl = POLICY_SESSION_FIRST; + sess.handle.auth.size = 4; + AssertIntEQ(wolfTPM2_FirmwareUpgradeHash_ex(&dev, TPM_ALG_SHA384, + hash, (uint32_t)sizeof(hash), NULL, 0, NULL, NULL, &sess), + BAD_FUNC_ARG); + + /* a bound session implies a non-empty session HMAC */ + XMEMSET(&sess, 0, sizeof(sess)); + sess.handle.hndl = POLICY_SESSION_FIRST; + sess.bind = &bindAuth; + AssertIntEQ(wolfTPM2_FirmwareUpgradeHash_ex(&dev, TPM_ALG_SHA384, + hash, (uint32_t)sizeof(hash), NULL, 0, NULL, NULL, &sess), + BAD_FUNC_ARG); + + /* a salted session implies a non-empty session HMAC */ + XMEMSET(&sess, 0, sizeof(sess)); + sess.handle.hndl = POLICY_SESSION_FIRST; + sess.salt.size = 16; + AssertIntEQ(wolfTPM2_FirmwareUpgradeHash_ex(&dev, TPM_ALG_SHA384, + hash, (uint32_t)sizeof(hash), NULL, 0, NULL, NULL, &sess), + BAD_FUNC_ARG); + + /* parameter encryption is not applied on this raw path */ + XMEMSET(&sess, 0, sizeof(sess)); + sess.handle.hndl = POLICY_SESSION_FIRST; + sess.sessionAttributes = TPMA_SESSION_encrypt; + AssertIntEQ(wolfTPM2_FirmwareUpgradeHash_ex(&dev, TPM_ALG_SHA384, + hash, (uint32_t)sizeof(hash), NULL, 0, NULL, NULL, &sess), + BAD_FUNC_ARG); + + /* the same validation guards the recover entry point */ + XMEMSET(&sess, 0, sizeof(sess)); + sess.handle.hndl = POLICY_SESSION_FIRST; + sess.handle.policyAuth = 1; + AssertIntEQ(wolfTPM2_FirmwareUpgradeRecover_ex(&dev, NULL, 0, NULL, NULL, + &sess), BAD_FUNC_ARG); + + wolfTPM2_Cleanup(&dev); + printf("Test FW Upgr _ex: %-40s Passed\n", "Session Validation:"); +#else + printf("Test FW Upgr _ex: %-40s Skipped (requires SWTPM)\n", + "Session Validation:"); +#endif /* WOLFTPM_SWTPM */ +} +#endif /* WOLFTPM_FIRMWARE_UPGRADE */ + +/* Regression test for hierarchy authorization after SetPrimaryPolicy. + * + * Per TPM 2.0 Part 1 Sec.19.7 a hierarchy is authorized by EITHER its + * authValue OR its authPolicy. Installing an authPolicy therefore does not + * lock out the password path, which is what lets the firmware examples roll + * back a policy they provisioned (see examples/firmware/firmware_policy.c). + * This pins that behavior: a password session can still clear the policy, and + * a policy session with a non-matching digest is still rejected (so the + * password success above is not simply an unchecked auth path). + * + * Simulator only - never provision a platform policy on a real TPM. */ +static void test_wolfTPM2_SetPrimaryPolicy_rollback(void) +{ +#if defined(WOLFTPM_SWTPM) && !defined(WOLFTPM2_NO_WOLFCRYPT) + int rc; + int startRc = 0, authRc = 0, clearRc = 0; + int mismatchRc = TPM_RC_SUCCESS; /* must end up != SUCCESS */ + WOLFTPM2_DEV dev; + WOLFTPM2_CAPS caps; + WOLFTPM2_SESSION sess; + byte policy[TPM_MAX_DIGEST_SIZE]; + word32 policySz = (word32)sizeof(policy); + + XMEMSET(&dev, 0, sizeof(dev)); + XMEMSET(&caps, 0, sizeof(caps)); + XMEMSET(&sess, 0, sizeof(sess)); + + rc = wolfTPM2_Init(&dev, TPM2_IoCb, NULL); + AssertIntEQ(rc, 0); + rc = wolfTPM2_GetCapabilities(&dev, &caps); + AssertIntEQ(rc, 0); + if (caps.mfg != TPM_MFG_UNKNOWN) { + wolfTPM2_Cleanup(&dev); + printf("Test SetPrimPol: %-40s Skipped (real TPM)\n", "Rollback:"); + return; + } + /* defensive: clear any policy a previously aborted run left behind */ + (void)wolfTPM2_SetPrimaryPolicy(&dev, TPM_RH_PLATFORM, TPM_ALG_NULL, + NULL, 0); + + /* Provision a platform authPolicy, mirroring what the firmware examples do + * before an upgrade. Use the same digest shape: PolicyCommandCode. */ + rc = wolfTPM2_PolicyCommandCodeMake(TPM_ALG_SHA256, policy, &policySz, + TPM_CC_SetPrimaryPolicy); + AssertIntEQ(rc, 0); + rc = wolfTPM2_SetPrimaryPolicy(&dev, TPM_RH_PLATFORM, TPM_ALG_SHA256, + policy, policySz); + AssertIntEQ(rc, TPM_RC_SUCCESS); + + /* From here the platform hierarchy is gated by that policy. Assert*() is + * abort(), so record results into locals and do not assert until the + * policy has been cleared again - otherwise a failure here would leave a + * persistent swtpm state dir with a policy-gated platform hierarchy, + * wedging every later run. */ + + /* A policy session whose running digest does not match the installed + * authPolicy must be rejected - proves the policy is actually enforced. */ + startRc = wolfTPM2_StartSession(&dev, &sess, NULL, NULL, TPM_SE_POLICY, + TPM_ALG_NULL); + if (startRc == 0) { + authRc = wolfTPM2_SetAuthSession(&dev, 0, &sess, 0); + if (authRc == 0) { + /* expected to FAIL: digest does not match */ + mismatchRc = wolfTPM2_SetPrimaryPolicy(&dev, TPM_RH_PLATFORM, + TPM_ALG_NULL, NULL, 0); + } + /* restore default password authorization and release the session */ + wolfTPM2_SetAuthPassword(&dev, 0, NULL); + wolfTPM2_UnloadHandle(&dev, &sess.handle); + } + + /* The password path still authorizes the hierarchy, so the example's + * rollback works without a session that satisfies the installed policy. + * This also restores the simulator to a clean state. */ + clearRc = wolfTPM2_SetPrimaryPolicy(&dev, TPM_RH_PLATFORM, TPM_ALG_NULL, + NULL, 0); + + wolfTPM2_Cleanup(&dev); + + /* safe to abort now - the platform hierarchy carries no policy */ + AssertIntEQ(startRc, 0); + AssertIntEQ(authRc, 0); + AssertIntNE(mismatchRc, TPM_RC_SUCCESS); + AssertIntEQ(clearRc, TPM_RC_SUCCESS); + printf("Test SetPrimPol: %-40s Passed\n", "Rollback:"); +#else + printf("Test SetPrimPol: %-40s Skipped (requires SWTPM)\n", "Rollback:"); +#endif /* WOLFTPM_SWTPM && !WOLFTPM2_NO_WOLFCRYPT */ +} + +/* Cover the mechanism behind firmware_policy_clear_by_policy(): when + * platformAuth is NOT the default empty password, the password rollback path + * fails and the example falls back to authorizing TPM2_SetPrimaryPolicy under + * the provisioned PolicyOR's PolicyCommandCode(TPM_CC_SetPrimaryPolicy) + * branch. The example helper itself lives in examples/ and is not linked into + * the unit suite, so this exercises the same library call sequence directly. + * + * Simulator only - this sets and clears platformAuth. */ +static void test_wolfTPM2_PolicyClear_underPolicy(void) +{ +#if defined(WOLFTPM_SWTPM) && !defined(WOLFTPM2_NO_WOLFCRYPT) + int rc; + int pwClearRc = 0, polClearRc = 0, finalRc = 0; + WOLFTPM2_DEV dev; + WOLFTPM2_CAPS caps; + WOLFTPM2_SESSION sess; + TPML_DIGEST orList; + TPM2B_AUTH platAuth; + HierarchyChangeAuth_In changeIn; + byte branchFu[TPM_MAX_DIGEST_SIZE]; + byte branchSpp[TPM_MAX_DIGEST_SIZE]; + byte concat[2 * TPM_MAX_DIGEST_SIZE]; + byte policy[TPM_MAX_DIGEST_SIZE]; + word32 aSz, bSz, polSz; + + XMEMSET(&dev, 0, sizeof(dev)); + XMEMSET(&caps, 0, sizeof(caps)); + XMEMSET(&sess, 0, sizeof(sess)); + XMEMSET(&orList, 0, sizeof(orList)); + XMEMSET(&platAuth, 0, sizeof(platAuth)); + + rc = wolfTPM2_Init(&dev, TPM2_IoCb, NULL); + AssertIntEQ(rc, 0); + rc = wolfTPM2_GetCapabilities(&dev, &caps); + AssertIntEQ(rc, 0); + if (caps.mfg != TPM_MFG_UNKNOWN) { + wolfTPM2_Cleanup(&dev); + printf("Test PolicyClear: %-40s Skipped (real TPM)\n", "Under Policy:"); + return; + } + (void)wolfTPM2_SetPrimaryPolicy(&dev, TPM_RH_PLATFORM, TPM_ALG_NULL, + NULL, 0); + + /* Build the two branches the example provisions: the firmware-start + * branch (stood in for by NV_Read here, since the vendor CC is not + * meaningful to the simulator) and the SetPrimaryPolicy rollback branch. */ + aSz = (word32)sizeof(branchFu); + AssertIntEQ(wolfTPM2_PolicyCommandCodeMake(TPM_ALG_SHA256, branchFu, &aSz, + TPM_CC_NV_Read), 0); + bSz = (word32)sizeof(branchSpp); + AssertIntEQ(wolfTPM2_PolicyCommandCodeMake(TPM_ALG_SHA256, branchSpp, &bSz, + TPM_CC_SetPrimaryPolicy), 0); + XMEMCPY(concat, branchFu, aSz); + XMEMCPY(&concat[aSz], branchSpp, bSz); + XMEMSET(policy, 0, sizeof(policy)); + polSz = TPM_SHA256_DIGEST_SIZE; + AssertIntEQ(wolfTPM2_PolicyHash(TPM_ALG_SHA256, policy, &polSz, + TPM_CC_PolicyOR, concat, aSz + bSz), 0); + + /* provision the platform authPolicy while platformAuth is still empty */ + AssertIntEQ(wolfTPM2_SetPrimaryPolicy(&dev, TPM_RH_PLATFORM, + TPM_ALG_SHA256, policy, polSz), TPM_RC_SUCCESS); + + /* Now make the password path unusable, the situation the fallback exists + * for. Use TPM2_HierarchyChangeAuth directly with a KNOWN value rather + * than wolfTPM2_ChangeHierarchyAuth, which sets a random auth that could + * never be restored - that would permanently wedge the simulator's + * platform hierarchy for every later test. Everything from here records + * into locals so an abort cannot leave the hierarchy modified. */ + XMEMSET(&changeIn, 0, sizeof(changeIn)); + changeIn.authHandle = TPM_RH_PLATFORM; + changeIn.newAuth.size = 4; + XMEMCPY(changeIn.newAuth.buffer, "hier", 4); + rc = TPM2_HierarchyChangeAuth(&changeIn); + /* Deliberately leave dev->session[0] holding the EMPTY password: that is + * what makes the example's first rollback attempt fail below, which is the + * condition the policy fallback exists to handle. platAuth is kept so the + * restore at the end can authorize itself. */ + platAuth.size = 4; + XMEMCPY(platAuth.buffer, "hier", 4); + + if (rc == 0) { + /* the example's first attempt - password auth - must now fail */ + pwClearRc = wolfTPM2_SetPrimaryPolicy(&dev, TPM_RH_PLATFORM, + TPM_ALG_NULL, NULL, 0); + + /* fallback: satisfy the SetPrimaryPolicy branch, then clear */ + if (wolfTPM2_StartSession(&dev, &sess, NULL, NULL, TPM_SE_POLICY, + TPM_ALG_NULL) == 0) { + if (wolfTPM2_PolicyCommandCode(&dev, &sess, + TPM_CC_SetPrimaryPolicy) == 0) { + orList.count = 2; + orList.digests[0].size = (UINT16)aSz; + XMEMCPY(orList.digests[0].buffer, branchFu, aSz); + orList.digests[1].size = (UINT16)bSz; + XMEMCPY(orList.digests[1].buffer, branchSpp, bSz); + if (wolfTPM2_PolicyOR(&dev, &sess, &orList) == 0 && + wolfTPM2_SetAuthSession(&dev, 0, &sess, 0) == 0) { + polClearRc = wolfTPM2_SetPrimaryPolicy(&dev, + TPM_RH_PLATFORM, TPM_ALG_NULL, NULL, 0); + } + } + wolfTPM2_SetAuthPassword(&dev, 0, NULL); + wolfTPM2_UnloadHandle(&dev, &sess.handle); + } + } + + /* restore the default empty platformAuth so later tests are unaffected. + * The current (known) auth authorizes this change. */ + wolfTPM2_SetAuthPassword(&dev, 0, &platAuth); + XMEMSET(&changeIn, 0, sizeof(changeIn)); + changeIn.authHandle = TPM_RH_PLATFORM; + changeIn.newAuth.size = 0; + finalRc = TPM2_HierarchyChangeAuth(&changeIn); + wolfTPM2_SetAuthPassword(&dev, 0, NULL); + (void)wolfTPM2_SetPrimaryPolicy(&dev, TPM_RH_PLATFORM, TPM_ALG_NULL, + NULL, 0); + wolfTPM2_Cleanup(&dev); + + /* safe to abort now - hierarchy state has been restored */ + AssertIntEQ(rc, 0); + AssertIntNE(pwClearRc, TPM_RC_SUCCESS); /* password path must fail */ + AssertIntEQ(polClearRc, TPM_RC_SUCCESS); /* policy fallback must work */ + AssertIntEQ(finalRc, 0); + printf("Test PolicyClear: %-40s Passed\n", "Under Policy:"); +#else + printf("Test PolicyClear: %-40s Skipped (requires SWTPM)\n", + "Under Policy:"); +#endif /* WOLFTPM_SWTPM && !WOLFTPM2_NO_WOLFCRYPT */ +} + +/* Argument-validation coverage for wolfTPM2_PolicyOR (host-side, no TPM). */ +static void test_wolfTPM2_PolicyOR(void) +{ + WOLFTPM2_DEV dev; + WOLFTPM2_SESSION sess; + TPML_DIGEST list; + word32 cap = (word32)(sizeof(list.digests) / sizeof(list.digests[0])); + + XMEMSET(&dev, 0, sizeof(dev)); + XMEMSET(&sess, 0, sizeof(sess)); + XMEMSET(&list, 0, sizeof(list)); + list.count = 2; + list.digests[0].size = TPM_SHA256_DIGEST_SIZE; + list.digests[1].size = TPM_SHA256_DIGEST_SIZE; + + /* NULL pointer arguments */ + AssertIntEQ(wolfTPM2_PolicyOR(NULL, &sess, &list), BAD_FUNC_ARG); + AssertIntEQ(wolfTPM2_PolicyOR(&dev, NULL, &list), BAD_FUNC_ARG); + AssertIntEQ(wolfTPM2_PolicyOR(&dev, &sess, NULL), BAD_FUNC_ARG); + + /* count of 0 is invalid */ + list.count = 0; + AssertIntEQ(wolfTPM2_PolicyOR(&dev, &sess, &list), BAD_FUNC_ARG); + + /* TPM2_PolicyOR requires at least two digests (TPM 2.0 Part 3 Sec.23.6), + * so a one-branch list must be rejected here rather than sent to the TPM, + * which would answer TPM_RC_VALUE. */ + list.count = 1; + AssertIntEQ(wolfTPM2_PolicyOR(&dev, &sess, &list), BAD_FUNC_ARG); + + /* count beyond the TPML_DIGEST capacity is invalid */ + list.count = cap + 1; + AssertIntEQ(wolfTPM2_PolicyOR(&dev, &sess, &list), BAD_FUNC_ARG); + + /* a branch digest size larger than the buffer is invalid (CWE-125). Use a + * valid count so this fails for the size reason, not the count reason. */ + list.count = 2; + list.digests[0].size = (UINT16)(sizeof(list.digests[0].buffer) + 1); + AssertIntEQ(wolfTPM2_PolicyOR(&dev, &sess, &list), BAD_FUNC_ARG); + + printf("Test PolicyOR: %-40s Passed\n", "Arg Validation:"); +} + +#ifndef WOLFTPM2_NO_WOLFCRYPT +/* Known-answer + arg-validation for wolfTPM2_PolicyCommandCodeMake (no TPM). + * Requires wolfCrypt for the policy hash. Vectors are the offline digest + * H(zeros(hashSz) || TPM_CC_PolicyCommandCode || TPM_CC_NV_Read). */ +static void test_wolfTPM2_PolicyCommandCodeMake(void) +{ + int rc; + byte digest[TPM_MAX_DIGEST_SIZE]; + byte guard[TPM_MAX_DIGEST_SIZE]; /* canary to detect any write */ + word32 digestSz = 0; + /* SHA2-256 (also in examples/nvram/extend.c) */ + static const byte expected256[] = { + 0x47,0xce,0x30,0x32,0xd8,0xba,0xd1,0xf3, + 0x08,0x9c,0xb0,0xc0,0x90,0x88,0xde,0x43, + 0x50,0x14,0x91,0xd4,0x60,0x40,0x2b,0x90, + 0xcd,0x1b,0x7f,0xc0,0xb6,0x8c,0xa9,0x2f + }; +#ifdef WOLFSSL_SHA384 + static const byte expected384[] = { + 0xfb,0xdd,0x14,0x92,0x1c,0x8b,0xd9,0x5c, + 0x9f,0x35,0x96,0x79,0xd2,0xbf,0x75,0x78, + 0xb1,0x47,0xe8,0x29,0x83,0x21,0xf8,0xe9, + 0xea,0xc4,0x4c,0x11,0x77,0x2f,0xfa,0x6e, + 0xe5,0x91,0x78,0x43,0x47,0x83,0x9b,0xef, + 0xf1,0x22,0xf2,0x14,0x4d,0xd0,0xb0,0xf0 + }; +#endif +#ifdef WOLFSSL_SHA512 + static const byte expected512[] = { + 0x31,0x38,0x6a,0xba,0x16,0xd8,0xf0,0x64, + 0xbd,0x51,0x4d,0x1d,0xd9,0x48,0x1c,0x65, + 0x6d,0x0e,0x32,0xe2,0xad,0x84,0x8e,0x1b, + 0xe9,0xb9,0xab,0x1d,0xd6,0x6f,0xfa,0xd2, + 0xc5,0xc0,0x2d,0x22,0x1c,0x61,0xd2,0x01, + 0x99,0x4e,0xd8,0x30,0x6b,0x77,0x0e,0x56, + 0xbb,0x13,0x05,0x32,0xdf,0x62,0xea,0x8d, + 0x06,0xc6,0xdf,0x53,0x5f,0x19,0xb8,0x21 + }; +#endif + + /* NULL argument rejection */ + digestSz = (word32)sizeof(digest); + AssertIntEQ(wolfTPM2_PolicyCommandCodeMake(TPM_ALG_SHA256, NULL, &digestSz, + TPM_CC_NV_Read), BAD_FUNC_ARG); + AssertIntEQ(wolfTPM2_PolicyCommandCodeMake(TPM_ALG_SHA256, digest, NULL, + TPM_CC_NV_Read), BAD_FUNC_ARG); + /* Unsupported hash algorithm rejection */ + AssertIntEQ(wolfTPM2_PolicyCommandCodeMake(TPM_ALG_NULL, digest, &digestSz, + TPM_CC_NV_Read), BAD_FUNC_ARG); + + /* digestSz is in/out: on input it is the buffer capacity. A capacity + * smaller than the hash size must return BUFFER_E and must not write to + * digest (which the function would otherwise zero and hash into) nor + * clobber the caller's capacity value. */ + XMEMSET(guard, 0xA5, sizeof(guard)); + XMEMCPY(digest, guard, sizeof(guard)); + digestSz = TPM_SHA256_DIGEST_SIZE - 1; + AssertIntEQ(wolfTPM2_PolicyCommandCodeMake(TPM_ALG_SHA256, digest, + &digestSz, TPM_CC_NV_Read), BUFFER_E); + AssertIntEQ(XMEMCMP(digest, guard, sizeof(guard)), 0); + AssertIntEQ((int)digestSz, TPM_SHA256_DIGEST_SIZE - 1); + + /* one-byte capacity must not be overrun either */ + digestSz = 1; + AssertIntEQ(wolfTPM2_PolicyCommandCodeMake(TPM_ALG_SHA256, digest, + &digestSz, TPM_CC_NV_Read), BUFFER_E); + AssertIntEQ(XMEMCMP(digest, guard, sizeof(guard)), 0); +#ifdef WOLFSSL_SHA512 + /* a SHA2-512 digest does not fit a SHA2-256 sized buffer */ + digestSz = TPM_SHA256_DIGEST_SIZE; + AssertIntEQ(wolfTPM2_PolicyCommandCodeMake(TPM_ALG_SHA512, digest, + &digestSz, TPM_CC_NV_Read), BUFFER_E); + AssertIntEQ(XMEMCMP(digest, guard, sizeof(guard)), 0); +#endif + /* exactly the hash size is sufficient */ + digestSz = TPM_SHA256_DIGEST_SIZE; + AssertIntEQ(wolfTPM2_PolicyCommandCodeMake(TPM_ALG_SHA256, digest, + &digestSz, TPM_CC_NV_Read), 0); + AssertIntEQ((int)digestSz, TPM_SHA256_DIGEST_SIZE); + + /* SHA2-256 known-answer */ + digestSz = (word32)sizeof(digest); + rc = wolfTPM2_PolicyCommandCodeMake(TPM_ALG_SHA256, digest, &digestSz, + TPM_CC_NV_Read); + AssertIntEQ(rc, 0); + AssertIntEQ((int)digestSz, (int)sizeof(expected256)); + AssertIntEQ(XMEMCMP(digest, expected256, sizeof(expected256)), 0); +#ifdef WOLFSSL_SHA384 + digestSz = (word32)sizeof(digest); + rc = wolfTPM2_PolicyCommandCodeMake(TPM_ALG_SHA384, digest, &digestSz, + TPM_CC_NV_Read); + AssertIntEQ(rc, 0); + AssertIntEQ((int)digestSz, (int)sizeof(expected384)); + AssertIntEQ(XMEMCMP(digest, expected384, sizeof(expected384)), 0); +#endif +#ifdef WOLFSSL_SHA512 + digestSz = (word32)sizeof(digest); + rc = wolfTPM2_PolicyCommandCodeMake(TPM_ALG_SHA512, digest, &digestSz, + TPM_CC_NV_Read); + AssertIntEQ(rc, 0); + AssertIntEQ((int)digestSz, (int)sizeof(expected512)); + AssertIntEQ(XMEMCMP(digest, expected512, sizeof(expected512)), 0); +#endif + + printf("Test PolicyCCMake:%-40s Passed\n", "Known Vectors:"); +} +#endif /* !WOLFTPM2_NO_WOLFCRYPT */ + +/* Arg-validation for wolfTPM2_SetPrimaryPolicy (no TPM). */ +static void test_wolfTPM2_SetPrimaryPolicy(void) +{ + WOLFTPM2_DEV dev; + byte pol[TPM_MAX_DIGEST_SIZE + 4]; + + XMEMSET(&dev, 0, sizeof(dev)); + XMEMSET(pol, 0, sizeof(pol)); + + /* NULL dev */ + AssertIntEQ(wolfTPM2_SetPrimaryPolicy(NULL, TPM_RH_PLATFORM, + TPM_ALG_SHA256, pol, TPM_SHA256_DIGEST_SIZE), BAD_FUNC_ARG); + /* policy digest larger than the buffer */ + AssertIntEQ(wolfTPM2_SetPrimaryPolicy(&dev, TPM_RH_PLATFORM, + TPM_ALG_SHA256, pol, (word32)sizeof(pol)), BAD_FUNC_ARG); + /* NULL policy with a non-zero size must not silently clear the policy */ + AssertIntEQ(wolfTPM2_SetPrimaryPolicy(&dev, TPM_RH_PLATFORM, + TPM_ALG_SHA256, NULL, TPM_SHA256_DIGEST_SIZE), BAD_FUNC_ARG); + + printf("Test SetPrimPol: %-40s Passed\n", "Arg Validation:"); +} + +/* Argument handling and, against the simulator, the success paths of + * wolfTPM2_IsAlgSupported. */ +static void test_wolfTPM2_IsAlgSupported(void) +{ + int isSupported = 1; /* seeded true to prove the error paths clear it */ +#if defined(WOLFTPM_SWTPM) + int rc; + WOLFTPM2_DEV dev; +#endif + + /* NULL dev must fail and must not leave the out-param saying "supported" */ + AssertIntEQ(wolfTPM2_IsAlgSupported(NULL, TPM_ALG_SHA256, &isSupported), + BAD_FUNC_ARG); + AssertIntEQ(isSupported, 0); + /* NULL out-param */ + AssertIntEQ(wolfTPM2_IsAlgSupported(NULL, TPM_ALG_SHA256, NULL), + BAD_FUNC_ARG); + +#if defined(WOLFTPM_SWTPM) + XMEMSET(&dev, 0, sizeof(dev)); + rc = wolfTPM2_Init(&dev, TPM2_IoCb, NULL); + AssertIntEQ(rc, 0); + + /* SHA2-256 is mandatory for a TPM 2.0 part, so it must report supported + * with a success rc */ + isSupported = 0; + AssertIntEQ(wolfTPM2_IsAlgSupported(&dev, TPM_ALG_SHA256, &isSupported), + TPM_RC_SUCCESS); + AssertIntEQ(isSupported, 1); + + /* an algorithm identifier no TPM implements must report unsupported, still + * with a success rc (the query itself worked) */ + isSupported = 1; + AssertIntEQ(wolfTPM2_IsAlgSupported(&dev, (TPM_ALG_ID)0x7FFF, + &isSupported), TPM_RC_SUCCESS); + AssertIntEQ(isSupported, 0); + + wolfTPM2_Cleanup(&dev); + printf("Test IsAlgSupp: %-40s Passed\n", "Args + Query:"); +#else + printf("Test IsAlgSupp: %-40s Passed\n", "Arg Validation:"); +#endif /* WOLFTPM_SWTPM */ +} + +/* Success path for wolfTPM2_PolicyOR: satisfy one branch of a real two-branch + * OR on a live policy session and confirm the TPM's running policy digest + * matches the offline computation. Simulator only. */ +static void test_wolfTPM2_PolicyOR_success(void) +{ +#if defined(WOLFTPM_SWTPM) && !defined(WOLFTPM2_NO_WOLFCRYPT) + int rc; + WOLFTPM2_DEV dev; + WOLFTPM2_SESSION sess; + TPML_DIGEST list; + byte branchA[TPM_MAX_DIGEST_SIZE]; + byte branchB[TPM_MAX_DIGEST_SIZE]; + byte concat[2 * TPM_MAX_DIGEST_SIZE]; + byte expected[TPM_MAX_DIGEST_SIZE]; + byte got[TPM_MAX_DIGEST_SIZE]; + word32 aSz, bSz, expSz, gotSz; + + XMEMSET(&dev, 0, sizeof(dev)); + XMEMSET(&sess, 0, sizeof(sess)); + XMEMSET(&list, 0, sizeof(list)); + + rc = wolfTPM2_Init(&dev, TPM2_IoCb, NULL); + AssertIntEQ(rc, 0); + + /* two distinct PolicyCommandCode branches */ + aSz = (word32)sizeof(branchA); + AssertIntEQ(wolfTPM2_PolicyCommandCodeMake(TPM_ALG_SHA256, branchA, &aSz, + TPM_CC_NV_Read), 0); + bSz = (word32)sizeof(branchB); + AssertIntEQ(wolfTPM2_PolicyCommandCodeMake(TPM_ALG_SHA256, branchB, &bSz, + TPM_CC_Unseal), 0); + + /* offline expected digest = H(zeros || TPM_CC_PolicyOR || A || B) */ + XMEMCPY(concat, branchA, aSz); + XMEMCPY(&concat[aSz], branchB, bSz); + XMEMSET(expected, 0, sizeof(expected)); + expSz = TPM_SHA256_DIGEST_SIZE; + AssertIntEQ(wolfTPM2_PolicyHash(TPM_ALG_SHA256, expected, &expSz, + TPM_CC_PolicyOR, concat, aSz + bSz), 0); + + /* satisfy branch A on a live session, then OR against {A,B} */ + rc = wolfTPM2_StartSession(&dev, &sess, NULL, NULL, TPM_SE_POLICY, + TPM_ALG_NULL); + AssertIntEQ(rc, 0); + AssertIntEQ(wolfTPM2_PolicyCommandCode(&dev, &sess, TPM_CC_NV_Read), 0); + + list.count = 2; + list.digests[0].size = (UINT16)aSz; + XMEMCPY(list.digests[0].buffer, branchA, aSz); + list.digests[1].size = (UINT16)bSz; + XMEMCPY(list.digests[1].buffer, branchB, bSz); + AssertIntEQ(wolfTPM2_PolicyOR(&dev, &sess, &list), 0); + + /* the TPM's running digest must match the offline value */ + gotSz = (word32)sizeof(got); + AssertIntEQ(wolfTPM2_GetPolicyDigest(&dev, sess.handle.hndl, got, &gotSz), + 0); + AssertIntEQ((int)gotSz, (int)expSz); + AssertIntEQ(XMEMCMP(got, expected, expSz), 0); + + wolfTPM2_UnloadHandle(&dev, &sess.handle); + wolfTPM2_Cleanup(&dev); + printf("Test PolicyOR: %-40s Passed\n", "Two-Branch Success:"); +#else + printf("Test PolicyOR: %-40s Skipped (requires SWTPM)\n", + "Two-Branch Success:"); +#endif /* WOLFTPM_SWTPM && !WOLFTPM2_NO_WOLFCRYPT */ +} + static void test_wolfTPM2_GetRandom(void) { int rc; @@ -7807,6 +8454,18 @@ int unit_tests(int argc, char *argv[]) test_wolfTPM2_ST33_FirmwareUpgrade(); #endif #endif + test_wolfTPM2_PolicyOR(); + #ifndef WOLFTPM2_NO_WOLFCRYPT + test_wolfTPM2_PolicyCommandCodeMake(); + #endif + test_wolfTPM2_SetPrimaryPolicy(); + test_wolfTPM2_SetPrimaryPolicy_rollback(); + test_wolfTPM2_PolicyClear_underPolicy(); + #ifdef WOLFTPM_FIRMWARE_UPGRADE + test_wolfTPM2_FirmwareUpgrade_ex_session(); + #endif + test_wolfTPM2_IsAlgSupported(); + test_wolfTPM2_PolicyOR_success(); #if defined(WOLFTPM_MLDSA) && defined(WOLFTPM_MLKEM) /* Run non-TPM-dependent tests first */ test_wolfTPM2_PQC_KeyTemplates(); diff --git a/wolftpm/tpm2_wrap.h b/wolftpm/tpm2_wrap.h index 4f40378b1..544377bd5 100644 --- a/wolftpm/tpm2_wrap.h +++ b/wolftpm/tpm2_wrap.h @@ -399,6 +399,36 @@ WOLFTPM_API int wolfTPM2_SelfTest(WOLFTPM2_DEV* dev); */ WOLFTPM_API int wolfTPM2_GetCapabilities(WOLFTPM2_DEV* dev, WOLFTPM2_CAPS* caps); +/*! + \ingroup wolfTPM2_Wrappers + + \brief Report whether the TPM implements a given algorithm + + \note Queries TPM_CAP_ALGS. Useful to skip a hash the TPM does not support + (for example SHA2-512 on parts limited to SHA2-256/384) before starting + a session with it. + + \note The result is returned through isSupported, not the return value, so a + capability-query failure cannot be mistaken for "supported". On any + error *isSupported is set to 0, so the call fails closed. + + \note This reports what the TPM implements, not what the local wolfCrypt + build supports. A caller that also hashes locally (for example to + precompute a policy digest) must check its own build as well. + + \return TPM_RC_SUCCESS: query completed; *isSupported is 1 or 0 + \return BAD_FUNC_ARG: dev or isSupported is NULL + \return a TPM_RC (or other non-zero error) if the capability query fails + + \param dev pointer to a TPM2_DEV struct + \param alg the algorithm identifier to test (for example TPM_ALG_SHA512) + \param isSupported output, set to 1 if implemented by the TPM, else 0 + + \sa wolfTPM2_GetCapabilities +*/ +WOLFTPM_API int wolfTPM2_IsAlgSupported(WOLFTPM2_DEV* dev, TPM_ALG_ID alg, + int* isSupported); + /*! \ingroup wolfTPM2_Wrappers \brief Gets a list of handles @@ -4875,6 +4905,36 @@ WOLFTPM_API int wolfTPM2_PolicyPCRMake(TPM_ALG_ID pcrAlg, byte* pcrArray, word32 pcrArraySz, const byte* pcrDigest, word32 pcrDigestSz, byte* digest, word32* digestSz); +/*! + \ingroup wolfTPM2_Wrappers + + \brief Compute the policy digest for PolicyCommandCode on a fresh session + + \note policyDigest = hash(zeroDigest || TPM_CC_PolicyCommandCode || cc). + Mirrors the running digest of a new policy session after + wolfTPM2_PolicyCommandCode. + + \note digestSz is in/out. On input it is the capacity of the digest buffer + in bytes, which must be at least the hash size or BUFFER_E is returned + without writing to digest. On output it is the hash size written. + + \return TPM_RC_SUCCESS: successful + \return BAD_FUNC_ARG: NULL digest/digestSz or unsupported hashAlg + \return BUFFER_E: *digestSz (the buffer capacity) is smaller than the + hash size for hashAlg + + \param hashAlg hash algorithm for the policy digest + \param digest output policy digest buffer + \param digestSz in/out: input buffer capacity, output digest size + \param cc the command code to bind (for example TPM_CC_NV_Read) + + \sa wolfTPM2_PolicyCommandCode + \sa wolfTPM2_PolicyHash + \sa wolfTPM2_PolicyPCRMake +*/ +WOLFTPM_API int wolfTPM2_PolicyCommandCodeMake(TPM_ALG_ID hashAlg, + byte* digest, word32* digestSz, TPM_CC cc); + /*! \ingroup wolfTPM2_Wrappers @@ -4979,6 +5039,63 @@ WOLFTPM_API int wolfTPM2_PolicyAuthValue(WOLFTPM2_DEV* dev, WOLFTPM_API int wolfTPM2_PolicyCommandCode(WOLFTPM2_DEV* dev, WOLFTPM2_SESSION* tpmSession, TPM_CC cc); +/*! + \ingroup wolfTPM2_Wrappers + + \brief Wrapper for satisfying a policy session with a compound OR of digests + + \note The digest list is hash-agnostic (each branch carries its own size), + so it supports SHA2-256 through SHA2-512 policy branches. The number of + branches (pHashList->count) must be between 2 and the TPML_DIGEST + capacity (the digests[] array length), and each branch's size must not + exceed the digest buffer length; branches beyond count are ignored. + + \note A minimum of two branches is required by TPM 2.0 Part 3 Sec.23.6. A + one-branch list is rejected here with BAD_FUNC_ARG rather than sent to + the TPM, which would answer TPM_RC_VALUE. + + \return TPM_RC_SUCCESS: successful + \return BAD_FUNC_ARG: bad pointer, count out of range (< 2 or > capacity), + or a branch size that exceeds the digest buffer + + \param dev pointer to a TPM2_DEV struct + \param tpmSession pointer to a WOLFTPM2_SESSION struct used with wolfTPM2_StartSession and wolfTPM2_SetAuthSession + \param pHashList list of pre-computed policy branch digests to OR together + + \sa wolfTPM2_PolicyPCR + \sa wolfTPM2_PolicyAuthorize + \sa wolfTPM2_GetPolicyDigest +*/ +WOLFTPM_API int wolfTPM2_PolicyOR(WOLFTPM2_DEV* dev, + WOLFTPM2_SESSION* tpmSession, const TPML_DIGEST* pHashList); + +/*! + \ingroup wolfTPM2_Wrappers + + \brief Set (or clear) the authPolicy of a hierarchy + + \note Wraps TPM2_SetPrimaryPolicy for owner/endorsement/platform/lockout. + Pass authPolicy=NULL, authPolicySz=0 and hashAlg=TPM_ALG_NULL to clear + an existing policy. The command itself is authorized by the hierarchy's + current auth (set it on the device's active session beforehand). + + \return TPM_RC_SUCCESS: successful + \return BAD_FUNC_ARG: NULL dev, authPolicySz exceeds the digest buffer, or + authPolicy is NULL with a non-zero authPolicySz + + \param dev pointer to a TPM2_DEV struct + \param authHandle the hierarchy (for example TPM_RH_PLATFORM) + \param hashAlg the policy digest hash algorithm (TPM_ALG_NULL to clear) + \param authPolicy the policy digest to set (NULL to clear) + \param authPolicySz size of the policy digest (0 to clear) + + \sa wolfTPM2_PolicyOR + \sa wolfTPM2_GetPolicyDigest +*/ +WOLFTPM_API int wolfTPM2_SetPrimaryPolicy(WOLFTPM2_DEV* dev, + TPMI_RH_HIERARCHY_AUTH authHandle, TPM_ALG_ID hashAlg, + const byte* authPolicy, word32 authPolicySz); + /* Pre-provisioned IAK and IDevID key/cert from TPM vendor */ /* Tested with ST33KTPM devices */ @@ -5111,6 +5228,59 @@ WOLFTPM_API int wolfTPM2_FirmwareUpgradeHash(WOLFTPM2_DEV* dev, uint8_t* manifest, uint32_t manifest_sz, wolfTPM2FwDataCb cb, void* cb_ctx); +/*! + \ingroup wolfTPM2_Wrappers + \brief Perform TPM firmware upgrade using a caller-supplied authorization session + \note Identical to wolfTPM2_FirmwareUpgradeHash except the caller controls how + the firmware-start command is authorized against the platform hierarchy. + \note When startSession is NULL this behaves exactly like + wolfTPM2_FirmwareUpgradeHash (library-managed platform authorization). + \note When startSession is non-NULL the caller is responsible for having + satisfied the platform authPolicy on that session (for example via + wolfTPM2_PolicyPCR / wolfTPM2_PolicyAuthorize / wolfTPM2_PolicyOR using + SHA2-256 or SHA2-512). For Infineon the platform primary policy is left + untouched (the caller provisions it); for ST33 the session replaces the + default TPM_RS_PW password authorization. + + \note The vendor firmware-start command carries a session handle with an + empty nonceCaller, zero session attributes and an empty HMAC, so + startSession must be an unsalted, unbound TPM_SE_POLICY session with + no auth value and no parameter encryption. Sessions satisfied with + wolfTPM2_PolicyAuthValue or wolfTPM2_PolicyPassword are NOT supported, + because the required session HMAC is not serialized. Any such session + is rejected with BAD_FUNC_ARG before the command is sent. + + \note On a successful firmware start the TPM consumes the session, and this + function sets startSession->handle.hndl to TPM_RH_NULL to record that. + The caller must not flush it; wolfTPM2_UnloadHandle is a no-op on + TPM_RH_NULL, so an unconditional cleanup call remains safe. + + \return TPM_RC_SUCCESS: successful + \return TPM_RC_FAILURE: generic failure (check TPM IO and TPM return code) + \return BAD_FUNC_ARG: check the provided arguments, or startSession is not + an unsalted/unbound policy session with no auth value + + \param dev pointer to a TPM2_DEV struct + \param hashAlg hash algorithm to use (TPM_ALG_SHA384 or TPM_ALG_SHA512) + \param manifest_hash buffer to store computed manifest hash + \param manifest_hash_sz size of manifest hash buffer + \param manifest pointer to firmware manifest data + \param manifest_sz size of firmware manifest + \param cb callback function for firmware data access + \param cb_ctx context pointer passed to callback + \param startSession optional caller-satisfied session authorizing the + firmware-start command (NULL for library-managed authorization) + + \sa wolfTPM2_FirmwareUpgradeHash + \sa wolfTPM2_PolicyOR + \sa wolfTPM2_StartSession_ex +*/ +WOLFTPM_API int wolfTPM2_FirmwareUpgradeHash_ex(WOLFTPM2_DEV* dev, + TPM_ALG_ID hashAlg, /* Can use SHA2-384 or SHA2-512 for manifest hash */ + uint8_t* manifest_hash, uint32_t manifest_hash_sz, + uint8_t* manifest, uint32_t manifest_sz, + wolfTPM2FwDataCb cb, void* cb_ctx, WOLFTPM2_SESSION* startSession); + #ifndef WOLFTPM2_NO_WOLFCRYPT /*! \ingroup wolfTPM2_Wrappers @@ -5138,6 +5308,32 @@ WOLFTPM_API int wolfTPM2_FirmwareUpgradeHash(WOLFTPM2_DEV* dev, WOLFTPM_API int wolfTPM2_FirmwareUpgrade(WOLFTPM2_DEV* dev, uint8_t* manifest, uint32_t manifest_sz, wolfTPM2FwDataCb cb, void* cb_ctx); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Perform TPM firmware upgrade using a caller-supplied authorization session + \note Same as wolfTPM2_FirmwareUpgrade but the caller controls how the + firmware-start command is authorized (see wolfTPM2_FirmwareUpgradeHash_ex). + startSession NULL preserves the default library-managed behavior. + + \return TPM_RC_SUCCESS: successful + \return NOT_COMPILED_IN: wolfSSL not built with WOLFSSL_SHA384 + + \param dev pointer to a TPM2_DEV struct + \param manifest pointer to firmware manifest data + \param manifest_sz size of firmware manifest + \param cb callback function for firmware data access + \param cb_ctx context pointer passed to callback + \param startSession optional caller-satisfied session (NULL for default). + See wolfTPM2_FirmwareUpgradeHash_ex for the supported session + contract: unsalted, unbound policy session with no auth value. + + \sa wolfTPM2_FirmwareUpgrade + \sa wolfTPM2_FirmwareUpgradeHash_ex +*/ +WOLFTPM_API int wolfTPM2_FirmwareUpgrade_ex(WOLFTPM2_DEV* dev, + uint8_t* manifest, uint32_t manifest_sz, + wolfTPM2FwDataCb cb, void* cb_ctx, WOLFTPM2_SESSION* startSession); #endif /* !WOLFTPM2_NO_WOLFCRYPT */ /*! @@ -5162,6 +5358,32 @@ WOLFTPM_API int wolfTPM2_FirmwareUpgradeRecover(WOLFTPM2_DEV* dev, uint8_t* manifest, uint32_t manifest_sz, wolfTPM2FwDataCb cb, void* cb_ctx); +/*! + \ingroup wolfTPM2_Wrappers + \brief Recover from a failed firmware upgrade using a caller-supplied session + \note Same as wolfTPM2_FirmwareUpgradeRecover but with caller-controlled + authorization (see wolfTPM2_FirmwareUpgradeHash_ex). startSession NULL + preserves the default library-managed behavior. + + \return TPM_RC_SUCCESS: successful + \return BAD_FUNC_ARG: check the provided arguments + + \param dev pointer to a TPM2_DEV struct + \param manifest pointer to firmware manifest data + \param manifest_sz size of firmware manifest + \param cb callback function for firmware data access + \param cb_ctx context pointer passed to callback + \param startSession optional caller-satisfied session (NULL for default). + See wolfTPM2_FirmwareUpgradeHash_ex for the supported session + contract: unsalted, unbound policy session with no auth value. + + \sa wolfTPM2_FirmwareUpgradeRecover + \sa wolfTPM2_FirmwareUpgradeHash_ex +*/ +WOLFTPM_API int wolfTPM2_FirmwareUpgradeRecover_ex(WOLFTPM2_DEV* dev, + uint8_t* manifest, uint32_t manifest_sz, + wolfTPM2FwDataCb cb, void* cb_ctx, WOLFTPM2_SESSION* startSession); + /*! \ingroup wolfTPM2_Wrappers \brief Cancel ongoing TPM firmware upgrade