diff --git a/examples/firmware/ifx_fw_extract.c b/examples/firmware/ifx_fw_extract.c index d90397a3..11a8b4af 100644 --- a/examples/firmware/ifx_fw_extract.c +++ b/examples/firmware/ifx_fw_extract.c @@ -180,7 +180,8 @@ static int extractFW( } READ_BE32(size32, fw, fw_size, offset); - if (offset + size32 > fw_size) { + /* offset <= fw_size here; subtract to avoid size_t wrap on 32-bit */ + if (size32 > fw_size - offset) { LOG("FW file too short"); return -1; } diff --git a/examples/nvram/read.c b/examples/nvram/read.c index f3e57cc3..67286fe5 100644 --- a/examples/nvram/read.c +++ b/examples/nvram/read.c @@ -211,6 +211,14 @@ int TPM2_NVRAM_Read_Example(void* userCtx, int argc, char *argv[]) printf("Successfully read public key part from NV\n\n"); offset += readSize; + /* pub.size comes from NV and must fit the destination buffer */ + if (readSize != sizeof(keyBlob.pub.size) || + sizeof(UINT16) + keyBlob.pub.size > sizeof(pubAreaBuffer)) { + printf("Invalid public key size marker from NV\n"); + rc = BUFFER_E; + goto exit; + } + readSize = sizeof(UINT16) + keyBlob.pub.size; /* account for TPM2B size marker */ printf("Trying to read %d bytes of public key part from NV\n", keyBlob.pub.size); rc = wolfTPM2_NVReadAuth(&dev, &nv, nvIndex, @@ -244,6 +252,14 @@ int TPM2_NVRAM_Read_Example(void* userCtx, int argc, char *argv[]) printf("Successfully read size marker from NV\n\n"); offset += readSize; + /* priv.size comes from NV and must fit the destination buffer */ + if (readSize != sizeof(keyBlob.priv.size) || + keyBlob.priv.size > sizeof(keyBlob.priv.buffer)) { + printf("Invalid private key size marker from NV\n"); + rc = BUFFER_E; + goto exit; + } + readSize = keyBlob.priv.size; printf("Trying to read %d bytes of private key part from NV\n", readSize); rc = wolfTPM2_NVReadAuth(&dev, &nv, nvIndex, diff --git a/scripts/tpm2_tools_test.sh b/scripts/tpm2_tools_test.sh index d255e8ce..06610d6d 100755 --- a/scripts/tpm2_tools_test.sh +++ b/scripts/tpm2_tools_test.sh @@ -522,10 +522,12 @@ flush_transient run_test "createprimary for attestation" \ tpm2_createprimary -C o -g sha256 -G rsa -c "$TEST_TMPDIR/att_primary.ctx" -# Create child AIK for signing +# Create child AIK for signing. Quote requires a restricted signing key +# (TPM 2.0 Part 3 Sec.18.4), so set the canonical attestation-key attributes. run_test "create AIK (RSA signing key)" \ tpm2_create -C "$TEST_TMPDIR/att_primary.ctx" \ -g sha256 -G rsa:rsassa:null \ + -a "fixedtpm|fixedparent|sensitivedataorigin|userwithauth|restricted|sign" \ -u "$TEST_TMPDIR/aik.pub" -r "$TEST_TMPDIR/aik.priv" run_test "load AIK" \ diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index ecb575e2..1d831047 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -69,6 +69,8 @@ static TPM_RC FwParseAttestParams(TPM2_Packet* cmd, int cmdSize, #ifndef FWTPM_NO_NV static FWTPM_NvIndex* FwFindNvIndex(FWTPM_CTX* ctx, TPMI_RH_NV_INDEX nvIndex); +static TPM_RC FwNvCheckAccess(TPM_HANDLE authHandle, + TPMI_RH_NV_INDEX nvHandle, UINT32 attributes, int isWrite); #endif static FWTPM_Object* FwFindObject(FWTPM_CTX* ctx, TPM_HANDLE handle); #ifdef WOLFTPM_V185 @@ -701,6 +703,8 @@ static TPM_RC FwCmd_Startup(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, } } ctx->globalNvWriteLock = 0; + /* Saved contexts are invalidated by TPM Reset */ + ctx->contextLiveCount = 0; #ifdef HAVE_ECC ctx->ecEphemeralCounter = 0; ctx->ecEphemeralKeySz = 0; @@ -710,6 +714,17 @@ static TPM_RC FwCmd_Startup(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, rc = wc_RNG_GenerateBlock(&ctx->rng, ctx->nullSeed, FWTPM_SEED_SIZE); if (rc != 0) rc = TPM_RC_FAILURE; + + /* TPM Reset: bump persisted resetCount, clear restartCount */ + if (rc == 0) { + ctx->resetCount++; + ctx->restartCount = 0; + rc = FWTPM_NV_SaveFlags(ctx); + } + } + else { + /* TPM Restart/Resume: bump volatile restartCount */ + ctx->restartCount++; } ctx->wasStarted = 1; @@ -828,6 +843,7 @@ static TPM_RC FwCmd_SelfTest(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, } if (rc == 0) { + ctx->selfTestRun = 1; FwRspFinalize(rsp, TPM_ST_NO_SESSIONS, TPM_RC_SUCCESS); } @@ -838,38 +854,63 @@ static TPM_RC FwCmd_SelfTest(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, static TPM_RC FwCmd_IncrementalSelfTest(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, TPM2_Packet* rsp, UINT16 cmdTag) { + TPM_RC rc = TPM_RC_SUCCESS; + UINT32 toTestCount = 0; + UINT32 i; + UINT16 alg; + (void)ctx; - (void)cmd; - (void)cmdSize; (void)cmdTag; + /* Require the TPML_ALG toTest parameter (count + count*TPM_ALG_ID) */ + if (cmdSize < TPM2_HEADER_SIZE + 4) { + rc = TPM_RC_COMMAND_SIZE; + } + if (rc == 0) { + TPM2_Packet_ParseU32(cmd, &toTestCount); + /* Bound by division to avoid the count*size multiply overflowing and + * wrapping the size check (which would allow an unbounded loop). */ + if (toTestCount > (UINT32)((cmdSize - (TPM2_HEADER_SIZE + 4)) / + (int)sizeof(alg))) { + rc = TPM_RC_COMMAND_SIZE; + } + } + if (rc == 0) { + for (i = 0; i < toTestCount; i++) + TPM2_Packet_ParseU16(cmd, &alg); + } + #ifdef DEBUG_WOLFTPM - printf("fwTPM: IncrementalSelfTest\n"); + if (rc == 0) + printf("fwTPM: IncrementalSelfTest(toTest=%u)\n", (unsigned)toTestCount); #endif - /* TODO: IncrementalSelfTest is currently a no-op stub. A real - * implementation would track per-algorithm CAST status and run any - * tests from toTest[] that have not yet passed. Returning an empty - * toDoList signals "nothing left to test" which is acceptable for the - * non-FIPS configuration but must be revisited for FIPS builds. */ - TPM2_Packet_AppendU32(rsp, 0); /* toDoList count = 0 */ - FwRspFinalize(rsp, TPM_ST_NO_SESSIONS, TPM_RC_SUCCESS); - return TPM_RC_SUCCESS; /* always succeeds */ + /* No-op stub: report nothing left to test via an empty toDoList. + * Acceptable for non-FIPS; must be revisited for FIPS builds. */ + if (rc == 0) { + TPM2_Packet_AppendU32(rsp, 0); /* toDoList count = 0 */ + FwRspFinalize(rsp, TPM_ST_NO_SESSIONS, TPM_RC_SUCCESS); + } + return rc; } /* --- TPM2_GetTestResult (CC 0x017C) --- */ static TPM_RC FwCmd_GetTestResult(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, TPM2_Packet* rsp, UINT16 cmdTag) { - (void)ctx; (void)cmd; (void)cmdSize; (void)cmdTag; + UINT32 testResult; + + (void)cmd; (void)cmdSize; (void)cmdTag; + + /* Report NEEDS_TEST until TPM2_SelfTest has completed successfully */ + testResult = ctx->selfTestRun ? TPM_RC_SUCCESS : TPM_RC_NEEDS_TEST; /* outData (TPM2B_MAX_BUFFER) - empty */ TPM2_Packet_AppendU16(rsp, 0); - /* testResult (TPM_RC) - success */ - TPM2_Packet_AppendU32(rsp, TPM_RC_SUCCESS); + TPM2_Packet_AppendU32(rsp, testResult); FwRspFinalize(rsp, TPM_ST_NO_SESSIONS, TPM_RC_SUCCESS); - return TPM_RC_SUCCESS; /* always succeeds */ + return TPM_RC_SUCCESS; } /* --- TPM2_GetRandom (CC 0x017B) --- */ @@ -1816,6 +1857,19 @@ static TPM_RC FwCmd_PCR_Event(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } + /* DRTM PCRs are locality-restricted (Part 1 Sec.11.4.6): + * PCR 17 requires locality 4; PCRs 18-22 require locality 3 or 4. */ + if (rc == 0) { + pcrIndex = pcrHandle - PCR_FIRST; + if (pcrIndex == 17 && ctx->activeLocality != 4) { + rc = TPM_RC_LOCALITY; + } + else if (pcrIndex >= 18 && pcrIndex <= 22 && + ctx->activeLocality != 3 && ctx->activeLocality != 4) { + rc = TPM_RC_LOCALITY; + } + } + if (rc == 0 && cmdTag == TPM_ST_SESSIONS) { rc = FwSkipAuthArea(cmd, cmdSize); } @@ -1842,8 +1896,6 @@ static TPM_RC FwCmd_PCR_Event(FWTPM_CTX* ctx, TPM2_Packet* cmd, } if (rc == 0) { - pcrIndex = pcrHandle - PCR_FIRST; - /* SHA-256 bank */ bankAlgs[0] = TPM_ALG_SHA256; digestSz[0] = TPM2_GetHashDigestSize(TPM_ALG_SHA256); @@ -2181,7 +2233,10 @@ static TPM_RC FwCmd_ClockSet(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* New time must be >= current (can only advance) */ if (rc == 0) { UINT64 currentTime = FWTPM_Clock_GetMs(ctx); - if (newTime < currentTime) { + /* Clock may move forward but at most 2^32-1 ms per call (Part 1 + * Sec.17.5.3); reject a far-future value that would freeze it. */ + if (newTime < currentTime || + newTime > currentTime + 0xFFFFFFFFULL) { rc = TPM_RC_VALUE; } } @@ -2357,6 +2412,30 @@ static void FwFlushAllSessions(FWTPM_CTX* ctx) } } +void FWTPM_ResetCommandClient(FWTPM_CTX* ctx) +{ + int i; + if (ctx == NULL) { + return; + } + FwFlushAllObjects(ctx); + FwFlushAllSessions(ctx); + /* Saved-context replay set belongs to the prior client */ + ctx->contextLiveCount = 0; + for (i = 0; i < FWTPM_MAX_HASH_SEQ; i++) { + if (ctx->hashSeq[i].used) { + FwFreeHashSeq(&ctx->hashSeq[i]); + } + } +#ifdef WOLFTPM_V185 + for (i = 0; i < FWTPM_MAX_SIGN_SEQ; i++) { + if (ctx->signSeq[i].used) { + FwFreeSignSeq(&ctx->signSeq[i]); + } + } +#endif +} + /* --- TPM2_CreatePrimary (CC 0x0131) --- */ static TPM_RC FwCmd_CreatePrimary(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, TPM2_Packet* rsp, UINT16 cmdTag) @@ -2963,9 +3042,22 @@ static TPM_RC FwCmd_ContextSave(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } + /* Only session contexts carry single-use replay tracking (the policy + * replay concern); object contexts are freely reloadable, so tracking + * them would exhaust the small live set under normal multi-load use. */ + if (rc == 0 && isSession && + ctx->contextLiveCount >= (int)(sizeof(ctx->contextLive) / + sizeof(ctx->contextLive[0]))) { + rc = TPM_RC_OBJECT_MEMORY; + } + if (rc == 0) { /* TPMS_CONTEXT: sequence(8) | savedHandle(4) | hierarchy(4) | blob */ ctx->contextSeqCounter++; + if (isSession) { + /* Record so this session context loads at most once, any order */ + ctx->contextLive[ctx->contextLiveCount++] = ctx->contextSeqCounter; + } seqHi = (UINT32)(ctx->contextSeqCounter >> 32); seqLo = (UINT32)(ctx->contextSeqCounter & 0xFFFFFFFFu); TPM2_Packet_AppendU32(rsp, seqHi); @@ -2979,8 +3071,8 @@ static TPM_RC FwCmd_ContextSave(FWTPM_CTX* ctx, TPM2_Packet* cmd, byte wrappedBuf[AES_BLOCK_SIZE + sizeof(FWTPM_Session) + WC_SHA256_DIGEST_SIZE]; int wrappedSz = 0; - rc = FwWrapContextBlob(ctx, (const byte*)sess, - (int)sizeof(FWTPM_Session), + rc = FwWrapContextBlob(ctx, ctx->contextSeqCounter, + (const byte*)sess, (int)sizeof(FWTPM_Session), wrappedBuf, (int)sizeof(wrappedBuf), &wrappedSz); if (rc == 0) { blobSz = 4 + 4 + (UINT16)wrappedSz; @@ -3029,9 +3121,12 @@ static TPM_RC FwCmd_ContextLoad(FWTPM_CTX* ctx, TPM2_Packet* cmd, UINT32 seqHi, seqLo, savedHandle, hierarchy; UINT16 blobSz = 0; UINT32 magic = 0, version = 0; + UINT64 loadSeq = 0; + int liveIdx = -1; + int liveScan; (void)cmdTag; - (void)seqHi; (void)seqLo; (void)hierarchy; + (void)hierarchy; if (cmdSize < TPM2_HEADER_SIZE + 18) { rc = TPM_RC_COMMAND_SIZE; @@ -3045,6 +3140,25 @@ static TPM_RC FwCmd_ContextLoad(FWTPM_CTX* ctx, TPM2_Packet* cmd, TPM2_Packet_ParseU16(cmd, &blobSz); } + /* Replay protection applies to session contexts only: a saved session + * must be a live (not-yet-loaded) sequence and a load consumes it, so a + * satisfied policy session cannot be replayed. Object contexts are + * reloadable any number of times and are not tracked. */ + if (rc == 0 && + ((savedHandle & 0xFF000000) == HMAC_SESSION_FIRST || + (savedHandle & 0xFF000000) == POLICY_SESSION_FIRST)) { + loadSeq = ((UINT64)seqHi << 32) | (UINT64)seqLo; + for (liveScan = 0; liveScan < ctx->contextLiveCount; liveScan++) { + if (ctx->contextLive[liveScan] == loadSeq) { + liveIdx = liveScan; + break; + } + } + if (liveIdx < 0) { + rc = TPM_RC_INTEGRITY; + } + } + /* Validate minimum blob size (magic + version = 8 bytes) */ if (rc == 0 && blobSz < 8) { rc = TPM_RC_SIZE; @@ -3082,7 +3196,7 @@ static TPM_RC FwCmd_ContextLoad(FWTPM_CTX* ctx, TPM2_Packet* cmd, byte wrappedBuf[AES_BLOCK_SIZE + sizeof(FWTPM_Session) + WC_SHA256_DIGEST_SIZE]; TPM2_Packet_ParseBytes(cmd, wrappedBuf, (int)dataLen); - rc = FwUnwrapContextBlob(ctx, wrappedBuf, (int)dataLen, + rc = FwUnwrapContextBlob(ctx, loadSeq, wrappedBuf, (int)dataLen, (byte*)&restored, (int)sizeof(restored), &restoredSz); TPM2_ForceZero(wrappedBuf, sizeof(wrappedBuf)); if (rc != 0) { @@ -3143,6 +3257,14 @@ static TPM_RC FwCmd_ContextLoad(FWTPM_CTX* ctx, TPM2_Packet* cmd, #ifdef DEBUG_WOLFTPM printf("fwTPM: ContextLoad(handle=0x%x)\n", savedHandle); #endif + /* Consume the sequence so this blob cannot be replayed */ + if (liveIdx >= 0 && liveIdx < ctx->contextLiveCount) { + for (liveScan = liveIdx; liveScan < ctx->contextLiveCount - 1; + liveScan++) { + ctx->contextLive[liveScan] = ctx->contextLive[liveScan + 1]; + } + ctx->contextLiveCount--; + } TPM2_Packet_AppendU32(rsp, savedHandle); FwRspFinalize(rsp, TPM_ST_NO_SESSIONS, TPM_RC_SUCCESS); } @@ -3722,6 +3844,19 @@ static TPM_RC FwCmd_EvictControl(FWTPM_CTX* ctx, TPM2_Packet* cmd, rc = TPM_RC_HIERARCHY; } + /* Per TPM 2.0 Part 3 Sec.28, platformAuth owns the PLATFORM_PERSISTENT + * sub-range and owner/endorsement auth the range below it; neither may + * manage a handle in the other's sub-range. */ + if (rc == 0) { + if (authHandle == TPM_RH_PLATFORM) { + if (persistentHandle < PLATFORM_PERSISTENT) + rc = TPM_RC_RANGE; + } + else if (persistentHandle >= PLATFORM_PERSISTENT) { + rc = TPM_RC_RANGE; + } + } + #ifdef DEBUG_WOLFTPM if (rc == 0) { printf("fwTPM: EvictControl(auth=0x%x, obj=0x%x, persist=0x%x)\n", @@ -4519,7 +4654,12 @@ static TPM_RC FwCmd_LoadExternal(FWTPM_CTX* ctx, TPM2_Packet* cmd, } if (rc == 0) { TPM2_Packet_ParseU32(cmd, &hierarchy); - (void)hierarchy; + /* Private key material may only be loaded under TPM_RH_NULL + * (Part 3 Sec.18.4); otherwise the object would claim a real + * hierarchy and yield a spoofed TPM_ST_VERIFIED ticket. */ + if (inPrivSize > 0 && hierarchy != TPM_RH_NULL) { + rc = TPM_RC_HIERARCHY; + } } #ifdef DEBUG_WOLFTPM @@ -5193,6 +5333,13 @@ static TPM_RC FwCmd_Duplicate(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (symAlg == TPM_ALG_NULL) { rc = TPM_RC_SYMMETRIC; } + /* The outer-wrap path to a real parent does not also apply the + * mandatory inner wrap, which would leave the new parent able to + * recover the sensitive. Require the inner-wrap export path + * (newParent == TPM_RH_NULL) for such objects. */ + else if (newParentHandle != TPM_RH_NULL) { + rc = TPM_RC_SYMMETRIC; + } } /* Find new parent (if not TPM_RH_NULL) */ @@ -5636,11 +5783,22 @@ static TPM_RC FwCmd_Rewrap(FWTPM_CTX* ctx, TPM2_Packet* cmd, rc = (TPM_RC_HANDLE | TPM_RC_1); } - /* Look up newParent */ - if (rc == 0 && newParentH != TPM_RH_NULL) { - newParent = FwFindObject(ctx, newParentH); - if (newParent == NULL) + /* Look up newParent. Rewrap must re-encrypt under a storage key; a + * TPM_RH_NULL newParent would emit the sensitive area in the clear. */ + if (rc == 0) { + if (newParentH == TPM_RH_NULL) { rc = (TPM_RC_HANDLE | TPM_RC_2); + } + else { + newParent = FwFindObject(ctx, newParentH); + if (newParent == NULL) { + rc = (TPM_RC_HANDLE | TPM_RC_2); + } + else if (!(newParent->pub.objectAttributes & TPMA_OBJECT_restricted) + || !(newParent->pub.objectAttributes & TPMA_OBJECT_decrypt)) { + rc = TPM_RC_ATTRIBUTES; + } + } } #ifdef DEBUG_WOLFTPM @@ -6484,6 +6642,21 @@ static TPM_RC FwCmd_RSA_Encrypt(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* Use the key's scheme if set, otherwise keep NULL */ if (obj->pub.parameters.rsaDetail.scheme.scheme != TPM_ALG_NULL) { encScheme = obj->pub.parameters.rsaDetail.scheme.scheme; + encHashAlg = obj->pub.parameters.rsaDetail.scheme.details + .anySig.hashAlg; + } + } + /* A key with a fixed scheme must not be used with a different wire + * scheme or hash (Part 3 Sec.17.2) */ + else if (rc == 0 && + obj->pub.parameters.rsaDetail.scheme.scheme != TPM_ALG_NULL) { + if (encScheme != obj->pub.parameters.rsaDetail.scheme.scheme) { + rc = TPM_RC_SCHEME; + } + else if (encHashAlg != TPM_ALG_NULL && + encHashAlg != obj->pub.parameters.rsaDetail.scheme.details + .anySig.hashAlg) { + rc = TPM_RC_SCHEME; } } @@ -6637,6 +6810,21 @@ static TPM_RC FwCmd_RSA_Decrypt(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (rc == 0 && decScheme == TPM_ALG_NULL) { if (obj->pub.parameters.rsaDetail.scheme.scheme != TPM_ALG_NULL) { decScheme = obj->pub.parameters.rsaDetail.scheme.scheme; + decHashAlg = obj->pub.parameters.rsaDetail.scheme.details + .anySig.hashAlg; + } + } + /* A key with a fixed scheme must not be used with a different wire + * scheme or hash (Part 3 Sec.17.2) */ + else if (rc == 0 && + obj->pub.parameters.rsaDetail.scheme.scheme != TPM_ALG_NULL) { + if (decScheme != obj->pub.parameters.rsaDetail.scheme.scheme) { + rc = TPM_RC_SCHEME; + } + else if (decHashAlg != TPM_ALG_NULL && + decHashAlg != obj->pub.parameters.rsaDetail.scheme.details + .anySig.hashAlg) { + rc = TPM_RC_SCHEME; } } @@ -6857,8 +7045,22 @@ static TPM_RC FwCmd_HMAC(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* Parse hashAlg */ TPM2_Packet_ParseU16(cmd, (UINT16*)&hashAlg); - /* If hashAlg is NULL, use the key's nameAlg */ - if (hashAlg == TPM_ALG_NULL) { + /* If the key binds an HMAC scheme, a NULL wire hash uses it and a + * non-NULL wire hash must match it (Part 3 Sec.17.4). A key with an + * unbound (NULL) scheme lets the caller choose, defaulting to the + * key's nameAlg. */ + if (obj->pub.parameters.keyedHashDetail.scheme.scheme + != TPM_ALG_NULL) { + if (hashAlg == TPM_ALG_NULL) { + hashAlg = obj->pub.parameters.keyedHashDetail.scheme + .details.hmac.hashAlg; + } + else if (hashAlg != obj->pub.parameters.keyedHashDetail.scheme + .details.hmac.hashAlg) { + rc = TPM_RC_VALUE; + } + } + else if (hashAlg == TPM_ALG_NULL) { hashAlg = obj->pub.nameAlg; } @@ -7495,8 +7697,17 @@ static TPM_RC FwCmd_EventSequenceComplete(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* Extend the result into the PCR */ if (rc == 0 && pcrHandle <= PCR_LAST) { pcrIndex = pcrHandle - PCR_FIRST; + /* DRTM PCRs are locality-restricted (Part 1 Sec.11.4.6): + * PCR 17 requires locality 4; PCRs 18-22 require locality 3 or 4. */ + if (pcrIndex == 17 && ctx->activeLocality != 4) { + rc = TPM_RC_LOCALITY; + } + else if (pcrIndex >= 18 && pcrIndex <= 22 && + ctx->activeLocality != 3 && ctx->activeLocality != 4) { + rc = TPM_RC_LOCALITY; + } bank = FwGetPcrBankIndex(seqHashAlg); - if (bank >= 0 && digestSz > 0) { + if (rc == 0 && bank >= 0 && digestSz > 0) { wcHash = FwGetWcHashType(seqHashAlg); XMEMCPY(concat, ctx->pcrDigest[pcrIndex][bank], digestSz); XMEMCPY(concat + digestSz, digest, digestSz); @@ -7576,6 +7787,11 @@ static TPM_RC FwCmd_ECDH_KeyGen(FWTPM_CTX* ctx, TPM2_Packet* cmd, rc = TPM_RC_KEY; } } + /* Key agreement requires a decryption key (Part 3 Sec.14.3.3) */ + if (rc == 0) { + if (!(obj->pub.objectAttributes & TPMA_OBJECT_decrypt)) + rc = TPM_RC_ATTRIBUTES; + } if (rc == 0) { curveId = obj->pub.parameters.eccDetail.curveID; @@ -7713,6 +7929,11 @@ static TPM_RC FwCmd_ECDH_ZGen(FWTPM_CTX* ctx, TPM2_Packet* cmd, rc = TPM_RC_KEY; } } + /* Key agreement requires a decryption key (Part 3 Sec.21.3) */ + if (rc == 0) { + if (!(obj->pub.objectAttributes & TPMA_OBJECT_decrypt)) + rc = TPM_RC_ATTRIBUTES; + } /* Skip auth area */ if (rc == 0 && cmdTag == TPM_ST_SESSIONS) { @@ -7894,6 +8115,12 @@ static TPM_RC FwCmd_StartAuthSession(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } + /* nonceCaller must carry at least the 16-octet minimum (Part 1 + * Sec.19.6.8); a tiny/zero nonce weakens the param-encryption IV. */ + if (rc == 0 && nonceCallerSize < 16) { + rc = TPM_RC_SIZE; + } + #ifdef DEBUG_WOLFTPM if (rc == 0) { printf("fwTPM: StartAuthSession(type=%d, hash=0x%x, tpmKey=0x%x, " @@ -7940,6 +8167,13 @@ static TPM_RC FwCmd_StartAuthSession(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (keyObj == NULL) { rc = TPM_RC_HANDLE; } + /* tpmKey must be a restricted decryption key (Part 1 Sec.11.2.2); + * a signing-only key would let an attacker derive the session key. */ + if (rc == 0 && + (!(keyObj->pub.objectAttributes & TPMA_OBJECT_decrypt) || + !(keyObj->pub.objectAttributes & TPMA_OBJECT_restricted))) { + rc = TPM_RC_KEY; + } if (rc == 0) { rc = FwDecryptSeed(ctx, keyObj, encSalt, encSaltSize, @@ -8700,6 +8934,8 @@ static TPM_RC FwCmd_PolicySecret(FWTPM_CTX* ctx, TPM2_Packet* cmd, UINT16 nonceTpmSz, cpHashASz, policyRefSz = 0; INT32 expiration; byte policyRef[64]; + byte nonceTpmBuf[TPM_MAX_DIGEST_SIZE]; + byte cpHashBuf[TPM_MAX_DIGEST_SIZE]; byte entityName[sizeof(TPM2B_NAME)]; int entityNameSz = 0; FWTPM_Session* sess; @@ -8712,19 +8948,25 @@ static TPM_RC FwCmd_PolicySecret(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (rc == 0) { TPM2_Packet_ParseU16(cmd, &nonceTpmSz); - if (cmd->pos + nonceTpmSz > cmdSize) { - rc = TPM_RC_COMMAND_SIZE; + if (nonceTpmSz > (UINT16)sizeof(nonceTpmBuf) || + cmd->pos + nonceTpmSz > cmdSize) { + rc = TPM_RC_SIZE; } } if (rc == 0) { - cmd->pos += nonceTpmSz; + if (nonceTpmSz > 0) { + TPM2_Packet_ParseBytes(cmd, nonceTpmBuf, nonceTpmSz); + } TPM2_Packet_ParseU16(cmd, &cpHashASz); - if (cmd->pos + cpHashASz > cmdSize) { - rc = TPM_RC_COMMAND_SIZE; + if (cpHashASz > (UINT16)sizeof(cpHashBuf) || + cmd->pos + cpHashASz > cmdSize) { + rc = TPM_RC_SIZE; } } if (rc == 0) { - cmd->pos += cpHashASz; + if (cpHashASz > 0) { + TPM2_Packet_ParseBytes(cmd, cpHashBuf, cpHashASz); + } TPM2_Packet_ParseU16(cmd, &policyRefSz); if (policyRefSz > (UINT16)sizeof(policyRef)) { rc = TPM_RC_SIZE; @@ -8753,6 +8995,16 @@ static TPM_RC FwCmd_PolicySecret(FWTPM_CTX* ctx, TPM2_Packet* cmd, rc = TPM_RC_AUTH_TYPE; } + /* A supplied nonceTPM must match the session nonce (Part 3 Sec.23.4), + * preventing replay of a PolicySecret authorization to another session. */ + if (rc == 0 && nonceTpmSz > 0) { + if (nonceTpmSz != sess->nonceTPM.size || + TPM2_ConstantCompare(nonceTpmBuf, sess->nonceTPM.buffer, + nonceTpmSz) != 0) { + rc = TPM_RC_VALUE; + } + } + if (rc == 0) { /* Auth verification for authHandle is handled by the command dispatch * framework (FWTPM_ProcessCommand) via the authorization area, not @@ -8770,6 +9022,23 @@ static TPM_RC FwCmd_PolicySecret(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } + /* Bind the command to cpHashA so policy enforcement can verify it. + * Lock-once: if already bound, a different value must be rejected + * rather than silently replacing the earlier binding. */ + if (rc == 0 && cpHashASz > 0) { + if (sess->cpHashA.size > 0) { + if (sess->cpHashA.size != cpHashASz || + TPM2_ConstantCompare(sess->cpHashA.buffer, cpHashBuf, + cpHashASz) != 0) { + rc = TPM_RC_CPHASH; + } + } + else { + sess->cpHashA.size = cpHashASz; + XMEMCPY(sess->cpHashA.buffer, cpHashBuf, cpHashASz); + } + } + if (rc == 0) { /* Response: timeout(TPM2B size=0) + ticket(TPMT_TK_AUTH) */ paramStart = FwRspParamsBegin(rsp, cmdTag, ¶mSzPos); @@ -9104,6 +9373,16 @@ static TPM_RC FwCmd_PolicyLocality(FWTPM_CTX* ctx, TPM2_Packet* cmd, NULL, 0, 0) != 0) { rc = TPM_RC_FAILURE; } + /* Bind the locality constraint (intersect across calls). A flag + * marks it present so a bitmap of 0 stays an unsatisfiable + * constraint rather than reading as "unset". */ + if (rc == 0) { + if (!sess->hasRequiredLocality) + sess->requiredLocality = locality; + else + sess->requiredLocality &= locality; + sess->hasRequiredLocality = 1; + } } if (rc == 0) { FwRspNoParams(rsp, cmdTag); @@ -9268,6 +9547,23 @@ static TPM_RC FwCmd_PolicySigned(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } + /* Bind the command to cpHashA so policy enforcement can verify it. + * Lock-once: if already bound, a different value must be rejected + * rather than silently replacing the earlier binding. */ + if (rc == 0 && cpHashASz > 0) { + if (sess->cpHashA.size > 0) { + if (sess->cpHashA.size != cpHashASz || + TPM2_ConstantCompare(sess->cpHashA.buffer, cpHashBuf, + cpHashASz) != 0) { + rc = TPM_RC_CPHASH; + } + } + else { + sess->cpHashA.size = cpHashASz; + XMEMCPY(sess->cpHashA.buffer, cpHashBuf, cpHashASz); + } + } + if (rc == 0) { /* Response: timeout(TPM2B size=0) + ticket(TPMT_TK_AUTH) */ paramStart = FwRspParamsBegin(rsp, cmdTag, ¶mSzPos); @@ -9346,8 +9642,6 @@ static TPM_RC FwCmd_PolicyNV(FWTPM_CTX* ctx, TPM2_Packet* cmd, } #endif - (void)authHandle; - /* Find NV index */ if (rc == 0) { nv = FwFindNvIndex(ctx, nvIndex); @@ -9356,6 +9650,12 @@ static TPM_RC FwCmd_PolicyNV(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } + /* Verify caller is authorized to read the NV index */ + if (rc == 0) { + rc = FwNvCheckAccess(authHandle, nvIndex, + nv->nvPublic.attributes, 0); + } + /* Find policy session */ if (rc == 0) { sess = FwFindSession(ctx, sessHandle); @@ -9970,10 +10270,8 @@ static TPM_RC FwCmd_PolicyCounterTimer(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* clockInfo.clock (8 bytes) */ FwStoreU64BE(timeInfo + p, t); p += 8; /* resetCount(4) + restartCount(4) + safe(1) */ - timeInfo[p++] = 0; timeInfo[p++] = 0; - timeInfo[p++] = 0; timeInfo[p++] = 0; - timeInfo[p++] = 0; timeInfo[p++] = 0; - timeInfo[p++] = 0; timeInfo[p++] = 0; + FwStoreU32BE(timeInfo + p, ctx->resetCount); p += 4; + FwStoreU32BE(timeInfo + p, ctx->restartCount); p += 4; timeInfo[p++] = 1; /* safe = YES */ if ((UINT32)offset + operandBSz > (UINT32)p) { @@ -10361,6 +10659,12 @@ static TPM_RC FwCmd_PolicyAuthorizeNV(FWTPM_CTX* ctx, TPM2_Packet* cmd, rc = FW_NV_HANDLE_ERR_2; } + /* Verify caller is authorized to read the NV index */ + if (rc == 0) { + rc = FwNvCheckAccess(authHandle, nvHandle, + nv->nvPublic.attributes, 0); + } + if (rc == 0 && !nv->written) { rc = TPM_RC_NV_UNINITIALIZED; } @@ -10402,8 +10706,6 @@ static TPM_RC FwCmd_PolicyAuthorizeNV(FWTPM_CTX* ctx, TPM2_Packet* cmd, printf("fwTPM: PolicyAuthorizeNV(auth=0x%x, nv=0x%x, sess=0x%x)\n", authHandle, nvHandle, sessHandle); #endif - (void)authHandle; - /* Step 1: Reset policyDigest to zero */ XMEMSET(sess->policyDigest.buffer, 0, dSz); sess->policyDigest.size = (UINT16)dSz; @@ -11310,7 +11612,10 @@ static TPM_RC FwCmd_NV_GlobalWriteLock(FWTPM_CTX* ctx, TPM2_Packet* cmd, printf("fwTPM: NV_GlobalWriteLock(auth=0x%x)\n", authHandle); #endif ctx->globalNvWriteLock = 1; - FwRspNoParams(rsp, cmdTag); + /* Persist so the lock survives a daemon restart (Resume) */ + rc = FWTPM_NV_SaveFlags(ctx); + if (rc == 0) + FwRspNoParams(rsp, cmdTag); } return rc; @@ -11370,6 +11675,11 @@ static TPM_RC FwCmd_DictionaryAttackParameters(FWTPM_CTX* ctx, TPM2_Packet_ParseU32(cmd, &lockoutRecovery); } + /* newMaxTries of 0 would permanently disable lockout enforcement */ + if (rc == 0 && newMaxTries == 0) { + rc = TPM_RC_VALUE; + } + if (rc == 0 && lockHandle != TPM_RH_LOCKOUT) { rc = TPM_RC_HIERARCHY; } @@ -11523,6 +11833,11 @@ static TPM_RC FwEncryptDecryptCore(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (rc == 0 && mode == TPM_ALG_NULL) { mode = obj->pub.parameters.symDetail.sym.mode.sym; } + /* A non-NULL wire mode must match the key's configured mode; otherwise + * e.g. ECB could be applied to a CFB key (Part 3 Sec.12.6.1). */ + else if (rc == 0 && mode != obj->pub.parameters.symDetail.sym.mode.sym) { + rc = TPM_RC_MODE; + } #ifdef DEBUG_WOLFTPM if (rc == 0) { @@ -11687,8 +12002,18 @@ static TPM_RC FwCmd_EncryptDecrypt2(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* Helper: Serialize TPMS_ATTEST common header into pkt. * Returns: number of bytes written (up to serialized position in pkt). */ #ifndef FWTPM_NO_ATTESTATION -static void FwAppendAttestCommonHeader(TPM2_Packet* pkt, UINT16 type, - const TPM2B_NAME* qualifiedSigner, const TPM2B_DATA* extraData) +/* Append a TPMS_CLOCK_INFO from live TPM state (clock + reset/restart). */ +static void FwAppendClockInfo(FWTPM_CTX* ctx, TPM2_Packet* pkt) +{ + TPM2_Packet_AppendU64(pkt, FWTPM_Clock_GetMs(ctx)); + TPM2_Packet_AppendU32(pkt, ctx->resetCount); + TPM2_Packet_AppendU32(pkt, ctx->restartCount); + TPM2_Packet_AppendU8(pkt, YES); /* safe */ +} + +static void FwAppendAttestCommonHeader(FWTPM_CTX* ctx, TPM2_Packet* pkt, + UINT16 type, const TPM2B_NAME* qualifiedSigner, + const TPM2B_DATA* extraData) { /* magic */ TPM2_Packet_AppendU32(pkt, TPM_GENERATED_VALUE); @@ -11702,10 +12027,7 @@ static void FwAppendAttestCommonHeader(TPM2_Packet* pkt, UINT16 type, TPM2_Packet_AppendU16(pkt, extraData->size); TPM2_Packet_AppendBytes(pkt, (byte*)extraData->buffer, extraData->size); /* clockInfo: clock(8) + resetCount(4) + restartCount(4) + safe(1) */ - TPM2_Packet_AppendU64(pkt, 0); /* clock */ - TPM2_Packet_AppendU32(pkt, 0); /* resetCount */ - TPM2_Packet_AppendU32(pkt, 0); /* restartCount */ - TPM2_Packet_AppendU8(pkt, 1); /* safe = YES */ + FwAppendClockInfo(ctx, pkt); /* firmwareVersion */ TPM2_Packet_AppendU64(pkt, ((UINT64)FWTPM_VERSION_MAJOR << 32) | FWTPM_VERSION_MINOR); @@ -11806,6 +12128,15 @@ static TPM_RC FwCmd_Quote(FWTPM_CTX* ctx, TPM2_Packet* cmd, rc = TPM_RC_HANDLE; } } + /* Quote requires a restricted signing key (Part 3 Sec.18.4) */ + if (rc == 0) { + if (!(sigObj->pub.objectAttributes & TPMA_OBJECT_sign)) + rc = TPM_RC_KEY; + } + if (rc == 0) { + if (!(sigObj->pub.objectAttributes & TPMA_OBJECT_restricted)) + rc = TPM_RC_ATTRIBUTES; + } if (rc == 0) { rc = FwParseAttestParams(cmd, cmdSize, cmdTag, @@ -11836,7 +12167,7 @@ static TPM_RC FwCmd_Quote(FWTPM_CTX* ctx, TPM2_Packet* cmd, attestPkt.pos = 0; attestPkt.size = (int)FWTPM_MAX_ATTEST_BUF; - FwAppendAttestCommonHeader(&attestPkt, TPM_ST_ATTEST_QUOTE, + FwAppendAttestCommonHeader(ctx, &attestPkt, TPM_ST_ATTEST_QUOTE, &sigObj->name, &qualifyingData); /* attested.quote: pcrSelect (TPML_PCR_SELECTION) + @@ -11965,7 +12296,7 @@ static TPM_RC FwCmd_Certify(FWTPM_CTX* ctx, TPM2_Packet* cmd, attestPkt.pos = 0; attestPkt.size = (int)FWTPM_MAX_ATTEST_BUF; - FwAppendAttestCommonHeader(&attestPkt, TPM_ST_ATTEST_CERTIFY, + FwAppendAttestCommonHeader(ctx, &attestPkt, TPM_ST_ATTEST_CERTIFY, &sigObj->name, &qualifyingData); /* attested.certify: name + qualifiedName */ @@ -12155,7 +12486,7 @@ static TPM_RC FwCmd_CertifyCreation(FWTPM_CTX* ctx, TPM2_Packet* cmd, attestPkt.pos = 0; attestPkt.size = (int)FWTPM_MAX_ATTEST_BUF; - FwAppendAttestCommonHeader(&attestPkt, TPM_ST_ATTEST_CREATION, + FwAppendAttestCommonHeader(ctx, &attestPkt, TPM_ST_ATTEST_CREATION, &sigObj->name, &qualifyingData); /* attested.creation: objectName (TPM2B_NAME) + @@ -12218,16 +12549,13 @@ static TPM_RC FwCmd_GetTime(FWTPM_CTX* ctx, TPM2_Packet* cmd, attestPkt.pos = 0; attestPkt.size = (int)FWTPM_MAX_PUB_BUF; - FwAppendAttestCommonHeader(&attestPkt, TPM_ST_ATTEST_TIME, + FwAppendAttestCommonHeader(ctx, &attestPkt, TPM_ST_ATTEST_TIME, &sigObj->name, &qualifyingData); /* attested.time: TPMS_TIME_INFO (time + clockInfo) + * firmwareVersion */ - TPM2_Packet_AppendU64(&attestPkt, 0); /* time */ - TPM2_Packet_AppendU64(&attestPkt, 0); /* clockInfo.clock */ - TPM2_Packet_AppendU32(&attestPkt, 0); /* clockInfo.resetCount */ - TPM2_Packet_AppendU32(&attestPkt, 0); /* clockInfo.restartCount */ - TPM2_Packet_AppendU8(&attestPkt, 1); /* clockInfo.safe */ + TPM2_Packet_AppendU64(&attestPkt, FWTPM_Clock_GetMs(ctx)); /* time */ + FwAppendClockInfo(ctx, &attestPkt); TPM2_Packet_AppendU64(&attestPkt, ((UINT64)FWTPM_VERSION_MAJOR << 32) | FWTPM_VERSION_MINOR); /* firmwareVersion */ @@ -12367,7 +12695,7 @@ static TPM_RC FwCmd_NV_Certify(FWTPM_CTX* ctx, TPM2_Packet* cmd, attestPkt.pos = 0; attestPkt.size = (int)FWTPM_MAX_ATTEST_BUF; - FwAppendAttestCommonHeader(&attestPkt, attestType, + FwAppendAttestCommonHeader(ctx, &attestPkt, attestType, &sigObj->name, &qualifyingData); if (digestMode) { @@ -12481,6 +12809,11 @@ static TPM_RC FwCmd_MakeCredential(FWTPM_CTX* ctx, TPM2_Packet* cmd, rc = TPM_RC_HANDLE; } } + /* Credential wrap/unwrap integrity is SHA-256 only; reject other + * nameAlgs rather than emit a mixed-hash (non-interoperable) blob. */ + if (rc == 0 && keyObj->pub.nameAlg != TPM_ALG_SHA256) { + rc = TPM_RC_HASH; + } /* MakeCredential has no auth area */ @@ -12549,7 +12882,7 @@ static TPM_RC FwCmd_MakeCredential(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* Derive symmetric and HMAC keys from seed */ if (rc == 0) { - rc = FwCredentialDeriveKeys(seed, seedSz, + rc = FwCredentialDeriveKeys(keyObj->pub.nameAlg, seed, seedSz, objectName.name, objectName.size, symKey, (int)sizeof(symKey), hmacKey, (int)sizeof(hmacKey)); @@ -12660,6 +12993,10 @@ static TPM_RC FwCmd_ActivateCredential(FWTPM_CTX* ctx, TPM2_Packet* cmd, rc = TPM_RC_HANDLE; } } + /* Credential wrap/unwrap integrity is SHA-256 only (see MakeCredential) */ + if (rc == 0 && keyObj->pub.nameAlg != TPM_ALG_SHA256) { + rc = TPM_RC_HASH; + } /* Skip auth area */ if (rc == 0 && cmdTag == TPM_ST_SESSIONS) { @@ -12745,7 +13082,7 @@ static TPM_RC FwCmd_ActivateCredential(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* Derive symmetric and HMAC keys from seed */ if (rc == 0) { objName = &activateObj->name; - rc = FwCredentialDeriveKeys(seed, seedSzInt, + rc = FwCredentialDeriveKeys(keyObj->pub.nameAlg, seed, seedSzInt, objName->name, objName->size, symKey, (int)sizeof(symKey), hmacKey, (int)sizeof(hmacKey)); @@ -13038,6 +13375,10 @@ static TPM_RC FwCmd_ZGen_2Phase(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (rc == 0 && keyA->pub.type != TPM_ALG_ECC) { rc = TPM_RC_KEY; } + /* Key agreement requires a decryption key (Part 3 Sec.14.7) */ + if (rc == 0 && !(keyA->pub.objectAttributes & TPMA_OBJECT_decrypt)) { + rc = TPM_RC_ATTRIBUTES; + } /* Parse inQsB (TPM2B_ECC_POINT) */ if (rc == 0) { @@ -13205,9 +13546,12 @@ static TPM_RC FwCmd_ZGen_2Phase(FWTPM_CTX* ctx, TPM2_Packet* cmd, TPM2_ForceZero(z1yBuf, sizeof(z1yBuf)); TPM2_ForceZero(z2xBuf, sizeof(z2xBuf)); TPM2_ForceZero(z2yBuf, sizeof(z2yBuf)); - /* Zero ephemeral key — it was consumed and must not be reused */ - TPM2_ForceZero(ctx->ecEphemeralKey, sizeof(ctx->ecEphemeralKey)); - ctx->ecEphemeralKeySz = 0; + /* Only consume the ephemeral on success; an error path must not let an + * unauthenticated caller destroy a victim's pending ephemeral. */ + if (rc == 0) { + TPM2_ForceZero(ctx->ecEphemeralKey, sizeof(ctx->ecEphemeralKey)); + ctx->ecEphemeralKeySz = 0; + } if (privAInit) wc_ecc_free(privKeyA); if (ephInit) wc_ecc_free(privEph); if (peerInit) wc_ecc_free(peerPub); @@ -15237,6 +15581,25 @@ static const FWTPM_CMD_ENTRY* FwFindCmdEntry(TPM_CC cc) return NULL; } +#ifndef FWTPM_NO_DA +/* Return 1 if the handle is an NV index with TPMA_NV_NO_DA set. Such an + * index is exempt from dictionary-attack lockout (Part 1 Sec.23.2). */ +static int FwHandleIsNoDA(FWTPM_CTX* ctx, TPM_HANDLE handle) +{ +#ifndef FWTPM_NO_NV + if ((handle & 0xFF000000) == (NV_INDEX_FIRST & 0xFF000000)) { + FWTPM_NvIndex* nv = FwFindNvIndex(ctx, handle); + if (nv != NULL && (nv->nvPublic.attributes & TPMA_NV_NO_DA)) { + return 1; + } + } +#else + (void)ctx; (void)handle; +#endif + return 0; +} +#endif /* !FWTPM_NO_DA */ + /* ================================================================== */ /* Public API: FWTPM_ProcessCommand */ /* ================================================================== */ @@ -15438,8 +15801,11 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, } #ifndef FWTPM_NO_PARAM_ENC - /* Detect encryption session (first non-PW with - * symmetric alg) */ + /* Detect encryption session (first non-PW with a + * symmetric alg). Unsalted/unbound sessions are + * accepted for client compatibility; over the + * loopback transport their key is only observable + * to a local peer. */ if (encSess == NULL && sess->symmetric.algorithm != TPM_ALG_NULL) { encSess = sess; @@ -15568,6 +15934,30 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, TPM_ST_NO_SESSIONS, TPM_RC_POLICY_FAIL); return TPM_RC_SUCCESS; } + /* Enforce any PolicyLocality constraint bound to the session */ + if (pSess->hasRequiredLocality && + (ctx->activeLocality > 4 || + !((1u << ctx->activeLocality) & pSess->requiredLocality))) { + *rspSize = FwBuildErrorResponse(rspBuf, + TPM_ST_NO_SESSIONS, TPM_RC_LOCALITY); + return TPM_RC_SUCCESS; + } + /* Enforce any PolicyCpHash command binding: the command's + * cpHash must equal the value the policy committed to. */ + if (pSess->cpHashA.size > 0) { + byte ccpHash[TPM_MAX_DIGEST_SIZE]; + int ccpHashSz = 0; + if (FwComputeCpHash(pSess->authHash, cmdCode, + cmdBuf, cmdSize, cmdHandles, cmdHandleCnt, + ctx, cpStart, ccpHash, &ccpHashSz) != 0 || + (int)pSess->cpHashA.size != ccpHashSz || + TPM2_ConstantCompare(pSess->cpHashA.buffer, + ccpHash, (word32)ccpHashSz) != 0) { + *rspSize = FwBuildErrorResponse(rspBuf, + TPM_ST_NO_SESSIONS, TPM_RC_POLICY_FAIL); + return TPM_RC_SUCCESS; + } + } } else if (authPolicy != NULL && authPolicy->size == 0 && cmdAuths[pj].cmdHmacSize == 0) { @@ -15599,7 +15989,8 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, cmdCode != TPM_CC_DictionaryAttackLockReset && cmdCode != TPM_CC_DictionaryAttackParameters && cmdCode != TPM_CC_StartAuthSession && - cmdCode != TPM_CC_FlushContext) { + cmdCode != TPM_CC_FlushContext && + !(cmdHandleCnt > 0 && FwHandleIsNoDA(ctx, cmdHandles[0]))) { *rspSize = FwBuildErrorResponse(rspBuf, TPM_ST_NO_SESSIONS, TPM_RC_LOCKOUT); return TPM_RC_SUCCESS; @@ -15655,11 +16046,14 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, "0x%x (CC=0x%x)\n", entityH, cmdCode); #endif #ifndef FWTPM_NO_DA - ctx->daFailedTries++; - if (ctx->daFailedTries >= ctx->daMaxTries) { - *rspSize = FwBuildErrorResponse(rspBuf, - TPM_ST_NO_SESSIONS, TPM_RC_LOCKOUT); - return TPM_RC_SUCCESS; + /* A failed auth against a NO_DA index must not feed lockout */ + if (!FwHandleIsNoDA(ctx, entityH)) { + ctx->daFailedTries++; + if (ctx->daFailedTries >= ctx->daMaxTries) { + *rspSize = FwBuildErrorResponse(rspBuf, + TPM_ST_NO_SESSIONS, TPM_RC_LOCKOUT); + return TPM_RC_SUCCESS; + } } #endif *rspSize = FwBuildErrorResponse(rspBuf, @@ -15749,11 +16143,14 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, "0x%x (CC=0x%x)\n", entityH, cmdCode); #endif #ifndef FWTPM_NO_DA - ctx->daFailedTries++; - if (ctx->daFailedTries >= ctx->daMaxTries) { - *rspSize = FwBuildErrorResponse(rspBuf, - TPM_ST_NO_SESSIONS, TPM_RC_LOCKOUT); - return TPM_RC_SUCCESS; + /* A failed auth against a NO_DA index must not feed lockout */ + if (!FwHandleIsNoDA(ctx, entityH)) { + ctx->daFailedTries++; + if (ctx->daFailedTries >= ctx->daMaxTries) { + *rspSize = FwBuildErrorResponse(rspBuf, + TPM_ST_NO_SESSIONS, TPM_RC_LOCKOUT); + return TPM_RC_SUCCESS; + } } #endif *rspSize = FwBuildErrorResponse(rspBuf, diff --git a/src/fwtpm/fwtpm_crypto.c b/src/fwtpm/fwtpm_crypto.c index 6a7cea80..3cce0232 100644 --- a/src/fwtpm/fwtpm_crypto.c +++ b/src/fwtpm/fwtpm_crypto.c @@ -2219,10 +2219,22 @@ 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. */ -int FwWrapContextBlob(FWTPM_CTX* ctx, +int FwWrapContextBlob(FWTPM_CTX* ctx, UINT64 seq, const byte* plain, int plainSz, byte* out, int outBufSz, int* 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, seq); + } if (rc == 0) { rc = wc_HmacFinal(hmac, out + AES_BLOCK_SIZE + plainSz); } @@ -2291,7 +2306,7 @@ int FwWrapContextBlob(FWTPM_CTX* ctx, } /* Verify-then-decrypt context blob. Returns 0 on success, sets *outSz. */ -int FwUnwrapContextBlob(FWTPM_CTX* ctx, +int FwUnwrapContextBlob(FWTPM_CTX* ctx, UINT64 seq, const byte* in, int inSz, byte* out, int outBufSz, int* outSz) { @@ -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, seq); + } if (rc == 0) { rc = wc_HmacFinal(hmac, computedHmac); } @@ -3611,6 +3629,26 @@ TPM_RC FwVerifySignatureCore(FWTPM_Object* obj, rsaInit = 1; } + /* A key with a fixed scheme must not verify a signature made + * under a different scheme (e.g. RSASSA vs RSAPSS) or a different + * (e.g. downgraded) hash (Part 3 Sec.20.2). */ + if (rc == 0 && + obj->pub.parameters.rsaDetail.scheme.scheme != TPM_ALG_NULL) { + if (sig->sigAlg != + obj->pub.parameters.rsaDetail.scheme.scheme) { + rc = TPM_RC_SCHEME; + } + else if (sig->signature.rsassa.hash != + obj->pub.parameters.rsaDetail.scheme.details + .anySig.hashAlg) { + rc = TPM_RC_SCHEME; + } + } + if (rc == 0 && digestSz != + TPM2_GetHashDigestSize(sig->signature.rsassa.hash)) { + rc = TPM_RC_SIZE; + } + if (rc == 0) { if (pad == WC_RSA_PSS_PAD) { FWTPM_DECLARE_BUF(decSig, FWTPM_MAX_PUB_BUF); @@ -3790,19 +3828,28 @@ int FwComputeNvName(FWTPM_NvIndex* nv, byte* buf, UINT16* sz) void FwResolveSignScheme(FWTPM_Object* obj, UINT16* sigScheme, UINT16* sigHashAlg) { - if (*sigScheme == TPM_ALG_NULL) { - if (obj->pub.type == TPM_ALG_RSA) { - *sigScheme = obj->pub.parameters.rsaDetail.scheme.scheme; - *sigHashAlg = obj->pub.parameters.rsaDetail.scheme.details - .anySig.hashAlg; - } - else if (obj->pub.type == TPM_ALG_ECC) { - *sigScheme = obj->pub.parameters.eccDetail.scheme.scheme; - *sigHashAlg = obj->pub.parameters.eccDetail.scheme.details - .any.hashAlg; - } + UINT16 keyScheme = TPM_ALG_NULL; + UINT16 keyHashAlg = TPM_ALG_NULL; + + if (obj->pub.type == TPM_ALG_RSA) { + keyScheme = obj->pub.parameters.rsaDetail.scheme.scheme; + keyHashAlg = obj->pub.parameters.rsaDetail.scheme.details + .anySig.hashAlg; } - if (*sigScheme == TPM_ALG_NULL) { + else if (obj->pub.type == TPM_ALG_ECC) { + keyScheme = obj->pub.parameters.eccDetail.scheme.scheme; + keyHashAlg = obj->pub.parameters.eccDetail.scheme.details + .any.hashAlg; + } + + /* A key that mandates a scheme overrides any wire-supplied scheme and + * hash. Honoring a differing wire pair would let an attacker downgrade + * a restricted SHA-256 attestation key to e.g. SHA-1. */ + if (keyScheme != TPM_ALG_NULL) { + *sigScheme = keyScheme; + *sigHashAlg = keyHashAlg; + } + else if (*sigScheme == TPM_ALG_NULL) { *sigScheme = (obj->pub.type == TPM_ALG_RSA) ? TPM_ALG_RSASSA : TPM_ALG_ECDSA; } @@ -3848,6 +3895,12 @@ TPM_RC FwSignAttest(FWTPM_CTX* ctx, FWTPM_Object* obj, int digestSz; enum wc_HashType wcHash; + /* Attestation must be signed by a signing key (Part 3 Sec.18). Reject + * decrypt-only keys so a forged attestation cannot be produced. */ + if (!(obj->pub.objectAttributes & TPMA_OBJECT_sign)) { + return TPM_RC_KEY; + } + /* Resolve scheme/hash from key if NULL */ FwResolveSignScheme(obj, &sigScheme, &sigHashAlg); @@ -3883,6 +3936,7 @@ TPM_RC FwSignAttest(FWTPM_CTX* ctx, FWTPM_Object* obj, /* Derive AES symmetric key ("STORAGE") and HMAC key ("INTEGRITY") from seed. * Per TPM 2.0 Part 1 Section 24. */ TPM_RC FwCredentialDeriveKeys( + TPMI_ALG_HASH nameAlg, const byte* seed, int seedSz, const byte* name, int nameSz, byte* symKey, int symKeySz, @@ -3890,12 +3944,14 @@ TPM_RC FwCredentialDeriveKeys( { int kdfRc; - kdfRc = TPM2_KDFa_ex(TPM_ALG_SHA256, seed, seedSz, + /* Derive under the credentialed key's nameAlg, not a hardcoded SHA-256, + * so a SHA-384 EK is not silently downgraded. */ + kdfRc = TPM2_KDFa_ex(nameAlg, seed, seedSz, "STORAGE", name, nameSz, NULL, 0, symKey, symKeySz); if (kdfRc != symKeySz) { return TPM_RC_FAILURE; } - kdfRc = TPM2_KDFa_ex(TPM_ALG_SHA256, seed, seedSz, + kdfRc = TPM2_KDFa_ex(nameAlg, seed, seedSz, "INTEGRITY", NULL, 0, NULL, 0, hmacKey, hmacKeySz); if (kdfRc != hmacKeySz) { TPM2_ForceZero(symKey, symKeySz); diff --git a/src/fwtpm/fwtpm_io.c b/src/fwtpm/fwtpm_io.c index d26f94d0..edff1999 100644 --- a/src/fwtpm/fwtpm_io.c +++ b/src/fwtpm/fwtpm_io.c @@ -293,14 +293,10 @@ static int HandlePlatformCommand(FWTPM_CTX* ctx, int clientFd) static int HandleMssimSignal(FWTPM_CTX* ctx, int clientFd, UINT32 tssCmd) { UINT32 netVal; + /* State-mutating signals (POWER_OFF/RESET) are rejected before reaching + * here on the command port; only POWER_ON is handled. */ if (tssCmd == FWTPM_TCP_SIGNAL_POWER_ON) ctx->powerOn = 1; - else if (tssCmd == FWTPM_TCP_SIGNAL_POWER_OFF) { - ctx->powerOn = 0; - ctx->wasStarted = 0; - } - else if (tssCmd == FWTPM_TCP_SIGNAL_RESET) - ctx->wasStarted = 0; #ifdef DEBUG_WOLFTPM printf("fwTPM: Cmd-port signal %u (ack)\n", tssCmd); #endif @@ -436,7 +432,15 @@ static int HandleCommandConnection(FWTPM_CTX* ctx, int clientFd) return TPM_RC_SUCCESS; } - /* Handle platform signals on command port */ + /* State-mutating platform signals belong on the platform port, not the + * unauthenticated command port; reject them here. */ + if (tssCmd == FWTPM_TCP_SIGNAL_POWER_OFF || + tssCmd == FWTPM_TCP_SIGNAL_RESET || + tssCmd == FWTPM_TCP_STOP) { + return TPM_RC_FAILURE; + } + + /* Handle remaining (non state-mutating) platform signals on command port */ if (IsMssimSignal(tssCmd)) { return HandleMssimSignal(ctx, clientFd, tssCmd); } @@ -677,6 +681,12 @@ int FWTPM_IO_ServerLoop(FWTPM_CTX* ctx) SOCKET_T newFd = accept(ctx->io.listenFd, NULL, NULL); if (newFd != FWTPM_INVALID_FD) { if (cmdFd != FWTPM_INVALID_FD) { + /* Consume any select-confirmed in-flight command on the + * old connection before dropping it, so a pending + * request is not silently lost. */ + if (FD_ISSET(cmdFd, &readFds)) { + HandleCommandConnection(ctx, cmdFd); + } #ifdef DEBUG_WOLFTPM printf("fwTPM: command connection replaced\n"); #endif @@ -699,6 +709,9 @@ int FWTPM_IO_ServerLoop(FWTPM_CTX* ctx) if (HandleCommandConnection(ctx, cmdFd) != TPM_RC_SUCCESS) { CloseSocket(cmdFd); cmdFd = FWTPM_INVALID_FD; + /* Transient state persists across command connections: the + * mssim transport reconnects per command for one logical TPM, + * so a clean disconnect must not flush handles. */ } } } diff --git a/src/fwtpm/fwtpm_nv.c b/src/fwtpm/fwtpm_nv.c index 2d63bf8d..d3394d77 100644 --- a/src/fwtpm/fwtpm_nv.c +++ b/src/fwtpm/fwtpm_nv.c @@ -42,12 +42,28 @@ #include #include +#include + +#if !defined(NO_FILESYSTEM) && !defined(_WIN32) + #include + #include + #include +#endif + +#define FWTPM_NV_KEY_SIZE 32 +#define FWTPM_NV_MAC_SIZE WC_SHA256_DIGEST_SIZE + #include #include /* TLV header size: tag(2) + length(2) */ #define TLV_HDR_SIZE 4 +/* Dictionary-attack bounds used to sanitize values replayed from the + * integrity-unprotected NV journal. */ +#define FWTPM_DA_DEFAULT_MAX_TRIES 32 +#define FWTPM_DA_MAX_TRIES_LIMIT 0xFFFF + /* ========================================================================= */ /* File-based NV backend */ /* ========================================================================= */ @@ -117,6 +133,17 @@ static int FwNvFileWrite(void* ctx, word32 offset, const byte* buf, } ret = (int)fwrite(buf, 1, size, f); + /* Flush to stable storage so a crash cannot lose committed NV state */ + if (fflush(f) != 0) { + fclose(f); + return TPM_RC_FAILURE; + } +#if !defined(_WIN32) + if (fsync(fileno(f)) != 0) { + fclose(f); + return TPM_RC_FAILURE; + } +#endif fclose(f); if (ret != (int)size) { @@ -148,7 +175,8 @@ static FWTPM_NV_HAL fwNvDefaultHal = { FwNvFileWrite, FwNvFileErase, (void*)FWTPM_NV_FILE, - FWTPM_NV_MAX_SIZE + FWTPM_NV_MAX_SIZE, + NULL /* get_integrity_key: file backend uses a key file */ }; #endif /* !NO_FILESYSTEM */ @@ -332,6 +360,12 @@ static int FwNvUnmarshalPublic(const byte* buf, word32* pos, word32 maxSz, if (pkt.pos <= 0 || (word32)pkt.pos > (maxSz - *pos)) { return TPM_RC_FAILURE; } + /* authPolicy.size must equal the nameAlg digest size (Part 3 Sec.31.3) */ + if (pub2b.publicArea.authPolicy.size > 0 && + (int)pub2b.publicArea.authPolicy.size != + TPM2_GetHashDigestSize(pub2b.publicArea.nameAlg)) { + return TPM_RC_FAILURE; + } XMEMCPY(pub, &pub2b.publicArea, sizeof(TPMT_PUBLIC)); *pos += pkt.pos; @@ -398,12 +432,25 @@ static int FwNvUnmarshalNvPublic(const byte* buf, word32* pos, word32 maxSz, { int rc; rc = FwNvUnmarshalU32(buf, pos, maxSz, &nvPub->nvIndex); + /* nvIndex must be in the NV handle range or a later lookup could be + * confused with another handle class (Part 2 Sec.7.4). */ + if (rc == 0 && + (nvPub->nvIndex < NV_INDEX_FIRST || + nvPub->nvIndex > NV_INDEX_LAST)) { + rc = TPM_RC_FAILURE; + } if (rc == 0) { rc = FwNvUnmarshalU16(buf, pos, maxSz, &nvPub->nameAlg); } if (rc == 0) { rc = FwNvUnmarshalU32(buf, pos, maxSz, &nvPub->attributes); } + /* Reserved TPMA_NV bits [9:8] and [24:20] must be clear (Part 2 + * Sec.13.4); a crafted journal entry could otherwise install an index + * with undefined access-control semantics. */ + if (rc == 0 && (nvPub->attributes & 0x01F00300UL) != 0) { + rc = TPM_RC_FAILURE; + } if (rc == 0) { rc = FwNvUnmarshalDigest(buf, pos, maxSz, &nvPub->authPolicy); } @@ -534,6 +581,12 @@ static int FwNvUnmarshalObject(const byte* buf, word32* pos, word32 maxSz, XMEMSET(obj, 0, sizeof(FWTPM_Object)); rc = FwNvUnmarshalU32(buf, pos, maxSz, &obj->handle); + /* A persistent object handle must stay in range or a later FwFindObject + * lookup could resolve a transient handle to this slot. */ + if (rc == 0 && + (obj->handle < PERSISTENT_FIRST || obj->handle > PERSISTENT_LAST)) { + rc = TPM_RC_VALUE; + } if (rc == 0) { rc = FwNvUnmarshalPublic(buf, pos, maxSz, &obj->pub); } @@ -627,6 +680,154 @@ static int FwNvUnmarshalPrimaryCache(const byte* buf, word32* pos, return rc; } +/* ========================================================================= */ +/* Journal Integrity */ +/* ========================================================================= */ + +#if !defined(NO_FILESYSTEM) +/* Load, or create on first use, the sibling key file for the default file + * backend so journal integrity is enabled without integrator action. */ +static int FwNvLoadOrCreateKeyFile(FWTPM_CTX* ctx, byte* key, word32* keySz) +{ + const char* nvPath = (const char*)ctx->nvHal.ctx; + char keyPath[256]; + size_t nvLen; + int ok = 0; +#if !defined(_WIN32) + int kfd; + int rfd; + int kflags = O_CREAT | O_EXCL | O_WRONLY; + int rflags = O_RDONLY; + struct stat kst; +#else + FILE* f; +#endif + + if (nvPath == NULL) { + return 0; + } + nvLen = XSTRLEN(nvPath); + if (nvLen + 5 > sizeof(keyPath)) { /* ".key" + NUL */ + return 0; + } + XMEMCPY(keyPath, nvPath, nvLen); + XMEMCPY(keyPath + nvLen, ".key", 5); + +#if !defined(_WIN32) + /* O_NOFOLLOW blocks a symlink swap to an attacker-chosen target on both + * the read and create paths. */ + #ifdef O_NOFOLLOW + rflags |= O_NOFOLLOW; + #endif + rfd = open(keyPath, rflags); + if (rfd >= 0) { + /* Only trust a regular file we own with no group/other access */ + if (fstat(rfd, &kst) == 0 && S_ISREG(kst.st_mode) && + kst.st_uid == geteuid() && + (kst.st_mode & (S_IRWXG | S_IRWXO)) == 0) { + ok = (read(rfd, key, FWTPM_NV_KEY_SIZE) == + (ssize_t)FWTPM_NV_KEY_SIZE); + } + close(rfd); + } + else { + if (wc_RNG_GenerateBlock(&ctx->rng, key, FWTPM_NV_KEY_SIZE) != 0) { + return 0; + } + /* O_EXCL prevents a pre-creation race */ + #ifdef O_NOFOLLOW + kflags |= O_NOFOLLOW; + #endif + kfd = open(keyPath, kflags, S_IRUSR | S_IWUSR); + if (kfd >= 0) { + ok = (write(kfd, key, FWTPM_NV_KEY_SIZE) == + (ssize_t)FWTPM_NV_KEY_SIZE); + close(kfd); + } + } +#else + f = fopen(keyPath, "rb"); + if (f != NULL) { + ok = ((int)fread(key, 1, FWTPM_NV_KEY_SIZE, f) == FWTPM_NV_KEY_SIZE); + fclose(f); + } + else { + if (wc_RNG_GenerateBlock(&ctx->rng, key, FWTPM_NV_KEY_SIZE) != 0) { + return 0; + } + f = fopen(keyPath, "wb"); + if (f != NULL) { + ok = ((int)fwrite(key, 1, FWTPM_NV_KEY_SIZE, f) + == FWTPM_NV_KEY_SIZE); + fclose(f); + } + } +#endif + + if (ok) { + *keySz = FWTPM_NV_KEY_SIZE; + return 1; + } + TPM2_ForceZero(key, FWTPM_NV_KEY_SIZE); + return 0; +} +#endif /* !NO_FILESYSTEM */ + +/* Resolve the journal integrity key: a platform-provided device secret if + * the HAL supplies one, else the default file backend's key file. */ +static int FwNvGetIntegrityKey(FWTPM_CTX* ctx, byte* key, word32* keySz) +{ + *keySz = 0; + if (ctx->nvHal.get_integrity_key != NULL) { + if (ctx->nvHal.get_integrity_key(ctx->nvHal.ctx, key, keySz) == 0 && + *keySz > 0) { + return 1; + } + return 0; + } +#if !defined(NO_FILESYSTEM) + if (ctx->nvHal.read == FwNvFileRead) { + return FwNvLoadOrCreateKeyFile(ctx, key, keySz); + } +#endif + return 0; +} + +/* HMAC-SHA256 over the journal body [header .. writePos). */ +static int FwNvComputeJournalMac(FWTPM_CTX* ctx, const byte* key, + word32 keySz, byte* macOut) +{ + FWTPM_NV_HAL* hal = &ctx->nvHal; + FWTPM_DECLARE_VAR(hmac, Hmac); + byte chunk[256]; + word32 off = sizeof(FWTPM_NV_HEADER); + int rc; + + FWTPM_ALLOC_VAR(hmac, Hmac); + + rc = wc_HmacInit(hmac, NULL, INVALID_DEVID); + if (rc == 0) { + rc = wc_HmacSetKey(hmac, WC_SHA256, key, keySz); + } + while (rc == 0 && off < ctx->nvWritePos) { + word32 n = ctx->nvWritePos - off; + if (n > sizeof(chunk)) { + n = sizeof(chunk); + } + rc = hal->read(hal->ctx, off, chunk, n); + if (rc == 0) { + rc = wc_HmacUpdate(hmac, chunk, n); + } + off += n; + } + if (rc == 0) { + rc = wc_HmacFinal(hmac, macOut); + } + wc_HmacFree(hmac); + FWTPM_FREE_VAR(hmac); + return rc; +} + /* ========================================================================= */ /* Journal Operations */ /* ========================================================================= */ @@ -637,12 +838,29 @@ static int FwNvWriteHeader(FWTPM_CTX* ctx) byte hdr[sizeof(FWTPM_NV_HEADER)]; /* 4 x UINT32 = 16 bytes */ FWTPM_NV_HAL* hal = &ctx->nvHal; + byte key[FWTPM_NV_KEY_SIZE]; + byte mac[FWTPM_NV_MAC_SIZE]; + word32 keySz = 0; + int rc; + FwStoreU32LE(hdr + 0, FWTPM_NV_MAGIC); FwStoreU32LE(hdr + 4, FWTPM_NV_VERSION); FwStoreU32LE(hdr + 8, ctx->nvWritePos); FwStoreU32LE(hdr + 12, hal->maxSize); - return hal->write(hal->ctx, 0, hdr, sizeof(hdr)); + rc = hal->write(hal->ctx, 0, hdr, sizeof(hdr)); + + /* Refresh the trailing journal MAC so a tampered journal is detected + * on the next load. The MAC sits at writePos and is rewritten after + * every append. */ + if (rc == TPM_RC_SUCCESS && FwNvGetIntegrityKey(ctx, key, &keySz)) { + rc = FwNvComputeJournalMac(ctx, key, keySz, mac); + if (rc == TPM_RC_SUCCESS) { + rc = hal->write(hal->ctx, ctx->nvWritePos, mac, sizeof(mac)); + } + TPM2_ForceZero(key, sizeof(key)); + } + return rc; } /* Append a single TLV entry to the journal */ @@ -658,8 +876,8 @@ static int FwNvAppendEntry(FWTPM_CTX* ctx, UINT16 tag, return TPM_RC_FAILURE; } - /* Check if journal has space */ - if (ctx->nvWritePos + entrySize > hal->maxSize) { + /* Reserve room for the trailing journal MAC written after each append */ + if (ctx->nvWritePos + entrySize + FWTPM_NV_MAC_SIZE > hal->maxSize) { /* If already compacting, NV is genuinely full */ if (ctx->nvCompacting) { return TPM_RC_NV_SPACE; @@ -670,7 +888,7 @@ static int FwNvAppendEntry(FWTPM_CTX* ctx, UINT16 tag, return rc; } /* After compaction, check again */ - if (ctx->nvWritePos + entrySize > hal->maxSize) { + if (ctx->nvWritePos + entrySize + FWTPM_NV_MAC_SIZE > hal->maxSize) { return TPM_RC_NV_SPACE; } } @@ -823,12 +1041,23 @@ static int FwNvProcessEntry(FWTPM_CTX* ctx, UINT16 tag, UINT8 flags8 = 0; FwNvUnmarshalU8(value, &vPos, vMax, &flags8); ctx->disableClear = (flags8 & 0x01) ? 1 : 0; + ctx->globalNvWriteLock = (flags8 & 0x02) ? 1 : 0; #ifndef FWTPM_NO_DA FwNvUnmarshalU32(value, &vPos, vMax, &ctx->daMaxTries); FwNvUnmarshalU32(value, &vPos, vMax, &ctx->daRecoveryTime); FwNvUnmarshalU32(value, &vPos, vMax, &ctx->daLockoutRecovery); + /* A tampered journal must not disable lockout with 0 or a value + * so large the gate never engages */ + if (ctx->daMaxTries == 0 || + ctx->daMaxTries > FWTPM_DA_MAX_TRIES_LIMIT) { + ctx->daMaxTries = FWTPM_DA_DEFAULT_MAX_TRIES; + } ctx->daFailedTries = 0; /* volatile - reset on load */ #endif + /* resetCount trails the DA fields in newer journals */ + if (vPos + 4 <= vMax) { + FwNvUnmarshalU32(value, &vPos, vMax, &ctx->resetCount); + } break; } @@ -1007,6 +1236,8 @@ int FWTPM_NV_Init(FWTPM_CTX* ctx) byte tlvHdr[TLV_HDR_SIZE]; byte* valueBuf = NULL; word32 valueBufSz = FWTPM_NV_MAX_ENTRY; + byte vKey[FWTPM_NV_KEY_SIZE]; + word32 vKeySz = 0; if (ctx == NULL) { return BAD_FUNC_ARG; @@ -1053,6 +1284,30 @@ int FWTPM_NV_Init(FWTPM_CTX* ctx) rc = TPM_RC_NV_UNINITIALIZED; } } + + /* Authenticate the journal before replaying it. A failed MAC means the + * journal was tampered, so it is discarded and fresh state generated + * rather than loading forged objects, clock, PCR state, or keys. */ + if (rc == TPM_RC_SUCCESS && + hdr.magic == FWTPM_NV_MAGIC && + hdr.version == FWTPM_NV_VERSION) { + ctx->nvWritePos = hdr.writePos; + if (FwNvGetIntegrityKey(ctx, vKey, &vKeySz)) { + byte storedMac[FWTPM_NV_MAC_SIZE]; + byte calcMac[FWTPM_NV_MAC_SIZE]; + if (ctx->nvWritePos + FWTPM_NV_MAC_SIZE > hal->maxSize || + hal->read(hal->ctx, ctx->nvWritePos, storedMac, + FWTPM_NV_MAC_SIZE) != TPM_RC_SUCCESS || + FwNvComputeJournalMac(ctx, vKey, vKeySz, calcMac) + != TPM_RC_SUCCESS || + TPM2_ConstantCompare(storedMac, calcMac, + FWTPM_NV_MAC_SIZE) != 0) { + rc = TPM_RC_NV_UNINITIALIZED; + } + TPM2_ForceZero(vKey, sizeof(vKey)); + } + } + if (rc == TPM_RC_SUCCESS && hdr.magic == FWTPM_NV_MAGIC && hdr.version == FWTPM_NV_VERSION) { @@ -1087,6 +1342,9 @@ int FWTPM_NV_Init(FWTPM_CTX* ctx) newBuf = (byte*)XMALLOC(len, NULL, DYNAMIC_TYPE_TMP_BUFFER); if (newBuf == NULL) { + /* Do not silently report success on an allocation + * failure mid-journal */ + rc = TPM_RC_MEMORY; break; } XFREE(valueBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER); @@ -1111,7 +1369,9 @@ int FWTPM_NV_Init(FWTPM_CTX* ctx) (int)hdr.version, (int)ctx->nvWritePos, (int)((ctx->nvWritePos - sizeof(FWTPM_NV_HEADER)))); #endif - rc = TPM_RC_SUCCESS; + /* rc already reflects the scan: SUCCESS for a clean or + * end-of-journal stop, or a genuine read/alloc error to propagate + * rather than masking as success. */ } else { /* No valid NV image — generate fresh hierarchy seeds */ @@ -1317,12 +1577,14 @@ int FWTPM_NV_Save(FWTPM_CTX* ctx) if (rc == 0) { pos = 0; FwNvMarshalU8(buf, &pos, bufSz, - (UINT8)(ctx->disableClear ? 0x01 : 0x00)); + (UINT8)((ctx->disableClear ? 0x01 : 0x00) | + (ctx->globalNvWriteLock ? 0x02 : 0x00))); #ifndef FWTPM_NO_DA FwNvMarshalU32(buf, &pos, bufSz, ctx->daMaxTries); FwNvMarshalU32(buf, &pos, bufSz, ctx->daRecoveryTime); FwNvMarshalU32(buf, &pos, bufSz, ctx->daLockoutRecovery); #endif + FwNvMarshalU32(buf, &pos, bufSz, ctx->resetCount); rc = FwNvAppendEntry(ctx, FWTPM_NV_TAG_FLAGS, buf, (UINT16)pos); } @@ -1595,8 +1857,15 @@ int FWTPM_NV_SavePcrAuth(FWTPM_CTX* ctx) int FWTPM_NV_SaveFlags(FWTPM_CTX* ctx) { +#ifdef FWTPM_NO_NV + /* No persistence without NV; nothing to save (callers treat as success) */ + if (ctx == NULL) { + return BAD_FUNC_ARG; + } + return TPM_RC_SUCCESS; +#else int rc; - byte buf[1 + 12]; /* flags + DA params */ + byte buf[1 + 12 + 4]; /* flags + DA params + resetCount */ word32 pos = 0; if (ctx == NULL) { @@ -1604,15 +1873,18 @@ int FWTPM_NV_SaveFlags(FWTPM_CTX* ctx) } FwNvMarshalU8(buf, &pos, sizeof(buf), - (UINT8)(ctx->disableClear ? 0x01 : 0x00)); + (UINT8)((ctx->disableClear ? 0x01 : 0x00) | + (ctx->globalNvWriteLock ? 0x02 : 0x00))); #ifndef FWTPM_NO_DA FwNvMarshalU32(buf, &pos, sizeof(buf), ctx->daMaxTries); FwNvMarshalU32(buf, &pos, sizeof(buf), ctx->daRecoveryTime); FwNvMarshalU32(buf, &pos, sizeof(buf), ctx->daLockoutRecovery); #endif + FwNvMarshalU32(buf, &pos, sizeof(buf), ctx->resetCount); rc = FwNvAppendEntry(ctx, FWTPM_NV_TAG_FLAGS, buf, (UINT16)pos); return rc; +#endif /* FWTPM_NO_NV */ } int FWTPM_NV_SaveClock(FWTPM_CTX* ctx) diff --git a/src/fwtpm/fwtpm_tis_shm.c b/src/fwtpm/fwtpm_tis_shm.c index 995a764b..a0bc5d40 100644 --- a/src/fwtpm/fwtpm_tis_shm.c +++ b/src/fwtpm/fwtpm_tis_shm.c @@ -46,6 +46,7 @@ #ifdef HAVE_UNISTD_H #include +#include #endif #include @@ -68,6 +69,7 @@ static int TisShmInit(void* ctx, FWTPM_TIS_REGS** regs) { FWTPM_TIS_SHM_CTX* shm = (FWTPM_TIS_SHM_CTX*)ctx; int fd; + struct stat shmStat; /* Threat model: fwtpm_server is a dev/test tool and is NOT intended to * run setuid or as a privileged daemon. O_NOFOLLOW + mode 0600 is * sufficient for non-privileged execution. We intentionally avoid @@ -90,6 +92,16 @@ static int TisShmInit(void* ctx, FWTPM_TIS_REGS** regs) return -1; } + /* A pre-existing file may carry attacker-controlled ownership or + * permissions; require our own UID and force 0600 (O_CREAT without + * O_EXCL cannot prevent the pre-creation race on its own). */ + if (fstat(fd, &shmStat) != 0 || shmStat.st_uid != getuid() || + fchmod(fd, S_IRUSR | S_IWUSR) != 0) { + fprintf(stderr, "fwTPM TIS: shm ownership/permission check failed\n"); + close(fd); + return -1; + } + if (ftruncate(fd, (off_t)sizeof(FWTPM_TIS_REGS)) < 0) { fprintf(stderr, "fwTPM TIS: ftruncate failed: %d (%s)\n", errno, strerror(errno)); diff --git a/src/spdm/spdm_session.c b/src/spdm/spdm_session.c index 15485722..2467442b 100644 --- a/src/spdm/spdm_session.c +++ b/src/spdm/spdm_session.c @@ -112,6 +112,12 @@ int wolfSPDM_Finish(WOLFSPDM_CTX* ctx) word32 decSz = sizeof(decBuf); int rc; + /* FINISH is only valid after a successful KEY_EXCHANGE; otherwise the + * session keys are unestablished (zero-entropy). */ + if (ctx == NULL || ctx->state < WOLFSPDM_STATE_KEY_EX) { + return WOLFSPDM_E_BAD_STATE; + } + rc = wolfSPDM_BuildFinish(ctx, finishBuf, &finishSz); /* FINISH must be sent encrypted (HANDSHAKE_IN_THE_CLEAR not negotiated) */ diff --git a/src/tpm2.c b/src/tpm2.c index 94b17ab9..09fa2841 100644 --- a/src/tpm2.c +++ b/src/tpm2.c @@ -466,6 +466,7 @@ static TPM_RC TPM2_SendCommandAuth(TPM2_CTX* ctx, TPM2_Packet* packet, { TPM_RC rc = TPM_RC_FAILURE; TPM_ST tag; + TPM_ST respTag; TPM_CC cmdCode; BYTE *cmd; UINT32 cmdSz, respSz; @@ -524,10 +525,18 @@ static TPM_RC TPM2_SendCommandAuth(TPM2_CTX* ctx, TPM2_Packet* packet, /* restart the unmarshalling position */ packet->pos = 0; - TPM2_Packet_ParseU16(packet, &tag); + TPM2_Packet_ParseU16(packet, &respTag); + + /* A command sent with sessions must receive a sessioned response. A + * man-in-the-middle flipping the response tag to TPM_ST_NO_SESSIONS + * would otherwise skip HMAC verification entirely. */ + if (rc == TPM_RC_SUCCESS && tag == TPM_ST_SESSIONS && + respTag != TPM_ST_SESSIONS) { + rc = TPM_RC_HMAC; + } /* Is auth session required for this TPM command? */ - if (rc == TPM_RC_SUCCESS && tag == TPM_ST_SESSIONS) { + if (rc == TPM_RC_SUCCESS && respTag == TPM_ST_SESSIONS) { rc = TPM2_ResponseProcess(ctx, packet, info, cmdCode, respSz); } @@ -970,7 +979,7 @@ TPM_RC TPM2_GetTestResult(GetTestResult_Out* out) if (wireSize > out->outData.size) TPM2_Packet_ParseBytes(&packet, NULL, wireSize - out->outData.size); - TPM2_Packet_ParseU16(&packet, &out->testResult); + TPM2_Packet_ParseU32(&packet, &out->testResult); } TPM2_ReleaseLock(ctx); diff --git a/src/tpm2_asn.c b/src/tpm2_asn.c index bab41b60..8faaa8de 100644 --- a/src/tpm2_asn.c +++ b/src/tpm2_asn.c @@ -381,7 +381,10 @@ int TPM2_ASN_RsaUnpadPkcsv15(uint8_t** pSig, int* sigSz) break; idx++; } - if (idx < *sigSz && sig[idx++] == 0x00) { + /* Require the null separator to be in bounds (avoids a 1-byte + * over-read) and at least 8 padding bytes per PKCS#1 v1.5 Sec.9.2 + * (also rejects the all-0xFF Bleichenbacher variant). */ + if (idx < *sigSz && (idx - 2) >= 8 && sig[idx++] == 0x00) { rc = 0; *pSig = &sig[idx]; *sigSz -= idx; diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index 659c47e0..c3f88b38 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -2705,8 +2705,10 @@ int wolfTPM2_CreatePrimaryKey_ex(WOLFTPM2_DEV* dev, WOLFTPM2_PKEY* pkey, * primary keys and may differ from other TPM stacks that accept * shorter auth values as-is. */ if (nameAlgDigestSz > 0) { + /* Reject oversized auth rather than silently truncating it, + * consistent with wolfTPM2_CreateKey */ if (authSz > nameAlgDigestSz) { - authSz = nameAlgDigestSz; + return BUFFER_E; } XMEMCPY(createPriAuth->buffer, auth, authSz); if (authSz < nameAlgDigestSz) { @@ -2806,6 +2808,9 @@ int wolfTPM2_ChangeAuthKey(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, changeIn.objectHandle = key->handle.hndl; changeIn.parentHandle = parent->hndl; if (auth) { + if (authSz < 0) { + return BAD_FUNC_ARG; + } /* Note: returns error instead of truncating for security (v3.11+) */ if (authSz > (int)sizeof(changeIn.newAuth.buffer)) { return BUFFER_E; @@ -2870,7 +2875,7 @@ int wolfTPM2_CreateKey(WOLFTPM2_DEV* dev, WOLFTPM2_KEYBLOB* keyBlob, Create_Out createOut; if (dev == NULL || keyBlob == NULL || parent == NULL || - publicTemplate == NULL) { + publicTemplate == NULL || authSz < 0) { return BAD_FUNC_ARG; } @@ -2887,8 +2892,9 @@ int wolfTPM2_CreateKey(WOLFTPM2_DEV* dev, WOLFTPM2_KEYBLOB* keyBlob, TPM2B_AUTH* pAuth = &createIn.inSensitive.sensitive.userAuth; int nameAlgDigestSz = TPM2_GetHashDigestSize(publicTemplate->nameAlg); if (nameAlgDigestSz > 0) { + /* Reject oversized auth rather than silently truncating it */ if (authSz > nameAlgDigestSz) { - authSz = nameAlgDigestSz; + return BUFFER_E; } XMEMCPY(pAuth->buffer, auth, authSz); if (authSz < nameAlgDigestSz) { @@ -3022,9 +3028,14 @@ int wolfTPM2_CreateLoadedKey(WOLFTPM2_DEV* dev, WOLFTPM2_KEYBLOB* keyBlob, if (auth) { TPM2B_AUTH* pAuth = &createLoadedIn.inSensitive.sensitive.userAuth; int nameAlgDigestSz = TPM2_GetHashDigestSize(publicTemplate->nameAlg); + if (authSz < 0) { + return BAD_FUNC_ARG; + } if (nameAlgDigestSz > 0) { + /* Reject oversized auth rather than silently truncating it, + * consistent with wolfTPM2_CreateKey */ if (authSz > nameAlgDigestSz) { - authSz = nameAlgDigestSz; + return BUFFER_E; } XMEMCPY(pAuth->buffer, auth, authSz); if (authSz < nameAlgDigestSz) { @@ -4308,8 +4319,8 @@ int wolfTPM2_ImportPublicKeyBuffer(WOLFTPM2_DEV* dev, int keyType, derSz = inSz; } - /* Handle DER Import */ - if (keyType == TPM_ALG_RSA) { + /* Handle DER Import (skip if PEM-to-DER conversion above failed) */ + if (rc == 0 && keyType == TPM_ALG_RSA) { #ifndef NO_RSA rc = wolfTPM2_DecodeRsaDer(derBuf, derSz, &key->pub, NULL, objectAttributes); @@ -4317,7 +4328,7 @@ int wolfTPM2_ImportPublicKeyBuffer(WOLFTPM2_DEV* dev, int keyType, rc = NOT_COMPILED_IN; #endif } - else if (keyType == TPM_ALG_ECC) { + else if (rc == 0 && keyType == TPM_ALG_ECC) { #ifdef HAVE_ECC rc = wolfTPM2_DecodeEccDer(derBuf, derSz, &key->pub, NULL, objectAttributes); @@ -4379,15 +4390,15 @@ int wolfTPM2_ImportPrivateKeyBuffer(WOLFTPM2_DEV* dev, derSz = inSz; } - /* Handle DER Import */ - if (keyType == TPM_ALG_RSA) { + /* Handle DER Import (skip if PEM-to-DER conversion above failed) */ + if (rc == 0 && keyType == TPM_ALG_RSA) { #ifndef NO_RSA rc = wolfTPM2_DecodeRsaDer(derBuf, derSz, pub, &sens, objectAttributes); #else rc = NOT_COMPILED_IN; #endif } - else if (keyType == TPM_ALG_ECC) { + else if (rc == 0 && keyType == TPM_ALG_ECC) { #ifdef HAVE_ECC rc = wolfTPM2_DecodeEccDer(derBuf, derSz, pub, &sens, objectAttributes); #else @@ -9047,6 +9058,9 @@ int wolfTPM2_CreateKeySeal_ex(WOLFTPM2_DEV* dev, WOLFTPM2_KEYBLOB* keyBlob, createIn.parentHandle = parent->hndl; if (auth) { TPM2B_AUTH* pAuth = &createIn.inSensitive.sensitive.userAuth; + if (authSz < 0) { + return BAD_FUNC_ARG; + } if (authSz > (int)sizeof(pAuth->buffer)) { return BUFFER_E; } diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index ae5c5c73..ba1d4ff0 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -792,6 +792,44 @@ static void test_fwtpm_pcr_extend_and_read(void) fwtpm_pass("PCR_Extend + Read(16):", 0); } +/* PCR_Event into DRTM PCR 17 must require locality 4 (Part 1 Sec.11.4.6). */ +static void test_fwtpm_pcr_event_drtm_locality_enforced(void) +{ + FWTPM_CTX ctx; + int pos, rspSize = 0; + byte ev[4]; + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + memset(ev, 0xAB, sizeof(ev)); + + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_PCR_Event); pos += 4; + PutU32BE(gCmd + pos, 17); pos += 4; /* pcrHandle = PCR 17 */ + PutU32BE(gCmd + pos, 9); pos += 4; /* auth area size */ + PutU32BE(gCmd + pos, TPM_RS_PW); pos += 4; + PutU16BE(gCmd + pos, 0); pos += 2; /* nonce */ + gCmd[pos++] = 0; /* attributes */ + PutU16BE(gCmd + pos, 0); pos += 2; /* hmac */ + PutU16BE(gCmd + pos, (UINT16)sizeof(ev)); pos += 2; + memcpy(gCmd + pos, ev, sizeof(ev)); pos += sizeof(ev); + PutU32BE(gCmd + 2, (UINT32)pos); + + /* Locality 0: rejected */ + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_LOCALITY); + + /* Locality 4: allowed */ + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 4); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + FWTPM_Cleanup(&ctx); + printf("Test fwTPM:\tPCR_Event DRTM locality enforced:\tPassed\n"); +} + /* Per TPM 2.0 Part 3 Sec.22.3, PCR_Extend takes Auth Role USER on the * PCR handle. When PCR_SetAuthValue has installed a non-empty * authValue, a subsequent password session with hmacSize=0 must be @@ -6531,12 +6569,11 @@ static UINT32 CreatePrimaryHelper(FWTPM_CTX* ctx, TPM_ALG_ID alg) #if defined(HAVE_ECC) && !defined(FWTPM_NO_ATTESTATION) && \ !defined(FWTPM_NO_NV) -/* Build a non-restricted ECC-P256 sign-capable primary (for tests that - * require TPMA_OBJECT_sign and a key with no scheme bound at create time). - * Only consumed by the attestation tests nested inside the NV-tests - * section, so gated to avoid -Werror=unused-function in - * FWTPM_NO_ATTESTATION or FWTPM_NO_NV builds. */ -static int BuildCreatePrimaryEccSignCmd(byte* buf) +/* Build an ECC-P256 sign-capable primary with caller-supplied attributes + * and no scheme bound at create time. Only consumed by the attestation + * tests nested inside the NV-tests section, so gated to avoid + * -Werror=unused-function in FWTPM_NO_ATTESTATION or FWTPM_NO_NV builds. */ +static int BuildCreatePrimaryEccSignCmd(byte* buf, UINT32 attributes) { int pos = 0; int pubAreaStart, pubAreaLen; @@ -6563,9 +6600,7 @@ static int BuildCreatePrimaryEccSignCmd(byte* buf) PutU16BE(buf + pos, 0); pos += 2; PutU16BE(buf + pos, TPM_ALG_ECC); pos += 2; PutU16BE(buf + pos, TPM_ALG_SHA256); pos += 2; - /* fixedTPM | fixedParent | sensitiveDataOrigin | userWithAuth | noDA | - * sign (non-restricted, sign-only) */ - PutU32BE(buf + pos, 0x00040472); pos += 4; + PutU32BE(buf + pos, attributes); pos += 4; PutU16BE(buf + pos, 0); pos += 2; /* authPolicy */ PutU16BE(buf + pos, TPM_ALG_NULL); pos += 2; /* sym.algorithm = NULL */ PutU16BE(buf + pos, TPM_ALG_NULL); pos += 2; /* scheme = NULL */ @@ -6587,7 +6622,18 @@ static int BuildCreatePrimaryEccSignCmd(byte* buf) static UINT32 CreatePrimaryEccSignHelper(FWTPM_CTX* ctx) { int cmdSz, rspSize = 0; - cmdSz = BuildCreatePrimaryEccSignCmd(gCmd); + /* fixedTPM|fixedParent|sensitiveDataOrigin|userWithAuth|noDA|sign */ + cmdSz = BuildCreatePrimaryEccSignCmd(gCmd, 0x00040472); + FWTPM_ProcessCommand(ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + if (GetRspRC(gRsp) != TPM_RC_SUCCESS) return 0; + return GetU32BE(gRsp + TPM2_HEADER_SIZE); +} + +static UINT32 CreatePrimaryEccSignRestrictedHelper(FWTPM_CTX* ctx) +{ + int cmdSz, rspSize = 0; + /* As above plus restricted: a valid attestation key (AIK) */ + cmdSz = BuildCreatePrimaryEccSignCmd(gCmd, 0x00050472); FWTPM_ProcessCommand(ctx, gCmd, cmdSz, gRsp, &rspSize, 0); if (GetRspRC(gRsp) != TPM_RC_SUCCESS) return 0; return GetU32BE(gRsp + TPM2_HEADER_SIZE); @@ -6980,6 +7026,213 @@ static void test_fwtpm_policyauthorize_null_ticket_rejected(void) fwtpm_pass("PolicyAuthorize zero-ticket (TICKET):", 0); } +#ifndef FWTPM_NO_NV +/* PolicyNV and PolicyAuthorizeNV must verify the caller is authorized to + * read the NV index. An OWNER authHandle against an index without + * TPMA_NV_OWNERREAD must be rejected with TPM_RC_NV_AUTHORIZATION. */ +static void test_fwtpm_policynv_owner_read_denied(void) +{ + FWTPM_CTX ctx; + int pos, cmdSz, rspSize = 0; + UINT32 sessH; + UINT32 nvIdx = 0x01500051; + UINT32 attrs = TPMA_NV_OWNERWRITE | TPMA_NV_AUTHREAD | TPMA_NV_NO_DA; + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + cmdSz = BuildNvDefineCmd(gCmd, nvIdx, 8, attrs); + FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + sessH = StartSessionHelper(&ctx, TPM_SE_TRIAL); + AssertIntNE(sessH, 0); + + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_PolicyNV); pos += 4; + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; /* authHandle */ + PutU32BE(gCmd + pos, nvIdx); pos += 4; + PutU32BE(gCmd + pos, sessH); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU16BE(gCmd + pos, 0); pos += 2; /* operandB */ + PutU16BE(gCmd + pos, 0); pos += 2; /* offset */ + PutU16BE(gCmd + pos, 0); pos += 2; /* operation */ + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_NV_AUTHORIZATION); + + FlushHandle(&ctx, sessH); + FWTPM_Cleanup(&ctx); + printf("Test fwTPM:\tPolicyNV OWNER read denied:\tPassed\n"); +} + +static void test_fwtpm_policyauthorizenv_owner_read_denied(void) +{ + FWTPM_CTX ctx; + int pos, cmdSz, rspSize = 0; + UINT32 sessH; + UINT32 nvIdx = 0x01500052; + UINT32 attrs = TPMA_NV_OWNERWRITE | TPMA_NV_AUTHREAD | TPMA_NV_NO_DA; + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + cmdSz = BuildNvDefineCmd(gCmd, nvIdx, 8, attrs); + FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + sessH = StartSessionHelper(&ctx, TPM_SE_TRIAL); + AssertIntNE(sessH, 0); + + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_PolicyAuthorizeNV); pos += 4; + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; /* authHandle */ + PutU32BE(gCmd + pos, nvIdx); pos += 4; + PutU32BE(gCmd + pos, sessH); 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_NV_AUTHORIZATION); + + FlushHandle(&ctx, sessH); + FWTPM_Cleanup(&ctx); + printf("Test fwTPM:\tPolicyAuthorizeNV OWNER read denied:\tPassed\n"); +} + +/* PolicyLocality must bind a locality constraint that is enforced when the + * policy session authorizes an entity. A session satisfying a locality-4 + * policy must not authorize a command issued at locality 0. */ +static void test_fwtpm_policy_locality_enforced(void) +{ + FWTPM_CTX ctx; + int pos, cmdSz, rspSize = 0; + UINT32 sessH; + UINT16 dSz; + byte digest[64]; + UINT32 nvIdx = 0x01500061; + 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); + + /* PolicyLocality(bit 4 = locality 4 only) */ + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_PolicyLocality); pos += 4; + PutU32BE(gCmd + pos, sessH); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + gCmd[pos++] = 0x10; + PutU32BE(gCmd + 2, (UINT32)pos); + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + /* Read back the resulting policyDigest */ + 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); + + /* Bind that policy to the owner hierarchy */ + 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); + + /* Authorize an owner command via the policy session at locality 0. + * The digest matches but locality 4 is required. */ + cmdSz = BuildNvDefineCmd(gCmd, nvIdx, 8, nvAttrs); + PutU32BE(gCmd + 18, sessH); /* replace TPM_RS_PW with the policy session */ + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_LOCALITY); + + FlushHandle(&ctx, sessH); + FWTPM_Cleanup(&ctx); + printf("Test fwTPM:\tPolicyLocality enforced:\tPassed\n"); +} + +/* PolicyCpHash binds a session to a specific command; a command whose real + * cpHash differs must be rejected even when the policyDigest matches. */ +static void test_fwtpm_policy_cphash_enforced(void) +{ + FWTPM_CTX ctx; + int pos, cmdSz, rspSize = 0; + UINT32 sessH; + UINT16 dSz; + byte digest[64]; + byte cph[32]; + UINT32 nvIdx = 0x01500062; + UINT32 nvAttrs = TPMA_NV_OWNERWRITE | TPMA_NV_OWNERREAD | TPMA_NV_NO_DA; + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + memset(cph, 0xCC, sizeof(cph)); + + sessH = StartSessionHelper(&ctx, TPM_SE_POLICY); + AssertIntNE(sessH, 0); + + /* Bind the session to a cpHash no real command will produce */ + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_PolicyCpHash); + PutU32BE(gCmd + pos, sessH); pos += 4; + PutU16BE(gCmd + pos, (UINT16)sizeof(cph)); pos += 2; + memcpy(gCmd + pos, cph, sizeof(cph)); pos += sizeof(cph); + PutU32BE(gCmd + 2, (UINT32)pos); + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), 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); + + /* The NV_DefineSpace cpHash will not match the bound cpHashA */ + 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_POLICY_FAIL); + + FlushHandle(&ctx, sessH); + FWTPM_Cleanup(&ctx); + printf("Test fwTPM:\tPolicyCpHash enforced:\tPassed\n"); +} +#endif /* !FWTPM_NO_NV */ + #endif /* !FWTPM_NO_POLICY */ /* ================================================================== */ @@ -7092,6 +7345,85 @@ static void test_fwtpm_nv_read_public(void) fwtpm_pass("NV_ReadPublic:", 0); } +/* In-memory NV backend with an integrity key, to prove a tampered journal is + * rejected on load (forged objects/clock/PCR state are not replayed). */ +#define TNV_SIZE (32 * 1024) +static byte gTnvBuf[TNV_SIZE]; +static int TnvRead(void* c, word32 off, byte* buf, word32 sz) +{ + (void)c; + if ((size_t)off + sz > sizeof(gTnvBuf)) return TPM_RC_FAILURE; + memcpy(buf, gTnvBuf + off, sz); + return TPM_RC_SUCCESS; +} +static int TnvWrite(void* c, word32 off, const byte* buf, word32 sz) +{ + (void)c; + if ((size_t)off + sz > sizeof(gTnvBuf)) return TPM_RC_FAILURE; + memcpy(gTnvBuf + off, buf, sz); + return TPM_RC_SUCCESS; +} +static int TnvKey(void* c, byte* key, word32* keySz) +{ + (void)c; + memset(key, 0x5A, 32); + *keySz = 32; + return 0; +} +static void TnvSetHal(FWTPM_CTX* ctx) +{ + memset(ctx, 0, sizeof(*ctx)); + ctx->nvHal.read = TnvRead; + ctx->nvHal.write = TnvWrite; + ctx->nvHal.maxSize = sizeof(gTnvBuf); + ctx->nvHal.get_integrity_key = TnvKey; +} + +static void test_fwtpm_nv_journal_tamper_rejected(void) +{ + FWTPM_CTX ctx; + int rspSize = 0, cmdSz, pos; + UINT32 nvIdx = 0x01500070; + UINT32 attrs = TPMA_NV_OWNERWRITE | TPMA_NV_OWNERREAD | TPMA_NV_NO_DA; + + memset(gTnvBuf, 0, sizeof(gTnvBuf)); + + /* Provision an NV index and persist the MAC'd journal */ + TnvSetHal(&ctx); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + cmdSz = BuildNvDefineCmd(gCmd, nvIdx, 8, attrs); + FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(FWTPM_NV_Save(&ctx), TPM_RC_SUCCESS); + FWTPM_Cleanup(&ctx); + + /* Untampered reload: the index persists */ + TnvSetHal(&ctx); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_NV_ReadPublic); + PutU32BE(gCmd + pos, nvIdx); pos += 4; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + FWTPM_Cleanup(&ctx); + + /* Flip a journal byte past the header, then reload: the journal MAC + * fails so the forged state is discarded and the index is gone. */ + gTnvBuf[sizeof(FWTPM_NV_HEADER) + 1] ^= 0xFF; + TnvSetHal(&ctx); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_NV_ReadPublic); + PutU32BE(gCmd + pos, nvIdx); pos += 4; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntNE(GetRspRC(gRsp), TPM_RC_SUCCESS); + FWTPM_Cleanup(&ctx); + + printf("Test fwTPM:\tNV journal tamper rejected:\tPassed\n"); +} + static void test_fwtpm_nv_counter(void) { FWTPM_CTX ctx; @@ -7160,7 +7492,7 @@ static void test_fwtpm_quote_ecdaa_scheme(void) memset(&ctx, 0, sizeof(ctx)); AssertIntEQ(fwtpm_test_startup(&ctx), 0); - keyH = CreatePrimaryHelper(&ctx, TPM_ALG_ECC); + keyH = CreatePrimaryEccSignRestrictedHelper(&ctx); AssertIntNE(keyH, 0); /* Build TPM2_Quote with ECDAA inScheme. */ @@ -7310,53 +7642,349 @@ static void test_fwtpm_sign_ecdaa_scheme(void) FWTPM_Cleanup(&ctx); printf("Test fwTPM:\tSign(ECDAA scheme):\t\tPassed\n"); } -#endif /* HAVE_ECC */ -/* NV_Certify with size=0 and offset=0 must emit TPMS_NV_DIGEST_CERTIFY_INFO - * inside a TPM_ST_ATTEST_NV_DIGEST (0x801C) attest, not the regular - * TPMS_NV_CERTIFY_INFO inside TPM_ST_ATTEST_NV (0x8014). Per TPM 2.0 - * Part 3 Sec. 31.16.1. */ -static void test_fwtpm_nv_certify_digest_mode(void) +/* ECDH key-agreement commands must reject a key without TPMA_OBJECT_decrypt + * per Part 3 Sec.14.3.3/14.7/21.3. A sign-only AIK would otherwise act as a + * CDH oracle over its private scalar. */ +static void test_fwtpm_ecdh_keygen_signkey_returns_attributes(void) { FWTPM_CTX ctx; - int pos, rspSize, cmdSz; - UINT32 nvIdx = 0x01500004; - UINT32 attrs = TPMA_NV_OWNERWRITE | TPMA_NV_OWNERREAD | TPMA_NV_NO_DA; + int rspSize = 0; UINT32 keyH; - UINT16 attestType; - byte testData[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; memset(&ctx, 0, sizeof(ctx)); AssertIntEQ(fwtpm_test_startup(&ctx), 0); - /* Create primary signing key (use existing helper - the FwTPM signer - * does not enforce sign attribute, so a restricted-decrypt key suffices - * to exercise the attest-tag path under test). */ -#ifdef HAVE_ECC - keyH = CreatePrimaryHelper(&ctx, TPM_ALG_ECC); -#else - keyH = CreatePrimaryHelper(&ctx, TPM_ALG_RSA); -#endif + keyH = CreatePrimaryEccSignHelper(&ctx); AssertIntNE(keyH, 0); - /* Define NV index */ - cmdSz = BuildNvDefineCmd(gCmd, nvIdx, 32, attrs); - rspSize = 0; - FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 14, TPM_CC_ECDH_KeyGen); + PutU32BE(gCmd + 10, keyH); + FWTPM_ProcessCommand(&ctx, gCmd, 14, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_ATTRIBUTES); - /* Write some data so the index is "written" (required for certify) */ - pos = 0; - PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; - PutU32BE(gCmd + pos, 0); pos += 4; - PutU32BE(gCmd + pos, TPM_CC_NV_Write); 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, (UINT16)sizeof(testData)); pos += 2; - memcpy(gCmd + pos, testData, sizeof(testData)); pos += sizeof(testData); - PutU16BE(gCmd + pos, 0); pos += 2; - PutU32BE(gCmd + 2, (UINT32)pos); + FlushHandle(&ctx, keyH); + FWTPM_Cleanup(&ctx); + printf("Test fwTPM:\tECDH_KeyGen(sign key) rejected:\tPassed\n"); +} + +static void test_fwtpm_ecdh_zgen_signkey_returns_attributes(void) +{ + FWTPM_CTX ctx; + int pos, rspSize = 0; + UINT32 keyH; + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + keyH = CreatePrimaryEccSignHelper(&ctx); + AssertIntNE(keyH, 0); + + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_ECDH_ZGen); pos += 4; + PutU32BE(gCmd + pos, keyH); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU16BE(gCmd + pos, 4); pos += 2; /* inPoint outer size */ + PutU16BE(gCmd + pos, 0); pos += 2; /* x.size */ + PutU16BE(gCmd + pos, 0); pos += 2; /* y.size */ + PutU32BE(gCmd + 2, (UINT32)pos); + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_ATTRIBUTES); + + FlushHandle(&ctx, keyH); + FWTPM_Cleanup(&ctx); + printf("Test fwTPM:\tECDH_ZGen(sign key) rejected:\tPassed\n"); +} + +static void test_fwtpm_zgen_2phase_signkey_returns_attributes(void) +{ + FWTPM_CTX ctx; + int pos, rspSize = 0; + UINT32 keyH; + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + keyH = CreatePrimaryEccSignHelper(&ctx); + AssertIntNE(keyH, 0); + + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_ZGen_2Phase); pos += 4; + PutU32BE(gCmd + pos, keyH); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU16BE(gCmd + pos, 4); pos += 2; PutU16BE(gCmd + pos, 0); pos += 2; + PutU16BE(gCmd + pos, 0); pos += 2; /* inQsB */ + PutU16BE(gCmd + pos, 4); pos += 2; PutU16BE(gCmd + pos, 0); pos += 2; + PutU16BE(gCmd + pos, 0); pos += 2; /* inQeB */ + PutU16BE(gCmd + pos, TPM_ALG_ECDH); pos += 2; /* inScheme */ + PutU16BE(gCmd + pos, 0); pos += 2; /* counter */ + PutU32BE(gCmd + 2, (UINT32)pos); + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_ATTRIBUTES); + + FlushHandle(&ctx, keyH); + FWTPM_Cleanup(&ctx); + printf("Test fwTPM:\tZGen_2Phase(sign key) rejected:\tPassed\n"); +} + +/* Quote requires a restricted signing key per Part 3 Sec.18.4. Build the + * command once and run it against keys that violate each requirement. */ +static int BuildQuoteCmd(byte* buf, UINT32 signHandle) +{ + int pos = 0; + PutU16BE(buf + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(buf + pos, 0); pos += 4; + PutU32BE(buf + pos, TPM_CC_Quote); pos += 4; + PutU32BE(buf + pos, signHandle); pos += 4; + pos = AppendPwAuth(buf, pos, NULL, 0); + PutU16BE(buf + pos, 0); pos += 2; /* qualifyingData */ + PutU16BE(buf + pos, TPM_ALG_NULL); pos += 2; /* inScheme */ + PutU32BE(buf + pos, 1); pos += 4; /* PCRselect count */ + PutU16BE(buf + pos, TPM_ALG_SHA256); pos += 2; + buf[pos++] = 3; buf[pos++] = 0; buf[pos++] = 0; buf[pos++] = 0; + PutU32BE(buf + 2, (UINT32)pos); + return pos; +} + +static void test_fwtpm_quote_decrypt_key_returns_key(void) +{ + FWTPM_CTX ctx; + int cmdSz, rspSize = 0; + UINT32 keyH; + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + /* Restricted decrypt (storage) key has no TPMA_OBJECT_sign */ + keyH = CreatePrimaryHelper(&ctx, TPM_ALG_ECC); + AssertIntNE(keyH, 0); + + cmdSz = BuildQuoteCmd(gCmd, keyH); + FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_KEY); + + FlushHandle(&ctx, keyH); + FWTPM_Cleanup(&ctx); + printf("Test fwTPM:\tQuote(decrypt key) rejected:\tPassed\n"); +} + +static void test_fwtpm_quote_unrestricted_sign_returns_attributes(void) +{ + FWTPM_CTX ctx; + int cmdSz, rspSize = 0; + UINT32 keyH; + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + /* Non-restricted signing key must not be usable for attestation */ + keyH = CreatePrimaryEccSignHelper(&ctx); + AssertIntNE(keyH, 0); + + cmdSz = BuildQuoteCmd(gCmd, keyH); + FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_ATTRIBUTES); + + FlushHandle(&ctx, keyH); + FWTPM_Cleanup(&ctx); + printf("Test fwTPM:\tQuote(unrestricted sign) rejected:\tPassed\n"); +} + +/* A key that declares a signing scheme must not be downgraded by a wire + * inScheme: signing with ECDSA-SHA1 against an ECDSA-SHA256 key must emit a + * SHA-256 signature, not SHA-1. */ +static void test_fwtpm_sign_scheme_downgrade_rejected(void) +{ + FWTPM_CTX ctx; + int pos, pubStart, sensStart, rspSize = 0; + UINT32 keyH; + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + /* CreatePrimary: ECC sign key declaring scheme ECDSA-SHA256 */ + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_CreatePrimary); pos += 4; + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; + PutU32BE(gCmd + pos, 9); pos += 4; + PutU32BE(gCmd + pos, TPM_RS_PW); pos += 4; + PutU16BE(gCmd + pos, 0); pos += 2; + gCmd[pos++] = 0; + PutU16BE(gCmd + pos, 0); pos += 2; + sensStart = pos; + PutU16BE(gCmd + pos, 0); pos += 2; + PutU16BE(gCmd + pos, 0); pos += 2; + PutU16BE(gCmd + pos, 0); pos += 2; + PutU16BE(gCmd + sensStart, (UINT16)(pos - sensStart - 2)); + pubStart = pos; + PutU16BE(gCmd + pos, 0); pos += 2; + PutU16BE(gCmd + pos, TPM_ALG_ECC); pos += 2; + PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; + PutU32BE(gCmd + pos, 0x00040472); pos += 4; /* sign, non-restricted */ + PutU16BE(gCmd + pos, 0); pos += 2; /* authPolicy */ + PutU16BE(gCmd + pos, TPM_ALG_NULL); pos += 2; /* sym */ + PutU16BE(gCmd + pos, TPM_ALG_ECDSA); pos += 2; /* declared scheme */ + PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; /* scheme hashAlg */ + PutU16BE(gCmd + pos, TPM_ECC_NIST_P256); pos += 2; + PutU16BE(gCmd + pos, TPM_ALG_NULL); pos += 2; /* kdf */ + PutU16BE(gCmd + pos, 0); pos += 2; + PutU16BE(gCmd + pos, 0); pos += 2; + PutU16BE(gCmd + pubStart, (UINT16)(pos - pubStart - 2)); + PutU16BE(gCmd + pos, 0); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + 2, (UINT32)pos); + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + keyH = GetU32BE(gRsp + TPM2_HEADER_SIZE); + AssertIntNE(keyH, 0); + + /* Sign with wire inScheme ECDSA-SHA1 */ + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_Sign); pos += 4; + PutU32BE(gCmd + pos, keyH); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU16BE(gCmd + pos, 32); pos += 2; + memset(gCmd + pos, 0xAB, 32); pos += 32; + PutU16BE(gCmd + pos, TPM_ALG_ECDSA); pos += 2; + PutU16BE(gCmd + pos, TPM_ALG_SHA1); pos += 2; + PutU16BE(gCmd + pos, TPM_ST_HASHCHECK); pos += 2; + PutU32BE(gCmd + pos, TPM_RH_NULL); pos += 4; + PutU16BE(gCmd + pos, 0); pos += 2; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + /* TPMT_SIGNATURE: sigAlg(2) then hashAlg(2) after paramSize */ + AssertIntEQ(GetU16BE(gRsp + TPM2_HEADER_SIZE + 4 + 2), TPM_ALG_SHA256); + + FlushHandle(&ctx, keyH); + FWTPM_Cleanup(&ctx); + printf("Test fwTPM:\tSign scheme downgrade rejected:\tPassed\n"); +} + +/* TPMS_ATTEST clockInfo must carry live TPM state, not hardcoded zeros. + * After Startup(CLEAR) the resetCount is non-zero, so a Quote attest must + * reflect that. */ +static void test_fwtpm_quote_clockinfo_resetcount_nonzero(void) +{ + FWTPM_CTX ctx; + int cmdSz, rspSize = 0, clockOff; + UINT32 keyH; + UINT16 nameSz, extraSz; + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + keyH = CreatePrimaryEccSignRestrictedHelper(&ctx); + AssertIntNE(keyH, 0); + + cmdSz = BuildQuoteCmd(gCmd, keyH); + FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + /* header(10)+paramSize(4)+attestSz(2): magic,type,qualifiedSigner@22 */ + nameSz = GetU16BE(gRsp + 22); + extraSz = GetU16BE(gRsp + 24 + nameSz); + clockOff = 24 + nameSz + 2 + extraSz; /* clock(8) then resetCount(4) */ + AssertIntNE(GetU32BE(gRsp + clockOff + 8), 0); + + FlushHandle(&ctx, keyH); + FWTPM_Cleanup(&ctx); + printf("Test fwTPM:\tQuote clockInfo resetCount nonzero:\tPassed\n"); +} + +/* Attestation commands must reject a decrypt-only signing key. Certify + * exercises the shared FwSignAttest gate that also guards GetTime and + * NV_Certify. */ +static void test_fwtpm_certify_decrypt_key_returns_key(void) +{ + FWTPM_CTX ctx; + int pos, rspSize = 0; + UINT32 keyH; + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + keyH = CreatePrimaryHelper(&ctx, TPM_ALG_ECC); /* restricted decrypt */ + AssertIntNE(keyH, 0); + + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_Certify); pos += 4; + PutU32BE(gCmd + pos, keyH); pos += 4; /* objectHandle */ + PutU32BE(gCmd + pos, keyH); pos += 4; /* signHandle */ + PutU32BE(gCmd + pos, 18); pos += 4; /* two PW auth sessions */ + PutU32BE(gCmd + pos, TPM_RS_PW); pos += 4; + PutU16BE(gCmd + pos, 0); pos += 2; gCmd[pos++] = 0; PutU16BE(gCmd + pos, 0); + pos += 2; + PutU32BE(gCmd + pos, TPM_RS_PW); pos += 4; + PutU16BE(gCmd + pos, 0); pos += 2; gCmd[pos++] = 0; PutU16BE(gCmd + pos, 0); + pos += 2; + PutU16BE(gCmd + pos, 0); pos += 2; /* qualifyingData */ + PutU16BE(gCmd + pos, TPM_ALG_NULL); pos += 2; /* inScheme */ + PutU32BE(gCmd + 2, (UINT32)pos); + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_KEY); + + FlushHandle(&ctx, keyH); + FWTPM_Cleanup(&ctx); + printf("Test fwTPM:\tCertify(decrypt key) rejected:\tPassed\n"); +} +#endif /* HAVE_ECC */ + +/* NV_Certify with size=0 and offset=0 must emit TPMS_NV_DIGEST_CERTIFY_INFO + * inside a TPM_ST_ATTEST_NV_DIGEST (0x801C) attest, not the regular + * TPMS_NV_CERTIFY_INFO inside TPM_ST_ATTEST_NV (0x8014). Per TPM 2.0 + * Part 3 Sec. 31.16.1. */ +static void test_fwtpm_nv_certify_digest_mode(void) +{ + FWTPM_CTX ctx; + int pos, rspSize, cmdSz; + UINT32 nvIdx = 0x01500004; + UINT32 attrs = TPMA_NV_OWNERWRITE | TPMA_NV_OWNERREAD | TPMA_NV_NO_DA; + UINT32 keyH; + UINT16 attestType; + byte testData[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + /* Create primary signing key (use existing helper - the FwTPM signer + * does not enforce sign attribute, so a restricted-decrypt key suffices + * to exercise the attest-tag path under test). */ +#ifdef HAVE_ECC + keyH = CreatePrimaryEccSignHelper(&ctx); /* attestation needs a sign key */ +#else + keyH = CreatePrimaryHelper(&ctx, TPM_ALG_RSA); +#endif + AssertIntNE(keyH, 0); + + /* Define NV index */ + cmdSz = BuildNvDefineCmd(gCmd, nvIdx, 32, attrs); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + /* Write some data so the index is "written" (required for certify) */ + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_NV_Write); 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, (UINT16)sizeof(testData)); pos += 2; + memcpy(gCmd + pos, testData, sizeof(testData)); pos += sizeof(testData); + PutU16BE(gCmd + pos, 0); pos += 2; + PutU32BE(gCmd + 2, (UINT32)pos); rspSize = 0; FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); @@ -7475,6 +8103,66 @@ static void test_fwtpm_incremental_selftest(void) fwtpm_pass("IncrementalSelfTest/GetResult:", 0); } +static void test_fwtpm_incremental_selftest_truncated_returns_cmd_size(void) +{ + FWTPM_CTX ctx; + int rspSize = 0; + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + /* Header only: the required TPML_ALG toTest parameter is missing */ + BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 10, TPM_CC_IncrementalSelfTest); + FWTPM_ProcessCommand(&ctx, gCmd, 10, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_COMMAND_SIZE); + + /* Overflow case: a huge count must be rejected, not wrap the size check */ + BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 14, TPM_CC_IncrementalSelfTest); + PutU32BE(gCmd + 10, 0xFFFFFFFFu); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, 14, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_COMMAND_SIZE); + + FWTPM_Cleanup(&ctx); + printf("Test fwTPM:\tIncrementalSelfTest truncated (CMD_SIZE):\tPassed\n"); +} + +static void test_fwtpm_gettestresult_needs_test_then_success(void) +{ + FWTPM_CTX ctx; + int cmdSz, rspSize = 0; + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(FWTPM_Init(&ctx), 0); + + /* Startup only -- do not run SelfTest yet */ + cmdSz = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 12, TPM_CC_Startup); + PutU16BE(gCmd + cmdSz, TPM_SU_CLEAR); cmdSz += 2; + PutU32BE(gCmd + 2, (UINT32)cmdSz); + FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 10, TPM_CC_GetTestResult); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, 10, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE + 2), TPM_RC_NEEDS_TEST); + + /* After SelfTest completes, status becomes SUCCESS */ + cmdSz = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 11, TPM_CC_SelfTest); + gCmd[cmdSz++] = 1; + PutU32BE(gCmd + 2, (UINT32)cmdSz); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 10, TPM_CC_GetTestResult); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, 10, gRsp, &rspSize, 0); + AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE + 2), TPM_RC_SUCCESS); + + FWTPM_Cleanup(&ctx); + printf("Test fwTPM:\tGetTestResult NEEDS_TEST then SUCCESS:\tPassed\n"); +} + static void test_fwtpm_pcr_reset(void) { FWTPM_CTX ctx; @@ -7721,6 +8409,31 @@ static void test_fwtpm_da_parameters_and_reset(void) FWTPM_Cleanup(&ctx); fwtpm_pass("DA Parameters/LockReset:", 0); } + +/* newMaxTries=0 must be rejected: it would disable lockout permanently. */ +static void test_fwtpm_da_parameters_zero_maxtries_rejected(void) +{ + FWTPM_CTX ctx; + int pos, rspSize = 0; + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_DictionaryAttackParameters); pos += 4; + PutU32BE(gCmd + pos, TPM_RH_LOCKOUT); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU32BE(gCmd + pos, 0); pos += 4; /* newMaxTries = 0 */ + PutU32BE(gCmd + pos, 60); pos += 4; + PutU32BE(gCmd + pos, 300); pos += 4; + PutU32BE(gCmd + 2, (UINT32)pos); + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_VALUE); + + FWTPM_Cleanup(&ctx); + printf("Test fwTPM:\tDA Parameters maxTries=0 rejected:\tPassed\n"); +} #endif /* !FWTPM_NO_DA */ static void test_fwtpm_read_public(void) @@ -7817,6 +8530,38 @@ static void test_fwtpm_loadexternal_symcipher_bad_keysize_rejected(void) fwtpm_pass("LoadExternal SYMCIPHER bad keySz (SIZE):", 0); } +/* Rewrap must re-encrypt under a storage parent. A TPM_RH_NULL newParent + * would serialize the unwrapped TPMT_SENSITIVE in the clear, so it must be + * rejected per Part 3 Sec.23.4.2. */ +static void test_fwtpm_rewrap_null_newparent_rejected(void) +{ + FWTPM_CTX ctx; + int pos, rspSize = 0; + byte secret[8]; + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + memset(secret, 0x5A, sizeof(secret)); + + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_Rewrap); pos += 4; + PutU32BE(gCmd + pos, TPM_RH_NULL); pos += 4; /* oldParent */ + PutU32BE(gCmd + pos, TPM_RH_NULL); pos += 4; /* newParent */ + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU16BE(gCmd + pos, (UINT16)sizeof(secret)); pos += 2; /* inDuplicate */ + memcpy(gCmd + pos, secret, sizeof(secret)); pos += sizeof(secret); + PutU16BE(gCmd + pos, 0); pos += 2; /* name */ + PutU16BE(gCmd + pos, 0); pos += 2; /* inSymSeed */ + PutU32BE(gCmd + 2, (UINT32)pos); + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), (TPM_RC_HANDLE | TPM_RC_2)); + + FWTPM_Cleanup(&ctx); + printf("Test fwTPM:\tRewrap(NULL newParent) rejected:\tPassed\n"); +} + /* ================================================================== */ /* Group D: Hash/HMAC Sequences */ /* ================================================================== */ @@ -7922,6 +8667,117 @@ 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, sessH; + byte savedObj[MAX_CONTEXT_SIZE]; + byte savedSess[MAX_CONTEXT_SIZE]; + + 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); + + /* Object contexts are freely reloadable (real TPM behavior, relied on by + * tpm2-tools): save once, load the same blob twice. */ + 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; + AssertIntGT(ctxSz, 0); + memcpy(savedObj, gRsp + TPM2_HEADER_SIZE, ctxSz); + + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_ContextLoad); + memcpy(gCmd + pos, savedObj, ctxSz); pos += ctxSz; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_ContextLoad); + memcpy(gCmd + pos, savedObj, ctxSz); pos += ctxSz; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + /* Session contexts are single-use: a satisfied policy/HMAC session must + * not be replayable. Save a session, load once, then reject the replay. */ + sessH = StartSessionHelper(&ctx, TPM_SE_HMAC); + AssertIntNE(sessH, 0); + BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 14, TPM_CC_ContextSave); + PutU32BE(gCmd + 10, sessH); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, 14, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + ctxSz = rspSize - TPM2_HEADER_SIZE; + AssertIntGT(ctxSz, 0); + memcpy(savedSess, gRsp + TPM2_HEADER_SIZE, ctxSz); + + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_ContextLoad); + memcpy(gCmd + pos, savedSess, ctxSz); pos += ctxSz; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_ContextLoad); + memcpy(gCmd + pos, savedSess, 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 object reload + session replay:\tPassed\n"); +} + +/* When the command client changes, transient objects must be flushed so a + * replacement client cannot enumerate and use the previous client's handles. */ +static void test_fwtpm_reset_command_client_flushes_transient(void) +{ + FWTPM_CTX ctx; + int rspSize = 0; + UINT32 keyH; + + 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); + + /* Object usable before the client change */ + 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); + + FWTPM_ResetCommandClient(&ctx); + + /* Handle no longer resolves after the reset */ + BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 14, TPM_CC_ContextSave); + PutU32BE(gCmd + 10, keyH); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, 14, gRsp, &rspSize, 0); + AssertIntNE(GetRspRC(gRsp), TPM_RC_SUCCESS); + + FWTPM_Cleanup(&ctx); + printf("Test fwTPM:\tResetCommandClient flushes transient:\tPassed\n"); +} + static void test_fwtpm_evict_control(void) { FWTPM_CTX ctx; @@ -7973,6 +8829,40 @@ static void test_fwtpm_evict_control(void) fwtpm_pass("EvictControl (persist/remove):", 0); } +/* Owner auth must not persist into the platform sub-range (Part 3 Sec.28). */ +static void test_fwtpm_evict_control_cross_hierarchy_rejected(void) +{ + FWTPM_CTX ctx; + int pos, rspSize = 0; + UINT32 keyH; + + 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); + + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_EvictControl); pos += 4; + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; /* owner auth */ + PutU32BE(gCmd + pos, keyH); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU32BE(gCmd + pos, 0x81800001); pos += 4; /* platform sub-range */ + PutU32BE(gCmd + 2, (UINT32)pos); + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_RANGE); + + FlushHandle(&ctx, keyH); + FWTPM_Cleanup(&ctx); + printf("Test fwTPM:\tEvictControl cross-hierarchy rejected:\tPassed\n"); +} + /* Per TPM 2.0 Part 2 Sec.7.4, persistent handles must fall in * 0x81000000..0x81FFFFFF. A persistentHandle outside this range must * be rejected so an attacker cannot plant a persistent record at a @@ -8314,6 +9204,7 @@ int fwtpm_unit_tests(int argc, char *argv[]) /* PCR operations */ test_fwtpm_pcr_read(); test_fwtpm_pcr_extend_and_read(); + test_fwtpm_pcr_event_drtm_locality_enforced(); test_fwtpm_pcr_extend_empty_pw_rejected_after_setauth(); test_fwtpm_pcr_reset(); test_fwtpm_pcr_reset_locality_enforced(); @@ -8416,10 +9307,14 @@ int fwtpm_unit_tests(int argc, char *argv[]) #endif test_fwtpm_read_public(); test_fwtpm_loadexternal_symcipher_bad_keysize_rejected(); + test_fwtpm_rewrap_null_newparent_rejected(); test_fwtpm_evict_control(); + test_fwtpm_evict_control_cross_hierarchy_rejected(); 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(); + test_fwtpm_reset_command_client_flushes_transient(); /* Crypto */ test_fwtpm_hash(); @@ -8438,6 +9333,7 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_set_primary_policy_bad_size_rejected(); #ifndef FWTPM_NO_DA test_fwtpm_da_parameters_and_reset(); + test_fwtpm_da_parameters_zero_maxtries_rejected(); #endif /* Policy */ @@ -8451,12 +9347,19 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_policy_pcr(); test_fwtpm_policy_ticket_zero_digest_rejected(); test_fwtpm_policyauthorize_null_ticket_rejected(); +#ifndef FWTPM_NO_NV + test_fwtpm_policynv_owner_read_denied(); + test_fwtpm_policyauthorizenv_owner_read_denied(); + test_fwtpm_policy_locality_enforced(); + test_fwtpm_policy_cphash_enforced(); +#endif #endif /* NV operations */ #ifndef FWTPM_NO_NV test_fwtpm_nv_define_write_read(); test_fwtpm_nv_read_public(); + test_fwtpm_nv_journal_tamper_rejected(); test_fwtpm_nv_counter(); #ifndef FWTPM_NO_ATTESTATION test_fwtpm_nv_certify_digest_mode(); @@ -8464,6 +9367,14 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_quote_ecdaa_scheme(); test_fwtpm_sign_ecdaa_scheme(); test_fwtpm_certify_creation_ecdaa_scheme(); + test_fwtpm_ecdh_keygen_signkey_returns_attributes(); + test_fwtpm_ecdh_zgen_signkey_returns_attributes(); + test_fwtpm_zgen_2phase_signkey_returns_attributes(); + test_fwtpm_quote_decrypt_key_returns_key(); + test_fwtpm_quote_unrestricted_sign_returns_attributes(); + test_fwtpm_sign_scheme_downgrade_rejected(); + test_fwtpm_quote_clockinfo_resetcount_nonzero(); + test_fwtpm_certify_decrypt_key_returns_key(); #endif #endif #endif @@ -8471,6 +9382,8 @@ int fwtpm_unit_tests(int argc, char *argv[]) /* Hierarchy & misc */ test_fwtpm_test_parms(); test_fwtpm_incremental_selftest(); + test_fwtpm_incremental_selftest_truncated_returns_cmd_size(); + test_fwtpm_gettestresult_needs_test_then_success(); /* Destructive tests last (Clear changes state) */ test_fwtpm_change_eps(); diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 9c7f9aa6..07a7f26c 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -1036,6 +1036,19 @@ static void test_TPM2_HmacCompute(void) digest, digestSz); AssertIntEQ(TPM_RC_INTEGRITY, rc); + /* A truncated (short) expected HMAC must be rejected on the length check */ + rc = TPM2_HmacVerify(TPM_ALG_SHA256, + hmacKey, 4, hmacData, 28, NULL, 0, + hmacExp, sizeof(hmacExp) - 1); + AssertIntEQ(TPM_RC_INTEGRITY, rc); + + /* An output buffer smaller than the digest must be rejected */ + digestSz = 31; + rc = TPM2_HmacCompute(TPM_ALG_SHA256, + hmacKey, 4, hmacData, 28, NULL, 0, + digest, &digestSz); + AssertIntEQ(BUFFER_E, rc); + printf("Test TPM Wrapper: %-40s Passed\n", "HmacCompute:"); #else printf("Test TPM Wrapper: %-40s Skipped\n", "HmacCompute:"); diff --git a/wolftpm/fwtpm/fwtpm.h b/wolftpm/fwtpm/fwtpm.h index 87a738f8..0736ce8c 100644 --- a/wolftpm/fwtpm/fwtpm.h +++ b/wolftpm/fwtpm/fwtpm.h @@ -529,6 +529,8 @@ typedef struct FWTPM_Session { TPM2B_DIGEST cpHashA; /* PolicyCpHash: locked once set */ TPM2B_DIGEST nameHash; /* PolicyNameHash: locked once set */ int isPPRequired; /* PolicyPhysicalPresence flag */ + int requiredLocality; /* PolicyLocality bitmap */ + int hasRequiredLocality; /* 1 once PolicyLocality has been called */ } FWTPM_Session; /* NV index slot (user NV RAM) */ @@ -594,6 +596,11 @@ struct FWTPM_NV_HAL_S { int (*erase)(void* ctx, word32 offset, word32 size); /* Optional */ void* ctx; word32 maxSize; /* Total NV region size */ + /* Override the NV-journal integrity key with a platform device secret + * (e.g. hardware-fused or host-TPM backed). Return 0 and set *keySz on + * success. When NULL the default file backend uses an auto-created key + * file; integrity verification is always performed when a key exists. */ + int (*get_integrity_key)(void* ctx, byte* key, word32* keySz); }; /* Clock HAL callbacks (optional - if not set, clockOffset used directly) */ @@ -633,6 +640,8 @@ typedef struct FWTPM_CTX { #endif int activeLocality; UINT64 clockOffset; /* Clock offset set by ClockSet */ + UINT32 resetCount; /* TPM Reset count, persisted across boots */ + UINT32 restartCount; /* TPM Restart/Resume count, volatile */ /* PCR state: [pcrIndex][bank][digest bytes] */ byte pcrDigest[IMPLEMENTATION_PCR][FWTPM_PCR_BANKS][TPM_MAX_DIGEST_SIZE]; @@ -713,6 +722,13 @@ typedef struct FWTPM_CTX { /* ContextSave sequence counter (monotonic, reset on init) */ UINT64 contextSeqCounter; + /* Live (saved-but-not-yet-loaded) context sequences. A context loads at + * most once and saved contexts may load in any order. */ + UINT64 contextLive[FWTPM_MAX_OBJECTS + FWTPM_MAX_SESSIONS]; + int contextLiveCount; + + /* Set once TPM2_SelfTest has completed successfully */ + int selfTestRun; #ifdef HAVE_ECC /* EC_Ephemeral commit counter and key storage (volatile) */ diff --git a/wolftpm/fwtpm/fwtpm_command.h b/wolftpm/fwtpm/fwtpm_command.h index 9c052fac..705b309a 100644 --- a/wolftpm/fwtpm/fwtpm_command.h +++ b/wolftpm/fwtpm/fwtpm_command.h @@ -56,6 +56,14 @@ WOLFTPM_API int FWTPM_ProcessCommand(FWTPM_CTX* ctx, const byte* cmdBuf, int cmdSize, byte* rspBuf, int* rspSize, int locality); +/*! + \brief Flush all transient objects, sessions, and sequences. + + Called when the active command client connection is replaced or closed so + a subsequent client cannot inherit the previous client's transient state. +*/ +WOLFTPM_API void FWTPM_ResetCommandClient(FWTPM_CTX* ctx); + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/wolftpm/fwtpm/fwtpm_crypto.h b/wolftpm/fwtpm/fwtpm_crypto.h index 37131d8a..fa20d1a5 100644 --- a/wolftpm/fwtpm/fwtpm_crypto.h +++ b/wolftpm/fwtpm/fwtpm_crypto.h @@ -255,10 +255,10 @@ int FwUnwrapPrivate(FWTPM_Object* parent, /* --- Context blob wrap/unwrap (ContextSave/Load) --- */ -int FwWrapContextBlob(FWTPM_CTX* ctx, +int FwWrapContextBlob(FWTPM_CTX* ctx, UINT64 seq, const byte* plain, int plainSz, byte* out, int outBufSz, int* outSz); -int FwUnwrapContextBlob(FWTPM_CTX* ctx, +int FwUnwrapContextBlob(FWTPM_CTX* ctx, UINT64 seq, const byte* in, int inSz, byte* out, int outBufSz, int* outSz); @@ -352,6 +352,7 @@ TPM_RC FwSignAttest(FWTPM_CTX* ctx, FWTPM_Object* obj, #ifndef FWTPM_NO_CREDENTIAL TPM_RC FwCredentialDeriveKeys( + TPMI_ALG_HASH nameAlg, const byte* seed, int seedSz, const byte* name, int nameSz, byte* symKey, int symKeySz, diff --git a/wolftpm/tpm2.h b/wolftpm/tpm2.h index e4973706..30569ab3 100644 --- a/wolftpm/tpm2.h +++ b/wolftpm/tpm2.h @@ -2271,7 +2271,7 @@ WOLFTPM_API TPM_RC TPM2_IncrementalSelfTest(IncrementalSelfTest_In* in, typedef struct { TPM2B_MAX_BUFFER outData; - UINT16 testResult; /* TPM_RC */ + UINT32 testResult; /* full 4-byte TPM_RC value on the wire */ } GetTestResult_Out; WOLFTPM_API TPM_RC TPM2_GetTestResult(GetTestResult_Out* out); diff --git a/wolftpm/tpm2_wrap.h b/wolftpm/tpm2_wrap.h index 1b1e85df..0c25f2fd 100644 --- a/wolftpm/tpm2_wrap.h +++ b/wolftpm/tpm2_wrap.h @@ -1276,10 +1276,16 @@ WOLFTPM_API int wolfTPM2_LoadRsaPrivateKey(WOLFTPM2_DEV* dev, \param scheme value of TPMI_ALG_RSA_SCHEME type, specifying the RSA scheme \param hashAlg value of TPMI_ALG_HASH type, specifying the TPM hashing algorithm + \warning If key->handle.auth is empty the key is loaded with empty + authorization and any caller holding the handle can use it. Set + key->handle.auth (e.g. via wolfTPM2_SetKeyAuthPassword) before loading + to require a password. + \sa wolfTPM2_LoadRsaPrivateKey \sa wolfTPM2_LoadPrivateKey \sa wolfTPM2_ImportRsaPrivateKey \sa wolfTPM2_LoadEccPrivateKey + \sa wolfTPM2_SetKeyAuthPassword */ WOLFTPM_API int wolfTPM2_LoadRsaPrivateKey_ex(WOLFTPM2_DEV* dev, const WOLFTPM2_KEY* parentKey, WOLFTPM2_KEY* key, @@ -1434,9 +1440,15 @@ WOLFTPM_API int wolfTPM2_ImportEccPrivateKeySeed(WOLFTPM2_DEV* dev, \param eccPriv pointer to a byte buffer containing the private material \param eccPrivSz integer value of word32 type, specifying the private material size + \warning If key->handle.auth is empty the key is loaded with empty + authorization and any caller holding the handle can use it. Set + key->handle.auth (e.g. via wolfTPM2_SetKeyAuthPassword) before loading + to require a password. + \sa wolfTPM2_ImportEccPrivateKey \sa wolfTPM2_LoadEccPublicKey \sa wolfTPM2_LoadPrivateKey + \sa wolfTPM2_SetKeyAuthPassword */ WOLFTPM_API int wolfTPM2_LoadEccPrivateKey(WOLFTPM2_DEV* dev, const WOLFTPM2_KEY* parentKey, WOLFTPM2_KEY* key, @@ -3270,8 +3282,14 @@ WOLFTPM_API int wolfTPM2_HmacFinish(WOLFTPM2_DEV* dev, WOLFTPM2_HMAC* hmac, \param keyBuf pointer to key material \param keySz size of key material in bytes + \warning If key->handle.auth is empty the key is loaded with empty + authorization and any caller holding the handle can use it. Set + key->handle.auth (e.g. via wolfTPM2_SetKeyAuthPassword) before loading + to require a password. + \sa wolfTPM2_EncryptDecryptBlock \sa wolfTPM2_EncryptDecrypt + \sa wolfTPM2_SetKeyAuthPassword */ WOLFTPM_API int wolfTPM2_LoadSymmetricKey(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, int alg, const byte* keyBuf, word32 keySz);