diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 1ec83025..40463b0b 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -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); } diff --git a/src/fwtpm/fwtpm_crypto.c b/src/fwtpm/fwtpm_crypto.c index 457a03fa..95b63097 100644 --- a/src/fwtpm/fwtpm_crypto.c +++ b/src/fwtpm/fwtpm_crypto.c @@ -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); } diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index 0e047e57..5b24909e 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -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();