diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index f5affad0..9ea3806b 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -2930,6 +2930,8 @@ static TPM_RC FwCmd_PCR_SetAuthPolicy(FWTPM_CTX* ctx, TPM2_Packet* cmd, UINT16 hashAlg = TPM_ALG_NULL; UINT32 pcrNum = 0; int pcrIndex; + TPM2B_DIGEST oldPcrPolicy; + TPMI_ALG_HASH oldPcrPolicyAlg; (void)cmdSize; @@ -2978,14 +2980,22 @@ static TPM_RC FwCmd_PCR_SetAuthPolicy(FWTPM_CTX* ctx, TPM2_Packet* cmd, pcrIndex, policySz, hashAlg); #endif + oldPcrPolicy = ctx->pcrPolicy[pcrIndex]; + oldPcrPolicyAlg = ctx->pcrPolicyAlg[pcrIndex]; ctx->pcrPolicy[pcrIndex].size = policySz; if (policySz > 0) { XMEMCPY(ctx->pcrPolicy[pcrIndex].buffer, policyBuf, policySz); } ctx->pcrPolicyAlg[pcrIndex] = (policySz > 0) ? hashAlg : (TPMI_ALG_HASH)TPM_ALG_NULL; - FWTPM_NV_SavePcrAuth(ctx); - FwRspNoParams(rsp, cmdTag); + rc = FWTPM_NV_SavePcrAuth(ctx); + if (rc == 0) { + FwRspNoParams(rsp, cmdTag); + } + else { + ctx->pcrPolicy[pcrIndex] = oldPcrPolicy; + ctx->pcrPolicyAlg[pcrIndex] = oldPcrPolicyAlg; + } } return rc; @@ -3001,6 +3011,9 @@ static TPM_RC FwCmd_PCR_SetAuthValue(FWTPM_CTX* ctx, TPM2_Packet* cmd, UINT16 newAuthSz = 0; byte newAuthBuf[TPM_MAX_DIGEST_SIZE]; int pcrIndex; + TPM2B_AUTH oldPcrAuth; + + XMEMSET(&oldPcrAuth, 0, sizeof(oldPcrAuth)); (void)cmdSize; @@ -3035,17 +3048,26 @@ static TPM_RC FwCmd_PCR_SetAuthValue(FWTPM_CTX* ctx, TPM2_Packet* cmd, pcrIndex, newAuthSz); #endif + oldPcrAuth = ctx->pcrAuth[pcrIndex]; TPM2_ForceZero(ctx->pcrAuth[pcrIndex].buffer, sizeof(ctx->pcrAuth[pcrIndex].buffer)); ctx->pcrAuth[pcrIndex].size = newAuthSz; if (newAuthSz > 0) { XMEMCPY(ctx->pcrAuth[pcrIndex].buffer, newAuthBuf, newAuthSz); } - FWTPM_NV_SavePcrAuth(ctx); - FwRspNoParams(rsp, cmdTag); + rc = FWTPM_NV_SavePcrAuth(ctx); + if (rc == 0) { + FwRspNoParams(rsp, cmdTag); + } + else { + TPM2_ForceZero(ctx->pcrAuth[pcrIndex].buffer, + sizeof(ctx->pcrAuth[pcrIndex].buffer)); + ctx->pcrAuth[pcrIndex] = oldPcrAuth; + } } TPM2_ForceZero(newAuthBuf, sizeof(newAuthBuf)); + TPM2_ForceZero(&oldPcrAuth, sizeof(oldPcrAuth)); return rc; } @@ -3117,6 +3139,7 @@ static TPM_RC FwCmd_ClockSet(FWTPM_CTX* ctx, TPM2_Packet* cmd, } if (rc == 0) { + UINT64 oldClockOffset = ctx->clockOffset; /* Calculate offset: if clock HAL is set, offset = newTime - halTime. * If no HAL, offset = newTime directly (original behavior). */ if (ctx->clockHal.get_ms != NULL) { @@ -3126,8 +3149,13 @@ static TPM_RC FwCmd_ClockSet(FWTPM_CTX* ctx, TPM2_Packet* cmd, else { ctx->clockOffset = newTime; } - FWTPM_NV_SaveClock(ctx); - FwRspNoParams(rsp, cmdTag); + rc = FWTPM_NV_SaveClock(ctx); + if (rc == 0) { + FwRspNoParams(rsp, cmdTag); + } + else { + ctx->clockOffset = oldClockOffset; + } } return rc; @@ -5762,49 +5790,35 @@ static TPM_RC FwCmd_HierarchyChangeAuth(FWTPM_CTX* ctx, TPM2_Packet* cmd, #endif if (rc == 0) { + TPM2B_AUTH* auth = NULL; + TPM2B_AUTH oldAuth; + + XMEMSET(&oldAuth, 0, sizeof(oldAuth)); switch (authHandle) { - case TPM_RH_OWNER: - TPM2_ForceZero(ctx->ownerAuth.buffer, - sizeof(ctx->ownerAuth.buffer)); - ctx->ownerAuth.size = newAuthSize; - if (newAuthSize > 0) { - XMEMCPY(ctx->ownerAuth.buffer, newAuthBuf, newAuthSize); - } - break; - case TPM_RH_ENDORSEMENT: - TPM2_ForceZero(ctx->endorsementAuth.buffer, - sizeof(ctx->endorsementAuth.buffer)); - ctx->endorsementAuth.size = newAuthSize; - if (newAuthSize > 0) { - XMEMCPY(ctx->endorsementAuth.buffer, newAuthBuf, - newAuthSize); - } - break; - case TPM_RH_PLATFORM: - TPM2_ForceZero(ctx->platformAuth.buffer, - sizeof(ctx->platformAuth.buffer)); - ctx->platformAuth.size = newAuthSize; - if (newAuthSize > 0) { - XMEMCPY(ctx->platformAuth.buffer, newAuthBuf, newAuthSize); - } - break; - case TPM_RH_LOCKOUT: - TPM2_ForceZero(ctx->lockoutAuth.buffer, - sizeof(ctx->lockoutAuth.buffer)); - ctx->lockoutAuth.size = newAuthSize; - if (newAuthSize > 0) { - XMEMCPY(ctx->lockoutAuth.buffer, newAuthBuf, newAuthSize); - } - break; - default: - rc = TPM_RC_HIERARCHY; - break; + case TPM_RH_OWNER: auth = &ctx->ownerAuth; break; + case TPM_RH_ENDORSEMENT: auth = &ctx->endorsementAuth; break; + case TPM_RH_PLATFORM: auth = &ctx->platformAuth; break; + case TPM_RH_LOCKOUT: auth = &ctx->lockoutAuth; break; + default: rc = TPM_RC_HIERARCHY; break; } - } - if (rc == 0) { - FWTPM_NV_SaveAuth(ctx, authHandle); - FwRspNoParams(rsp, cmdTag); + if (rc == 0) { + oldAuth = *auth; + TPM2_ForceZero(auth->buffer, sizeof(auth->buffer)); + auth->size = newAuthSize; + if (newAuthSize > 0) { + XMEMCPY(auth->buffer, newAuthBuf, newAuthSize); + } + rc = FWTPM_NV_SaveAuth(ctx, authHandle); + if (rc == 0) { + FwRspNoParams(rsp, cmdTag); + } + else { + TPM2_ForceZero(auth->buffer, sizeof(auth->buffer)); + *auth = oldAuth; + } + TPM2_ForceZero(&oldAuth, sizeof(oldAuth)); + } } TPM2_ForceZero(newAuthBuf, sizeof(newAuthBuf)); @@ -5888,13 +5902,21 @@ static TPM_RC FwCmd_SetPrimaryPolicy(FWTPM_CTX* ctx, TPM2_Packet* cmd, break; } if (rc == 0 && policy != NULL) { + TPM2B_DIGEST oldPolicy = *policy; + TPMI_ALG_HASH oldPolicyAlg = *policyAlg; policy->size = policySz; if (policySz > 0) { XMEMCPY(policy->buffer, policyBuf, policySz); } *policyAlg = hashAlg; - FWTPM_NV_SaveHierarchyPolicy(ctx, authHandle); - FwRspNoParams(rsp, cmdTag); + rc = FWTPM_NV_SaveHierarchyPolicy(ctx, authHandle); + if (rc == 0) { + FwRspNoParams(rsp, cmdTag); + } + else { + *policy = oldPolicy; + *policyAlg = oldPolicyAlg; + } } } @@ -5974,12 +5996,12 @@ static TPM_RC FwCmd_EvictControl(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* If objectHandle is persistent and matches persistentHandle -> evict */ if (rc == 0 && (objectHandle & 0xFF000000) == 0x81000000 && objectHandle == persistentHandle) { - /* Find and remove the persistent object */ + /* Locate the persistent object; the slot is cleared only after the + * journal delete below has committed. */ found = 0; for (i = 0; i < FWTPM_MAX_PERSISTENT; i++) { if (ctx->persistent[i].used && ctx->persistent[i].handle == persistentHandle) { - TPM2_ForceZero(&ctx->persistent[i], sizeof(FWTPM_Object)); found = 1; break; } @@ -6036,14 +6058,24 @@ static TPM_RC FwCmd_EvictControl(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (rc == 0) { if ((objectHandle & 0xFF000000) == 0x81000000 && objectHandle == persistentHandle) { - /* Was evict: delete from journal */ - FWTPM_NV_DeletePersistent(ctx, persistentHandle); + /* Was evict: delete from the journal first, then clear the RAM + * slot only once the delete has committed. */ + rc = FWTPM_NV_DeletePersistent(ctx, persistentHandle); + if (rc == 0) { + TPM2_ForceZero(&ctx->persistent[i], sizeof(FWTPM_Object)); + } } else { - /* Was make-persistent: save to journal */ - FWTPM_NV_SavePersistent(ctx, i); + /* Was make-persistent: save to journal, freeing the new slot if + * the write fails so RAM does not outlive the NV record. */ + rc = FWTPM_NV_SavePersistent(ctx, i); + if (rc != 0) { + TPM2_ForceZero(&ctx->persistent[i], sizeof(FWTPM_Object)); + } + } + if (rc == 0) { + FwRspNoParams(rsp, cmdTag); } - FwRspNoParams(rsp, cmdTag); } return rc; @@ -6344,11 +6376,16 @@ static TPM_RC FwCmd_Create(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } - /* Wrap private key into TPM2B_PRIVATE */ + /* Wrap private key into TPM2B_PRIVATE, bound to the child's Name */ if (rc == 0) { + TPM2B_NAME childName; XMEMSET(outPrivate, 0, sizeof(*outPrivate)); - rc = FwWrapPrivate(parent, inPublic->publicArea.type, &userAuth, - privKeyDer, privKeyDerSz, outPrivate); + rc = FwComputePublicName(&inPublic->publicArea, &childName); + if (rc == 0) { + rc = FwWrapPrivate(parent, &ctx->rng, &childName, + inPublic->publicArea.type, &userAuth, + privKeyDer, privKeyDerSz, outPrivate); + } } /* --- Build response (no handle for Create) --- */ @@ -6495,18 +6532,16 @@ static TPM_RC FwCmd_ObjectChangeAuth(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } - /* Update auth on live object */ + /* Re-wrap private key with new auth, then update the live object */ if (rc == 0) { - XMEMCPY(&obj->authValue, &newAuth, sizeof(newAuth)); - } - - /* Re-wrap private key with new auth */ - if (rc == 0) { - rc = FwWrapPrivate(parent, obj->pub.type, &newAuth, - obj->privKey, obj->privKeySize, &outPrivate); + rc = FwWrapPrivate(parent, &ctx->rng, &obj->name, obj->pub.type, + &newAuth, obj->privKey, obj->privKeySize, &outPrivate); if (rc != 0) { rc = TPM_RC_FAILURE; } + else { + XMEMCPY(&obj->authValue, &newAuth, sizeof(newAuth)); + } } #ifdef DEBUG_WOLFTPM @@ -6613,15 +6648,17 @@ static TPM_RC FwCmd_Load(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } - /* Copy public area */ + /* Copy public area and compute its Name; the private blob only unwraps + * under the Name it was wrapped with */ if (rc == 0) { XMEMCPY(&obj->pub, &inPublic.publicArea, sizeof(TPMT_PUBLIC)); obj->hierarchy = parent->hierarchy; + rc = FwComputeObjectName(obj); } /* Unwrap private */ if (rc == 0) { - rc = FwUnwrapPrivate(parent, &inPrivate, + rc = FwUnwrapPrivate(parent, &obj->name, &inPrivate, &sensitiveType, &obj->authValue, obj->privKey, &obj->privKeySize); #ifdef DEBUG_WOLFTPM @@ -6638,11 +6675,6 @@ static TPM_RC FwCmd_Load(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } - /* Compute name */ - if (rc == 0) { - rc = FwComputeObjectName(obj); - } - /* --- Build response --- */ if (rc == 0) { /* objectHandle */ @@ -7397,11 +7429,15 @@ static TPM_RC FwCmd_Import(FWTPM_CTX* ctx, TPM2_Packet* cmd, } - /* Wrap private for output */ + /* Wrap private for output, bound to the imported object's Name */ if (rc == 0) { + TPM2B_NAME childName; XMEMSET(outPrivate, 0, sizeof(*outPrivate)); - rc = FwWrapPrivate(parent, sensType, &importedAuth, - privKeyDer, privKeyDerSz, outPrivate); + rc = FwComputePublicName(&objectPublic->publicArea, &childName); + if (rc == 0) { + rc = FwWrapPrivate(parent, &ctx->rng, &childName, sensType, + &importedAuth, privKeyDer, privKeyDerSz, outPrivate); + } } /* Build response */ @@ -8413,10 +8449,15 @@ static TPM_RC FwCmd_CreateLoaded(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } - /* Wrap private key */ + /* Wrap private key, bound to the child's Name */ if (rc == 0) { - rc = FwWrapPrivate(parent, inPublic->publicArea.type, &userAuth, - privKeyDer, privKeyDerSz, outPrivate); + TPM2B_NAME childName; + rc = FwComputePublicName(&inPublic->publicArea, &childName); + if (rc == 0) { + rc = FwWrapPrivate(parent, &ctx->rng, &childName, + inPublic->publicArea.type, &userAuth, + privKeyDer, privKeyDerSz, outPrivate); + } } /* Load into transient slot */ @@ -10620,6 +10661,8 @@ static TPM_RC FwCmd_PolicyRestart(FWTPM_CTX* ctx, TPM2_Packet* cmd, sess->templateHash.size = 0; sess->checkNvWritten = 0; sess->nvWrittenState = 0; + sess->pcrUpdateCounter = 0; + sess->hasPcrUpdateCounter = 0; FwRspFinalize(rsp, TPM_ST_NO_SESSIONS, TPM_RC_SUCCESS); } @@ -10635,6 +10678,7 @@ static TPM_RC FwCmd_PolicyPCR(FWTPM_CTX* ctx, TPM2_Packet* cmd, UINT32 sessHandle; UINT16 pcrDigestSize; byte pcrDigest[TPM_MAX_DIGEST_SIZE]; + byte liveDigest[TPM_MAX_DIGEST_SIZE]; TPML_PCR_SELECTION pcrs; FWTPM_Session* sess = NULL; int digestSz = 0; @@ -10701,8 +10745,17 @@ static TPM_RC FwCmd_PolicyPCR(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } - /* If pcrDigest.size == 0, compute it from current PCR values */ - if (rc == 0 && pcrDigestSize == 0) { + /* A PCR change since this session's last PolicyPCR invalidates it */ + if (rc == 0 && sess->sessionType == TPM_SE_POLICY && + sess->hasPcrUpdateCounter && + sess->pcrUpdateCounter != ctx->pcrUpdateCounter) { + rc = TPM_RC_PCR_CHANGED; + } + + /* Compute the digest of the selected PCRs when none was supplied, and + * always for a real policy session so a supplied digest is verified + * against the live PCR values rather than trusted (Part 3 Sec.23.7). */ + if (rc == 0 && (pcrDigestSize == 0 || sess->sessionType == TPM_SE_POLICY)) { /* Hash together all selected PCR values */ wcHash = FwGetWcHashType(sess->authHash); if (wc_HashInit_ex(hashCtx, wcHash, NULL, INVALID_DEVID) != 0) { @@ -10718,17 +10771,29 @@ static TPM_RC FwCmd_PolicyPCR(FWTPM_CTX* ctx, TPM2_Packet* cmd, pcrs.pcrSelections[i].hash); if (bankIdx < 0 || pcrDSz == 0) continue; - for (j = 0; j < IMPLEMENTATION_PCR; j++) { + for (j = 0; j < IMPLEMENTATION_PCR && rc == 0; j++) { if (j / 8 < pcrs.pcrSelections[i].sizeofSelect && (pcrs.pcrSelections[i].pcrSelect[j / 8] & (1 << (j % 8)))) { - wc_HashUpdate(hashCtx, wcHash, - ctx->pcrDigest[j][bankIdx], pcrDSz); + if (wc_HashUpdate(hashCtx, wcHash, + ctx->pcrDigest[j][bankIdx], pcrDSz) != 0) { + rc = TPM_RC_FAILURE; + } } } } - pcrDigestSize = digestSz; - wc_HashFinal(hashCtx, wcHash, pcrDigest); + if (rc == 0 && wc_HashFinal(hashCtx, wcHash, liveDigest) != 0) { + rc = TPM_RC_FAILURE; + } + if (rc == 0 && pcrDigestSize != 0 && + (pcrDigestSize != digestSz || + TPM2_ConstantCompare(pcrDigest, liveDigest, digestSz) != 0)) { + rc = TPM_RC_VALUE; + } + if (rc == 0) { + pcrDigestSize = (UINT16)digestSz; + XMEMCPY(pcrDigest, liveDigest, digestSz); + } } if (hashInit) { wc_HashFree(hashCtx, wcHash); @@ -10772,8 +10837,16 @@ static TPM_RC FwCmd_PolicyPCR(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* PCR digest */ wc_HashUpdate(hashCtx, wcHash, pcrDigest, pcrDigestSize); - wc_HashFinal(hashCtx, wcHash, sess->policyDigest.buffer); - sess->policyDigest.size = digestSz; + if (wc_HashFinal(hashCtx, wcHash, sess->policyDigest.buffer) != 0) { + rc = TPM_RC_FAILURE; + } + else { + sess->policyDigest.size = digestSz; + if (sess->sessionType == TPM_SE_POLICY) { + sess->pcrUpdateCounter = ctx->pcrUpdateCounter; + sess->hasPcrUpdateCounter = 1; + } + } } if (hashInit) { wc_HashFree(hashCtx, wcHash); @@ -13374,9 +13447,14 @@ static TPM_RC FwCmd_NV_DefineSpace(FWTPM_CTX* ctx, TPM2_Packet* cmd, slot->written = 1; } - FWTPM_NV_SaveNvIndex(ctx, + rc = FWTPM_NV_SaveNvIndex(ctx, (int)(slot - ctx->nvIndices)); - FwRspNoParams(rsp, cmdTag); + if (rc == 0) { + FwRspNoParams(rsp, cmdTag); + } + else { + TPM2_ForceZero(slot, sizeof(FWTPM_NvIndex)); + } } TPM2_ForceZero(&auth, sizeof(auth)); @@ -13420,9 +13498,11 @@ static TPM_RC FwCmd_NV_UndefineSpace(FWTPM_CTX* ctx, TPM2_Packet* cmd, } if (rc == 0) { - XMEMSET(nv, 0, sizeof(FWTPM_NvIndex)); - FWTPM_NV_DeleteNvIndex(ctx, nvHandle); - FwRspNoParams(rsp, cmdTag); + rc = FWTPM_NV_DeleteNvIndex(ctx, nvHandle); + if (rc == 0) { + XMEMSET(nv, 0, sizeof(FWTPM_NvIndex)); + FwRspNoParams(rsp, cmdTag); + } } return rc; @@ -13465,9 +13545,11 @@ static TPM_RC FwCmd_NV_UndefineSpaceSpecial(FWTPM_CTX* ctx, TPM2_Packet* cmd, #ifdef DEBUG_WOLFTPM printf("fwTPM: NV_UndefineSpaceSpecial(nv=0x%x)\n", nvHandle); #endif - XMEMSET(nv, 0, sizeof(FWTPM_NvIndex)); - FWTPM_NV_DeleteNvIndex(ctx, nvHandle); - FwRspNoParams(rsp, cmdTag); + rc = FWTPM_NV_DeleteNvIndex(ctx, nvHandle); + if (rc == 0) { + XMEMSET(nv, 0, sizeof(FWTPM_NvIndex)); + FwRspNoParams(rsp, cmdTag); + } } return rc; @@ -13527,21 +13609,27 @@ static TPM_RC FwCmd_NV_Write(FWTPM_CTX* ctx, TPM2_Packet* cmd, TPM_RC rc = TPM_RC_SUCCESS; TPM_HANDLE authHandle; TPMI_RH_NV_INDEX nvHandle; - FWTPM_NvIndex* nv; + FWTPM_NvIndex* nv = NULL; UINT16 dataSize = 0, offset = 0; FWTPM_DECLARE_BUF(dataBuf, FWTPM_MAX_NV_DATA); + FWTPM_DECLARE_BUF(oldData, FWTPM_MAX_NV_DATA); (void)cmdSize; FWTPM_ALLOC_BUF(dataBuf, FWTPM_MAX_NV_DATA); + FWTPM_ALLOC_BUF(oldData, FWTPM_MAX_NV_DATA); TPM2_Packet_ParseU32(cmd, &authHandle); TPM2_Packet_ParseU32(cmd, &nvHandle); - if (cmdTag == TPM_ST_SESSIONS) rc = FwSkipAuthArea(cmd, cmdSize); + if (rc == 0 && cmdTag == TPM_ST_SESSIONS) { + rc = FwSkipAuthArea(cmd, cmdSize); + } - nv = FwFindNvIndex(ctx, nvHandle); - if (nv == NULL) { - rc = FW_NV_HANDLE_ERR_2; + if (rc == 0) { + nv = FwFindNvIndex(ctx, nvHandle); + if (nv == NULL) { + rc = FW_NV_HANDLE_ERR_2; + } } if (rc == 0) { rc = FwNvCheckAccess(authHandle, nvHandle, @@ -13594,6 +13682,9 @@ static TPM_RC FwCmd_NV_Write(FWTPM_CTX* ctx, TPM2_Packet* cmd, } if (rc == 0) { + int oldWritten = nv->written; + UINT32 oldAttrs = nv->nvPublic.attributes; + XMEMCPY(oldData, nv->data + offset, dataSize); XMEMCPY(nv->data + offset, dataBuf, dataSize); nv->written = 1; @@ -13607,9 +13698,16 @@ static TPM_RC FwCmd_NV_Write(FWTPM_CTX* ctx, TPM2_Packet* cmd, nv->nvPublic.attributes |= TPMA_NV_WRITELOCKED; } - FWTPM_NV_SaveNvIndex(ctx, (int)(nv - ctx->nvIndices)); + rc = FWTPM_NV_SaveNvIndex(ctx, (int)(nv - ctx->nvIndices)); - FwRspNoParams(rsp, cmdTag); + if (rc == 0) { + FwRspNoParams(rsp, cmdTag); + } + else { + XMEMCPY(nv->data + offset, oldData, dataSize); + nv->written = oldWritten; + nv->nvPublic.attributes = oldAttrs; + } } #ifdef WOLFTPM_SMALL_STACK @@ -13617,6 +13715,11 @@ static TPM_RC FwCmd_NV_Write(FWTPM_CTX* ctx, TPM2_Packet* cmd, #endif TPM2_ForceZero(dataBuf, FWTPM_MAX_NV_DATA); FWTPM_FREE_BUF(dataBuf); +#ifdef WOLFTPM_SMALL_STACK + if (oldData != NULL) +#endif + TPM2_ForceZero(oldData, FWTPM_MAX_NV_DATA); + FWTPM_FREE_BUF(oldData); return rc; } @@ -13750,6 +13853,10 @@ static TPM_RC FwCmd_NV_Extend(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } if (rc == 0) { + byte oldData[TPM_MAX_DIGEST_SIZE]; + int oldWritten = nv->written; + UINT32 oldAttrs = nv->nvPublic.attributes; + XMEMCPY(oldData, nv->data, hSz); wc_HashUpdate(hashCtx, wcHash, nv->data, hSz); wc_HashUpdate(hashCtx, wcHash, dataBuf, dataSize); wc_HashFinal(hashCtx, wcHash, newVal); @@ -13758,9 +13865,17 @@ static TPM_RC FwCmd_NV_Extend(FWTPM_CTX* ctx, TPM2_Packet* cmd, nv->written = 1; nv->nvPublic.attributes |= 0x20000000UL; /* TPMA_NV_WRITTEN */ - FWTPM_NV_SaveNvIndex(ctx, (int)(nv - ctx->nvIndices)); + rc = FWTPM_NV_SaveNvIndex(ctx, (int)(nv - ctx->nvIndices)); - FwRspNoParams(rsp, cmdTag); + if (rc == 0) { + FwRspNoParams(rsp, cmdTag); + } + else { + XMEMCPY(nv->data, oldData, hSz); + nv->written = oldWritten; + nv->nvPublic.attributes = oldAttrs; + } + TPM2_ForceZero(oldData, sizeof(oldData)); } if (hashInit) { wc_HashFree(hashCtx, wcHash); @@ -13808,6 +13923,10 @@ static TPM_RC FwCmd_NV_Increment(FWTPM_CTX* ctx, TPM2_Packet* cmd, } if (rc == 0) { + byte oldData[8]; + int oldWritten = nv->written; + UINT32 oldAttrs = nv->nvPublic.attributes; + XMEMCPY(oldData, nv->data, 8); /* Read big-endian counter, increment, write back */ counter = FwLoadU64BE(nv->data); counter++; @@ -13815,9 +13934,17 @@ static TPM_RC FwCmd_NV_Increment(FWTPM_CTX* ctx, TPM2_Packet* cmd, nv->written = 1; nv->nvPublic.attributes |= 0x20000000UL; /* TPMA_NV_WRITTEN */ - FWTPM_NV_SaveNvIndex(ctx, (int)(nv - ctx->nvIndices)); + rc = FWTPM_NV_SaveNvIndex(ctx, (int)(nv - ctx->nvIndices)); - FwRspNoParams(rsp, cmdTag); + if (rc == 0) { + FwRspNoParams(rsp, cmdTag); + } + else { + XMEMCPY(nv->data, oldData, 8); + nv->written = oldWritten; + nv->nvPublic.attributes = oldAttrs; + } + TPM2_ForceZero(oldData, sizeof(oldData)); } return rc; @@ -13857,10 +13984,16 @@ static TPM_RC FwCmd_NV_WriteLock(FWTPM_CTX* ctx, TPM2_Packet* cmd, } if (rc == 0) { + UINT32 oldAttrs = nv->nvPublic.attributes; nv->nvPublic.attributes |= TPMA_NV_WRITELOCKED; - FWTPM_NV_SaveNvIndex(ctx, (int)(nv - ctx->nvIndices)); + rc = FWTPM_NV_SaveNvIndex(ctx, (int)(nv - ctx->nvIndices)); - FwRspNoParams(rsp, cmdTag); + if (rc == 0) { + FwRspNoParams(rsp, cmdTag); + } + else { + nv->nvPublic.attributes = oldAttrs; + } } return rc; @@ -13899,10 +14032,16 @@ static TPM_RC FwCmd_NV_ReadLock(FWTPM_CTX* ctx, TPM2_Packet* cmd, } if (rc == 0) { + UINT32 oldAttrs = nv->nvPublic.attributes; nv->nvPublic.attributes |= TPMA_NV_READLOCKED; - FWTPM_NV_SaveNvIndex(ctx, (int)(nv - ctx->nvIndices)); + rc = FWTPM_NV_SaveNvIndex(ctx, (int)(nv - ctx->nvIndices)); - FwRspNoParams(rsp, cmdTag); + if (rc == 0) { + FwRspNoParams(rsp, cmdTag); + } + else { + nv->nvPublic.attributes = oldAttrs; + } } return rc; @@ -13951,6 +14090,10 @@ static TPM_RC FwCmd_NV_SetBits(FWTPM_CTX* ctx, TPM2_Packet* cmd, } if (rc == 0) { + byte oldData[8]; + int oldWritten = nv->written; + UINT32 oldAttrs = nv->nvPublic.attributes; + XMEMCPY(oldData, nv->data, 8); #ifdef DEBUG_WOLFTPM printf("fwTPM: NV_SetBits(nv=0x%x, bits=0x%llx)\n", nvHandle, (unsigned long long)bits); @@ -13963,9 +14106,17 @@ static TPM_RC FwCmd_NV_SetBits(FWTPM_CTX* ctx, TPM2_Packet* cmd, nv->written = 1; nv->nvPublic.attributes |= 0x20000000UL; /* TPMA_NV_WRITTEN */ - FWTPM_NV_SaveNvIndex(ctx, (int)(nv - ctx->nvIndices)); + rc = FWTPM_NV_SaveNvIndex(ctx, (int)(nv - ctx->nvIndices)); - FwRspNoParams(rsp, cmdTag); + if (rc == 0) { + FwRspNoParams(rsp, cmdTag); + } + else { + XMEMCPY(nv->data, oldData, 8); + nv->written = oldWritten; + nv->nvPublic.attributes = oldAttrs; + } + TPM2_ForceZero(oldData, sizeof(oldData)); } return rc; @@ -14016,6 +14167,7 @@ static TPM_RC FwCmd_NV_ChangeAuth(FWTPM_CTX* ctx, TPM2_Packet* cmd, } if (rc == 0) { + TPM2B_AUTH oldNvAuth = nv->authValue; #ifdef DEBUG_WOLFTPM printf("fwTPM: NV_ChangeAuth(nv=0x%x, newAuthSz=%d)\n", nvHandle, newAuthSize); @@ -14028,9 +14180,16 @@ static TPM_RC FwCmd_NV_ChangeAuth(FWTPM_CTX* ctx, TPM2_Packet* cmd, XMEMCPY(nv->authValue.buffer, newAuthBuf, newAuthSize); } - FWTPM_NV_SaveNvIndex(ctx, (int)(nv - ctx->nvIndices)); + rc = FWTPM_NV_SaveNvIndex(ctx, (int)(nv - ctx->nvIndices)); - FwRspNoParams(rsp, cmdTag); + if (rc == 0) { + FwRspNoParams(rsp, cmdTag); + } + else { + TPM2_ForceZero(nv->authValue.buffer, sizeof(nv->authValue.buffer)); + nv->authValue = oldNvAuth; + } + TPM2_ForceZero(&oldNvAuth, sizeof(oldNvAuth)); } /* Zero stack copy of new auth value before returning */ @@ -14094,6 +14253,10 @@ static TPM_RC FwCmd_DictionaryAttackLockReset(FWTPM_CTX* ctx, } if (rc == 0) { + UINT32 oldFailedTries = ctx->daFailedTries; + int oldLockoutFailed = ctx->lockoutAuthFailed; + UINT64 oldLockoutHealMs = ctx->daLockoutHealMs; + UINT64 oldSelfHealMs = ctx->daSelfHealMs; #ifdef DEBUG_WOLFTPM printf("fwTPM: DictionaryAttackLockReset\n"); #endif @@ -14101,8 +14264,16 @@ static TPM_RC FwCmd_DictionaryAttackLockReset(FWTPM_CTX* ctx, ctx->lockoutAuthFailed = 0; ctx->daLockoutHealMs = 0; ctx->daSelfHealMs = FwDaNowMs(ctx); - (void)FWTPM_NV_SaveFlags(ctx); - FwRspNoParams(rsp, cmdTag); + rc = FWTPM_NV_SaveFlags(ctx); + if (rc == 0) { + FwRspNoParams(rsp, cmdTag); + } + else { + ctx->daFailedTries = oldFailedTries; + ctx->lockoutAuthFailed = oldLockoutFailed; + ctx->daLockoutHealMs = oldLockoutHealMs; + ctx->daSelfHealMs = oldSelfHealMs; + } } return rc; @@ -14141,6 +14312,13 @@ static TPM_RC FwCmd_DictionaryAttackParameters(FWTPM_CTX* ctx, } if (rc == 0) { + UINT32 oldMaxTries = ctx->daMaxTries; + UINT32 oldRecoveryTime = ctx->daRecoveryTime; + UINT32 oldLockoutRecovery = ctx->daLockoutRecovery; + UINT32 oldFailedTries = ctx->daFailedTries; + int oldLockoutFailed = ctx->lockoutAuthFailed; + UINT64 oldLockoutHealMs = ctx->daLockoutHealMs; + UINT64 oldSelfHealMs = ctx->daSelfHealMs; #ifdef DEBUG_WOLFTPM printf("fwTPM: DictionaryAttackParameters(max=%u, recovery=%u, " "lockout=%u)\n", newMaxTries, newRecoveryTime, lockoutRecovery); @@ -14153,8 +14331,19 @@ static TPM_RC FwCmd_DictionaryAttackParameters(FWTPM_CTX* ctx, ctx->lockoutAuthFailed = 0; ctx->daLockoutHealMs = 0; ctx->daSelfHealMs = FwDaNowMs(ctx); - (void)FWTPM_NV_SaveFlags(ctx); - FwRspNoParams(rsp, cmdTag); + rc = FWTPM_NV_SaveFlags(ctx); + if (rc == 0) { + FwRspNoParams(rsp, cmdTag); + } + else { + ctx->daMaxTries = oldMaxTries; + ctx->daRecoveryTime = oldRecoveryTime; + ctx->daLockoutRecovery = oldLockoutRecovery; + ctx->daFailedTries = oldFailedTries; + ctx->lockoutAuthFailed = oldLockoutFailed; + ctx->daLockoutHealMs = oldLockoutHealMs; + ctx->daSelfHealMs = oldSelfHealMs; + } } return rc; @@ -19026,6 +19215,15 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, int policyDiff; word32 cmpSz; + /* PCR values changed since PolicyPCR: the session no longer + * reflects the PCR state it was evaluated against. */ + if (pSess->hasPcrUpdateCounter && + pSess->pcrUpdateCounter != ctx->pcrUpdateCounter) { + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, + TPM_ST_NO_SESSIONS, TPM_RC_PCR_CHANGED); + return TPM_RC_SUCCESS; + } + /* Find entity's authPolicy by handle type */ #ifndef FWTPM_NO_NV if ((entityH & 0xFF000000) == (NV_INDEX_FIRST & 0xFF000000)) { diff --git a/src/fwtpm/fwtpm_crypto.c b/src/fwtpm/fwtpm_crypto.c index 215ce863..6636d9b4 100644 --- a/src/fwtpm/fwtpm_crypto.c +++ b/src/fwtpm/fwtpm_crypto.c @@ -182,7 +182,7 @@ TPM_RC FwDeriveSymmetricPrimaryKey(TPMI_ALG_HASH nameAlg, /** \brief Compute TPM object name: nameAlg(2) || Hash(marshaledPublicArea). * Stores result in obj->name. */ -int FwComputeObjectName(FWTPM_Object* obj) +int FwComputePublicName(TPMT_PUBLIC* pub, TPM2B_NAME* name) { int rc = TPM_RC_SUCCESS; FWTPM_DECLARE_BUF(pubBuf, FWTPM_MAX_PUB_BUF); @@ -197,20 +197,20 @@ int FwComputeObjectName(FWTPM_Object* obj) tmpPkt.buf = pubBuf; tmpPkt.pos = 0; tmpPkt.size = (int)FWTPM_MAX_PUB_BUF; - TPM2_Packet_AppendPublicArea(&tmpPkt, &obj->pub); + TPM2_Packet_AppendPublicArea(&tmpPkt, pub); pubSz = tmpPkt.pos; - wcHash = FwGetWcHashType(obj->pub.nameAlg); - digestSz = TPM2_GetHashDigestSize(obj->pub.nameAlg); + wcHash = FwGetWcHashType(pub->nameAlg); + digestSz = TPM2_GetHashDigestSize(pub->nameAlg); if (wcHash == WC_HASH_TYPE_NONE || digestSz == 0) { rc = TPM_RC_HASH; } if (rc == 0) { /* name = nameAlg(2 bytes big-endian) || Hash(publicArea) */ - obj->name.size = 2 + digestSz; - FwStoreU16BE(obj->name.name, obj->pub.nameAlg); - rc = wc_Hash(wcHash, pubBuf, pubSz, obj->name.name + 2, digestSz); + name->size = 2 + digestSz; + FwStoreU16BE(name->name, pub->nameAlg); + rc = wc_Hash(wcHash, pubBuf, pubSz, name->name + 2, digestSz); if (rc != 0) { rc = TPM_RC_FAILURE; } @@ -220,6 +220,11 @@ int FwComputeObjectName(FWTPM_Object* obj) return rc; } +int FwComputeObjectName(FWTPM_Object* obj) +{ + return FwComputePublicName(&obj->pub, &obj->name); +} + /** \brief Get hierarchy seed pointer for a given hierarchy handle. * \return Pointer to seed bytes, or NULL for unknown hierarchy. */ byte* FwGetHierarchySeed(FWTPM_CTX* ctx, UINT32 hierarchy) @@ -2034,21 +2039,24 @@ TPM_RC FwDeriveRsaPrimaryKey(TPMI_ALG_HASH nameAlg, /* Private key wrapping/unwrapping for Create/Load */ /* ================================================================== */ -/* Derive a 32-byte AES key and 16-byte IV from parent's private key. - * Used to wrap child key sensitive data in TPM2B_PRIVATE. */ -int FwDeriveWrapKey(const FWTPM_Object* parent, - byte* aesKey, byte* aesIV) +/* Derive the 32-byte AES key and 32-byte MAC key from parent's private key + * and the child's Name, so a blob only unwraps under the public area it was + * created with. Used to wrap child key sensitive data in TPM2B_PRIVATE. */ +int FwDeriveWrapKey(const FWTPM_Object* parent, const TPM2B_NAME* name, + byte* aesKey, byte* macKey) { int rc; - byte keyMaterial[WC_SHA256_DIGEST_SIZE]; - byte ivMaterial[WC_SHA256_DIGEST_SIZE]; FWTPM_DECLARE_VAR(hmac, Hmac); + if (name == NULL || name->size == 0 || name->size > sizeof(name->name)) { + return TPM_RC_FAILURE; + } + FWTPM_ALLOC_VAR(hmac, Hmac); rc = wc_HmacInit(hmac, NULL, INVALID_DEVID); - /* AES key = HMAC-SHA256(parentPriv, "fwTPM-wrap-key") + /* AES key = HMAC-SHA256(parentPriv, "fwTPM-wrap-key" || name) * Use full parent private key as HMAC key — HMAC handles arbitrary-length * keys via internal hashing. The previous 32-byte truncation used * predictable ASN.1 DER header bytes for RSA keys. */ @@ -2060,35 +2068,31 @@ int FwDeriveWrapKey(const FWTPM_Object* parent, rc = wc_HmacUpdate(hmac, (const byte*)"fwTPM-wrap-key", 14); } if (rc == 0) { - rc = wc_HmacFinal(hmac, keyMaterial); + rc = wc_HmacUpdate(hmac, name->name, name->size); } if (rc == 0) { - XMEMCPY(aesKey, keyMaterial, 32); + rc = wc_HmacFinal(hmac, aesKey); } - /* IV = HMAC-SHA256(parentPriv, "fwTPM-wrap-iv") truncated to 16. - * Use full parent private key (same as AES key above) — HMAC handles - * arbitrary-length keys via internal hashing. */ + /* MAC key = HMAC-SHA256(parentPriv, "fwTPM-wrap-mac" || name) */ if (rc == 0) { rc = wc_HmacSetKey(hmac, WC_SHA256, parent->privKey, parent->privKeySize); } if (rc == 0) { - rc = wc_HmacUpdate(hmac, (const byte*)"fwTPM-wrap-iv", 13); + rc = wc_HmacUpdate(hmac, (const byte*)"fwTPM-wrap-mac", 14); } if (rc == 0) { - rc = wc_HmacFinal(hmac, ivMaterial); + rc = wc_HmacUpdate(hmac, name->name, name->size); } if (rc == 0) { - XMEMCPY(aesIV, ivMaterial, AES_BLOCK_SIZE); + rc = wc_HmacFinal(hmac, macKey); } if (rc != 0) { rc = TPM_RC_FAILURE; } - TPM2_ForceZero(keyMaterial, sizeof(keyMaterial)); - TPM2_ForceZero(ivMaterial, sizeof(ivMaterial)); wc_HmacFree(hmac); FWTPM_FREE_VAR(hmac); return rc; @@ -2221,16 +2225,20 @@ int FwUnmarshalSensitive(const byte* buf, int bufSz, return pos; } -/* Wrap sensitive into TPM2B_PRIVATE using parent's key. - * Format: integritySize(2) + integrity(32) + encSensSize(2) + encSens(N) +/* Wrap sensitive into TPM2B_PRIVATE using parent's key. A fresh random IV + * per blob keeps every child on its own AES-CFB keystream. + * Format: integritySize(2) + integrity(32) + iv(16) + encSensSize(2) + + * encSens(N) */ -int FwWrapPrivate(FWTPM_Object* parent, +int FwWrapPrivate(FWTPM_Object* parent, WC_RNG* rng, + const TPM2B_NAME* name, UINT16 sensitiveType, const TPM2B_AUTH* auth, const byte* privKeyDer, int privKeyDerSz, TPM2B_PRIVATE* outPriv) { int rc = TPM_RC_SUCCESS; byte aesKey[FWTPM_MAX_SYM_KEY_SIZE], aesIV[AES_BLOCK_SIZE]; + byte macKey[WC_SHA256_DIGEST_SIZE]; FWTPM_DECLARE_BUF(sensBuf, FWTPM_MAX_PRIVKEY_DER + 128); byte hmacDigest[WC_SHA256_DIGEST_SIZE]; FWTPM_DECLARE_VAR(aes, Aes); @@ -2250,9 +2258,15 @@ int FwWrapPrivate(FWTPM_Object* parent, rc = TPM_RC_FAILURE; } - /* Derive wrapping key/IV from parent */ + /* Derive wrapping keys from parent and child Name, fresh IV per blob */ if (rc == 0) { - rc = FwDeriveWrapKey(parent, aesKey, aesIV); + rc = FwDeriveWrapKey(parent, name, aesKey, macKey); + } + if (rc == 0) { + if (rng == NULL || + wc_RNG_GenerateBlock(rng, aesIV, AES_BLOCK_SIZE) != 0) { + rc = TPM_RC_FAILURE; + } } /* AES-CFB encrypt in place */ @@ -2270,12 +2284,15 @@ int FwWrapPrivate(FWTPM_Object* parent, } } - /* HMAC integrity over encrypted data */ + /* HMAC integrity over IV and encrypted data */ if (rc == 0) { rc = wc_HmacInit(hmac, NULL, INVALID_DEVID); } if (rc == 0) { - rc = wc_HmacSetKey(hmac, WC_SHA256, aesKey, 32); + rc = wc_HmacSetKey(hmac, WC_SHA256, macKey, sizeof(macKey)); + } + if (rc == 0) { + rc = wc_HmacUpdate(hmac, aesIV, AES_BLOCK_SIZE); } if (rc == 0) { rc = wc_HmacUpdate(hmac, sensBuf, sensSz); @@ -2287,17 +2304,18 @@ int FwWrapPrivate(FWTPM_Object* parent, /* Pack into TPM2B_PRIVATE */ if (rc == 0) { - int totalSz = 2 + WC_SHA256_DIGEST_SIZE + 2 + sensSz; + int totalSz = 2 + WC_SHA256_DIGEST_SIZE + AES_BLOCK_SIZE + 2 + sensSz; if (totalSz > (int)sizeof(outPriv->buffer)) { rc = TPM_RC_SIZE; } } if (rc == 0) { - /* integritySize(2) + integrity(32) + encSensSize(2) + encSens(N) */ outPriv->buffer[pos++] = 0; outPriv->buffer[pos++] = WC_SHA256_DIGEST_SIZE; XMEMCPY(outPriv->buffer + pos, hmacDigest, WC_SHA256_DIGEST_SIZE); pos += WC_SHA256_DIGEST_SIZE; + XMEMCPY(outPriv->buffer + pos, aesIV, AES_BLOCK_SIZE); + pos += AES_BLOCK_SIZE; FwStoreU16BE(outPriv->buffer + pos, (UINT16)sensSz); pos += 2; XMEMCPY(outPriv->buffer + pos, sensBuf, sensSz); @@ -2310,6 +2328,7 @@ int FwWrapPrivate(FWTPM_Object* parent, } TPM2_ForceZero(aesKey, sizeof(aesKey)); + TPM2_ForceZero(macKey, sizeof(macKey)); TPM2_ForceZero(aesIV, sizeof(aesIV)); TPM2_ForceZero(hmacDigest, sizeof(hmacDigest)); TPM2_ForceZero(sensBuf, FWTPM_MAX_PRIVKEY_DER + 128); @@ -2320,13 +2339,14 @@ int FwWrapPrivate(FWTPM_Object* parent, } /* Unwrap TPM2B_PRIVATE using parent's key */ -int FwUnwrapPrivate(FWTPM_Object* parent, +int FwUnwrapPrivate(FWTPM_Object* parent, const TPM2B_NAME* name, const TPM2B_PRIVATE* inPriv, UINT16* sensitiveType, TPM2B_AUTH* auth, byte* privKeyDer, int* privKeyDerSz) { int rc = TPM_RC_SUCCESS; byte aesKey[FWTPM_MAX_SYM_KEY_SIZE], aesIV[AES_BLOCK_SIZE]; + byte macKey[WC_SHA256_DIGEST_SIZE]; byte hmacDigest[WC_SHA256_DIGEST_SIZE]; byte hmacCheck[WC_SHA256_DIGEST_SIZE]; FWTPM_DECLARE_BUF(decBuf, FWTPM_MAX_PRIVKEY_DER + 128); @@ -2340,11 +2360,11 @@ int FwUnwrapPrivate(FWTPM_Object* parent, FWTPM_ALLOC_VAR(aes, Aes); FWTPM_ALLOC_VAR(hmac, Hmac); - if (inPriv->size < 36) { - rc = TPM_RC_FAILURE; /* min: 2+32+2 */ + if (inPriv->size < 2 + WC_SHA256_DIGEST_SIZE + AES_BLOCK_SIZE + 2) { + rc = TPM_RC_FAILURE; } - /* Parse integrity */ + /* Parse integrity and IV */ if (rc == 0) { integritySize = FwLoadU16BE(inPriv->buffer + pos); pos += 2; @@ -2355,6 +2375,8 @@ int FwUnwrapPrivate(FWTPM_Object* parent, if (rc == 0) { XMEMCPY(hmacDigest, inPriv->buffer + pos, WC_SHA256_DIGEST_SIZE); pos += WC_SHA256_DIGEST_SIZE; + XMEMCPY(aesIV, inPriv->buffer + pos, AES_BLOCK_SIZE); + pos += AES_BLOCK_SIZE; } /* Parse encrypted sensitive size */ @@ -2372,17 +2394,20 @@ int FwUnwrapPrivate(FWTPM_Object* parent, } } - /* Derive wrapping key/IV from parent */ + /* Derive wrapping keys from parent and the presented public area's Name */ if (rc == 0) { - rc = FwDeriveWrapKey(parent, aesKey, aesIV); + rc = FwDeriveWrapKey(parent, name, aesKey, macKey); } - /* Verify HMAC */ + /* Verify HMAC over IV and encrypted data */ if (rc == 0) { rc = wc_HmacInit(hmac, NULL, INVALID_DEVID); } if (rc == 0) { - rc = wc_HmacSetKey(hmac, WC_SHA256, aesKey, 32); + rc = wc_HmacSetKey(hmac, WC_SHA256, macKey, sizeof(macKey)); + } + if (rc == 0) { + rc = wc_HmacUpdate(hmac, aesIV, AES_BLOCK_SIZE); } if (rc == 0) { rc = wc_HmacUpdate(hmac, inPriv->buffer + pos, encSensSize); @@ -2425,6 +2450,7 @@ int FwUnwrapPrivate(FWTPM_Object* parent, } TPM2_ForceZero(aesKey, sizeof(aesKey)); + TPM2_ForceZero(macKey, sizeof(macKey)); TPM2_ForceZero(aesIV, sizeof(aesIV)); TPM2_ForceZero(hmacCheck, sizeof(hmacCheck)); TPM2_ForceZero(decBuf, FWTPM_MAX_PRIVKEY_DER + 128); diff --git a/src/fwtpm/fwtpm_nv.c b/src/fwtpm/fwtpm_nv.c index 78f94e4e..fe1059c5 100644 --- a/src/fwtpm/fwtpm_nv.c +++ b/src/fwtpm/fwtpm_nv.c @@ -1101,6 +1101,16 @@ static int FwNvAppendCheckpoint(FWTPM_CTX* ctx) } #endif /* WOLFTPM_FWTPM_NV_APPEND_ONLY */ +/* A deletion cannot be captured by compaction of the live context (which + * still holds the item), so these tombstone tags must not be treated as + * committed-by-compaction. */ +static int FwNvTagIsDelete(UINT16 tag) +{ + return tag == FWTPM_NV_TAG_NV_INDEX_DEL || + tag == FWTPM_NV_TAG_PERSISTENT_DEL || + tag == FWTPM_NV_TAG_PRIMARY_CACHE_DEL; +} + /* Append a single TLV entry to the journal */ static int FwNvAppendEntry(FWTPM_CTX* ctx, UINT16 tag, const byte* value, UINT16 valueLen) @@ -1109,11 +1119,28 @@ static int FwNvAppendEntry(FWTPM_CTX* ctx, UINT16 tag, word32 entrySize = TLV_HDR_SIZE + valueLen; word32 reserve = FWTPM_NV_MAC_SIZE; byte tlvHdr[TLV_HDR_SIZE]; + word32 savedWritePos; int rc; if (hal->write == NULL) { +#ifdef FWTPM_NO_NV + /* Volatile-only build: there is no backing store, so a state change + * succeeds without being persisted rather than reporting a failure. */ + return TPM_RC_SUCCESS; +#else return TPM_RC_FAILURE; +#endif + } + +#ifdef WOLFTPM_FWTPM_NV_APPEND_ONLY + /* A rejected append left an unsealed entry on the log that the next + * checkpoint would authenticate. Rewriting the single region now would + * erase the last committed image while the medium may still be failing, + * so refuse further mutations; the loader compacts the tail on restart. */ + if (FW_NV_APPEND_ONLY(hal) && ctx->nvRebuild && !ctx->nvCompacting) { + return TPM_RC_NV_UNAVAILABLE; } +#endif /* Append-only also appends a checkpoint and rounds up to a granule. */ if (FW_NV_APPEND_ONLY(hal)) { @@ -1126,12 +1153,17 @@ static int FwNvAppendEntry(FWTPM_CTX* ctx, UINT16 tag, if (ctx->nvCompacting) { return TPM_RC_NV_SPACE; } - /* Compact and retry */ + /* Compact. The rewrite from the live context is the commit for a + * non-delete change, and for a deletion whose target the rewrite + * omitted; appending a redundant record afterwards would only put the + * compacted journal's seal at risk. */ rc = FWTPM_NV_Save(ctx); if (rc != TPM_RC_SUCCESS) { return rc; } - /* After compaction, check again */ + if (!FwNvTagIsDelete(tag) || ctx->nvDeleteHandle != 0) { + return TPM_RC_SUCCESS; + } if (ctx->nvWritePos + entrySize + reserve > hal->maxSize) { return TPM_RC_NV_SPACE; } @@ -1141,6 +1173,7 @@ static int FwNvAppendEntry(FWTPM_CTX* ctx, UINT16 tag, FwStoreU16LE(tlvHdr, tag); FwStoreU16LE(tlvHdr + 2, valueLen); + savedWritePos = ctx->nvWritePos; rc = FwNvHalWrite(ctx, ctx->nvWritePos, tlvHdr, TLV_HDR_SIZE); if (rc == TPM_RC_SUCCESS && valueLen > 0) { rc = FwNvHalWrite(ctx, ctx->nvWritePos + TLV_HDR_SIZE, @@ -1149,10 +1182,16 @@ static int FwNvAppendEntry(FWTPM_CTX* ctx, UINT16 tag, if (rc == TPM_RC_SUCCESS) { ctx->nvWritePos += entrySize; #ifdef WOLFTPM_FWTPM_NV_APPEND_ONLY - /* Commit via a checkpoint; compaction emits one at the end instead. */ + /* Commit via a checkpoint; compaction emits one at the end instead. An + * entry whose checkpoint fails is not durable, yet it stays on the log + * where the next checkpoint would seal it: no more appends until the + * loader has compacted it away. */ if (FW_NV_APPEND_ONLY(hal)) { if (!ctx->nvCompacting) { rc = FwNvAppendCheckpoint(ctx); + if (rc != TPM_RC_SUCCESS) { + ctx->nvRebuild = 1; + } } } else @@ -1161,6 +1200,17 @@ static int FwNvAppendEntry(FWTPM_CTX* ctx, UINT16 tag, rc = FwNvWriteHeader(ctx); /* byte-addressable: header + MAC */ } } + if (rc != TPM_RC_SUCCESS) { + /* A failed append must leave no committable remnant. On a byte- + * addressable backend roll the write cursor back so the partial entry + * is overwritten by the next append and never sealed into the journal + * by a later header write. Append-only flash cannot overwrite, so its + * cursor and granule state are left intact for recovery at the next + * compaction. */ + if (!FW_NV_APPEND_ONLY(hal)) { + ctx->nvWritePos = savedWritePos; + } + } return rc; } @@ -1756,6 +1806,10 @@ int FWTPM_NV_Init(FWTPM_CTX* ctx) return BAD_FUNC_ARG; } +#ifdef WOLFTPM_FWTPM_NV_APPEND_ONLY + ctx->nvRebuild = 0; +#endif + /* Use custom HAL if set, otherwise default file-based */ if (ctx->nvHal.read != NULL && ctx->nvHal.write != NULL) { hal = &ctx->nvHal; @@ -2142,9 +2196,10 @@ int FWTPM_NV_Save(FWTPM_CTX* ctx) } } - /* --- NV indices (only used slots) --- */ + /* --- NV indices (only used slots, minus a pending deletion) --- */ for (i = 0; i < FWTPM_MAX_NV_INDICES && rc == 0; i++) { - if (ctx->nvIndices[i].inUse) { + if (ctx->nvIndices[i].inUse && + ctx->nvIndices[i].nvPublic.nvIndex != ctx->nvDeleteHandle) { word32 needed; pos = 0; /* Estimate: ensure buf is large enough */ @@ -2171,9 +2226,10 @@ int FWTPM_NV_Save(FWTPM_CTX* ctx) } } - /* --- Persistent objects (only used slots) --- */ + /* --- Persistent objects (only used slots, minus a pending deletion) --- */ for (i = 0; i < FWTPM_MAX_PERSISTENT && rc == 0; i++) { - if (ctx->persistent[i].used) { + if (ctx->persistent[i].used && + ctx->persistent[i].handle != ctx->nvDeleteHandle) { word32 needed; pos = 0; needed = 4 + FWTPM_NV_PUBAREA_EST + FWTPM_NV_NAME_EST + 2 + @@ -2239,6 +2295,11 @@ int FWTPM_NV_Save(FWTPM_CTX* ctx) rc = FwNvWriteHeader(ctx); } } +#ifdef WOLFTPM_FWTPM_NV_APPEND_ONLY + /* A compaction that did not reach its checkpoint leaves the rewritten + * snapshot unsealed on the log, so it must be rebuilt before any append. */ + ctx->nvRebuild = (rc != TPM_RC_SUCCESS); +#endif #ifdef DEBUG_WOLFTPM printf("fwTPM: NV saved (compact, %d bytes)\n", (int)ctx->nvWritePos); @@ -2514,6 +2575,7 @@ int FWTPM_NV_SaveNvIndex(FWTPM_CTX* ctx, int slot) int FWTPM_NV_DeleteNvIndex(FWTPM_CTX* ctx, UINT32 nvHandle) { + int rc; byte buf[4]; word32 pos = 0; @@ -2522,8 +2584,10 @@ int FWTPM_NV_DeleteNvIndex(FWTPM_CTX* ctx, UINT32 nvHandle) } FwNvMarshalU32(buf, &pos, sizeof(buf), nvHandle); - return FwNvAppendEntry(ctx, FWTPM_NV_TAG_NV_INDEX_DEL, - buf, (UINT16)pos); + ctx->nvDeleteHandle = nvHandle; + rc = FwNvAppendEntry(ctx, FWTPM_NV_TAG_NV_INDEX_DEL, buf, (UINT16)pos); + ctx->nvDeleteHandle = 0; + return rc; } int FWTPM_NV_SavePersistent(FWTPM_CTX* ctx, int slot) @@ -2564,6 +2628,7 @@ int FWTPM_NV_SavePersistent(FWTPM_CTX* ctx, int slot) int FWTPM_NV_DeletePersistent(FWTPM_CTX* ctx, UINT32 handle) { + int rc; byte buf[4]; word32 pos = 0; @@ -2572,8 +2637,10 @@ int FWTPM_NV_DeletePersistent(FWTPM_CTX* ctx, UINT32 handle) } FwNvMarshalU32(buf, &pos, sizeof(buf), handle); - return FwNvAppendEntry(ctx, FWTPM_NV_TAG_PERSISTENT_DEL, - buf, (UINT16)pos); + ctx->nvDeleteHandle = handle; + rc = FwNvAppendEntry(ctx, FWTPM_NV_TAG_PERSISTENT_DEL, buf, (UINT16)pos); + ctx->nvDeleteHandle = 0; + return rc; } int FWTPM_NV_SavePrimaryCache(FWTPM_CTX* ctx, int slot) diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index 43b3c82f..4d216327 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -9587,6 +9587,236 @@ static void test_fwtpm_policy_cphash_enforced(void) } #endif /* !FWTPM_NO_NV */ +#if !defined(NO_RSA) && defined(WOLFSSL_KEY_GEN) +/* Create a child under parent; copy its outPrivate and raw outPublic. */ +static void CreateChildBlobs(FWTPM_CTX* ctx, UINT32 parent, + byte* priv, UINT16* privSz, byte* pub, UINT16* pubSz) +{ + int rc, rspSize = 0, cmdSz, pos; + + cmdSz = BuildCreatePrimaryCmd(gCmd, TPM_ALG_RSA); + AssertIntGT(cmdSz, 0); + PutU32BE(gCmd + 6, TPM_CC_Create); + PutU32BE(gCmd + 10, parent); + rc = FWTPM_ProcessCommand(ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + pos = TPM2_HEADER_SIZE + 4; /* skip parameterSize */ + *privSz = GetU16BE(gRsp + pos); pos += 2; + AssertIntGT(*privSz, 0); + memcpy(priv, gRsp + pos, *privSz); pos += *privSz; + *pubSz = GetU16BE(gRsp + pos); + AssertIntGT(*pubSz, 0); + memcpy(pub, gRsp + pos, 2 + *pubSz); /* keep the size prefix */ +} + +/* Load(parent, priv, pub) and return the response code */ +static TPM_RC SendLoadCmd(FWTPM_CTX* ctx, UINT32 parent, + const byte* priv, UINT16 privSz, const byte* pub, UINT16 pubSz) +{ + int pos, rspSize = 0; + + pos = BuildCmdHeader(gCmd, TPM_ST_SESSIONS, 0, TPM_CC_Load); + PutU32BE(gCmd + pos, parent); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU16BE(gCmd + pos, privSz); pos += 2; + memcpy(gCmd + pos, priv, privSz); pos += privSz; + memcpy(gCmd + pos, pub, 2 + pubSz); pos += 2 + pubSz; + PutU32BE(gCmd + 2, (UINT32)pos); + FWTPM_ProcessCommand(ctx, gCmd, pos, gRsp, &rspSize, 0); + return GetRspRC(gRsp); +} + +/* Two children wrapped under one parent must not share an IV, so identical + * leading sensitive bytes never produce identical leading ciphertext. */ +static void test_fwtpm_wrap_private_unique_iv(void) +{ + FWTPM_CTX ctx; + UINT32 srk, child; + byte priv[2][sizeof(TPM2B_PRIVATE)]; + byte pub[2][sizeof(TPM2B_PUBLIC)]; + UINT16 privSz[2], pubSz[2]; + int ivOff = 2 + 32; /* after integrity */ + int encOff = ivOff + 16 + 2; /* after IV + size */ + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + srk = CreatePrimaryHelper(&ctx, TPM_ALG_RSA); + AssertIntNE(srk, 0); + + CreateChildBlobs(&ctx, srk, priv[0], &privSz[0], pub[0], &pubSz[0]); + CreateChildBlobs(&ctx, srk, priv[1], &privSz[1], pub[1], &pubSz[1]); + AssertIntGT(privSz[0], encOff + 16); + AssertIntGT(privSz[1], encOff + 16); + AssertIntNE(memcmp(priv[0] + ivOff, priv[1] + ivOff, 16), 0); + AssertIntNE(memcmp(priv[0] + encOff, priv[1] + encOff, 16), 0); + + /* The blobs still round-trip through Load */ + AssertIntEQ(SendLoadCmd(&ctx, srk, priv[0], privSz[0], pub[0], pubSz[0]), + TPM_RC_SUCCESS); + child = GetU32BE(gRsp + TPM2_HEADER_SIZE); + AssertIntNE(child, 0); + FlushHandle(&ctx, child); + FlushHandle(&ctx, srk); + FWTPM_Cleanup(&ctx); + fwtpm_pass("Wrapped private blobs use unique IVs:", 0); +} + +/* A private blob is bound to the public area it was created with; loading it + * under a swapped or altered public area must fail the integrity check. */ +static void test_fwtpm_load_private_bound_to_public(void) +{ + FWTPM_CTX ctx; + UINT32 srk; + byte priv[2][sizeof(TPM2B_PRIVATE)]; + byte pub[2][sizeof(TPM2B_PUBLIC)]; + UINT16 privSz[2], pubSz[2]; + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + srk = CreatePrimaryHelper(&ctx, TPM_ALG_RSA); + AssertIntNE(srk, 0); + + CreateChildBlobs(&ctx, srk, priv[0], &privSz[0], pub[0], &pubSz[0]); + CreateChildBlobs(&ctx, srk, priv[1], &privSz[1], pub[1], &pubSz[1]); + + /* Another object's public area */ + AssertIntEQ(SendLoadCmd(&ctx, srk, priv[0], privSz[0], pub[1], pubSz[1]), + TPM_RC_INTEGRITY); + + /* The right public area with one bit of its unique field altered */ + pub[0][2 + pubSz[0] - 1] ^= 0x01; + AssertIntEQ(SendLoadCmd(&ctx, srk, priv[0], privSz[0], pub[0], pubSz[0]), + TPM_RC_INTEGRITY); + + FlushHandle(&ctx, srk); + FWTPM_Cleanup(&ctx); + fwtpm_pass("Private blob bound to its public area:", 0); +} +#endif /* !NO_RSA && WOLFSSL_KEY_GEN */ + +/* PolicyPCR selecting PCR 0 in the SHA-256 bank with an optional caller digest */ +static TPM_RC SendPolicyPcrCmd(FWTPM_CTX* ctx, UINT32 sessH, + const byte* digest, UINT16 digestSz) +{ + int pos = 0, rspSize = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_PolicyPCR); pos += 4; + PutU32BE(gCmd + pos, sessH); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU16BE(gCmd + pos, digestSz); pos += 2; + if (digestSz > 0) { + memcpy(gCmd + pos, digest, digestSz); pos += digestSz; + } + PutU32BE(gCmd + pos, 1); pos += 4; /* count */ + PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; /* hash */ + gCmd[pos++] = 3; /* sizeofSelect */ + gCmd[pos++] = 0x01; gCmd[pos++] = 0x00; gCmd[pos++] = 0x00; /* PCR 0 */ + PutU32BE(gCmd + 2, (UINT32)pos); + FWTPM_ProcessCommand(ctx, gCmd, pos, gRsp, &rspSize, 0); + return GetRspRC(gRsp); +} + +/* A real policy session must verify a caller-supplied pcrDigest against the + * live PCR values; only a trial session may take it on faith. */ +static void test_fwtpm_policy_pcr_digest_verified(void) +{ + FWTPM_CTX ctx; + UINT32 sessH; + byte pcr0[32]; + byte expect[32]; + byte wrong[32]; + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + memset(pcr0, 0, sizeof(pcr0)); /* PCR 0 reset by Startup */ + AssertIntEQ(wc_Hash(WC_HASH_TYPE_SHA256, pcr0, sizeof(pcr0), + expect, sizeof(expect)), 0); + memset(wrong, 0xAB, sizeof(wrong)); + + sessH = StartSessionHelper(&ctx, TPM_SE_POLICY); + AssertIntNE(sessH, 0); + AssertIntEQ(SendPolicyPcrCmd(&ctx, sessH, wrong, sizeof(wrong)), + TPM_RC_VALUE); + AssertIntEQ(SendPolicyPcrCmd(&ctx, sessH, expect, 16), TPM_RC_VALUE); + AssertIntEQ(SendPolicyPcrCmd(&ctx, sessH, expect, sizeof(expect)), + TPM_RC_SUCCESS); + FlushHandle(&ctx, sessH); + + sessH = StartSessionHelper(&ctx, TPM_SE_TRIAL); + AssertIntNE(sessH, 0); + AssertIntEQ(SendPolicyPcrCmd(&ctx, sessH, wrong, sizeof(wrong)), + TPM_RC_SUCCESS); + FlushHandle(&ctx, sessH); + + FWTPM_Cleanup(&ctx); + fwtpm_pass("PolicyPCR digest verified:", 0); +} + +#ifndef FWTPM_NO_NV +static TPM_RC SendPcrExtendLoc(FWTPM_CTX* ctx, int pcrIndex, int locality); + +/* A policy session evaluated against PCR values must stop authorizing once a + * PCR changes, and must be restarted before PolicyPCR is accepted again. */ +static void test_fwtpm_policy_pcr_change_invalidates(void) +{ + FWTPM_CTX ctx; + int pos, cmdSz, rspSize = 0; + UINT32 sessH; + UINT16 dSz; + byte digest[64]; + UINT32 nvIdx = 0x01500063; + UINT32 nvAttrs = TPMA_NV_OWNERWRITE | TPMA_NV_OWNERREAD | TPMA_NV_NO_DA; + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + sessH = StartSessionHelper(&ctx, TPM_SE_POLICY); + AssertIntNE(sessH, 0); + AssertIntEQ(SendPolicyPcrCmd(&ctx, sessH, NULL, 0), TPM_RC_SUCCESS); + + AssertIntEQ(SendPolicyCmd(&ctx, TPM_CC_PolicyGetDigest, sessH), + TPM_RC_SUCCESS); + dSz = GetU16BE(gRsp + TPM2_HEADER_SIZE + 4); + AssertIntEQ(dSz, 32); + memcpy(digest, gRsp + TPM2_HEADER_SIZE + 6, dSz); + + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_SetPrimaryPolicy); pos += 4; + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU16BE(gCmd + pos, dSz); pos += 2; + memcpy(gCmd + pos, digest, dSz); pos += dSz; + PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + AssertIntEQ(SendPcrExtendLoc(&ctx, 0, 0), TPM_RC_SUCCESS); + + /* The policy digest still matches, but the PCRs it attests to moved */ + cmdSz = BuildNvDefineCmd(gCmd, nvIdx, 8, nvAttrs); + PutU32BE(gCmd + 18, sessH); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_PCR_CHANGED); + + AssertIntEQ(SendPolicyPcrCmd(&ctx, sessH, NULL, 0), TPM_RC_PCR_CHANGED); + AssertIntEQ(SendPolicyCmd(&ctx, TPM_CC_PolicyRestart, sessH), + TPM_RC_SUCCESS); + AssertIntEQ(SendPolicyPcrCmd(&ctx, sessH, NULL, 0), TPM_RC_SUCCESS); + + FlushHandle(&ctx, sessH); + FWTPM_Cleanup(&ctx); + fwtpm_pass("PolicyPCR invalidated by PCR change:", 0); +} +#endif /* !FWTPM_NO_NV */ + #if !defined(NO_RSA) && defined(WOLFSSL_KEY_GEN) static void test_fwtpm_admin_authorization_requires_policy(void) { @@ -11386,6 +11616,120 @@ static void test_fwtpm_clear_control_nv_failure(void) FWTPM_Cleanup(&ctx); fwtpm_pass("ClearControl NV failure rollback:", 0); } + +/* State-changing handlers that persist to NV must report failure, not + * success, when the journal write fails. Representative commands from the + * clock, NV-index and hierarchy-auth families run on one context; + * HierarchyChangeAuth is exercised last because its volatile auth change would + * otherwise block the empty-password owner authorization of later commands. */ +static void test_fwtpm_state_change_nv_failure(void) +{ + FWTPM_CTX ctx; + FWTPM_NV_HAL oldHal, failHal; + int rspSize, pos, cmdSz; + UINT32 nvIdx = 0x01500007; + UINT32 attrs = TPMA_NV_OWNERWRITE | TPMA_NV_OWNERREAD | TPMA_NV_NO_DA | + ((UINT32)TPM_NT_COUNTER << 4); + byte newAuth[] = {0x0A, 0x0B, 0x0C, 0x0D}; + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + /* Define a counter while persistence works. */ + cmdSz = BuildNvDefineCmd(gCmd, nvIdx, 8, attrs); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + oldHal = ctx.nvHal; + failHal = oldHal; + failHal.write = fail_nv_write; + AssertIntEQ(FWTPM_NV_SetHAL(&ctx, &failHal), TPM_RC_SUCCESS); + +#ifndef FWTPM_NO_CLOCK + /* ClockSet: a failed persist must report failure and roll the offset + * back. */ + AssertIntEQ((int)ctx.clockOffset, 0); + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_ClockSet); pos += 4; + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU32BE(gCmd + pos, 0); pos += 4; /* newTime high */ + PutU32BE(gCmd + pos, 1000); pos += 4; /* newTime low */ + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_FAILURE); + AssertIntEQ((int)ctx.clockOffset, 0); +#endif + + /* NV_Increment: a failed persist must report failure. */ + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_NV_Increment); pos += 4; + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; + PutU32BE(gCmd + pos, nvIdx); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_FAILURE); + + /* Restore persistence and confirm the counter was rolled back (== 0). */ + AssertIntEQ(FWTPM_NV_SetHAL(&ctx, &oldHal), TPM_RC_SUCCESS); + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_NV_Read); pos += 4; + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; + PutU32BE(gCmd + pos, nvIdx); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU16BE(gCmd + pos, 8); pos += 2; /* size */ + PutU16BE(gCmd + pos, 0); pos += 2; /* offset */ + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(GetU16BE(gRsp + 14), 8); + AssertIntEQ((int)GetU32BE(gRsp + 16), 0); + AssertIntEQ((int)GetU32BE(gRsp + 20), 0); + + /* Remove the counter while owner auth is still empty. */ + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_NV_UndefineSpace); pos += 4; + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; + PutU32BE(gCmd + pos, nvIdx); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + + /* HierarchyChangeAuth: a failed persist must report failure and roll the + * auth back (empty). */ + AssertIntEQ(FWTPM_NV_SetHAL(&ctx, &failHal), TPM_RC_SUCCESS); + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_HierarchyChangeAuth); pos += 4; + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU16BE(gCmd + pos, (UINT16)sizeof(newAuth)); pos += 2; + memcpy(gCmd + pos, newAuth, sizeof(newAuth)); pos += sizeof(newAuth); + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_FAILURE); + AssertIntEQ(ctx.ownerAuth.size, 0); + + AssertIntEQ(FWTPM_NV_SetHAL(&ctx, &oldHal), TPM_RC_SUCCESS); + FWTPM_Cleanup(&ctx); + fwtpm_pass("State-change NV failure reporting:", 0); +} #endif /* !FWTPM_NO_NV */ /* Per Part 3 Sec.24.6 Table 134, TPM2_Clear has Auth Index 1, Auth Role USER @@ -13196,6 +13540,14 @@ static int mock_nv_write(void* c, word32 off, const byte* buf, word32 sz) gMockNvWrites++; return TPM_RC_SUCCESS; } +/* Header-write failure injection: gMockNvFailHeader fails every header write; + * gMockNvFailAfterCompact fails the first header write issued outside a + * compaction once one has run (gMockNvCtx exposes the compacting flag). */ +static int gMockNvFailHeader; +static int gMockNvFailAfterCompact; +static int gMockNvCompactSeen; +static FWTPM_CTX* gMockNvCtx; + static int mock_nv_erase(void* c, word32 off, word32 sz) { (void)c; @@ -13204,8 +13556,254 @@ static int mock_nv_erase(void* c, word32 off, word32 sz) } memset(gMockNvStore + off, 0xFF, sz); gMockNvErases++; + if (gMockNvFailAfterCompact) { + gMockNvCompactSeen = 1; + } return TPM_RC_SUCCESS; } +static int mock_nv_key(void* c, byte* key, word32* keySz) +{ + (void)c; + memset(key, 0xA5, 32); + *keySz = 32; + return 0; +} + +/* Fail only the trailing journal header write (offset 0). This models an + * entry whose bytes landed but whose commit did not, exercising the + * append-atomicity rollback. */ +static int mock_nv_write_failhdr(void* c, word32 off, const byte* buf, + word32 sz) +{ + if (off == 0) { + if (gMockNvFailHeader || + (gMockNvCompactSeen && gMockNvCtx != NULL && + !gMockNvCtx->nvCompacting)) { + return TPM_RC_FAILURE; + } + } + return mock_nv_write(c, off, buf, sz); +} + +/* Keyed byte-addressable journal: when a save must compact first, the + * compacted snapshot is the commit and its seal must survive whatever + * happens next, so the prior state is still there after a reboot. */ +static void test_fwtpm_nv_compaction_commit(void) +{ + FWTPM_CTX ctx; + FWTPM_NV_HAL hal; + byte savedSeed[FWTPM_SEED_SIZE]; + int rc, i, baseErases; + + memset(gMockNvStore, 0xFF, sizeof(gMockNvStore)); + gMockNvFailHeader = 0; + gMockNvFailAfterCompact = 0; + gMockNvCompactSeen = 0; + memset(&ctx, 0, sizeof(ctx)); + memset(&hal, 0, sizeof(hal)); + hal.read = mock_nv_read; + hal.write = mock_nv_write_failhdr; + hal.erase = mock_nv_erase; + hal.get_integrity_key = mock_nv_key; + hal.maxSize = MOCK_NV_SIZE; + AssertIntEQ(FWTPM_NV_SetHAL(&ctx, &hal), 0); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + memcpy(savedSeed, ctx.ownerSeed, FWTPM_SEED_SIZE); + + /* Fill the journal until a save has to compact; the write that would + * follow that compaction's seal fails. */ + baseErases = gMockNvErases; + gMockNvCtx = &ctx; + gMockNvFailAfterCompact = 1; + for (i = 0; i < 20000 && gMockNvErases == baseErases; i++) { + ctx.disableClear = 1; + rc = FWTPM_NV_SaveFlags(&ctx); + AssertIntEQ(rc, 0); + } + AssertIntGT(gMockNvErases, baseErases); + gMockNvFailAfterCompact = 0; + gMockNvCompactSeen = 0; + gMockNvCtx = NULL; + + /* Unclean restart: the compacted state must load intact. */ + wc_FreeRng(&ctx.rng); + memset(&ctx, 0, sizeof(ctx)); + memset(&hal, 0, sizeof(hal)); + hal.read = mock_nv_read; + hal.write = mock_nv_write_failhdr; + hal.erase = mock_nv_erase; + hal.get_integrity_key = mock_nv_key; + hal.maxSize = MOCK_NV_SIZE; + AssertIntEQ(FWTPM_NV_SetHAL(&ctx, &hal), 0); + AssertIntEQ(FWTPM_Init(&ctx), 0); + AssertIntEQ(memcmp(ctx.ownerSeed, savedSeed, FWTPM_SEED_SIZE), 0); + AssertIntEQ((int)ctx.disableClear, 1); + + FWTPM_Cleanup(&ctx); + fwtpm_pass("NV compaction is the commit:", 0); +} + +/* A deletion that has to compact first must still complete when the compacted + * journal leaves no room for a separate deletion record. */ +static void test_fwtpm_nv_delete_at_capacity(void) +{ + FWTPM_CTX ctx; + FWTPM_NV_HAL hal; + UINT32 nvIdx = 0x0150000A; + UINT32 attrs = TPMA_NV_OWNERWRITE | TPMA_NV_OWNERREAD | TPMA_NV_NO_DA; + int rc, i, rspSize, cmdSz, found; + word32 maxSize; + + memset(gMockNvStore, 0xFF, sizeof(gMockNvStore)); + gMockNvFailHeader = 0; + gMockNvFailAfterCompact = 0; + gMockNvCompactSeen = 0; + gMockNvCtx = NULL; + memset(&ctx, 0, sizeof(ctx)); + memset(&hal, 0, sizeof(hal)); + hal.read = mock_nv_read; + hal.write = mock_nv_write; + hal.erase = mock_nv_erase; + hal.maxSize = MOCK_NV_SIZE; + AssertIntEQ(FWTPM_NV_SetHAL(&ctx, &hal), 0); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + cmdSz = BuildNvDefineCmd(gCmd, nvIdx, 8, attrs); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + /* Compact, then shrink the store so the compacted image leaves less than + * a deletion record plus the journal seal free. */ + AssertIntEQ(FWTPM_NV_Save(&ctx), 0); + maxSize = ctx.nvWritePos + WC_SHA256_DIGEST_SIZE + 4; + ctx.nvHal.maxSize = maxSize; + + rc = FWTPM_NV_DeleteNvIndex(&ctx, nvIdx); + AssertIntEQ(rc, 0); + for (i = 0; i < FWTPM_MAX_NV_INDICES; i++) { + if (ctx.nvIndices[i].inUse && + ctx.nvIndices[i].nvPublic.nvIndex == nvIdx) { + memset(&ctx.nvIndices[i], 0, sizeof(ctx.nvIndices[i])); + } + } + + /* Unclean restart on the same store: the index is gone. */ + wc_FreeRng(&ctx.rng); + memset(&ctx, 0, sizeof(ctx)); + memset(&hal, 0, sizeof(hal)); + hal.read = mock_nv_read; + hal.write = mock_nv_write; + hal.erase = mock_nv_erase; + hal.maxSize = maxSize; + AssertIntEQ(FWTPM_NV_SetHAL(&ctx, &hal), 0); + AssertIntEQ(FWTPM_Init(&ctx), 0); + found = 0; + for (i = 0; i < FWTPM_MAX_NV_INDICES; i++) { + if (ctx.nvIndices[i].inUse && + ctx.nvIndices[i].nvPublic.nvIndex == nvIdx) { + found = 1; + } + } + AssertIntEQ(found, 0); + + FWTPM_Cleanup(&ctx); + fwtpm_pass("NV delete completes at capacity:", 0); +} + +/* A journal append whose header commit fails must leave no committable + * remnant: after a later successful append and a simulated reboot, the failed + * command's change is absent while the later change persists. */ +static void test_fwtpm_nv_append_atomic(void) +{ + FWTPM_CTX ctx; + FWTPM_NV_HAL hal; + UINT32 nvIdx = 0x01500009; + UINT32 attrs = TPMA_NV_OWNERWRITE | TPMA_NV_OWNERREAD | TPMA_NV_NO_DA | + ((UINT32)TPM_NT_COUNTER << 4); + byte newAuth[] = {0x0A, 0x0B, 0x0C, 0x0D}; + int rspSize, pos, cmdSz, i, found; + + memset(gMockNvStore, 0xFF, sizeof(gMockNvStore)); + gMockNvFailHeader = 0; + memset(&ctx, 0, sizeof(ctx)); + memset(&hal, 0, sizeof(hal)); + hal.read = mock_nv_read; + hal.write = mock_nv_write_failhdr; + hal.erase = mock_nv_erase; + hal.maxSize = MOCK_NV_SIZE; + AssertIntEQ(FWTPM_NV_SetHAL(&ctx, &hal), 0); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + /* Define a counter so the later successful append records observable + * state for the reboot to confirm. */ + cmdSz = BuildNvDefineCmd(gCmd, nvIdx, 8, attrs); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + /* HierarchyChangeAuth whose header commit fails: the auth entry bytes are + * written but the command must report failure. An always-present command + * is used so the scenario also runs under FWTPM_NO_CLOCK. */ + gMockNvFailHeader = 1; + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_HierarchyChangeAuth); pos += 4; + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU16BE(gCmd + pos, (UINT16)sizeof(newAuth)); pos += 2; + memcpy(gCmd + pos, newAuth, sizeof(newAuth)); pos += sizeof(newAuth); + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntNE(GetRspRC(gRsp), TPM_RC_SUCCESS); + + /* A subsequent successful append must overwrite any orphan left by the + * failed auth change rather than seal it into the journal. */ + gMockNvFailHeader = 0; + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_NV_Increment); pos += 4; + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; + PutU32BE(gCmd + pos, nvIdx); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + /* Unclean restart: reload the same journal image WITHOUT a clean shutdown + * (a clean FWTPM_Cleanup would recompact the whole live context). Free the + * live RNG first since the reinit allocates a fresh one. The failed auth + * change must be absent (owner auth empty) while the increment persisted + * (counter == 1). */ + wc_FreeRng(&ctx.rng); + memset(&ctx, 0, sizeof(ctx)); + memset(&hal, 0, sizeof(hal)); + hal.read = mock_nv_read; + hal.write = mock_nv_write_failhdr; + hal.erase = mock_nv_erase; + hal.maxSize = MOCK_NV_SIZE; + AssertIntEQ(FWTPM_NV_SetHAL(&ctx, &hal), 0); + AssertIntEQ(FWTPM_Init(&ctx), 0); + AssertIntEQ(ctx.ownerAuth.size, 0); + + found = 0; + for (i = 0; i < FWTPM_MAX_NV_INDICES; i++) { + if (ctx.nvIndices[i].inUse && + ctx.nvIndices[i].nvPublic.nvIndex == nvIdx) { + found = 1; + AssertIntEQ(ctx.nvIndices[i].data[7], 1); + break; + } + } + AssertIntEQ(found, 1); + + FWTPM_Cleanup(&ctx); + fwtpm_pass("NV append atomic on commit failure:", 0); +} #ifndef FWTPM_NO_DA static void PutU32LE(byte* p, UINT32 v) @@ -13401,10 +13999,20 @@ static void test_fwtpm_nv_sethal_mock(void) static byte gFlashNv[FNV_SIZE]; static int gFlashEraseCnt; static int gFlashProgCnt; +static int gFlashFailRead; /* when set, reads fail (used to fail a checkpoint + * MAC computation without touching the log) */ + +static int gFlashFailReadInCompact; /* fail reads only while compacting */ +static FWTPM_CTX* gFlashCtx; static int flash_read(void* c, word32 off, byte* buf, word32 sz) { (void)c; + if (gFlashFailRead || + (gFlashFailReadInCompact && gFlashCtx != NULL && + gFlashCtx->nvCompacting)) { + return -1; + } if ((size_t)off + sz > FNV_SIZE) { return -1; } @@ -13693,6 +14301,163 @@ static void test_fwtpm_nv_flash_append_only(void) #endif } +/* Append-only: a save whose checkpoint fails must report failure, and the + * unsealed entry it leaves on the log must never be authenticated afterwards, + * whether the next event is a reboot or a further successful save. */ +static void test_fwtpm_nv_append_checkpoint_failure(void) +{ +#if defined(WOLFTPM_FWTPM_NV_APPEND_ONLY) && !defined(FWTPM_NO_NV) + FWTPM_CTX ctx; + FWTPM_NV_HAL hal; + byte savedSeed[FWTPM_SEED_SIZE]; + int rc; + int pass; + + for (pass = 0; pass < 2; pass++) { + XMEMSET(gFlashNv, 0xFF, sizeof(gFlashNv)); + gFlashEraseCnt = 0; + gFlashProgCnt = 0; + rc = flash_boot(&ctx, &hal, FNV_PROG, 1); + AssertIntEQ(rc, 0); + XMEMCPY(savedSeed, ctx.ownerSeed, FWTPM_SEED_SIZE); + AssertIntEQ((int)ctx.ownerAuth.size, 0); + + ctx.ownerAuth.size = 4; + XMEMSET(ctx.ownerAuth.buffer, 0xA1, 4); + gFlashFailRead = 1; + rc = FWTPM_NV_SaveAuth(&ctx, TPM_RH_OWNER); + gFlashFailRead = 0; + AssertIntNE(rc, 0); + ctx.ownerAuth.size = 0; /* as the handler rolls back */ + XMEMSET(ctx.ownerAuth.buffer, 0, sizeof(ctx.ownerAuth.buffer)); + + if (pass == 1) { + /* NV stays unavailable until a restart compacts the tail */ + ctx.disableClear = 1; + rc = FWTPM_NV_SaveFlags(&ctx); + AssertIntEQ(rc, TPM_RC_NV_UNAVAILABLE); + } + wc_FreeRng(&ctx.rng); /* drop ctx without a save */ + + rc = flash_boot(&ctx, &hal, FNV_PROG, 1); + AssertIntEQ(rc, 0); + AssertIntEQ(XMEMCMP(ctx.ownerSeed, savedSeed, FWTPM_SEED_SIZE), 0); + AssertIntEQ((int)ctx.ownerAuth.size, 0); + AssertIntEQ((int)ctx.disableClear, 0); + ctx.disableClear = 1; + rc = FWTPM_NV_SaveFlags(&ctx); /* writable again */ + AssertIntEQ(rc, 0); + FWTPM_Cleanup(&ctx); + } + + fwtpm_pass("NV append-only checkpoint failure:", 0); +#else + printf("Test fwTPM: %-6s %-42s Skipped\n", "", + "NV append-only checkpoint failure:"); +#endif +} + +/* Append-only: a compaction that fails at its checkpoint leaves the rewritten + * snapshot on the log; state rolled back after that failure must never be + * sealed by a later save, so NV stays unavailable until a restart. */ +static void test_fwtpm_nv_append_compaction_failure(void) +{ +#if defined(WOLFTPM_FWTPM_NV_APPEND_ONLY) && !defined(FWTPM_NO_NV) + FWTPM_CTX ctx; + FWTPM_NV_HAL hal; + int rc = 0; + int i; + int eraseBase; + + XMEMSET(gFlashNv, 0xFF, sizeof(gFlashNv)); + gFlashEraseCnt = 0; + gFlashProgCnt = 0; + rc = flash_boot(&ctx, &hal, FNV_PROG, 1); + AssertIntEQ(rc, 0); + AssertIntEQ((int)ctx.ownerAuth.size, 0); + + /* Proposed state that only a compaction would carry to the log; fill the + * journal until a save has to compact, and fail that compaction's seal. */ + ctx.ownerAuth.size = 4; + XMEMSET(ctx.ownerAuth.buffer, 0xA1, 4); + eraseBase = gFlashEraseCnt; + gFlashCtx = &ctx; + gFlashFailReadInCompact = 1; + for (i = 0; i < 5000 && rc == 0; i++) { + rc = FWTPM_NV_SaveFlags(&ctx); + } + gFlashFailReadInCompact = 0; + gFlashCtx = NULL; + AssertIntNE(rc, 0); + AssertIntGT(gFlashEraseCnt, eraseBase); + ctx.ownerAuth.size = 0; /* as the handler rolls back */ + XMEMSET(ctx.ownerAuth.buffer, 0, sizeof(ctx.ownerAuth.buffer)); + + ctx.disableClear = 1; + eraseBase = gFlashEraseCnt; + rc = FWTPM_NV_SaveFlags(&ctx); + AssertIntEQ(rc, TPM_RC_NV_UNAVAILABLE); + AssertIntEQ(gFlashEraseCnt, eraseBase); + wc_FreeRng(&ctx.rng); /* drop ctx without a save */ + + rc = flash_boot(&ctx, &hal, FNV_PROG, 1); + AssertIntEQ(rc, 0); + AssertIntEQ((int)ctx.ownerAuth.size, 0); + AssertIntEQ((int)ctx.disableClear, 0); + FWTPM_Cleanup(&ctx); + + fwtpm_pass("NV append-only compaction failure:", 0); +#else + printf("Test fwTPM: %-6s %-42s Skipped\n", "", + "NV append-only compaction failure:"); +#endif +} + +/* Append-only: after a checkpoint failure no further mutation may touch the + * medium, so the last committed image survives a persisting fault. */ +static void test_fwtpm_nv_append_rebuild_probe(void) +{ +#if defined(WOLFTPM_FWTPM_NV_APPEND_ONLY) && !defined(FWTPM_NO_NV) + FWTPM_CTX ctx; + FWTPM_NV_HAL hal; + byte savedSeed[FWTPM_SEED_SIZE]; + int rc; + int eraseBase; + + XMEMSET(gFlashNv, 0xFF, sizeof(gFlashNv)); + gFlashEraseCnt = 0; + gFlashProgCnt = 0; + rc = flash_boot(&ctx, &hal, FNV_PROG, 1); + AssertIntEQ(rc, 0); + XMEMCPY(savedSeed, ctx.ownerSeed, FWTPM_SEED_SIZE); + ctx.disableClear = 1; + rc = FWTPM_NV_SaveFlags(&ctx); + AssertIntEQ(rc, 0); + + gFlashFailRead = 1; + ctx.disableClear = 0; + rc = FWTPM_NV_SaveFlags(&ctx); /* checkpoint fails */ + AssertIntNE(rc, 0); + eraseBase = gFlashEraseCnt; + rc = FWTPM_NV_SaveFlags(&ctx); /* refused, no erase */ + AssertIntEQ(rc, TPM_RC_NV_UNAVAILABLE); + AssertIntEQ(gFlashEraseCnt, eraseBase); + gFlashFailRead = 0; + wc_FreeRng(&ctx.rng); /* drop ctx without a save */ + + rc = flash_boot(&ctx, &hal, FNV_PROG, 1); + AssertIntEQ(rc, 0); + AssertIntEQ(XMEMCMP(ctx.ownerSeed, savedSeed, FWTPM_SEED_SIZE), 0); + AssertIntEQ((int)ctx.disableClear, 1); + FWTPM_Cleanup(&ctx); + + fwtpm_pass("NV append-only unavailable after checkpoint failure:", 0); +#else + printf("Test fwTPM: %-6s %-42s Skipped\n", "", + "NV append-only unavailable after checkpoint failure:"); +#endif +} + /* ================================================================== */ /* main */ /* ================================================================== */ @@ -13793,6 +14558,9 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_clock_sethal(); test_fwtpm_nv_sethal_mock(); test_fwtpm_nv_flash_append_only(); + test_fwtpm_nv_append_checkpoint_failure(); + test_fwtpm_nv_append_compaction_failure(); + test_fwtpm_nv_append_rebuild_probe(); (void)remove(FWTPM_NV_FILE); /* Key operations */ @@ -14005,6 +14773,14 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_policy_command_code(); test_fwtpm_policy_locality(); test_fwtpm_policy_pcr(); + test_fwtpm_policy_pcr_digest_verified(); +#ifndef FWTPM_NO_NV + test_fwtpm_policy_pcr_change_invalidates(); +#endif +#if !defined(NO_RSA) && defined(WOLFSSL_KEY_GEN) + test_fwtpm_wrap_private_unique_iv(); + test_fwtpm_load_private_bound_to_public(); +#endif test_fwtpm_policy_ticket_zero_digest_rejected(); test_fwtpm_policyauthorize_null_ticket_rejected(); #ifndef FWTPM_NO_NV @@ -14065,6 +14841,10 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_sessions_missing_authsize_command_size(); #ifndef FWTPM_NO_NV test_fwtpm_clear_control_nv_failure(); + test_fwtpm_state_change_nv_failure(); + test_fwtpm_nv_append_atomic(); + test_fwtpm_nv_compaction_commit(); + test_fwtpm_nv_delete_at_capacity(); #endif /* !FWTPM_NO_NV */ test_fwtpm_clear(); diff --git a/wolftpm/fwtpm/fwtpm.h b/wolftpm/fwtpm/fwtpm.h index f3c110cc..d25cb1af 100644 --- a/wolftpm/fwtpm/fwtpm.h +++ b/wolftpm/fwtpm/fwtpm.h @@ -618,6 +618,8 @@ typedef struct FWTPM_Session { TPM2B_DIGEST templateHash; /* PolicyTemplate: locked once set */ int checkNvWritten; /* 1 once PolicyNvWritten has been called */ int nvWrittenState; /* PolicyNvWritten writtenSet */ + UINT32 pcrUpdateCounter; /* PCR update counter seen by PolicyPCR */ + int hasPcrUpdateCounter; /* 1 once PolicyPCR has been evaluated */ } FWTPM_Session; /* NV index slot (user NV RAM) */ @@ -679,6 +681,8 @@ typedef struct FWTPM_IO_CTX { * fwtpm_nv.h). */ struct FWTPM_NV_HAL_S { int (*read)(void* ctx, word32 offset, byte* buf, word32 size); + /* Must return non-zero only if the bytes are not durable: a state change + * whose write fails is rolled back and reported as failed. */ int (*write)(void* ctx, word32 offset, const byte* buf, word32 size); int (*erase)(void* ctx, word32 offset, word32 size); /* Optional */ void* ctx; @@ -855,6 +859,7 @@ typedef struct FWTPM_CTX { /* NV journal write position (next append offset) */ word32 nvWritePos; int nvCompacting; /* Guard flag to prevent cyclic recursion during NV compaction */ + UINT32 nvDeleteHandle; /* Item a pending deletion omits from compaction */ #ifdef WOLFTPM_FWTPM_NV_APPEND_ONLY /* Append-only pending program granule (word-backed for alignment; element @@ -863,6 +868,8 @@ typedef struct FWTPM_CTX { / sizeof(word32)]; word32 nvGranuleBase; /* aligned offset of the pending granule */ word32 nvGranuleFill; /* bytes buffered (0..writeAlign) */ + int nvRebuild; /* an unsealed append is on the log: NV refuses + * mutations until a restart compacts it away */ #endif #ifndef FWTPM_NO_CONTEXT diff --git a/wolftpm/fwtpm/fwtpm_crypto.h b/wolftpm/fwtpm/fwtpm_crypto.h index c6b2a8a7..6405555b 100644 --- a/wolftpm/fwtpm/fwtpm_crypto.h +++ b/wolftpm/fwtpm/fwtpm_crypto.h @@ -237,8 +237,10 @@ TPM_RC FwVerifyMldsaHash(TPMI_MLDSA_PARAMETER_SET parameterSet, /* --- Key wrapping --- */ -int FwDeriveWrapKey(const FWTPM_Object* parent, - byte* aesKey, byte* aesIV); +int FwComputePublicName(TPMT_PUBLIC* pub, TPM2B_NAME* name); + +int FwDeriveWrapKey(const FWTPM_Object* parent, const TPM2B_NAME* name, + byte* aesKey, byte* macKey); int FwMarshalSensitive(byte* buf, int bufSz, UINT16 sensitiveType, const TPM2B_AUTH* auth, @@ -252,12 +254,13 @@ int FwUnmarshalSensitive(const byte* buf, int bufSz, UINT16* sensitiveType, TPM2B_AUTH* auth, byte* privKeyDer, int* privKeyDerSz); -int FwWrapPrivate(FWTPM_Object* parent, +int FwWrapPrivate(FWTPM_Object* parent, WC_RNG* rng, + const TPM2B_NAME* name, UINT16 sensitiveType, const TPM2B_AUTH* auth, const byte* privKeyDer, int privKeyDerSz, TPM2B_PRIVATE* outPriv); -int FwUnwrapPrivate(FWTPM_Object* parent, +int FwUnwrapPrivate(FWTPM_Object* parent, const TPM2B_NAME* name, const TPM2B_PRIVATE* inPriv, UINT16* sensitiveType, TPM2B_AUTH* auth, byte* privKeyDer, int* privKeyDerSz); diff --git a/wolftpm/tpm2_param_enc.h b/wolftpm/tpm2_param_enc.h index 331df6cc..a617d5b4 100644 --- a/wolftpm/tpm2_param_enc.h +++ b/wolftpm/tpm2_param_enc.h @@ -31,9 +31,10 @@ #endif /* Maximum XOR mask size. RSA-2048 inSensitive parameter blobs on Create can - * exceed MAX_DIGEST_BUFFER (1024), so leave headroom to ~1250 bytes. */ + * exceed MAX_DIGEST_BUFFER (1024), and an fwTPM outPrivate carries a 32-byte + * integrity value plus a 16-byte IV on top of the sensitive area. */ #ifndef TPM2_XOR_MASK_MAX -#define TPM2_XOR_MASK_MAX 1280 +#define TPM2_XOR_MASK_MAX 1536 #endif /* XOR parameter encryption/decryption (raw pointer interface).