Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions examples/firmware/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,58 @@ 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.
- **ST33**: the supplied session replaces the default `TPM_RS_PW` password authorization.

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/Authorize, PolicyAuthValue, ...), then
* OR against the full branch list the platform authPolicy encodes. Set count
* and each digests[i].size/buffer for every branch you populate. */
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:
/* the TPM consumes the session on a successful start; release it otherwise */
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.

**Note:** the example `--policy`/`--policyor` modes provision the platform hierarchy `authPolicy` via `TPM2_SetPrimaryPolicy` before the upgrade. On failure the example restores the default (clears the policy) so a later default-auth run is not locked out; on success the required TPM reset clears it. If a run is interrupted before that cleanup, the platform hierarchy may still require the policy until the TPM is reset/power-cycled.
331 changes: 331 additions & 0 deletions examples/firmware/firmware_policy.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,331 @@
/* 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 <config.h>
#endif

#include <examples/firmware/firmware_policy.h>

#ifdef WOLFTPM_FIRMWARE_UPGRADE

#include <stdio.h>

/* 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");
}

/* Build a PolicyCommandCode branch digest offline. This matches the running
* policy digest of a fresh policy session after wolfTPM2_PolicyCommandCode. */
static int BuildPolicyCommandCode(TPMI_ALG_HASH hashAlg,
byte* digest, word32* digestSz, TPM_CC cc)
{
byte val[4]; /* command code big-endian, matching the TPM wire format */
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));
}

/* Return 1 if the TPM implements the given algorithm, 0 otherwise. Used to
* skip a policy hash (for example SHA2-512) that the TPM firmware does not
* support, rather than issuing a session that fails with TPM_RC_SIZE. */
static int firmware_hash_supported(TPM_ALG_ID alg)
{
GetCapability_In capIn;
GetCapability_Out capOut;
TPML_ALG_PROPERTY* algs;

XMEMSET(&capIn, 0, sizeof(capIn));
XMEMSET(&capOut, 0, sizeof(capOut));
capIn.capability = TPM_CAP_ALGS;
capIn.property = alg;
capIn.propertyCount = 1;
if (TPM2_GetCapability(&capIn, &capOut) != TPM_RC_SUCCESS) {
return 0; /* conservative: treat query failure as unsupported */
}
/* The TPM returns algorithms with ID >= property; a match at index 0
* means the requested algorithm is implemented. */
algs = &capOut.capabilityData.data.algorithms;
if (algs->count >= 1 && algs->algProperties[0].alg == alg) {
return 1;
}
return 0;
}

/* Exercise wolfTPM2_PolicyOR at the requested hash and verify the TPM's
* running policy digest matches an offline computation. Non-destructive.
* Returns 0 on match, 1 if the hash is not implemented (intentional skip),
* -1 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 rc;
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, bSz, expSz, gotSz;

XMEMSET(&sess, 0, sizeof(sess));
XMEMSET(&orList, 0, sizeof(orList));

if (hsz == 0 || hsz > TPM_MAX_DIGEST_SIZE) {
return BAD_FUNC_ARG;
}

/* Skip cleanly if the TPM firmware does not implement this hash */
if (!firmware_hash_supported(hashAlg)) {
printf(" %s: skipped (not implemented by this TPM)\n", name);
return 1; /* intentional skip, not a failure */
}

/* Offline: two distinct PolicyCommandCode branch digests */
XMEMSET(branchA, 0, sizeof(branchA));
aSz = hsz;
rc = BuildPolicyCommandCode(hashAlg, branchA, &aSz, TPM_CC_NV_Read);
if (rc == 0) {
XMEMSET(branchB, 0, sizeof(branchB));
bSz = hsz;
rc = BuildPolicyCommandCode(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);
rc = -1;
}
}
else {
printf(" %s PolicyOR: ERROR 0x%x: %s\n",
name, rc, TPM2_GetRCString(rc));
}

wolfTPM2_UnloadHandle(dev, &sess.handle);
return rc;
}

