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. |
||
|---|---|---|
| .. | ||
| Makefile | ||
| README.md | ||
| firmware_policy.c | ||
| firmware_policy.h | ||
| ifx_fw_extract.c | ||
| ifx_fw_update.c | ||
| ifx_fw_update.h | ||
| include.am | ||
| st33_fw_update.c | ||
README.md
TPM Firmware Update Support
Currently wolfTPM supports firmware update capability for:
- Infineon SLB9672 (SPI) and SLB9673 (I2C) TPM 2.0 modules. Infineon has open sourced their firmware update.
- STMicroelectronics ST33KTPM TPM 2.0 modules. Support includes both Generation 1 firmware versions (< 512, without LMS signature) and Generation 2 firmware versions (>= 512, with LMS signature requirement).
Infineon Firmware
Extracting the firmware
Infineon releases firmware as a .bin file (example: TPM20_15.23.17664.0_R1.BIN).
The .bin contains a 16-byte GUID header, at least one manifest based on key group and the firmware. A typical manifest is 3KB and firmware is 920KB.
We have included a host side tool ifx_fw_extract for extracting the manifest and firmware data file required for a TPM upgrade.
Example usage:
# Build host tool
make
# Help
./ifx_fw_extract --help
Usage:
ifx_fw_extract <fw-file>
ifx_fw_extract <fw-file> <keygroup_id> <manifest-file> <data-file>
# Find key groups in .bin
./ifx_fw_extract TPM20_26.13.17770.0_R1.BIN
Reading TPM20_26.13.17770.0_R1.BIN
Found group 00000007
# Extract manifest and firmware data files for key group
./ifx_fw_extract TPM20_26.13.17770.0_R1.BIN 7 TPM20_26.13.17770.0_R1.MANIFEST TPM20_26.13.17770.0_R1.DATA
Reading TPM20_26.13.17770.0_R1.BIN
Found group 00000007
Chosen group found: 00000007
Manifest size is 3224
Data size is 934693
Writing TPM20_26.13.17770.0_R1.MANIFEST
Writing TPM20_26.13.17770.0_R1.DATA
Updating the firmware
The ifx_fw_update tool uses the manifest (header) and firmware data file.
The TPM has a vendor capability for getting the key group id. This is populated in the WOLFTPM2_CAPS.keyGroupId when wolfTPM2_GetCapabilities is called. This value should match the firmware extract tool keygroup_id.
# Help
./ifx_fw_update --help
Infineon Firmware Update Usage:
./ifx_fw_update (get info)
./ifx_fw_update --abandon (cancel)
./ifx_fw_update --policytest (safe policy auth self-test)
./ifx_fw_update [policy opts] <manifest_file> <firmware_file>
./ifx_fw_update <manifest_file> <firmware_file> (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
Infineon Firmware Update Tool
TPM2: Caps 0x1ae00082, Did 0x001c, Vid 0x15d1, Rid 0x16
TPM2_Startup pass
Mfg IFX (1), Vendor SLB9673, Fw 26.13 (0x456a)
Operational mode: Normal TPM operational mode (0x0)
KeyGroupId 0x7, FwCounter 1254 (255 same)
# Run with manifest and firmware files
./ifx_fw_update TPM20_26.13.17770.0_R1.MANIFEST TPM20_26.13.17770.0_R1.DATA
Infineon Firmware Update Tool
Manifest File: TPM20_26.13.17770.0_R1.MANIFEST
Firmware File: TPM20_26.13.17770.0_R1.DATA
TPM2: Caps 0x1ae00082, Did 0x001c, Vid 0x15d1, Rid 0x16
TPM2_Startup pass
Mfg IFX (1), Vendor SLB9673, Fw 26.13 (0x456a)
Operational mode: Normal TPM operational mode (0x0)
KeyGroupId 0x7, FwCounter 1254 (255 same)
TPM2_StartAuthSession: handle 0x3000000, algorithm NULL
TPM2_FlushContext: Closed handle 0x3000000
TPM2_StartAuthSession: handle 0x3000000, algorithm NULL
Firmware manifest chunk 1024 offset (0 / 3224), state 1
Firmware manifest chunk 1024 offset (1024 / 3224), state 2
Firmware manifest chunk 1024 offset (2048 / 3224), state 2
Firmware manifest chunk 152 offset (3072 / 3224), state 0
Firmware data chunk offset 0
Firmware data chunk offset 1024
Firmware data chunk offset 2048
Firmware data chunk offset 3072
...
Firmware data chunk offset 932864
Firmware data chunk offset 933888
Firmware data done
Mfg IFX (1), Vendor , Fw 0.0 (0x0)
Operational mode: After finalize or abandon, reboot required (0x4)
KeyGroupId 0x7, FwCounter 1253 (254 same)
TPM2_Shutdown failed 304: Unknown
# Reset or power cycle TPM
./ifx_fw_update
Infineon Firmware Update Tool
TPM2: Caps 0x1ae00082, Did 0x001c, Vid 0x15d1, Rid 0x16
TPM2_Startup pass
Mfg IFX (1), Vendor SLB9673, Fw 26.13 (0x456a)
Operational mode: Normal TPM operational mode (0x0)
KeyGroupId 0x7, FwCounter 1253 (254 same)
ST33 Firmware Update
Firmware Format Auto-Detection
ST33KTPM firmware update automatically detects the required format based on TPM firmware version:
-
Legacy firmware (< 512, e.g., 9.257): Non-LMS format
- Manifest size: 177 bytes
- Generation 1 firmware (ECC-only)
-
Modern firmware (>= 512, e.g., 9.512): LMS format
- Manifest size: 2697 bytes (includes embedded LMS signature)
- Generation 2 firmware (LMS mandatory)
The firmware version is automatically detected from fwVerMinor in TPM capabilities. The correct manifest size is determined automatically - no manual format selection is needed.
Updating the firmware
The st33_fw_update tool automatically detects the firmware format.
# Help
./st33_fw_update --help
ST33 Firmware Update Usage:
./st33_fw_update (get info)
./st33_fw_update --abandon (cancel)
./st33_fw_update --policytest (safe policy auth self-test)
./st33_fw_update [policy opts] <firmware.fi>
./st33_fw_update <firmware.fi> (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)
- Firmware >= 512: LMS format (2697 byte manifest with embedded signature)
# Run without arguments to display the current firmware information
./st33_fw_update
ST33 Firmware Update Tool
TPM2: Caps 0x30000415, Did 0x0003, Vid 0x104a, Rid 0x 1
TPM2_Startup pass
Mfg STM (2), Vendor ST33KTPM2X, Fw 9.257 (0x0)
Firmware version details: Major=9, Minor=257, Vendor=0x0
Hardware: ST33K (legacy firmware, Generation 1)
Firmware update: Non-LMS format required
# Run with firmware file (format auto-detected from TPM version)
./st33_fw_update TPM_ST33KTPM2X_00090200_V1.fi
ST33 Firmware Update Tool
Firmware File: TPM_ST33KTPM2X_00090200_V1.fi
TPM2: Caps 0x30000415, Did 0x0003, Vid 0x104a, Rid 0x 1
TPM2_Startup pass
Mfg STM (2), Vendor ST33KTPM2X, Fw 9.257 (0x0)
Firmware version details: Major=9, Minor=257, Vendor=0x0
Hardware: ST33K (legacy firmware, Generation 1)
Firmware update: Non-LMS format required
Format: Non-LMS (from TPM firmware version)
Firmware Update:
Total file size: 364290 bytes
Manifest (blob0): 177 bytes
Firmware data: 364113 bytes
...
Firmware update completed successfully.
Please reset or power cycle the TPM.
# Example with LMS firmware (Generation 2 TPM, firmware >= 512)
./st33_fw_update ST33KTPM2X_FAC_00090200_V2.fi
ST33 Firmware Update Tool
Firmware File: ST33KTPM2X_FAC_00090200_V2.fi
TPM2: Caps 0x30000415, Did 0x0003, Vid 0x104a, Rid 0x 3
TPM2_Startup pass
Mfg STM (2), Vendor ST33KTPM2X, Fw 9.512 (0x0)
Firmware version details: Major=9, Minor=512, Vendor=0x0
Hardware: ST33K (modern firmware, Generation 2)
Firmware update: LMS format required
Format: LMS (from TPM firmware version)
Firmware Update:
Total file size: 360092 bytes
Manifest (blob0): 2697 bytes
Firmware data: 357395 bytes
...
Firmware update completed successfully.
Please reset or power cycle the TPM.
# Cancel an ongoing firmware update
./st33_fw_update --abandon
ST33 Firmware Update Tool
TPM2: Caps 0x30000415, Did 0x0003, Vid 0x104a, Rid 0x 1
TPM2_Startup pass
Mfg STM (2), Vendor ST33KTPM2X, Fw 9.257 (0x0)
Firmware version details: Major=9, Minor=257, Vendor=0x0
Hardware: ST33K (legacy firmware, Generation 1)
Firmware update: Non-LMS format required
Firmware Update Abandon:
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
authPolicyyourself (viaTPM2_SetPrimaryPolicywithauthHandle = TPM_RH_PLATFORM, using SHA2-256 or SHA2-512) and pass a session that satisfies it. Note this applies to the library: the--policy/--policyorexample modes are themselves such a caller, and their helper (examples/firmware/firmware_policy.c) does overwrite the platformauthPolicywith 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_PWpassword 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:
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
authValueor itsauthPolicy, so installing anauthPolicydoes not disable the password path. With the default emptyplatformAuththe clear always succeeds. --policyoradditionally provisions aPolicyCommandCode(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-defaultplatformAuth), the example retries the clear under that branch.--policyprovisions a singlePolicyCommandCode(FieldUpgradeStart)branch and therefore has no policy-based rollback path. It relies entirely onplatformAuthstill 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.