Skip to content

Commit 1446aad

Browse files
authored
Merge pull request #560 from dgarske/firmware_upgrade_policy_auth
Add caller-supplied policy authorization for firmware upgrade
2 parents 55d1185 + 2b8e41c commit 1446aad

11 files changed

Lines changed: 2206 additions & 113 deletions

File tree

CMakeLists.txt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -480,8 +480,13 @@ if (WOLFTPM_EXAMPLES AND BUILD_WOLFTPM_LIB)
480480
endif()
481481

482482
function(add_tpm_example name src)
483+
# Optional additional sources may be passed after 'src' (ARGN)
484+
set(_example_srcs examples/${src})
485+
foreach(_extra ${ARGN})
486+
list(APPEND _example_srcs examples/${_extra})
487+
endforeach()
483488
add_executable(${name}
484-
examples/${src}
489+
${_example_srcs}
485490
)
486491
target_link_libraries(${name} PRIVATE wolftpm tpm_test_lib wolftpm_wolfssl_dep)
487492
if(WIN32)
@@ -705,7 +710,15 @@ if (WOLFTPM_EXAMPLES AND BUILD_WOLFTPM_LIB)
705710
add_tpm_example(secure_rot boot/secure_rot.c)
706711
add_tpm_example(csr csr/csr.c)
707712
add_tpm_example(get_ek_certs endorsement/get_ek_certs.c)
708-
add_tpm_example(ifx_fw_update firmware/ifx_fw_update.c)
713+
# firmware_policy.c is entirely inside a WOLFTPM_FIRMWARE_UPGRADE guard, so
714+
# compiling it with firmware upgrade disabled yields an empty translation
715+
# unit (which ISO C forbids and -Wpedantic rejects).
716+
if(WOLFTPM_FIRMWARE)
717+
add_tpm_example(ifx_fw_update firmware/ifx_fw_update.c
718+
firmware/firmware_policy.c)
719+
else()
720+
add_tpm_example(ifx_fw_update firmware/ifx_fw_update.c)
721+
endif()
709722
add_tpm_example(gpio_config gpio/gpio_config.c)
710723
add_tpm_example(gpio_read gpio/gpio_read.c)
711724
add_tpm_example(gpio_set gpio/gpio_set.c)

examples/firmware/README.md

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,13 @@ The TPM has a vendor capability for getting the key group id. This is populated
5454
Infineon Firmware Update Usage:
5555
./ifx_fw_update (get info)
5656
./ifx_fw_update --abandon (cancel)
57-
./ifx_fw_update <manifest_file> <firmware_file>
57+
./ifx_fw_update --policytest (safe policy auth self-test)
58+
./ifx_fw_update [policy opts] <manifest_file> <firmware_file>
59+
./ifx_fw_update <manifest_file> <firmware_file> (default auth)
60+
Policy options (caller-supplied authorization):
61+
--policy provision+satisfy a PolicyCommandCode
62+
--policyor provision+satisfy a PolicyOR (multi-branch)
63+
--sha256|--sha384|--sha512 policy hash (default SHA-256)
5864

5965
# Run without arguments to display the current firmware information including key group id and operational mode
6066
./ifx_fw_update
@@ -131,7 +137,13 @@ The `st33_fw_update` tool automatically detects the firmware format.
131137
ST33 Firmware Update Usage:
132138
./st33_fw_update (get info)
133139
./st33_fw_update --abandon (cancel)
134-
./st33_fw_update <firmware.fi>
140+
./st33_fw_update --policytest (safe policy auth self-test)
141+
./st33_fw_update [policy opts] <firmware.fi>
142+
./st33_fw_update <firmware.fi> (default password auth)
143+
Policy options (caller-supplied authorization):
144+
--policy provision+satisfy a PolicyCommandCode
145+
--policyor provision+satisfy a PolicyOR (multi-branch)
146+
--sha256|--sha384|--sha512 policy hash (default SHA-256)
135147

