F-4673 - Bind context sequence counter to blob and reject replay

pull/518/head
aidan garske 2026-06-02 12:12:52 -07:00
parent c03db2d23d
commit 60aef67a05
3 changed files with 76 additions and 1 deletions

View File

@ -3077,7 +3077,7 @@ static TPM_RC FwCmd_ContextLoad(FWTPM_CTX* ctx, TPM2_Packet* cmd,
UINT32 magic = 0, version = 0;
(void)cmdTag;
(void)seqHi; (void)seqLo; (void)hierarchy;
(void)hierarchy;
if (cmdSize < TPM2_HEADER_SIZE + 18) {
rc = TPM_RC_COMMAND_SIZE;
@ -3091,6 +3091,13 @@ static TPM_RC FwCmd_ContextLoad(FWTPM_CTX* ctx, TPM2_Packet* cmd,
TPM2_Packet_ParseU16(cmd, &blobSz);
}
/* Replay protection: a saved context is bound to the sequence counter
* value it was created with, and each successful load consumes it. */
if (rc == 0 &&
(((UINT64)seqHi << 32) | (UINT64)seqLo) != ctx->contextSeqCounter) {
rc = TPM_RC_INTEGRITY;
}
/* Validate minimum blob size (magic + version = 8 bytes) */
if (rc == 0 && blobSz < 8) {
rc = TPM_RC_SIZE;
@ -3189,6 +3196,8 @@ static TPM_RC FwCmd_ContextLoad(FWTPM_CTX* ctx, TPM2_Packet* cmd,
#ifdef DEBUG_WOLFTPM
printf("fwTPM: ContextLoad(handle=0x%x)\n", savedHandle);
#endif
/* Consume this sequence value so the same blob cannot be replayed */
ctx->contextSeqCounter++;
TPM2_Packet_AppendU32(rsp, savedHandle);
FwRspFinalize(rsp, TPM_ST_NO_SESSIONS, TPM_RC_SUCCESS);
}

View File

@ -2219,6 +2219,18 @@ int FwUnwrapPrivate(FWTPM_Object* parent,
/* Context blob wrap/unwrap (ContextSave/ContextLoad) */
/* ================================================================== */
/* Fold a 64-bit value into an HMAC as big-endian, used to bind the context
* sequence counter into the blob MAC for replay protection. */
static int FwHmacUpdateU64(Hmac* hmac, UINT64 v)
{
byte b[8];
int i;
for (i = 0; i < 8; i++) {
b[i] = (byte)(v >> (56 - 8 * i));
}
return wc_HmacUpdate(hmac, b, (word32)sizeof(b));
}
/* Encrypt-then-MAC context blob protection using the per-boot key.
* Layout: iv(16) | ciphertext(plainSz) | hmac(32)
* Returns 0 on success, sets *outSz. */
@ -2271,6 +2283,9 @@ int FwWrapContextBlob(FWTPM_CTX* ctx,
if (rc == 0) {
rc = wc_HmacUpdate(hmac, out, AES_BLOCK_SIZE + plainSz);
}
if (rc == 0) {
rc = FwHmacUpdateU64(hmac, ctx->contextSeqCounter);
}
if (rc == 0) {
rc = wc_HmacFinal(hmac, out + AES_BLOCK_SIZE + plainSz);
}
@ -2328,6 +2343,9 @@ int FwUnwrapContextBlob(FWTPM_CTX* ctx,
if (rc == 0) {
rc = wc_HmacUpdate(hmac, in, AES_BLOCK_SIZE + cipherSz);
}
if (rc == 0) {
rc = FwHmacUpdateU64(hmac, ctx->contextSeqCounter);
}
if (rc == 0) {
rc = wc_HmacFinal(hmac, computedHmac);
}

View File

@ -8482,6 +8482,53 @@ static void test_fwtpm_context_save(void)
fwtpm_pass("ContextSave:", 0);
}
/* A saved context must load at most once; replaying the same blob is
* rejected to prevent resurrecting a satisfied policy session. */
static void test_fwtpm_context_load_replay_rejected(void)
{
FWTPM_CTX ctx;
int rspSize = 0, ctxSz, pos;
UINT32 keyH;
byte savedCtx[512];
memset(&ctx, 0, sizeof(ctx));
AssertIntEQ(fwtpm_test_startup(&ctx), 0);
#ifdef HAVE_ECC
keyH = CreatePrimaryHelper(&ctx, TPM_ALG_ECC);
#else
keyH = CreatePrimaryHelper(&ctx, TPM_ALG_RSA);
#endif
AssertIntNE(keyH, 0);
BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 14, TPM_CC_ContextSave);
PutU32BE(gCmd + 10, keyH);
FWTPM_ProcessCommand(&ctx, gCmd, 14, gRsp, &rspSize, 0);
AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS);
ctxSz = rspSize - TPM2_HEADER_SIZE; /* TPMS_CONTEXT follows the header */
AssertIntGT(ctxSz, 0);
memcpy(savedCtx, gRsp + TPM2_HEADER_SIZE, ctxSz);
/* First load succeeds */
pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_ContextLoad);
memcpy(gCmd + pos, savedCtx, ctxSz); pos += ctxSz;
PutU32BE(gCmd + 2, (UINT32)pos);
rspSize = 0;
FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0);
AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS);
/* Replaying the identical blob is rejected */
pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_ContextLoad);
memcpy(gCmd + pos, savedCtx, ctxSz); pos += ctxSz;
PutU32BE(gCmd + 2, (UINT32)pos);
rspSize = 0;
FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0);
AssertIntNE(GetRspRC(gRsp), TPM_RC_SUCCESS);
FlushHandle(&ctx, keyH);
FWTPM_Cleanup(&ctx);
printf("Test fwTPM:\tContextLoad replay rejected:\tPassed\n");
}
static void test_fwtpm_evict_control(void)
{
FWTPM_CTX ctx;
@ -9017,6 +9064,7 @@ int fwtpm_unit_tests(int argc, char *argv[])
test_fwtpm_evict_control_bad_persistent_handle_rejected();
test_fwtpm_evict_control_persistent_object_rejected();
test_fwtpm_context_save();
test_fwtpm_context_load_replay_rejected();
/* Crypto */
test_fwtpm_hash();