int firmware_policy_selftest_all(WOLFTPM2_DEV* dev)
{
int i, rc, hardFail = 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++) {
rc = firmware_policy_selftest(dev, hashes[i].alg, hashes[i].name);
/* rc == 1 is an intentional "hash not implemented" skip. Any other
* non-zero (digest mismatch, bad arg, or a TPM rc) is a failure. */
if (rc != 0 && rc != 1) {
hardFail = 1;
}
}
return hardFail ? -1 : 0;
}

/* Clear any platform authPolicy we provisioned so a later default-auth run is
* not locked out (the platform policy is otherwise cleared only on reset). */
static void firmware_policy_clear(WOLFTPM2_DEV* dev)
{
SetPrimaryPolicy_In clr;
(void)dev;
XMEMSET(&clr, 0, sizeof(clr));
clr.authHandle = TPM_RH_PLATFORM;
clr.hashAlg = TPM_ALG_NULL; /* empty policy */
clr.authPolicy.size = 0;
if (TPM2_SetPrimaryPolicy(&clr) == TPM_RC_SUCCESS) {
printf(" Cleared platform policy after setup failure\n");
}
}

int firmware_policy_session_setup(WOLFTPM2_DEV* dev,
TPMI_ALG_HASH hashAlg, int useOr, TPM_CC fuStartCC,
WOLFTPM2_SESSION* session)
{
int rc;
int provisioned = 0;
SetPrimaryPolicy_In policyIn;
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 platformPolicy[TPM_MAX_DIGEST_SIZE];
word32 aSz, bSz = 0, polSz = 0;

if (hsz == 0 || hsz > TPM_MAX_DIGEST_SIZE) {
return BAD_FUNC_ARG;
}
XMEMSET(session, 0, sizeof(*session));
XMEMSET(&orList, 0, sizeof(orList));

/* Fail early (before provisioning) if the TPM can't use this policy hash */
if (!firmware_hash_supported(hashAlg)) {
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));

/* Branch A: PolicyCommandCode(FieldUpgradeStart) - required to start FU */
XMEMSET(branchA, 0, sizeof(branchA));
aSz = hsz;
rc = BuildPolicyCommandCode(hashAlg, branchA, &aSz, fuStartCC);

/* Compute the platform authPolicy digest */
if (rc == 0) {
if (useOr) {
/* Branch B: a second, distinct policy branch */
XMEMSET(branchB, 0, sizeof(branchB));
bSz = hsz;
rc = BuildPolicyCommandCode(hashAlg, branchB, &bSz,
TPM_CC_NV_Read);
if (rc == 0) {
XMEMCPY(concat, branchA, aSz);
XMEMCPY(&concat[aSz], branchB, bSz);
XMEMSET(platformPolicy, 0, sizeof(platformPolicy));
polSz = hsz;
rc = wolfTPM2_PolicyHash(hashAlg, platformPolicy, &polSz,
TPM_CC_PolicyOR, concat, aSz + bSz);
}
}
else {
XMEMCPY(platformPolicy, branchA, aSz);
polSz = aSz;
}
}

/* Provision the platform primary policy (empty platformAuth) */
if (rc == 0) {
XMEMSET(&policyIn, 0, sizeof(policyIn));
policyIn.authHandle = TPM_RH_PLATFORM;
policyIn.hashAlg = hashAlg;
policyIn.authPolicy.size = (UINT16)polSz;
XMEMCPY(policyIn.authPolicy.buffer, platformPolicy, polSz);
rc = TPM2_SetPrimaryPolicy(&policyIn);
if (rc != 0) {
printf(" SetPrimaryPolicy failed 0x%x: %s\n",
rc, TPM2_GetRCString(rc));
}
else {
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) {
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, 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 */
if (provisioned) {
firmware_policy_clear(dev);
}
}
return rc;
}

#endif /* WOLFTPM_FIRMWARE_UPGRADE */
Loading
Loading