Skip to content

Commit b0c0bba

Browse files
committed
fwtpm: peer-review batch 3 (design changes)
ContextSave/ContextLoad integrity + confidentiality: - Add per-boot ctxProtectKey (volatile, 32-byte random) in FWTPM_CTX - FwWrapContextBlob: AES-CFB encrypt then HMAC-SHA256 session blob - FwUnwrapContextBlob: verify HMAC then AES-CFB decrypt, reject on integrity failure with TPM_RC_INTEGRITY - ContextLoad validates restored session fields (sessionType, authHash) - ContextSave uses TPM2_ForceZero instead of XMEMSET for session clear - Addresses CWE-502 (deserialization) and CWE-311 (missing encryption) Auth framework: - Policy session validation now checks hierarchy handles (owner, endorsement, platform, lockout) against SetPrimaryPolicy authPolicy - PolicyRestart resets all policy flags (isPasswordPolicy, isAuthValuePolicy, isPPRequired, cpHashA, nameHash) - wolfTPM2_SetSessionHandle: return BUFFER_E instead of truncating
1 parent d118866 commit b0c0bba

6 files changed

Lines changed: 271 additions & 24 deletions

File tree

src/fwtpm/fwtpm.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,19 @@ int FWTPM_Init(FWTPM_CTX* ctx)
9191
return rc;
9292
}
9393

94+
/* Generate per-boot context protection key (volatile only) for
95+
* ContextSave/ContextLoad HMAC + AES-CFB session blob protection. */
96+
rc = wc_RNG_GenerateBlock(&ctx->rng, ctx->ctxProtectKey,
97+
sizeof(ctx->ctxProtectKey));
98+
if (rc == 0) {
99+
ctx->ctxProtectKeyValid = 1;
100+
}
101+
else {
102+
wc_FreeRng(&ctx->rng);
103+
wolfCrypt_Cleanup();
104+
return rc;
105+
}
106+
94107
/* Initialize NV storage - loads existing state or creates fresh seeds */
95108
#ifndef FWTPM_NO_NV
96109
rc = FWTPM_NV_Init(ctx);

src/fwtpm/fwtpm_command.c

