Skip to content

Commit 0b72b95

Browse files
committed
Add caller-supplied policy authorization for TPM firmware upgrade
1 parent 6b63b67 commit 0b72b95

9 files changed

Lines changed: 946 additions & 83 deletions

File tree

examples/firmware/README.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,3 +199,58 @@ Success: Please reset or power cycle TPM
199199
```
200200

201201
**Note**: Firmware files cannot be made public and must be obtained separately from STMicroelectronics.
202+
203+
## Policy-Based Authorization (Advanced)
204+
205+
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.
206+
207+
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:
208+
209+
- **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.
210+
- **ST33**: the supplied session replaces the default `TPM_RS_PW` password authorization.
211+
212+
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.
213+
214+
Example: satisfy a multi-branch `PolicyOR` (up to 8 branches, SHA2-512 shown) and start the upgrade under it:
215+
216+
```c
217+
WOLFTPM2_SESSION session;
218+
TPML_DIGEST orList;
219+
uint8_t manifest_hash[TPM_SHA512_DIGEST_SIZE];
220+
int rc;
221+
222+
/* zero both structs - orList must not carry uninitialized branch sizes */
223+
XMEMSET(&session, 0, sizeof(session));
224+
XMEMSET(&orList, 0, sizeof(orList));
225+
226+
/* start a policy session using the desired policy hash (SHA2-512 for PQC) */
227+
rc = wolfTPM2_StartSession_ex(&dev, &session, NULL, NULL,
228+
TPM_SE_POLICY, TPM_ALG_NULL, TPM_ALG_SHA512);
229+
if (rc != TPM_RC_SUCCESS) goto cleanup;
230+
231+
/* Satisfy one branch (PCR, PolicySigned/Authorize, PolicyAuthValue, ...), then
232+
* OR against the full branch list the platform authPolicy encodes. Set count
233+
* and each digests[i].size/buffer for every branch you populate. */
234+
orList.count = 2;
235+
/* orList.digests[0].size = ...; XMEMCPY(orList.digests[0].buffer, ...); */
236+
/* orList.digests[1].size = ...; XMEMCPY(orList.digests[1].buffer, ...); */
237+
rc = wolfTPM2_PolicyOR(&dev, &session, &orList);
238+
if (rc != TPM_RC_SUCCESS) goto cleanup;
239+
240+
/* hash the manifest with the matching algorithm, then start the upgrade under
241+
* the caller-satisfied session (NULL would use the library-default auth) */
242+
rc = wc_Sha512Hash(manifest, manifest_sz, manifest_hash);
243+
if (rc != 0) goto cleanup;
244+
rc = wolfTPM2_FirmwareUpgradeHash_ex(&dev, TPM_ALG_SHA512,
245+
manifest_hash, (uint32_t)sizeof(manifest_hash),
246+
manifest, manifest_sz, fwDataCb, fwCbCtx, &session);
247+
248+
cleanup:
249+
/* the TPM consumes the session on a successful start; release it otherwise */
250+
if (session.handle.hndl != 0)
251+
wolfTPM2_UnloadHandle(&dev, &session.handle);
252+
```
253+
254+
Passing `NULL` for the final `startSession` argument makes `wolfTPM2_FirmwareUpgradeHash_ex()` behave exactly like `wolfTPM2_FirmwareUpgradeHash()` (library-managed authorization), so existing code is unaffected.
255+
256+
**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.
Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
/* firmware_policy.c
2+
*
3+
* Copyright (C) 2006-2026 wolfSSL Inc.
4+
*
5+
* This file is part of wolfTPM.
6+
*
7+
* wolfTPM is free software; you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation; either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* wolfTPM is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program; if not, write to the Free Software
19+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
20+
*/
21+
22+
#ifdef HAVE_CONFIG_H
23+
#include <config.h>
24+
#endif
25+
26+
#include <examples/firmware/firmware_policy.h>
27+
28+
#ifdef WOLFTPM_FIRMWARE_UPGRADE
29+
30+
#include <stdio.h>
31+
32+
/* Print a digest as hex. Unlike TPM2_PrintBin (a no-op unless DEBUG_WOLFTPM),
33+
* this is always available so the self-test failure report is usable in a
34+
* stock build. */
35+
static void firmware_print_hex(const byte* buf, word32 len)
36+
{
37+
word32 j;
38+
for (j = 0; j < len; j++) {
39+
printf("%02x", buf[j]);
40+
}
41+
printf("\n");
42+
}
43+
44+
/* Build a PolicyCommandCode branch digest offline. This matches the running
45+
* policy digest of a fresh policy session after wolfTPM2_PolicyCommandCode. */
46+
static int BuildPolicyCommandCode(TPMI_ALG_HASH hashAlg,
47+
byte* digest, word32* digestSz, TPM_CC cc)
48+
{
49+
byte val[4]; /* command code big-endian, matching the TPM wire format */
50+
val[0] = (byte)((cc >> 24) & 0xFF);
51+
val[1] = (byte)((cc >> 16) & 0xFF);
52+
val[2] = (byte)((cc >> 8) & 0xFF);
53+
val[3] = (byte)(cc & 0xFF);
54+
return wolfTPM2_PolicyHash(hashAlg, digest, digestSz,
55+
TPM_CC_PolicyCommandCode, val, sizeof(val));
56+
}
57+
58+
/* Return 1 if the TPM implements the given algorithm, 0 otherwise. Used to
59+
* skip a policy hash (for example SHA2-512) that the TPM firmware does not
60+
* support, rather than issuing a session that fails with TPM_RC_SIZE. */
61+
static int firmware_hash_supported(TPM_ALG_ID alg)
62+
{
63+
GetCapability_In capIn;
64+
GetCapability_Out capOut;
65+
TPML_ALG_PROPERTY* algs;
66+
67+
XMEMSET(&capIn, 0, sizeof(capIn));
68+
XMEMSET(&capOut, 0, sizeof(capOut));
69+
capIn.capability = TPM_CAP_ALGS;
70+
capIn.property = alg;
71+
capIn.propertyCount = 1;
72+
if (TPM2_GetCapability(&capIn, &capOut) != TPM_RC_SUCCESS) {
73+
return 0; /* conservative: treat query failure as unsupported */
74+
}
75+
/* The TPM returns algorithms with ID >= property; a match at index 0
76+
* means the requested algorithm is implemented. */
77+
algs = &capOut.capabilityData.data.algorithms;
78+
if (algs->count >= 1 && algs->algProperties[0].alg == alg) {
79+
return 1;
80+
}
81+
return 0;
82+
}
83+
84+
/* Exercise wolfTPM2_PolicyOR at the requested hash and verify the TPM's
85+
* running policy digest matches an offline computation. Non-destructive.
86+
* Returns 0 on match, 1 if the hash is not implemented (intentional skip),
87+
* -1 on digest mismatch, or a TPM rc / BAD_FUNC_ARG on other errors. */
88+
static int firmware_policy_selftest(WOLFTPM2_DEV* dev, TPMI_ALG_HASH hashAlg,
89+
const char* name)
90+
{
91+
int rc;
92+
WOLFTPM2_SESSION sess;
93+
TPML_DIGEST orList;
94+
word32 hsz = (word32)TPM2_GetHashDigestSize(hashAlg);
95+
byte branchA[TPM_MAX_DIGEST_SIZE];
96+
byte branchB[TPM_MAX_DIGEST_SIZE];
97+
byte concat[2 * TPM_MAX_DIGEST_SIZE];
98+
byte expected[TPM_MAX_DIGEST_SIZE];
99+
byte got[TPM_MAX_DIGEST_SIZE];
100+
word32 aSz, bSz, expSz, gotSz;
101+
102+
XMEMSET(&sess, 0, sizeof(sess));
103+
XMEMSET(&orList, 0, sizeof(orList));
104+
105+
if (hsz == 0 || hsz > TPM_MAX_DIGEST_SIZE) {
106+
return BAD_FUNC_ARG;
107+
}
108+
109+
/* Skip cleanly if the TPM firmware does not implement this hash */
110+
if (!firmware_hash_supported(hashAlg)) {
111+
printf(" %s: skipped (not implemented by this TPM)\n", name);
112+
return 1; /* intentional skip, not a failure */
113+
}
114+
115+
/* Offline: two distinct PolicyCommandCode branch digests */
116+
XMEMSET(branchA, 0, sizeof(branchA));
117+
aSz = hsz;
118+
rc = BuildPolicyCommandCode(hashAlg, branchA, &aSz, TPM_CC_NV_Read);
119+
if (rc == 0) {
120+
XMEMSET(branchB, 0, sizeof(branchB));
121+
bSz = hsz;
122+
rc = BuildPolicyCommandCode(hashAlg, branchB, &bSz, TPM_CC_Unseal);
123+
}
124+
/* Offline PolicyOR digest = H(zeros || TPM_CC_PolicyOR || A || B) */
125+
if (rc == 0) {
126+
XMEMCPY(concat, branchA, aSz);
127+
XMEMCPY(&concat[aSz], branchB, bSz);
128+
XMEMSET(expected, 0, sizeof(expected));
129+
expSz = hsz;
130+
rc = wolfTPM2_PolicyHash(hashAlg, expected, &expSz,
131+
TPM_CC_PolicyOR, concat, aSz + bSz);
132+
}
133+
134+
/* On-TPM: start a policy session using the requested hash algorithm */
135+
if (rc == 0) {
136+
rc = wolfTPM2_StartSession_ex(dev, &sess, NULL, NULL,
137+
TPM_SE_POLICY, TPM_ALG_NULL, hashAlg);
138+
if (rc != 0) {
139+
printf(" %s: StartSession failed 0x%x: %s\n",
140+
name, rc, TPM2_GetRCString(rc));
141+
return rc;
142+
}
143+
}
144+
/* Satisfy branch A, then OR against {A,B} with the new wrapper */
145+
if (rc == 0) {
146+
rc = wolfTPM2_PolicyCommandCode(dev, &sess, TPM_CC_NV_Read);
147+
}
148+
if (rc == 0) {
149+
orList.count = 2;
150+
orList.digests[0].size = (UINT16)aSz;
151+
XMEMCPY(orList.digests[0].buffer, branchA, aSz);
152+
orList.digests[1].size = (UINT16)bSz;
153+
XMEMCPY(orList.digests[1].buffer, branchB, bSz);
154+
rc = wolfTPM2_PolicyOR(dev, &sess, &orList);
155+
}
156+
if (rc == 0) {
157+
gotSz = (word32)sizeof(got);
158+
rc = wolfTPM2_GetPolicyDigest(dev, sess.handle.hndl, got, &gotSz);
159+
}
160+
161+
if (rc == 0) {
162+
if (gotSz == expSz && XMEMCMP(got, expected, expSz) == 0) {
163+
printf(" %s PolicyOR: PASS (%u byte digest matches)\n",
164+
name, expSz);
165+
}
166+
else {
167+
printf(" %s PolicyOR: FAIL (digest mismatch)\n", name);
168+
printf(" expected: ");
169+
firmware_print_hex(expected, expSz);
170+
printf(" got: ");
171+
firmware_print_hex(got, gotSz);
172+
rc = -1;
173+
}
174+
}
175+
else {
176+
printf(" %s PolicyOR: ERROR 0x%x: %s\n",
177+
name, rc, TPM2_GetRCString(rc));
178+
}
179+
180+
wolfTPM2_UnloadHandle(dev, &sess.handle);
181+
return rc;
182+
}
183+
184+
int firmware_policy_selftest_all(WOLFTPM2_DEV* dev)
185+
{
186+
int i, rc, hardFail = 0;
187+
struct { TPMI_ALG_HASH alg; const char* name; } hashes[3];
188+
189+
hashes[0].alg = TPM_ALG_SHA256; hashes[0].name = "SHA2-256";
190+
hashes[1].alg = TPM_ALG_SHA384; hashes[1].name = "SHA2-384";
191+
hashes[2].alg = TPM_ALG_SHA512; hashes[2].name = "SHA2-512";
192+
193+
printf("Firmware policy authorization self-test "
194+
"(no firmware changes):\n");
195+
for (i = 0; i < 3; i++) {
196+
rc = firmware_policy_selftest(dev, hashes[i].alg, hashes[i].name);
197+
/* rc == 1 is an intentional "hash not implemented" skip. Any other
198+
* non-zero (digest mismatch, bad arg, or a TPM rc) is a failure. */
199+
if (rc != 0 && rc != 1) {
200+
hardFail = 1;
201+
}
202+
}
203+
return hardFail ? -1 : 0;
204+
}
205+
206+
/* Clear any platform authPolicy we provisioned so a later default-auth run is
207+
* not locked out (the platform policy is otherwise cleared only on reset). */
208+
static void firmware_policy_clear(WOLFTPM2_DEV* dev)
209+
{
210+
SetPrimaryPolicy_In clr;
211+
(void)dev;
212+
XMEMSET(&clr, 0, sizeof(clr));
213+
clr.authHandle = TPM_RH_PLATFORM;
214+
clr.hashAlg = TPM_ALG_NULL; /* empty policy */
215+
clr.authPolicy.size = 0;
216+
if (TPM2_SetPrimaryPolicy(&clr) == TPM_RC_SUCCESS) {
217+
printf(" Cleared platform policy after setup failure\n");
218+
}
219+
}
220+
221+
int firmware_policy_session_setup(WOLFTPM2_DEV* dev,
222+
TPMI_ALG_HASH hashAlg, int useOr, TPM_CC fuStartCC,
223+
WOLFTPM2_SESSION* session)
224+
{
225+
int rc;
226+
int provisioned = 0;
227+
SetPrimaryPolicy_In policyIn;
228+
TPML_DIGEST orList;
229+
word32 hsz = (word32)TPM2_GetHashDigestSize(hashAlg);
230+
byte branchA[TPM_MAX_DIGEST_SIZE];
231+
byte branchB[TPM_MAX_DIGEST_SIZE];
232+
byte concat[2 * TPM_MAX_DIGEST_SIZE];
233+
byte platformPolicy[TPM_MAX_DIGEST_SIZE];
234+
word32 aSz, bSz = 0, polSz = 0;
235+
236+
if (hsz == 0 || hsz > TPM_MAX_DIGEST_SIZE) {
237+
return BAD_FUNC_ARG;
238+
}
239+
XMEMSET(session, 0, sizeof(*session));
240+
XMEMSET(&orList, 0, sizeof(orList));
241+
242+
/* Fail early (before provisioning) if the TPM can't use this policy hash */
243+
if (!firmware_hash_supported(hashAlg)) {
244+
printf("Policy hash %s not implemented by this TPM\n",
245+
TPM2_GetAlgName(hashAlg));
246+
return BAD_FUNC_ARG;
247+
}
248+
249+
printf("Provisioning platform policy (%s, %s)\n",
250+
useOr ? "PolicyOR" : "PolicyCommandCode",
251+
TPM2_GetAlgName(hashAlg));
252+
253+
/* Branch A: PolicyCommandCode(FieldUpgradeStart) - required to start FU */
254+
XMEMSET(branchA, 0, sizeof(branchA));
255+
aSz = hsz;
256+
rc = BuildPolicyCommandCode(hashAlg, branchA, &aSz, fuStartCC);
257+
258+
/* Compute the platform authPolicy digest */
259+
if (rc == 0) {
260+
if (useOr) {
261+
/* Branch B: a second, distinct policy branch */
262+
XMEMSET(branchB, 0, sizeof(branchB));
263+
bSz = hsz;
264+
rc = BuildPolicyCommandCode(hashAlg, branchB, &bSz,
265+
TPM_CC_NV_Read);
266+
if (rc == 0) {
267+
XMEMCPY(concat, branchA, aSz);
268+
XMEMCPY(&concat[aSz], branchB, bSz);
269+
XMEMSET(platformPolicy, 0, sizeof(platformPolicy));
270+
polSz = hsz;
271+
rc = wolfTPM2_PolicyHash(hashAlg, platformPolicy, &polSz,
272+
TPM_CC_PolicyOR, concat, aSz + bSz);
273+
}
274+
}
275+
else {
276+
XMEMCPY(platformPolicy, branchA, aSz);
277+
polSz = aSz;
278+
}
279+
}
280+
281+
/* Provision the platform primary policy (empty platformAuth) */
282+
if (rc == 0) {
283+
XMEMSET(&policyIn, 0, sizeof(policyIn));
284+
policyIn.authHandle = TPM_RH_PLATFORM;
285+
policyIn.hashAlg = hashAlg;
286+
policyIn.authPolicy.size = (UINT16)polSz;
287+
XMEMCPY(policyIn.authPolicy.buffer, platformPolicy, polSz);
288+
rc = TPM2_SetPrimaryPolicy(&policyIn);
289+
if (rc != 0) {
290+
printf(" SetPrimaryPolicy failed 0x%x: %s\n",
291+
rc, TPM2_GetRCString(rc));
292+
}
293+
else {
294+
provisioned = 1;
295+
}
296+
}
297+
298+
/* Start a policy session and satisfy the platform policy */
299+
if (rc == 0) {
300+
rc = wolfTPM2_StartSession_ex(dev, session, NULL, NULL,
301+
TPM_SE_POLICY, TPM_ALG_NULL, hashAlg);
302+
if (rc != 0) {
303+
printf(" StartSession failed 0x%x: %s\n",
304+
rc, TPM2_GetRCString(rc));
305+
}
306+
}
307+
if (rc == 0) {
308+
rc = wolfTPM2_PolicyCommandCode(dev, session, fuStartCC);
309+
}
310+
if (rc == 0 && useOr) {
311+
orList.count = 2;
312+
orList.digests[0].size = (UINT16)aSz;
313+
XMEMCPY(orList.digests[0].buffer, branchA, aSz);
314+
orList.digests[1].size = (UINT16)bSz;
315+
XMEMCPY(orList.digests[1].buffer, branchB, bSz);
316+
rc = wolfTPM2_PolicyOR(dev, session, &orList);
317+
}
318+
319+
if (rc != 0) {
320+
if (session->handle.hndl != 0) {
321+
wolfTPM2_UnloadHandle(dev, &session->handle);
322+
}
323+
/* Restore default platform auth so a later run is not locked out */
324+
if (provisioned) {
325+
firmware_policy_clear(dev);
326+
}
327+
}
328+
return rc;
329+
}
330+
331+
#endif /* WOLFTPM_FIRMWARE_UPGRADE */

0 commit comments

Comments
 (0)