136148
Firmware format is auto-detected from TPM firmware version:
137149
- Firmware < 512: Non-LMS format (177 byte manifest)
@@ -199,3 +211,81 @@ Success: Please reset or power cycle TPM
199211
```
200212

201213
**Note**: Firmware files cannot be made public and must be obtained separately from STMicroelectronics.
214+
215+
## Policy-Based Authorization (Advanced)
216+
217+
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.
218+
219+
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:
220+
221+
- **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.
222+
- **ST33**: the supplied session replaces the default `TPM_RS_PW` password authorization.
223+
224+
**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.
225+
226+
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.
227+
228+
Example: satisfy a multi-branch `PolicyOR` (up to 8 branches, SHA2-512 shown) and start the upgrade under it:
229+
230+
```c
231+
WOLFTPM2_SESSION session;
232+
TPML_DIGEST orList;
233+
uint8_t manifest_hash[TPM_SHA512_DIGEST_SIZE];
234+
int rc;
235+
236+
/* zero both structs - orList must not carry uninitialized branch sizes */
237+
XMEMSET(&session, 0, sizeof(session));
238+
XMEMSET(&orList, 0, sizeof(orList));
239+
240+
/* start a policy session using the desired policy hash (SHA2-512 for PQC) */
241+
rc = wolfTPM2_StartSession_ex(&dev, &session, NULL, NULL,
242+
TPM_SE_POLICY, TPM_ALG_NULL, TPM_ALG_SHA512);
243+
if (rc != TPM_RC_SUCCESS) goto cleanup;
244+
245+
/* Satisfy one branch (PCR, PolicySigned, PolicyAuthorize, PolicyCommandCode,
246+
* ...), then OR against the full branch list the platform authPolicy encodes.
247+
* Set count and each digests[i].size/buffer for every branch you populate.
248+
* PolicyOR requires at least 2 branches. */
249+
orList.count = 2;
250+
/* orList.digests[0].size = ...; XMEMCPY(orList.digests[0].buffer, ...); */
251+
/* orList.digests[1].size = ...; XMEMCPY(orList.digests[1].buffer, ...); */
252+
rc = wolfTPM2_PolicyOR(&dev, &session, &orList);
253+
if (rc != TPM_RC_SUCCESS) goto cleanup;
254+
255+
/* hash the manifest with the matching algorithm, then start the upgrade under
256+
* the caller-satisfied session (NULL would use the library-default auth) */
257+
rc = wc_Sha512Hash(manifest, manifest_sz, manifest_hash);
258+
if (rc != 0) goto cleanup;
259+
rc = wolfTPM2_FirmwareUpgradeHash_ex(&dev, TPM_ALG_SHA512,
260+
manifest_hash, (uint32_t)sizeof(manifest_hash),
261+
manifest, manifest_sz, fwDataCb, fwCbCtx, &session);
262+
263+
cleanup:
264+
/* On a successful FieldUpgradeStart the TPM consumes the session and the
265+
* library sets session.handle.hndl to TPM_RH_NULL (0x40000007) - it is NOT
266+
* zeroed, so do not test for == 0 to detect consumption. Calling
267+
* wolfTPM2_UnloadHandle is always safe: it is a no-op on TPM_RH_NULL, so this
268+
* only releases a session that is still loaded. */
269+
if (session.handle.hndl != 0)
270+
wolfTPM2_UnloadHandle(&dev, &session.handle);
271+
```
272+
273+
Passing `NULL` for the final `startSession` argument makes `wolfTPM2_FirmwareUpgradeHash_ex()` behave exactly like `wolfTPM2_FirmwareUpgradeHash()` (library-managed authorization), so existing code is unaffected.
274+
275+
### Destructive: provisioning replaces any existing platform policy
276+
277+
`--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.
278+
279+
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`.
280+
281+
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.
282+
283+
### Rollback of the example-provisioned policy
284+
285+
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.
286+
287+
- 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.
288+
- `--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.
289+
- `--policy` provisions a single `PolicyCommandCode(FieldUpgradeStart)` branch and therefore has no policy-based rollback path. It relies entirely on `platformAuth` still being usable.
290+
- 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.
291+
- 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.

0 commit comments

Comments
 (0)