Lines changed: 85 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2580,17 +2580,26 @@ static TPM_RC FwCmd_ContextSave(FWTPM_CTX* ctx, TPM2_Packet* cmd,
25802580
TPM2_Packet_AppendU32(rsp, hierarchy);
25812581

25822582
if (isSession && sess != NULL) {
2583-
/* Session: serialize full FWTPM_Session into blob and free slot.
2584-
* Format: magic(4) | version(4) | FWTPM_Session (raw struct) */
2585-
blobSz = 4 + 4 + (UINT16)sizeof(FWTPM_Session);
2586-
TPM2_Packet_AppendU16(rsp, blobSz);
2587-
tmp32 = TPM2_Packet_SwapU32(FWTPM_CTX_MAGIC);
2588-
TPM2_Packet_AppendBytes(rsp, (byte*)&tmp32, 4);
2589-
tmp32 = TPM2_Packet_SwapU32(FWTPM_CTX_VER);
2590-
TPM2_Packet_AppendBytes(rsp, (byte*)&tmp32, 4);
2591-
TPM2_Packet_AppendBytes(rsp, (byte*)sess, sizeof(FWTPM_Session));
2592-
/* Free the session slot — ContextLoad will restore it */
2593-
XMEMSET(sess, 0, sizeof(FWTPM_Session));
2583+
/* Session: HMAC + AES-CFB protected blob per TPM 2.0 §30.
2584+
* Format: magic(4) | version(4) | wrappedBlob(iv+cipher+hmac) */
2585+
byte wrappedBuf[AES_BLOCK_SIZE + sizeof(FWTPM_Session) +
2586+
WC_SHA256_DIGEST_SIZE];
2587+
int wrappedSz = 0;
2588+
rc = FwWrapContextBlob(ctx, (const byte*)sess,
2589+
(int)sizeof(FWTPM_Session),
2590+
wrappedBuf, (int)sizeof(wrappedBuf), &wrappedSz);
2591+
if (rc == 0) {
2592+
blobSz = 4 + 4 + (UINT16)wrappedSz;
2593+
TPM2_Packet_AppendU16(rsp, blobSz);
2594+
tmp32 = TPM2_Packet_SwapU32(FWTPM_CTX_MAGIC);
2595+
TPM2_Packet_AppendBytes(rsp, (byte*)&tmp32, 4);
2596+
tmp32 = TPM2_Packet_SwapU32(FWTPM_CTX_VER);
2597+
TPM2_Packet_AppendBytes(rsp, (byte*)&tmp32, 4);
2598+
TPM2_Packet_AppendBytes(rsp, wrappedBuf, wrappedSz);
2599+
}
2600+
TPM2_ForceZero(wrappedBuf, sizeof(wrappedBuf));
2601+
/* Free the session slot */
2602+
TPM2_ForceZero(sess, sizeof(FWTPM_Session));
25942603
}
25952604
else {
25962605
/* Object: opaque handle reference (object stays in slot).
@@ -2660,14 +2669,44 @@ static TPM_RC FwCmd_ContextLoad(FWTPM_CTX* ctx, TPM2_Packet* cmd,
26602669

26612670
if ((savedHandle & 0xFF000000) == HMAC_SESSION_FIRST ||
26622671
(savedHandle & 0xFF000000) == POLICY_SESSION_FIRST) {
2663-
/* Session context: restore FWTPM_Session from blob into free slot */
2664-
if (dataLen != (UINT16)sizeof(FWTPM_Session)) {
2672+
/* Session context: verify integrity + decrypt, then restore.
2673+
* Wrapped blob: iv(16) + ciphertext(sizeof(FWTPM_Session)) + hmac(32) */
2674+
int expectedWrapSz = AES_BLOCK_SIZE +
2675+
(int)sizeof(FWTPM_Session) + WC_SHA256_DIGEST_SIZE;
2676+
FWTPM_Session restored;
2677+
int restoredSz = 0;
2678+
int si;
2679+
int found = 0;
2680+
2681+
if ((int)dataLen != expectedWrapSz) {
26652682
rc = TPM_RC_SIZE;
26662683
}
26672684
if (rc == 0) {
2668-
/* Restore session to any free slot */
2669-
int si;
2670-
int found = 0;
2685+
byte wrappedBuf[AES_BLOCK_SIZE + sizeof(FWTPM_Session) +
2686+
WC_SHA256_DIGEST_SIZE];
2687+
TPM2_Packet_ParseBytes(cmd, wrappedBuf, (int)dataLen);
2688+
rc = FwUnwrapContextBlob(ctx, wrappedBuf, (int)dataLen,
2689+
(byte*)&restored, (int)sizeof(restored), &restoredSz);
2690+
TPM2_ForceZero(wrappedBuf, sizeof(wrappedBuf));
2691+
if (rc != 0) {
2692+
/* HMAC mismatch or decrypt failure */
2693+
if (rc != TPM_RC_INTEGRITY)
2694+
rc = TPM_RC_INTEGRITY;
2695+
}
2696+
}
2697+
/* Validate deserialized session fields */
2698+
if (rc == 0) {
2699+
if (restored.sessionType != TPM_SE_HMAC &&
2700+
restored.sessionType != TPM_SE_POLICY &&
2701+
restored.sessionType != TPM_SE_TRIAL) {
2702+
rc = TPM_RC_VALUE;
2703+
}
2704+
if (TPM2_GetHashDigestSize(restored.authHash) <= 0) {
2705+
rc = TPM_RC_VALUE;
2706+
}
2707+
}
2708+
if (rc == 0) {
2709+
/* Find a free session slot */
26712710
for (si = 0; si < FWTPM_MAX_SESSIONS; si++) {
26722711
if (!ctx->sessions[si].used) {
26732712
found = 1;
@@ -2677,14 +2716,15 @@ static TPM_RC FwCmd_ContextLoad(FWTPM_CTX* ctx, TPM2_Packet* cmd,
26772716
if (!found) {
26782717
rc = TPM_RC_SESSION_MEMORY;
26792718
}
2680-
if (rc == 0) {
2681-
TPM2_Packet_ParseBytes(cmd, (byte*)&ctx->sessions[si],
2682-
sizeof(FWTPM_Session));
2683-
/* Keep the original handle — cpHash includes session
2684-
* handle as entity name, so it must match what ESYS
2685-
* computed. FwFindSession searches by handle value. */
2686-
}
26872719
}
2720+
if (rc == 0) {
2721+
XMEMCPY(&ctx->sessions[si], &restored,
2722+
sizeof(FWTPM_Session));
2723+
/* Keep the original handle — cpHash includes session
2724+
* handle as entity name, so it must match what ESYS
2725+
* computed. FwFindSession searches by handle value. */
2726+
}
2727+
TPM2_ForceZero(&restored, sizeof(restored));
26882728
}
26892729
else if ((savedHandle & 0xFF000000) == TRANSIENT_FIRST) {
26902730
/* Object context: verify object still in slot (opaque handle) */
@@ -7294,6 +7334,15 @@ static TPM_RC FwCmd_PolicyRestart(FWTPM_CTX* ctx, TPM2_Packet* cmd,
72947334
/* Reset policy digest to all zeros */
72957335
XMEMSET(sess->policyDigest.buffer, 0, sess->policyDigest.size);
72967336

7337+
/* Reset all policy-related session flags so stale assertions from
7338+
* prior policy evaluations don't affect future HMAC computation
7339+
* or authorization checks. */
7340+
sess->isPasswordPolicy = 0;
7341+
sess->isAuthValuePolicy = 0;
7342+
sess->isPPRequired = 0;
7343+
sess->cpHashA.size = 0;
7344+
sess->nameHash.size = 0;
7345+
72977346
FwRspFinalize(rsp, TPM_ST_NO_SESSIONS, TPM_RC_SUCCESS);
72987347
}
72997348

@@ -12477,6 +12526,19 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx,
1247712526
authPolicy = &objEnt->pub.authPolicy;
1247812527
}
1247912528
}
12529+
/* Hierarchy handles: check SetPrimaryPolicy-assigned policy */
12530+
else if (entityH == TPM_RH_OWNER) {
12531+
authPolicy = &ctx->ownerPolicy;
12532+
}
12533+
else if (entityH == TPM_RH_ENDORSEMENT) {
12534+
authPolicy = &ctx->endorsementPolicy;
12535+
}
12536+
else if (entityH == TPM_RH_PLATFORM) {
12537+
authPolicy = &ctx->platformPolicy;
12538+
}
12539+
else if (entityH == TPM_RH_LOCKOUT) {
12540+
authPolicy = &ctx->lockoutPolicy;
12541+
}
1248012542

1248112543
/* If entity has a non-empty authPolicy, it must match */
1248212544
if (authPolicy != NULL && authPolicy->size > 0) {

src/fwtpm/fwtpm_crypto.c

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,6 +1208,163 @@ int FwUnwrapPrivate(FWTPM_Object* parent,
12081208
return rc;
12091209
}
12101210

1211+
/* ================================================================== */
1212+
/* Context blob wrap/unwrap (ContextSave/ContextLoad) */
1213+
/* ================================================================== */
1214+
1215+
/* Encrypt-then-MAC context blob protection using the per-boot key.
1216+
* Layout: iv(16) | ciphertext(plainSz) | hmac(32)
1217+
* Returns 0 on success, sets *outSz. */
1218+
int FwWrapContextBlob(FWTPM_CTX* ctx,
1219+
const byte* plain, int plainSz,
1220+
byte* out, int outBufSz, int* outSz)
1221+
{
1222+
int rc = TPM_RC_SUCCESS;
1223+
byte iv[AES_BLOCK_SIZE];
1224+
FWTPM_DECLARE_VAR(aes, Aes);
1225+
FWTPM_DECLARE_VAR(hmac, Hmac);
1226+
int aesInit = 0;
1227+
int totalSz = AES_BLOCK_SIZE + plainSz + WC_SHA256_DIGEST_SIZE;
1228+
1229+
*outSz = 0;
1230+
if (!ctx->ctxProtectKeyValid || totalSz > outBufSz) {
1231+
return TPM_RC_FAILURE;
1232+
}
1233+
1234+
FWTPM_ALLOC_VAR(aes, Aes);
1235+
FWTPM_ALLOC_VAR(hmac, Hmac);
1236+
1237+
/* Generate random IV */
1238+
rc = wc_RNG_GenerateBlock(&ctx->rng, iv, AES_BLOCK_SIZE);
1239+
1240+
/* AES-CFB encrypt */
1241+
if (rc == 0) {
1242+
rc = wc_AesInit(aes, NULL, INVALID_DEVID);
1243+
if (rc == 0) {
1244+
aesInit = 1;
1245+
rc = wc_AesSetKey(aes, ctx->ctxProtectKey, 32, iv, AES_ENCRYPTION);
1246+
}
1247+
}
1248+
if (rc == 0) {
1249+
XMEMCPY(out, iv, AES_BLOCK_SIZE);
1250+
rc = wc_AesCfbEncrypt(aes, out + AES_BLOCK_SIZE, plain, plainSz);
1251+
}
1252+
if (aesInit) {
1253+
wc_AesFree(aes);
1254+
}
1255+
1256+
/* HMAC-SHA256 over iv || ciphertext */
1257+
if (rc == 0) {
1258+
rc = wc_HmacInit(hmac, NULL, INVALID_DEVID);
1259+
}
1260+
if (rc == 0) {
1261+
rc = wc_HmacSetKey(hmac, WC_SHA256,
1262+
ctx->ctxProtectKey, sizeof(ctx->ctxProtectKey));
1263+
}
1264+
if (rc == 0) {
1265+
rc = wc_HmacUpdate(hmac, out, AES_BLOCK_SIZE + plainSz);
1266+
}
1267+
if (rc == 0) {
1268+
rc = wc_HmacFinal(hmac, out + AES_BLOCK_SIZE + plainSz);
1269+
}
1270+
wc_HmacFree(hmac);
1271+
1272+
if (rc == 0) {
1273+
*outSz = totalSz;
1274+
}
1275+
else {
1276+
TPM2_ForceZero(out, totalSz);
1277+
rc = TPM_RC_FAILURE;
1278+
}
1279+
1280+
TPM2_ForceZero(iv, sizeof(iv));
1281+
FWTPM_FREE_VAR(aes);
1282+
FWTPM_FREE_VAR(hmac);
1283+
return rc;
1284+
}
1285+
1286+
/* Verify-then-decrypt context blob. Returns 0 on success, sets *outSz. */
1287+
int FwUnwrapContextBlob(FWTPM_CTX* ctx,
1288+
const byte* in, int inSz,
1289+
byte* out, int outBufSz, int* outSz)
1290+
{
1291+
int rc = TPM_RC_SUCCESS;
1292+
byte computedHmac[WC_SHA256_DIGEST_SIZE];
1293+
FWTPM_DECLARE_VAR(aes, Aes);
1294+
FWTPM_DECLARE_VAR(hmac, Hmac);
1295+
int aesInit = 0;
1296+
int cipherSz;
1297+
1298+
*outSz = 0;
1299+
/* Minimum: iv(16) + at least 1 byte ciphertext + hmac(32) */
1300+
if (!ctx->ctxProtectKeyValid ||
1301+
inSz < AES_BLOCK_SIZE + 1 + WC_SHA256_DIGEST_SIZE) {
1302+
return TPM_RC_FAILURE;
1303+
}
1304+
1305+
cipherSz = inSz - AES_BLOCK_SIZE - WC_SHA256_DIGEST_SIZE;
1306+
if (cipherSz > outBufSz) {
1307+
return TPM_RC_SIZE;
1308+
}
1309+
1310+
FWTPM_ALLOC_VAR(aes, Aes);
1311+
FWTPM_ALLOC_VAR(hmac, Hmac);
1312+
1313+
/* Verify HMAC over iv || ciphertext */
1314+
if (rc == 0) {
1315+
rc = wc_HmacInit(hmac, NULL, INVALID_DEVID);
1316+
}
1317+
if (rc == 0) {
1318+
rc = wc_HmacSetKey(hmac, WC_SHA256,
1319+
ctx->ctxProtectKey, sizeof(ctx->ctxProtectKey));
1320+
}
1321+
if (rc == 0) {
1322+
rc = wc_HmacUpdate(hmac, in, AES_BLOCK_SIZE + cipherSz);
1323+
}
1324+
if (rc == 0) {
1325+
rc = wc_HmacFinal(hmac, computedHmac);
1326+
}
1327+
wc_HmacFree(hmac);
1328+
1329+
if (rc == 0) {
1330+
if (TPM2_ConstantCompare(computedHmac,
1331+
in + AES_BLOCK_SIZE + cipherSz,
1332+
WC_SHA256_DIGEST_SIZE) != 0) {
1333+
rc = TPM_RC_INTEGRITY;
1334+
}
1335+
}
1336+
1337+
/* AES-CFB decrypt using IV from blob.
1338+
* CFB mode uses the encrypt direction internally for both
1339+
* encryption and decryption (encrypts IV, XORs with data). */
1340+
if (rc == 0) {
1341+
rc = wc_AesInit(aes, NULL, INVALID_DEVID);
1342+
if (rc == 0) {
1343+
aesInit = 1;
1344+
rc = wc_AesSetKey(aes, ctx->ctxProtectKey, 32, in, AES_ENCRYPTION);
1345+
}
1346+
}
1347+
if (rc == 0) {
1348+
rc = wc_AesCfbDecrypt(aes, out, in + AES_BLOCK_SIZE, cipherSz);
1349+
}
1350+
if (aesInit) {
1351+
wc_AesFree(aes);
1352+
}
1353+
1354+
if (rc == 0) {
1355+
*outSz = cipherSz;
1356+
}
1357+
else if (rc != TPM_RC_INTEGRITY) {
1358+
TPM2_ForceZero(out, cipherSz);
1359+
rc = TPM_RC_FAILURE;
1360+
}
1361+
1362+
TPM2_ForceZero(computedHmac, sizeof(computedHmac));
1363+
FWTPM_FREE_VAR(aes);
1364+
FWTPM_FREE_VAR(hmac);
1365+
return rc;
1366+
}
1367+
12111368
/* ================================================================== */
12121369
/* Seed encrypt/decrypt */
12131370
/* ================================================================== */

src/tpm2_wrap.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1866,7 +1866,7 @@ int wolfTPM2_SetSessionHandle(WOLFTPM2_DEV* dev, int index,
18661866

18671867
session->auth.size = tpmSession->handle.auth.size;
18681868
if (session->auth.size > sizeof(session->auth.buffer)) {
1869-
session->auth.size = sizeof(session->auth.buffer); /* truncate */
1869+
return BUFFER_E;
18701870
}
18711871
XMEMCPY(session->auth.buffer, tpmSession->handle.auth.buffer,
18721872
session->auth.size);

wolftpm/fwtpm/fwtpm.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,12 @@ typedef struct FWTPM_CTX {
425425
TPM2B_DIGEST lockoutPolicy;
426426
TPMI_ALG_HASH lockoutPolicyAlg;
427427

428+
/* Per-boot context protection key (volatile only, never persisted).
429+
* Used by ContextSave/ContextLoad for HMAC + AES-CFB protection of
430+
* session context blobs per TPM 2.0 Part 1 §30. */
431+
byte ctxProtectKey[32];
432+
int ctxProtectKeyValid;
433+
428434
/* TIS transport state (when not using sockets) */
429435
#ifdef WOLFTPM_FWTPM_TIS
430436
FWTPM_TIS_HAL tisHal; /* Transport HAL callbacks */

wolftpm/fwtpm/fwtpm_crypto.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,15 @@ int FwUnwrapPrivate(FWTPM_Object* parent,
162162
UINT16* sensitiveType, TPM2B_AUTH* auth,
163163
byte* privKeyDer, int* privKeyDerSz);
164164

165+
/* --- Context blob wrap/unwrap (ContextSave/Load) --- */
166+
167+
int FwWrapContextBlob(FWTPM_CTX* ctx,
168+
const byte* plain, int plainSz,
169+
byte* out, int outBufSz, int* outSz);
170+
int FwUnwrapContextBlob(FWTPM_CTX* ctx,
171+
const byte* in, int inSz,
172+
byte* out, int outBufSz, int* outSz);
173+
165174
/* --- Seed encrypt/decrypt --- */
166175

167176
TPM_RC FwDecryptSeed(FWTPM_CTX* ctx,

0 commit comments

Comments
 (0)