From 3ff7ad44788a5c68420b08409acabcb81bda5912 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 26 Oct 2026 13:01:52 -0700 Subject: [PATCH 01/12] Pad fwTPM ECDSA scalars and handle all-zero digests --- src/fwtpm/fwtpm_command.c | 3 + src/fwtpm/fwtpm_crypto.c | 31 +++++++++- tests/fwtpm_unit_tests.c | 122 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 154 insertions(+), 2 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index b3dac526..aa183f22 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -9596,6 +9596,9 @@ static TPM_RC FwCmd_Sign(FWTPM_CTX* ctx, TPM2_Packet* cmd, rc = FwSignDigestAndAppend(ctx, obj, sigScheme, sigHashAlg, digest.buffer, digest.size, rsp); + if (rc == TPM_RC_VALUE) { + rc |= TPM_RC_P | TPM_RC_1; + } } if (rc == 0) { diff --git a/src/fwtpm/fwtpm_crypto.c b/src/fwtpm/fwtpm_crypto.c index f4e08ade..357dc50e 100644 --- a/src/fwtpm/fwtpm_crypto.c +++ b/src/fwtpm/fwtpm_crypto.c @@ -3949,6 +3949,7 @@ TPM_RC FwSignDigestAndAppend(FWTPM_CTX* ctx, FWTPM_Object* obj, word32 sSz = (word32)sizeof(sBuf); int wcRc; int eccInit = 0; + int i = 0; FWTPM_ALLOC_VAR(eccKey, ecc_key); FWTPM_ALLOC_BUF(derSig, FWTPM_MAX_DER_SIG_BUF); @@ -3962,8 +3963,15 @@ TPM_RC FwSignDigestAndAppend(FWTPM_CTX* ctx, FWTPM_Object* obj, if (rc == 0) { wcRc = wc_ecc_sign_hash(digest, digestSz, derSig, &derSigSz, &ctx->rng, eccKey); - if (wcRc != 0) - rc = TPM_RC_FAILURE; + if (wcRc != 0) { + /* wolfCrypt refuses an all-zero digest with + * ECC_BAD_ARG_E unless WC_ALLOW_ECC_ZERO_HASH */ + while (wcRc == ECC_BAD_ARG_E && i < digestSz && + digest[i] == 0) + i++; + rc = (i > 0 && i == digestSz) ? TPM_RC_VALUE : + TPM_RC_FAILURE; + } } if (eccInit) @@ -3976,6 +3984,25 @@ TPM_RC FwSignDigestAndAppend(FWTPM_CTX* ctx, FWTPM_Object* obj, rc = TPM_RC_FAILURE; } + /* Match the reference TPM's fixed-width ECDSA scalar encoding. */ + if (rc == 0) { + int keySz = FwGetEccKeySize( + obj->pub.parameters.eccDetail.curveID); + if (keySz <= 0 || keySz > (int)sizeof(rBuf) || + keySz > (int)sizeof(sBuf) || + rSz > (word32)keySz || sSz > (word32)keySz) { + rc = TPM_RC_FAILURE; + } + else { + XMEMMOVE(rBuf + keySz - rSz, rBuf, rSz); + XMEMSET(rBuf, 0, keySz - rSz); + rSz = (word32)keySz; + XMEMMOVE(sBuf + keySz - sSz, sBuf, sSz); + XMEMSET(sBuf, 0, keySz - sSz); + sSz = (word32)keySz; + } + } + if (rc == 0) { TPM2_Packet_AppendU16(rsp, sigScheme); TPM2_Packet_AppendU16(rsp, sigHashAlg); diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index a2119154..a775e796 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -13352,6 +13352,64 @@ static void test_fwtpm_sign_ecdaa_scheme(void) printf("Test fwTPM:\tSign(ECDAA scheme):\t\tPassed\n"); } +/* An all-zero ECDSA digest is signed when wolfCrypt allows it, as the + * reference TPM does, and is otherwise a digest parameter error. The TPM must + * not report TPM_RC_FAILURE and must keep serving commands. */ +static void test_fwtpm_sign_ecdsa_zero_digest(void) +{ + FWTPM_CTX ctx; + int pos, rspSize; + UINT32 keyH; + byte digest[32]; + ecc_key probe; + byte probeSig[ECC_MAX_SIG_SIZE]; + word32 probeSigSz = (word32)sizeof(probeSig); + int zeroSigned; + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + memset(digest, 0, sizeof(digest)); + AssertIntEQ(wc_ecc_init(&probe), 0); + AssertIntEQ(wc_ecc_make_key(&ctx.rng, 32, &probe), 0); + zeroSigned = (wc_ecc_sign_hash(digest, sizeof(digest), probeSig, + &probeSigSz, &ctx.rng, &probe) == 0); + wc_ecc_free(&probe); + + 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_Sign); pos += 4; + PutU32BE(gCmd + pos, keyH); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU16BE(gCmd + pos, (UINT16)sizeof(digest)); pos += 2; + memcpy(gCmd + pos, digest, sizeof(digest)); pos += sizeof(digest); + PutU16BE(gCmd + pos, TPM_ALG_ECDSA); pos += 2; + PutU16BE(gCmd + pos, TPM_ALG_SHA256); 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), zeroSigned ? TPM_RC_SUCCESS : + (TPM_RC_VALUE | TPM_RC_P | TPM_RC_1)); + + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 12, TPM_CC_GetRandom); + PutU16BE(gCmd + pos, 16); pos += 2; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + FlushHandle(&ctx, keyH); + FWTPM_Cleanup(&ctx); + printf("Test fwTPM:\tSign(ECDSA zero digest):\tPassed\n"); +} + /* 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. */ @@ -19262,6 +19320,56 @@ static void test_fwtpm_public_name_rsa_max(void) /* main */ /* ================================================================== */ +#ifdef HAVE_ECC +/* Fixed-width scalars must remain valid signatures, including a leading zero + * on P-521. The reference TPM emits the order's full byte width. */ +static void test_fwtpm_ecdsa_signature_padding(void) +{ + static const UINT16 curves[] = { + TPM_ECC_NIST_P256, TPM_ECC_NIST_P384, TPM_ECC_NIST_P521 + }; + FWTPM_CTX ctx; + FWTPM_Object obj; + TPM2_Packet packet; + TPMT_SIGNATURE sig; + byte digest[TPM_SHA256_DIGEST_SIZE]; + int i, j, keySz; + + XMEMSET(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), TPM_RC_SUCCESS); + for (i = 0; i < (int)(sizeof(curves) / sizeof(curves[0])); i++) { + if (FwGetWcCurveId(curves[i]) < 0) { + continue; + } + keySz = FwGetEccKeySize(curves[i]); + XMEMSET(&obj, 0, sizeof(obj)); + obj.pub.type = TPM_ALG_ECC; + obj.pub.parameters.eccDetail.curveID = curves[i]; + AssertIntEQ(FwGenerateEccKey(&ctx.rng, curves[i], &obj.pub.unique.ecc, + obj.privKey, sizeof(obj.privKey), &obj.privKeySize), 0); + for (j = 0; j < 64; j++) { + XMEMSET(digest, j + 1, sizeof(digest)); + XMEMSET(&packet, 0, sizeof(packet)); + packet.buf = gRsp; + packet.size = sizeof(gRsp); + AssertIntEQ(FwSignDigestAndAppend(&ctx, &obj, TPM_ALG_ECDSA, + TPM_ALG_SHA256, digest, sizeof(digest), &packet), 0); + AssertFalse(packet.overflow); + packet.pos = 0; + XMEMSET(&sig, 0, sizeof(sig)); + TPM2_Packet_ParseSignature(&packet, &sig); + AssertIntEQ(sig.signature.ecdsa.signatureR.size, keySz); + AssertIntEQ(sig.signature.ecdsa.signatureS.size, keySz); + AssertIntEQ(FwVerifySignatureCore(&obj, digest, sizeof(digest), + &sig), TPM_RC_SUCCESS); + } + } + wc_ForceZero(&obj, sizeof(obj)); + FWTPM_Cleanup(&ctx); + fwtpm_pass("ECDSA fixed-width verified signatures:", 0); +} +#endif /* HAVE_ECC */ + #ifndef NO_MAIN_DRIVER int main(int argc, char *argv[]) #else @@ -19273,6 +19381,12 @@ int fwtpm_unit_tests(int argc, char *argv[]) /* Remove stale NV state to ensure clean test runs */ (void)remove(FWTPM_NV_FILE); +#ifdef HAVE_ECC + if (argc == 1) { + test_fwtpm_ecdsa_signature_padding(); + } +#endif + #ifdef WOLFTPM_FWTPM_UNIT_TEST if (argc == 2 && XSTRCMP(argv[1], "custom-io-hal") == 0) { test_fwtpm_custom_io_hal(); @@ -19294,6 +19408,13 @@ int fwtpm_unit_tests(int argc, char *argv[]) } #endif +#ifdef HAVE_ECC + if (argc == 2 && XSTRCMP(argv[1], "ecdsa-padding") == 0) { + test_fwtpm_ecdsa_signature_padding(); + return 0; + } +#endif + if (argc == 2 && XSTRCMP(argv[1], "clear-seed") == 0) { test_fwtpm_clear(); return 0; @@ -19770,6 +19891,7 @@ int fwtpm_unit_tests(int argc, char *argv[]) #ifdef HAVE_ECC test_fwtpm_quote_ecdaa_scheme(); test_fwtpm_sign_ecdaa_scheme(); + test_fwtpm_sign_ecdsa_zero_digest(); test_fwtpm_certify_creation_ecdaa_scheme(); #ifndef FWTPM_NO_ECDH test_fwtpm_ecdh_keygen_signkey_returns_attributes(); From 0a509455048859fe688cae5f82f98e70dab25f73 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 26 Oct 2026 13:00:07 -0700 Subject: [PATCH 02/12] Align fwTPM RSA hashes and algorithm capabilities --- src/fwtpm/fwtpm_command.c | 45 +++++-- src/fwtpm/fwtpm_crypto.c | 34 ++++- tests/fwtpm_unit_tests.c | 275 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 340 insertions(+), 14 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index aa183f22..7da97c2c 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -1836,12 +1836,27 @@ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd, { TPM_ALG_RSA, TPMA_ALGORITHM_asymmetric | TPMA_ALGORITHM_object }, #endif + #ifndef NO_SHA + { TPM_ALG_SHA1, TPMA_ALGORITHM_hash }, + #endif { TPM_ALG_SHA256, TPMA_ALGORITHM_hash }, #ifdef WOLFSSL_SHA384 { TPM_ALG_SHA384, TPMA_ALGORITHM_hash }, #endif #ifdef WOLFSSL_SHA512 { TPM_ALG_SHA512, TPMA_ALGORITHM_hash }, + #endif + #if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_256) + { TPM_ALG_SHA3_256, TPMA_ALGORITHM_hash }, + #endif + #if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_384) + { TPM_ALG_SHA3_384, TPMA_ALGORITHM_hash }, + #endif + #if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_512) + { TPM_ALG_SHA3_512, TPMA_ALGORITHM_hash }, + #endif + #ifdef WOLFSSL_SM3 + { TPM_ALG_SM3_256, TPMA_ALGORITHM_hash }, #endif { TPM_ALG_HMAC, TPMA_ALGORITHM_hash | TPMA_ALGORITHM_signing }, @@ -9831,11 +9846,16 @@ static TPM_RC FwCmd_RSA_Encrypt(FWTPM_CTX* ctx, TPM2_Packet* cmd, TPMI_ALG_HASH oaepHash = (encHashAlg != TPM_ALG_NULL) ? encHashAlg : (TPMI_ALG_HASH)TPM_ALG_SHA256; int wcHashType = FwGetRsaHashOid(oaepHash); - outSz = wc_RsaPublicEncrypt_ex(message->buffer, message->size, - outBuf, (word32)FWTPM_MAX_PUB_BUF, rsaKey, &ctx->rng, - WC_RSA_OAEP_PAD, (enum wc_HashType)wcHashType, - FwGetMgfType(oaepHash), - label, oaepLabelSz); + int mgf = FwGetMgfType(oaepHash); + if (mgf == WC_MGF1NONE) { + rc = TPM_RC_SCHEME; + } + else { + outSz = wc_RsaPublicEncrypt_ex(message->buffer, message->size, + outBuf, (word32)FWTPM_MAX_PUB_BUF, rsaKey, &ctx->rng, + WC_RSA_OAEP_PAD, (enum wc_HashType)wcHashType, mgf, + label, oaepLabelSz); + } } else if (encScheme == TPM_ALG_NULL) { /* Raw RSA (no padding) */ @@ -10014,11 +10034,16 @@ static TPM_RC FwCmd_RSA_Decrypt(FWTPM_CTX* ctx, TPM2_Packet* cmd, TPMI_ALG_HASH oaepHash = (decHashAlg != TPM_ALG_NULL) ? decHashAlg : (TPMI_ALG_HASH)TPM_ALG_SHA256; int wcHashType = FwGetRsaHashOid(oaepHash); - outSz = wc_RsaPrivateDecrypt_ex(cipherText->buffer, cipherText->size, - outBuf, (word32)FWTPM_MAX_PUB_BUF, rsaKey, - WC_RSA_OAEP_PAD, (enum wc_HashType)wcHashType, - FwGetMgfType(oaepHash), - label, oaepLabelSz); + int mgf = FwGetMgfType(oaepHash); + if (mgf == WC_MGF1NONE) { + rc = TPM_RC_SCHEME; + } + else { + outSz = wc_RsaPrivateDecrypt_ex(cipherText->buffer, + cipherText->size, outBuf, (word32)FWTPM_MAX_PUB_BUF, rsaKey, + WC_RSA_OAEP_PAD, (enum wc_HashType)wcHashType, mgf, + label, oaepLabelSz); + } } else if (decScheme == TPM_ALG_NULL) { /* Raw RSA (no padding) */ diff --git a/src/fwtpm/fwtpm_crypto.c b/src/fwtpm/fwtpm_crypto.c index 357dc50e..86a9408a 100644 --- a/src/fwtpm/fwtpm_crypto.c +++ b/src/fwtpm/fwtpm_crypto.c @@ -82,14 +82,29 @@ enum wc_HashType FwGetWcHashType(UINT16 hashAlg) int FwGetMgfType(UINT16 hashAlg) { switch (hashAlg) { + #ifndef NO_SHA + case TPM_ALG_SHA1: return WC_MGF1SHA1; + #endif #ifdef WOLFSSL_SHA512 case TPM_ALG_SHA512: return WC_MGF1SHA512; #endif #ifdef WOLFSSL_SHA384 case TPM_ALG_SHA384: return WC_MGF1SHA384; #endif - case TPM_ALG_SHA256: /* fallthrough */ - default: return WC_MGF1SHA256; + #if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_256) && \ + defined(WC_MGF1SHA3_256) + case TPM_ALG_SHA3_256: return WC_MGF1SHA3_256; + #endif + #if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_384) && \ + defined(WC_MGF1SHA3_384) + case TPM_ALG_SHA3_384: return WC_MGF1SHA3_384; + #endif + #if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_512) && \ + defined(WC_MGF1SHA3_512) + case TPM_ALG_SHA3_512: return WC_MGF1SHA3_512; + #endif + case TPM_ALG_SHA256: return WC_MGF1SHA256; + default: return WC_MGF1NONE; } } #endif /* !NO_RSA */ @@ -3853,6 +3868,15 @@ int FwGetRsaHashOid(UINT16 hashAlg) #endif #ifndef NO_SHA case TPM_ALG_SHA1: return WC_HASH_TYPE_SHA; + #endif + #if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_256) + case TPM_ALG_SHA3_256: return WC_HASH_TYPE_SHA3_256; + #endif + #if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_384) + case TPM_ALG_SHA3_384: return WC_HASH_TYPE_SHA3_384; + #endif + #if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_512) + case TPM_ALG_SHA3_512: return WC_HASH_TYPE_SHA3_512; #endif default: return WC_HASH_TYPE_NONE; } @@ -3885,7 +3909,8 @@ TPM_RC FwSignDigestAndAppend(FWTPM_CTX* ctx, FWTPM_Object* obj, FWTPM_ALLOC_VAR(rsaKey, RsaKey); FWTPM_ALLOC_BUF(sigBuf, FWTPM_MAX_PUB_BUF); - if (pad < 0) + if (pad < 0 || (pad == WC_RSA_PSS_PAD && + FwGetMgfType(sigHashAlg) == WC_MGF1NONE)) rc = TPM_RC_SCHEME; if (rc == 0) { @@ -4038,7 +4063,8 @@ TPM_RC FwVerifySignatureCore(FWTPM_Object* obj, FWTPM_ALLOC_VAR(rsaKey, RsaKey); - if (pad < 0) + if (pad < 0 || (pad == WC_RSA_PSS_PAD && + FwGetMgfType(sig->signature.rsapss.hash) == WC_MGF1NONE)) rc = TPM_RC_SCHEME; if (rc == 0) { diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index a775e796..74234eda 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -19370,6 +19370,263 @@ static void test_fwtpm_ecdsa_signature_padding(void) } #endif /* HAVE_ECC */ +/* Hash capabilities must match the available SHA-1 and SHA-3 algorithms, + * including builds that disable the whole family or individual SHA-3 sizes. */ +static void test_fwtpm_hash_capabilities(void) +{ + static const struct { + UINT16 hashAlg; + int enabled; + } hashes[] = { + { TPM_ALG_SHA1, + #ifndef NO_SHA + 1 + #else + 0 + #endif + }, + { TPM_ALG_SHA3_256, + #if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_256) + 1 + #else + 0 + #endif + }, + { TPM_ALG_SHA3_384, + #if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_384) + 1 + #else + 0 + #endif + }, + { TPM_ALG_SM3_256, + #ifdef WOLFSSL_SM3 + 1 + #else + 0 + #endif + }, + { TPM_ALG_SHA3_512, + #if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_512) + 1 + #else + 0 + #endif + } + }; + FWTPM_CTX ctx; + int pos, rspSize = 0, i; + UINT32 count; + + XMEMSET(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), TPM_RC_SUCCESS); + for (i = 0; i < (int)(sizeof(hashes) / sizeof(hashes[0])); i++) { + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 22, + TPM_CC_GetCapability); + PutU32BE(gCmd + pos, TPM_CAP_ALGS); pos += 4; + PutU32BE(gCmd + pos, hashes[i].hashAlg); pos += 4; + PutU32BE(gCmd + pos, 1); pos += 4; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + count = GetU32BE(gRsp + TPM2_HEADER_SIZE + 5); + if (hashes[i].enabled) { + AssertIntEQ(count, 1); + AssertIntEQ(GetU16BE(gRsp + TPM2_HEADER_SIZE + 9), + hashes[i].hashAlg); + AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE + 11), + TPMA_ALGORITHM_hash); + } + else if (count != 0) { + AssertIntNE(GetU16BE(gRsp + TPM2_HEADER_SIZE + 9), + hashes[i].hashAlg); + } + } + FWTPM_Cleanup(&ctx); + fwtpm_pass("SHA1 and SHA3 algorithm capabilities:", 0); +} + +#if !defined(NO_RSA) && defined(WOLFSSL_KEY_GEN) +/* Compare OAEP and PSS in both directions with independently selected + * wolfCrypt hash/MGF pairs, so a shared fwTPM mapping bug cannot cancel out. */ +static void test_fwtpm_rsa_hash_interop(UINT16 hashAlg, + enum wc_HashType hashType, int mgf) +{ + static const byte message[] = "RSA hash interoperability"; + FWTPM_CTX ctx; + FWTPM_Object* obj; + RsaKey key; + byte ciphertext[MAX_RSA_KEY_BYTES]; + byte plaintext[MAX_RSA_KEY_BYTES]; + byte digest[TPM_MAX_DIGEST_SIZE]; + byte signatureBuf[MAX_RSA_KEY_BYTES + 6]; + TPMT_SIGNATURE sig; + TPM2_Packet packet; + int pos, rspSize = 0, cipherSz, plainSz, digestSz; + + XMEMSET(&ctx, 0, sizeof(ctx)); + XMEMSET(&key, 0, sizeof(key)); + AssertIntEQ(fwtpm_test_startup(&ctx), TPM_RC_SUCCESS); + obj = &ctx.objects[0]; + obj->used = 1; + obj->handle = TRANSIENT_FIRST; + obj->hierarchy = TPM_RH_OWNER; + obj->pub.type = TPM_ALG_RSA; + obj->pub.nameAlg = TPM_ALG_SHA256; + obj->pub.objectAttributes = TPMA_OBJECT_decrypt | TPMA_OBJECT_userWithAuth | + TPMA_OBJECT_noDA | TPMA_OBJECT_sign; + obj->pub.parameters.rsaDetail.keyBits = 2048; + obj->pub.parameters.rsaDetail.symmetric.algorithm = TPM_ALG_NULL; + obj->pub.parameters.rsaDetail.scheme.scheme = TPM_ALG_NULL; + AssertIntEQ(FwGenerateRsaKey(&ctx.rng, 2048, WC_RSA_EXPONENT, + &obj->pub.unique.rsa, obj->privKey, sizeof(obj->privKey), + &obj->privKeySize), TPM_RC_SUCCESS); + AssertIntEQ(FwComputePublicName(&obj->pub, &obj->name), TPM_RC_SUCCESS); + AssertIntEQ(FwImportRsaKey(obj, &key), TPM_RC_SUCCESS); + AssertIntEQ(wc_RsaSetRNG(&key, &ctx.rng), 0); + cipherSz = wc_RsaPublicEncrypt_ex(message, sizeof(message) - 1, + ciphertext, sizeof(ciphertext), &key, &ctx.rng, WC_RSA_OAEP_PAD, + hashType, mgf, NULL, 0); + AssertIntEQ(cipherSz, 256); + + pos = BuildCmdHeader(gCmd, TPM_ST_SESSIONS, 0, TPM_CC_RSA_Decrypt); + PutU32BE(gCmd + pos, obj->handle); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU16BE(gCmd + pos, (UINT16)cipherSz); pos += 2; + XMEMCPY(gCmd + pos, ciphertext, cipherSz); pos += cipherSz; + PutU16BE(gCmd + pos, TPM_ALG_OAEP); pos += 2; + PutU16BE(gCmd + pos, hashAlg); pos += 2; + PutU16BE(gCmd + pos, 0); pos += 2; + PutU32BE(gCmd + 2, (UINT32)pos); + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(GetU16BE(gRsp + TPM2_HEADER_SIZE + 4), sizeof(message) - 1); + AssertIntEQ(XMEMCMP(gRsp + TPM2_HEADER_SIZE + 6, message, + sizeof(message) - 1), 0); + + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_RSA_Encrypt); + PutU32BE(gCmd + pos, obj->handle); pos += 4; + PutU16BE(gCmd + pos, sizeof(message) - 1); pos += 2; + XMEMCPY(gCmd + pos, message, sizeof(message) - 1); + pos += sizeof(message) - 1; + PutU16BE(gCmd + pos, TPM_ALG_OAEP); pos += 2; + PutU16BE(gCmd + pos, hashAlg); pos += 2; + 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); + cipherSz = GetU16BE(gRsp + TPM2_HEADER_SIZE); + AssertIntEQ(cipherSz, 256); + XMEMCPY(ciphertext, gRsp + TPM2_HEADER_SIZE + 2, cipherSz); + plainSz = wc_RsaPrivateDecrypt_ex(ciphertext, cipherSz, plaintext, + sizeof(plaintext), &key, WC_RSA_OAEP_PAD, hashType, + mgf, NULL, 0); + AssertIntEQ(plainSz, sizeof(message) - 1); + AssertIntEQ(XMEMCMP(plaintext, message, plainSz), 0); + digestSz = wc_HashGetDigestSize(hashType); + AssertIntGT(digestSz, 0); + AssertIntEQ(wc_Hash(hashType, message, sizeof(message) - 1, + digest, digestSz), 0); + XMEMSET(&packet, 0, sizeof(packet)); + packet.buf = signatureBuf; + packet.size = sizeof(signatureBuf); + AssertIntEQ(FwSignDigestAndAppend(&ctx, obj, TPM_ALG_RSAPSS, hashAlg, + digest, digestSz, &packet), TPM_RC_SUCCESS); + AssertIntEQ(packet.overflow, 0); + AssertIntEQ(packet.pos, 6 + 256); + cipherSz = GetU16BE(signatureBuf + 4); + AssertIntEQ(cipherSz, 256); + XMEMCPY(ciphertext, signatureBuf + 6, cipherSz); + AssertIntGT(wc_RsaPSS_VerifyCheck(ciphertext, cipherSz, plaintext, + sizeof(plaintext), digest, digestSz, hashType, mgf, &key), 0); + + XMEMSET(&sig, 0, sizeof(sig)); + sig.sigAlg = TPM_ALG_RSAPSS; + sig.signature.rsapss.hash = hashAlg; + cipherSz = wc_RsaPSS_Sign_ex(digest, digestSz, + sig.signature.rsapss.sig.buffer, + sizeof(sig.signature.rsapss.sig.buffer), hashType, mgf, + RSA_PSS_SALT_LEN_DEFAULT, &key, &ctx.rng); + AssertIntEQ(cipherSz, 256); + sig.signature.rsapss.sig.size = (UINT16)cipherSz; + AssertIntEQ(FwVerifySignatureCore(obj, digest, digestSz, &sig), + TPM_RC_SUCCESS); + wc_FreeRsaKey(&key); + FWTPM_Cleanup(&ctx); + fwtpm_pass("RSA OAEP and PSS hash interoperability:", 0); +} + +#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_256) && \ + !defined(WC_MGF1SHA3_256) +/* SHA3-256 without a wolfCrypt MGF1 selector: PSS is refused rather than + * masked with another hash. */ +static void test_fwtpm_rsa_sha3_no_mgf(void) +{ + FWTPM_CTX ctx; + FWTPM_Object* obj; + byte digest[WC_SHA3_256_DIGEST_SIZE]; + byte signatureBuf[64]; + TPMT_SIGNATURE sig; + TPM2_Packet packet; + + XMEMSET(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), TPM_RC_SUCCESS); + obj = &ctx.objects[0]; + obj->pub.type = TPM_ALG_RSA; + XMEMSET(digest, 0x5A, sizeof(digest)); + XMEMSET(&packet, 0, sizeof(packet)); + packet.buf = signatureBuf; + packet.size = sizeof(signatureBuf); + AssertIntEQ(FwGetMgfType(TPM_ALG_SHA3_256), WC_MGF1NONE); + AssertIntEQ(FwSignDigestAndAppend(&ctx, obj, TPM_ALG_RSAPSS, + TPM_ALG_SHA3_256, digest, sizeof(digest), &packet), TPM_RC_SCHEME); + XMEMSET(&sig, 0, sizeof(sig)); + sig.sigAlg = TPM_ALG_RSAPSS; + sig.signature.rsapss.hash = TPM_ALG_SHA3_256; + AssertIntEQ(FwVerifySignatureCore(obj, digest, sizeof(digest), &sig), + TPM_RC_SCHEME); + FWTPM_Cleanup(&ctx); + fwtpm_pass("RSA SHA3-256 without MGF1 selector refused:", 0); +} +#endif + +static void test_fwtpm_rsa_hash_matrix(void) +{ +#ifndef NO_SHA + test_fwtpm_rsa_hash_interop(TPM_ALG_SHA1, + WC_HASH_TYPE_SHA, WC_MGF1SHA1); +#endif + test_fwtpm_rsa_hash_interop(TPM_ALG_SHA256, + WC_HASH_TYPE_SHA256, WC_MGF1SHA256); +#ifdef WOLFSSL_SHA384 + test_fwtpm_rsa_hash_interop(TPM_ALG_SHA384, + WC_HASH_TYPE_SHA384, WC_MGF1SHA384); +#endif +#ifdef WOLFSSL_SHA512 + test_fwtpm_rsa_hash_interop(TPM_ALG_SHA512, + WC_HASH_TYPE_SHA512, WC_MGF1SHA512); +#endif +#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_256) && \ + defined(WC_MGF1SHA3_256) + test_fwtpm_rsa_hash_interop(TPM_ALG_SHA3_256, + WC_HASH_TYPE_SHA3_256, WC_MGF1SHA3_256); +#elif defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_256) + test_fwtpm_rsa_sha3_no_mgf(); +#endif +#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_384) && \ + defined(WC_MGF1SHA3_384) + test_fwtpm_rsa_hash_interop(TPM_ALG_SHA3_384, + WC_HASH_TYPE_SHA3_384, WC_MGF1SHA3_384); +#endif +#if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_512) && \ + defined(WC_MGF1SHA3_512) + test_fwtpm_rsa_hash_interop(TPM_ALG_SHA3_512, + WC_HASH_TYPE_SHA3_512, WC_MGF1SHA3_512); +#endif +} + +#endif + #ifndef NO_MAIN_DRIVER int main(int argc, char *argv[]) #else @@ -19387,6 +19644,13 @@ int fwtpm_unit_tests(int argc, char *argv[]) } #endif + if (argc == 1) { + test_fwtpm_hash_capabilities(); +#if !defined(NO_RSA) && defined(WOLFSSL_KEY_GEN) + test_fwtpm_rsa_hash_matrix(); +#endif + } + #ifdef WOLFTPM_FWTPM_UNIT_TEST if (argc == 2 && XSTRCMP(argv[1], "custom-io-hal") == 0) { test_fwtpm_custom_io_hal(); @@ -19415,6 +19679,17 @@ int fwtpm_unit_tests(int argc, char *argv[]) } #endif + if (argc == 2 && XSTRCMP(argv[1], "sha1-cap") == 0) { + test_fwtpm_hash_capabilities(); + return 0; + } +#if !defined(NO_RSA) && defined(WOLFSSL_KEY_GEN) + if (argc == 2 && XSTRCMP(argv[1], "sha1-oaep") == 0) { + test_fwtpm_rsa_hash_matrix(); + return 0; + } +#endif + if (argc == 2 && XSTRCMP(argv[1], "clear-seed") == 0) { test_fwtpm_clear(); return 0; From 5f5cc148a54f906bef95fd77e368ef18e008a7a7 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 26 Oct 2026 12:57:58 -0700 Subject: [PATCH 03/12] Validate fwTPM TestParms and report parameter errors --- src/fwtpm/fwtpm_command.c | 79 +++++++++++++++--- tests/fwtpm_unit_tests.c | 165 +++++++++++++++++++++++++++++++++++--- 2 files changed, 223 insertions(+), 21 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 7da97c2c..14b99962 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -2421,7 +2421,7 @@ static TPM_RC FwTestHashAlg(UINT16 hashAlg) { TPM_RC rc = TPM_RC_SUCCESS; - if (TPM2_GetHashDigestSize(hashAlg) <= 0) { + if (wc_HashGetDigestSize(FwGetWcHashType(hashAlg)) <= 0) { rc = TPM_RC_HASH; } return rc; @@ -2468,6 +2468,10 @@ static TPM_RC FwTestRsaScheme(const TPMT_RSA_SCHEME* scheme) else if (alg == TPM_ALG_RSASSA || alg == TPM_ALG_RSAPSS || alg == TPM_ALG_OAEP) { rc = FwTestHashAlg(scheme->details.anySig.hashAlg); + if (rc == 0 && alg != TPM_ALG_RSASSA && + FwGetMgfType(scheme->details.anySig.hashAlg) == WC_MGF1NONE) { + rc = TPM_RC_SCHEME; + } } else { rc = TPM_RC_SCHEME; @@ -2517,6 +2521,51 @@ static TPM_RC FwTestKdfScheme(const TPMT_KDF_SCHEME* kdf) } #endif /* HAVE_ECC */ +/* Reject an RSA key size or ECC curve this build cannot generate. + * TPMI_RSA_KEY_BITS has no default encoding and must describe a size the + * public and private buffers can hold. */ +static TPM_RC FwTestKeyParms(UINT16 type, const TPMU_PUBLIC_PARMS* params) +{ +#ifndef NO_RSA + if (type == TPM_ALG_RSA) { + UINT16 keyBits = params->rsaDetail.keyBits; + if (keyBits > MAX_RSA_KEY_BITS || + (keyBits != 1024 && keyBits != 2048 && + keyBits != 3072 && keyBits != 4096)) { + return TPM_RC_VALUE; + } + } +#endif +#ifdef HAVE_ECC + if (type == TPM_ALG_ECC && + FwGetWcCurveId(params->eccDetail.curveID) < 0) { + return TPM_RC_CURVE; + } +#endif + (void)type; + (void)params; + return TPM_RC_SUCCESS; +} + +/* Check an object creation template (inPublic, parameter 2). A decrypt-capable + * RSA key also needs an MGF1 for its nameAlg, which encrypts seeds for its + * children, imports and salted sessions with RSA-OAEP. */ +static TPM_RC FwTestCreateParms(const TPMT_PUBLIC* pub) +{ + TPM_RC rc = FwTestKeyParms(pub->type, &pub->parameters); +#ifndef NO_RSA + if (rc == 0 && pub->type == TPM_ALG_RSA && + (pub->objectAttributes & TPMA_OBJECT_decrypt) != 0 && + FwGetMgfType(pub->nameAlg) == WC_MGF1NONE) { + rc = TPM_RC_HASH; + } +#endif + if (rc != 0) { + rc |= TPM_RC_P | TPM_RC_2; + } + return rc; +} + /* Validate the TPMU_PUBLIC_PARMS body of a TPMT_PUBLIC_PARMS. The type * selector has already been consumed from the command packet. */ static TPM_RC FwTestPublicParms(TPM2_Packet* cmd, UINT16 algType) @@ -2553,15 +2602,12 @@ static TPM_RC FwTestPublicParms(TPM2_Packet* cmd, UINT16 algType) } #ifndef NO_RSA else if (algType == TPM_ALG_RSA) { - UINT16 keyBits = params.rsaDetail.keyBits; rc = FwTestSymDef(¶ms.rsaDetail.symmetric); if (rc == 0) { rc = FwTestRsaScheme(¶ms.rsaDetail.scheme); } - /* keyBits 0 selects the 2048-bit default in FwDeriveRsaPrimaryKey. */ - if (rc == 0 && keyBits != 0 && keyBits != 1024 && keyBits != 2048 && - keyBits != 3072 && keyBits != 4096) { - rc = TPM_RC_KEY_SIZE; + if (rc == 0) { + rc = FwTestKeyParms(algType, ¶ms); } /* exponent 0 selects the default; anything else must be odd > 2. */ if (rc == 0 && params.rsaDetail.exponent != 0 && @@ -2577,8 +2623,8 @@ static TPM_RC FwTestPublicParms(TPM2_Packet* cmd, UINT16 algType) if (rc == 0) { rc = FwTestEccScheme(¶ms.eccDetail.scheme); } - if (rc == 0 && FwGetWcCurveId(params.eccDetail.curveID) < 0) { - rc = TPM_RC_CURVE; + if (rc == 0) { + rc = FwTestKeyParms(algType, ¶ms); } if (rc == 0) { rc = FwTestKdfScheme(¶ms.eccDetail.kdf); @@ -2588,7 +2634,7 @@ static TPM_RC FwTestPublicParms(TPM2_Packet* cmd, UINT16 algType) else { rc = TPM_RC_TYPE; } - return rc; + return rc == TPM_RC_KEY_SIZE ? TPM_RC_VALUE : rc; } #ifdef WOLFTPM_MLDSA @@ -2978,6 +3024,12 @@ static TPM_RC FwCmd_TestParms(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, FwRspFinalize(rsp, TPM_ST_NO_SESSIONS, TPM_RC_SUCCESS); } + /* TestParms has one compound parameter. Format-one validation errors + * identify it; format-zero command errors have no parameter index. */ + if ((rc & RC_FMT1) != 0) { + rc |= TPM_RC_P | TPM_RC_1; + } + return rc; } @@ -4103,6 +4155,9 @@ static TPM_RC FwCmd_CreatePrimary(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (rc == 0) { rc = FwValidatePublicTemplate(&inPublic->publicArea, 0); } + if (rc == 0) { + rc = FwTestCreateParms(&inPublic->publicArea); + } if (rc == 0) { rc = FwValidateCreateAttributes(&inPublic->publicArea, NULL, sensDataSize); @@ -7029,6 +7084,9 @@ static TPM_RC FwCmd_Create(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (rc == 0) { rc = FwValidatePublicTemplate(&inPublic->publicArea, 0); } + if (rc == 0) { + rc = FwTestCreateParms(&inPublic->publicArea); + } if (rc == 0) { rc = FwValidateCreateAttributes(&inPublic->publicArea, parent, sensDataSize); @@ -9182,6 +9240,9 @@ static TPM_RC FwCmd_CreateLoaded(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (rc == 0) { rc = FwValidatePublicTemplate(&inPublic->publicArea, 0); } + if (rc == 0) { + rc = FwTestCreateParms(&inPublic->publicArea); + } /* Generate key -- same logic as Create */ if (rc == 0) { diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index 74234eda..2a37d1ec 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -7538,7 +7538,7 @@ static void test_fwtpm_testparms_mldsa_extmu_returns_ext_mu(void) rspSize = 0; rc = FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); AssertIntEQ(rc, TPM_RC_SUCCESS); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_EXT_MU); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_EXT_MU | TPM_RC_P | TPM_RC_1); FWTPM_Cleanup(&ctx); fwtpm_pass("TestParms MLDSA extMu=YES (EXT_MU):", 1); @@ -9935,7 +9935,8 @@ static void test_fwtpm_hash_mldsa_weak_prehash(void) PutU32BE(gCmd + 2, (UINT32)pos); rspSize = 0; FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); - AssertIntEQ(GetRspRC(gRsp), gPreHashCases[i].rc); + AssertIntEQ(GetRspRC(gRsp), gPreHashCases[i].rc == TPM_RC_SUCCESS ? + TPM_RC_SUCCESS : gPreHashCases[i].rc | TPM_RC_P | TPM_RC_1); } /* An unknown parameter set stays TPM_RC_PARMS, not TPM_RC_HASH. */ @@ -13910,6 +13911,14 @@ static void test_fwtpm_test_parms(void) { FWTPM_CTX ctx; int pos, rspSize; + static const UINT16 hashes[] = { TPM_ALG_SHA1, TPM_ALG_SHA256, + TPM_ALG_SHA384, TPM_ALG_SHA512, TPM_ALG_SHA3_256, + TPM_ALG_SHA3_384, TPM_ALG_SHA3_512, TPM_ALG_SM3_256 }; + UINT32 hashIndex; +#ifndef NO_RSA + static const UINT16 keyBits[] = { 0, 1024, 2048, 3072, 4096 }; + UINT32 i; +#endif memset(&ctx, 0, sizeof(ctx)); AssertIntEQ(fwtpm_test_startup(&ctx), 0); @@ -13927,6 +13936,34 @@ static void test_fwtpm_test_parms(void) FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + /* TPMI_RSA_KEY_BITS has no zero/default encoding, and TestParms must + * reject standard sizes that exceed this build's object buffers. */ + for (i = 0; i < sizeof(keyBits) / sizeof(keyBits[0]); i++) { + TPM_RC expected = keyBits[i] != 0 && + keyBits[i] <= MAX_RSA_KEY_BITS ? TPM_RC_SUCCESS : + TPM_RC_VALUE | TPM_RC_P | TPM_RC_1; + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, + TPM_CC_TestParms); + PutU16BE(gCmd + pos, TPM_ALG_RSA); pos += 2; + PutU16BE(gCmd + pos, TPM_ALG_NULL); pos += 2; + PutU16BE(gCmd + pos, TPM_ALG_NULL); pos += 2; + PutU16BE(gCmd + pos, keyBits[i]); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), expected); + /* Object creation applies the same check to inPublic. */ + if (expected != TPM_RC_SUCCESS) { + pos = BuildCreatePrimaryCmd(gCmd, TPM_ALG_RSA); + AssertIntEQ(GetU16BE(gCmd + 53), 2048); + PutU16BE(gCmd + 53, keyBits[i]); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_VALUE | TPM_RC_P | TPM_RC_2); + } + } + /* An unsupported RSA key size must be rejected, not accepted. */ pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_TestParms); PutU16BE(gCmd + pos, TPM_ALG_RSA); pos += 2; @@ -13937,7 +13974,7 @@ static void test_fwtpm_test_parms(void) PutU32BE(gCmd + 2, (UINT32)pos); rspSize = 0; FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_KEY_SIZE); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_VALUE | TPM_RC_P | TPM_RC_1); /* A bogus RSA signing scheme must be rejected. */ pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_TestParms); @@ -13947,7 +13984,7 @@ static void test_fwtpm_test_parms(void) PutU32BE(gCmd + 2, (UINT32)pos); rspSize = 0; FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_SCHEME); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SCHEME | TPM_RC_P | TPM_RC_1); #else /* An ECC-only build drops TPM_ALG_RSA from the TPMI_ALG_PUBLIC selectors * TestParms accepts, so it must report the type as unsupported rather than @@ -13961,7 +13998,7 @@ static void test_fwtpm_test_parms(void) PutU32BE(gCmd + 2, (UINT32)pos); rspSize = 0; FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_TYPE); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_TYPE | TPM_RC_P | TPM_RC_1); #endif /* !NO_RSA */ #ifdef HAVE_ECC @@ -13975,7 +14012,15 @@ static void test_fwtpm_test_parms(void) PutU32BE(gCmd + 2, (UINT32)pos); rspSize = 0; FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_CURVE); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_CURVE | TPM_RC_P | TPM_RC_1); + + /* CreatePrimary reports the same curve error on inPublic. */ + pos = BuildCreatePrimaryCmd(gCmd, TPM_ALG_ECC); + AssertIntEQ(GetU16BE(gCmd + 53), TPM_ECC_NIST_P256); + PutU16BE(gCmd + 53, 0x7F7F); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_CURVE | TPM_RC_P | TPM_RC_2); /* The DHKEM shape used by Encapsulate/Decapsulate must be accepted. */ pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_TestParms); @@ -14001,16 +14046,59 @@ static void test_fwtpm_test_parms(void) PutU32BE(gCmd + 2, (UINT32)pos); rspSize = 0; FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_HASH); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_HASH | TPM_RC_P | TPM_RC_1); #endif + /* A recognized hash identifier does not mean the build implements it. */ + for (hashIndex = 0; hashIndex < sizeof(hashes) / sizeof(hashes[0]); + hashIndex++) { + TPM_RC expected = wc_HashGetDigestSize(FwGetWcHashType( + hashes[hashIndex])) > 0 ? TPM_RC_SUCCESS : + TPM_RC_HASH | TPM_RC_P | TPM_RC_1; + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_TestParms); + PutU16BE(gCmd + pos, TPM_ALG_KEYEDHASH); pos += 2; + PutU16BE(gCmd + pos, TPM_ALG_HMAC); pos += 2; + PutU16BE(gCmd + pos, hashes[hashIndex]); pos += 2; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), expected); + #ifndef NO_RSA + /* PSS also needs an MGF1 selector for the same hash. */ + if (expected == TPM_RC_SUCCESS && + FwGetMgfType(hashes[hashIndex]) == WC_MGF1NONE) { + expected = TPM_RC_SCHEME | TPM_RC_P | TPM_RC_1; + } + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_TestParms); + PutU16BE(gCmd + pos, TPM_ALG_RSA); pos += 2; + PutU16BE(gCmd + pos, TPM_ALG_NULL); pos += 2; + PutU16BE(gCmd + pos, TPM_ALG_RSAPSS); pos += 2; + PutU16BE(gCmd + pos, hashes[hashIndex]); pos += 2; + PutU16BE(gCmd + pos, 2048); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), expected); + /* So does a decrypt key's nameAlg, which seeds RSA-OAEP. */ + if (expected == (TPM_RC_SCHEME | TPM_RC_P | TPM_RC_1)) { + pos = BuildCreatePrimaryCmd(gCmd, TPM_ALG_RSA); + AssertIntEQ(GetU16BE(gCmd + 37), TPM_ALG_SHA256); + PutU16BE(gCmd + 37, hashes[hashIndex]); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_HASH | TPM_RC_P | TPM_RC_2); + } + #endif + } + /* A hash algorithm is not a TPMI_ALG_PUBLIC selector. */ pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_TestParms); 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_TYPE); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_TYPE | TPM_RC_P | TPM_RC_1); /* Neither is a bare symmetric algorithm or TPM_ALG_NULL. */ pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_TestParms); @@ -14018,14 +14106,14 @@ static void test_fwtpm_test_parms(void) PutU32BE(gCmd + 2, (UINT32)pos); rspSize = 0; FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_TYPE); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_TYPE | TPM_RC_P | TPM_RC_1); pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_TestParms); PutU16BE(gCmd + pos, TPM_ALG_NULL); pos += 2; PutU32BE(gCmd + 2, (UINT32)pos); rspSize = 0; FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_TYPE); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_TYPE | TPM_RC_P | TPM_RC_1); #ifndef NO_AES /* SYMCIPHER: AES-128-CFB is supported, a bad key size is not. */ @@ -14047,7 +14135,7 @@ static void test_fwtpm_test_parms(void) PutU32BE(gCmd + 2, (UINT32)pos); rspSize = 0; FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_KEY_SIZE); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_VALUE | TPM_RC_P | TPM_RC_1); #else /* Without AES the only symmetric algorithm is unsupported. */ pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_TestParms); @@ -14058,9 +14146,16 @@ static void test_fwtpm_test_parms(void) PutU32BE(gCmd + 2, (UINT32)pos); rspSize = 0; FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_SYMMETRIC); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SYMMETRIC | TPM_RC_P | TPM_RC_1); #endif /* !NO_AES */ + /* Format-zero framing errors must not acquire parameter bits. */ + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, TPM2_HEADER_SIZE, + TPM_CC_TestParms); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_COMMAND_SIZE); + FWTPM_Cleanup(&ctx); #ifndef NO_RSA fwtpm_pass("TestParms(RSA-2048):", 0); @@ -19590,8 +19685,49 @@ static void test_fwtpm_rsa_sha3_no_mgf(void) } #endif +/* RSA-OAEP seed encryption works for every nameAlg an RSA decrypt key can be + * created with. */ +static void test_fwtpm_rsa_seed_hashes(void) +{ + static const UINT16 hashes[] = { TPM_ALG_SHA1, TPM_ALG_SHA256, + TPM_ALG_SHA384, TPM_ALG_SHA512, TPM_ALG_SHA3_256, + TPM_ALG_SHA3_384, TPM_ALG_SHA3_512 }; + FWTPM_CTX ctx; + FWTPM_Object* obj; + UINT32 keyH; + UINT32 i; + byte seed1[TPM_MAX_DIGEST_SIZE], seed2[TPM_MAX_DIGEST_SIZE]; + byte encSeed[MAX_RSA_KEY_BYTES]; + int seed1Sz, seed2Sz, encSeedSz; + + XMEMSET(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), TPM_RC_SUCCESS); + keyH = CreatePrimaryHelper(&ctx, TPM_ALG_RSA); + AssertIntNE(keyH, 0); + obj = &ctx.objects[keyH - TRANSIENT_FIRST]; + for (i = 0; i < sizeof(hashes) / sizeof(hashes[0]); i++) { + if (wc_HashGetDigestSize(FwGetWcHashType(hashes[i])) <= 0 || + FwGetMgfType(hashes[i]) == WC_MGF1NONE) { + continue; + } + obj->pub.nameAlg = hashes[i]; + seed1Sz = seed2Sz = encSeedSz = 0; + AssertIntEQ(FwEncryptSeed(&ctx, obj, NULL, 0, "SECRET", + seed1, (int)sizeof(seed1), &seed1Sz, + encSeed, (int)sizeof(encSeed), &encSeedSz), TPM_RC_SUCCESS); + AssertIntEQ(FwDecryptSeed(&ctx, obj, encSeed, (UINT16)encSeedSz, + NULL, 0, "SECRET", seed2, (int)sizeof(seed2), &seed2Sz), + TPM_RC_SUCCESS); + AssertIntEQ(seed1Sz, seed2Sz); + AssertIntEQ(XMEMCMP(seed1, seed2, seed1Sz), 0); + } + FWTPM_Cleanup(&ctx); + fwtpm_pass("RSA seed encryption for each nameAlg:", 0); +} + static void test_fwtpm_rsa_hash_matrix(void) { + test_fwtpm_rsa_seed_hashes(); #ifndef NO_SHA test_fwtpm_rsa_hash_interop(TPM_ALG_SHA1, WC_HASH_TYPE_SHA, WC_MGF1SHA1); @@ -19690,6 +19826,11 @@ int fwtpm_unit_tests(int argc, char *argv[]) } #endif + if (argc == 2 && XSTRCMP(argv[1], "test-parms") == 0) { + test_fwtpm_test_parms(); + return 0; + } + if (argc == 2 && XSTRCMP(argv[1], "clear-seed") == 0) { test_fwtpm_clear(); return 0; From 11481239bade306c36ee4c2a29bf14ac79ca590d Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 26 Oct 2026 13:12:17 -0700 Subject: [PATCH 04/12] Restore and validate protected fwTPM contexts --- configure.ac | 10 ++ docs/fwtpm/building.md | 2 +- docs/ja/fwtpm/building.md | 2 +- src/fwtpm/fwtpm_command.c | 291 +++++++++++++++++++++++++++++++------- src/fwtpm/include.am | 6 +- tests/fwtpm_unit_tests.c | 218 +++++++++++++++++++++++++++- wolftpm/tpm2_types.h | 10 +- 7 files changed, 483 insertions(+), 56 deletions(-) diff --git a/configure.ac b/configure.ac index c90bdc20..64595bf0 100644 --- a/configure.ac +++ b/configure.ac @@ -1087,6 +1087,16 @@ if test "x$ENABLED_FWTPM" = "xyes"; then OPTION_FLAGS="$OPTION_FLAGS -DWOLFTPM_FWTPM_BUILD" fi +# The fwtpm_server simulator serves tools without a resource manager, which +# keep every ContextLoad copy loaded; give it more transient object slots +# unless the build sets its own. +FWTPM_SERVER_CFLAGS="" +case "$CFLAGS $CPPFLAGS" in + *FWTPM_MAX_OBJECTS*) ;; + *) FWTPM_SERVER_CFLAGS="-DFWTPM_MAX_OBJECTS=7" ;; +esac +AC_SUBST([FWTPM_SERVER_CFLAGS]) + # The following AM_CONDITIONAL statements set flags for use in the Makefiles. diff --git a/docs/fwtpm/building.md b/docs/fwtpm/building.md index 1591b59d..ed75d28d 100644 --- a/docs/fwtpm/building.md +++ b/docs/fwtpm/building.md @@ -94,7 +94,7 @@ All macros are compile-time overridable (for example `-DFWTPM_MAX_OBJECTS=8`). |-------|---------|-------------| | `FWTPM_MAX_COMMAND_SIZE` | 4096 | Maximum command and response buffer size (bytes) | | `FWTPM_MAX_RANDOM_BYTES` | 48 | Maximum bytes per `GetRandom` call | -| `FWTPM_MAX_OBJECTS` | 3 | Maximum concurrently loaded transient objects | +| `FWTPM_MAX_OBJECTS` | 3 (autoconf `fwtpm_server`: 7) | Maximum concurrently loaded transient objects | | `FWTPM_MAX_PERSISTENT` | 8 | Maximum persistent objects (via `EvictControl`) | | `FWTPM_MAX_PRIVKEY_DER` | 1280 (256 with `NO_RSA`) | Maximum DER-encoded private key size (bytes) | | `FWTPM_MAX_HASH_SEQ` | 4 | Maximum concurrent hash and HMAC sequences | diff --git a/docs/ja/fwtpm/building.md b/docs/ja/fwtpm/building.md index 148f2274..342be151 100644 --- a/docs/ja/fwtpm/building.md +++ b/docs/ja/fwtpm/building.md @@ -94,7 +94,7 @@ make |-------|---------|-------------| | `FWTPM_MAX_COMMAND_SIZE` | 4096 | コマンドおよびレスポンスバッファの最大サイズ (バイト) | | `FWTPM_MAX_RANDOM_BYTES` | 48 | `GetRandom` 1 回あたりの最大バイト数 | -| `FWTPM_MAX_OBJECTS` | 3 | 同時にロードできるトランジェントオブジェクトの最大数 | +| `FWTPM_MAX_OBJECTS` | 3 (autoconf `fwtpm_server`: 7) | 同時にロードできるトランジェントオブジェクトの最大数 | | `FWTPM_MAX_PERSISTENT` | 8 | 永続オブジェクトの最大数 (`EvictControl` 経由) | | `FWTPM_MAX_PRIVKEY_DER` | 1280 (`NO_RSA` の場合は 256) | DER エンコードされた秘密鍵の最大サイズ (バイト) | | `FWTPM_MAX_HASH_SEQ` | 4 | 同時に実行できるハッシュおよび HMAC シーケンスの最大数 | diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 14b99962..9f07b739 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -4844,6 +4844,13 @@ static int FwCopyHmacContext(Hmac* src, Hmac* dst) #ifndef FWTPM_NO_CONTEXT #define FWTPM_CTX_MAGIC 0x4657544Du /* 'FWTM' */ #define FWTPM_CTX_VER 1u +#define FWTPM_CTX_SAVED_STCLEAR (TRANSIENT_FIRST + 2u) +#define FWTPM_OBJ_CTX_WRAPPED_MAX \ + ((MAX_CONTEXT_SIZE - 8 < FWTPM_MAX_COMMAND_SIZE - TPM2_HEADER_SIZE - 26) ? \ + MAX_CONTEXT_SIZE - 8 : FWTPM_MAX_COMMAND_SIZE - TPM2_HEADER_SIZE - 26) +#define FWTPM_OBJ_CTX_PLAIN_MAX \ + (FWTPM_OBJ_CTX_WRAPPED_MAX - AES_BLOCK_SIZE - WC_SHA256_DIGEST_SIZE) + #define FWTPM_CTX_SAVED_SEQUENCE (TRANSIENT_FIRST + 1u) #define FWTPM_SEQ_CTX_PLAIN_MAX \ (MAX_CONTEXT_SIZE - 8 - AES_BLOCK_SIZE - WC_SHA256_DIGEST_SIZE) @@ -5636,6 +5643,167 @@ static TPM_RC FwRestoreSequence(FWTPM_CTX* ctx, byte* plain, int plainSz, return TPM_RC_INTEGRITY; } +static int FwHandleHierarchyDisabled(FWTPM_CTX* ctx, TPM_HANDLE handle); + +/* A legacy-provenance object (hierarchy 0) has no single hierarchy seed. Its + * proof binds both the owner and endorsement seeds, so Clear or ChangeEPS + * invalidates the saved context. */ +static TPM_RC FwObjectContextProof(FWTPM_CTX* ctx, UINT32 hierarchy, + byte* proof, int proofSz) +{ + if (hierarchy != 0) { + return FwComputeProofValue(ctx, hierarchy, TPM_ALG_SHA256, proof, + proofSz); + } + if (TPM2_KDFa_ex(TPM_ALG_SHA256, ctx->ownerSeed, FWTPM_SEED_SIZE, + "LEGACY PROOF", ctx->endorsementSeed, FWTPM_SEED_SIZE, + NULL, 0, proof, proofSz) != proofSz) { + return TPM_RC_FAILURE; + } + return TPM_RC_SUCCESS; +} + +/* Save variable-length fields, never pointers or a reference to a live slot. + * The hierarchy proof inside the encrypted snapshot invalidates it when the + * hierarchy seed changes, independently of the per-boot context key. */ +static TPM_RC FwSerializeObjectContext(FWTPM_CTX* ctx, FWTPM_Object* obj, + byte* plain, int plainMax, int* plainSz) +{ + TPM2_Packet packet; + byte proof[WC_SHA256_DIGEST_SIZE]; + int pubStart; + TPM_RC rc; + + *plainSz = 0; + if (obj->authValue.size > sizeof(obj->authValue.buffer) || + obj->privKeySize < 0 || + obj->privKeySize > (int)sizeof(obj->privKey)) { + return TPM_RC_FAILURE; + } + rc = FwObjectContextProof(ctx, obj->hierarchy, proof, sizeof(proof)); + if (rc != 0) { + wc_ForceZero(proof, sizeof(proof)); + return rc; + } + XMEMSET(&packet, 0, sizeof(packet)); + packet.buf = plain; + packet.size = plainMax; + TPM2_Packet_AppendU32(&packet, obj->hierarchy); + TPM2_Packet_AppendBytes(&packet, proof, sizeof(proof)); + pubStart = packet.pos; + TPM2_Packet_AppendU16(&packet, 0); + TPM2_Packet_AppendPublicArea(&packet, &obj->pub); + if (!packet.overflow) { + FwStoreU16BE(plain + pubStart, (UINT16)(packet.pos - pubStart - 2)); + } + TPM2_Packet_AppendU16(&packet, obj->authValue.size); + TPM2_Packet_AppendBytes(&packet, obj->authValue.buffer, obj->authValue.size); + TPM2_Packet_AppendU16(&packet, (UINT16)obj->privKeySize); + TPM2_Packet_AppendBytes(&packet, obj->privKey, obj->privKeySize); + if (packet.overflow) { + rc = TPM_RC_SIZE; + } + else { + *plainSz = packet.pos; + } + wc_ForceZero(proof, sizeof(proof)); + return rc; +} + +static TPM_RC FwRestoreObjectContext(FWTPM_CTX* ctx, byte* plain, + int plainSz, UINT32 hierarchy, UINT32 savedMarker, UINT32* loadedHandle) +{ + TPM_RC rc = TPM_RC_SUCCESS; + TPM2_Packet packet; + FWTPM_DECLARE_VAR(pub, TPM2B_PUBLIC); + FWTPM_Object* obj = NULL; + byte proof[WC_SHA256_DIGEST_SIZE]; + UINT16 pubSz, authSz = 0, privateSz = 0; + int authPos = 0, privatePos = 0, publicEnd; + UINT32 expectedMarker; + + /* hierarchy + proof + public/auth/private size fields */ + if (plainSz < 4 + (int)sizeof(proof) + 6 || + FwLoadU32BE(plain) != hierarchy) { + return TPM_RC_INTEGRITY; + } + rc = FwObjectContextProof(ctx, hierarchy, proof, sizeof(proof)); + if (rc == 0 && TPM2_ConstantCompare(proof, plain + 4, sizeof(proof)) != 0) { + rc = TPM_RC_INTEGRITY; + } + wc_ForceZero(proof, sizeof(proof)); + if (rc != 0) return TPM_RC_INTEGRITY; + if (FwHandleHierarchyDisabled(ctx, hierarchy) || + (hierarchy == 0 && (ctx->shDisabled || ctx->ehDisabled))) { + return TPM_RC_HIERARCHY; + } + + FWTPM_CALLOC_VAR(pub, TPM2B_PUBLIC); + if (rc == 0) { + XMEMSET(&packet, 0, sizeof(packet)); + packet.buf = plain; + packet.pos = 4 + sizeof(proof); + pubSz = FwLoadU16BE(plain + packet.pos); + publicEnd = packet.pos + 2 + pubSz; + if (publicEnd > plainSz - 4) { + rc = TPM_RC_INTEGRITY; + } + else { + packet.size = publicEnd; + TPM2_Packet_ParsePublic(&packet, pub); + if (packet.overflow || packet.pos != publicEnd) { + rc = TPM_RC_INTEGRITY; + } + } + } + if (rc == 0) { + expectedMarker = (pub->publicArea.objectAttributes & + TPMA_OBJECT_stClear) != 0 ? FWTPM_CTX_SAVED_STCLEAR : + (UINT32)TRANSIENT_FIRST; + if (savedMarker != expectedMarker) { + rc = TPM_RC_INTEGRITY; + } + } + if (rc == 0) { + packet.size = plainSz; + TPM2_Packet_ParseU16(&packet, &authSz); + authPos = packet.pos; + if (authSz > sizeof(((FWTPM_Object*)0)->authValue.buffer) || + authPos + authSz > plainSz - 2) { + rc = TPM_RC_INTEGRITY; + } + } + if (rc == 0) { + packet.pos += authSz; + TPM2_Packet_ParseU16(&packet, &privateSz); + privatePos = packet.pos; + if (privateSz > FWTPM_MAX_PRIVKEY_DER || + privatePos + privateSz != plainSz) { + rc = TPM_RC_INTEGRITY; + } + } + if (rc == 0) { + obj = FwAllocObject(ctx, loadedHandle); + if (obj == NULL) { + rc = TPM_RC_OBJECT_MEMORY; + } + } + if (rc == 0) { + obj->hierarchy = hierarchy; + obj->pub = pub->publicArea; + obj->authValue.size = authSz; + XMEMCPY(obj->authValue.buffer, plain + authPos, authSz); + obj->privKeySize = privateSz; + XMEMCPY(obj->privKey, plain + privatePos, privateSz); + rc = FwComputePublicName(&obj->pub, &obj->name); + if (rc != 0) { + wc_ForceZero(obj, sizeof(*obj)); + } + } + FWTPM_FREE_VAR(pub); + return rc; +} + static TPM_RC FwCmd_ContextSave(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, TPM2_Packet* rsp, UINT16 cmdTag) { @@ -5649,6 +5817,7 @@ static TPM_RC FwCmd_ContextSave(FWTPM_CTX* ctx, TPM2_Packet* cmd, int isSession = 0; int isSequence = 0; FWTPM_Session* sess = NULL; + FWTPM_Object* saveObj = NULL; (void)cmdTag; @@ -5665,7 +5834,7 @@ static TPM_RC FwCmd_ContextSave(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* Validate handle */ if ((saveHandle & 0xFF000000) == TRANSIENT_FIRST) { - FWTPM_Object* saveObj = FwFindObject(ctx, saveHandle); + saveObj = FwFindObject(ctx, saveHandle); if (saveObj == NULL) { if (FwIsSequenceHandle(ctx, saveHandle)) { hierarchy = TPM_RH_NULL; @@ -5720,7 +5889,8 @@ static TPM_RC FwCmd_ContextSave(FWTPM_CTX* ctx, TPM2_Packet* cmd, * trusted handle lives only inside the integrity-protected blob. */ savedMarker = isSession ? saveHandle : (isSequence ? FWTPM_CTX_SAVED_SEQUENCE : - (UINT32)TRANSIENT_FIRST); + ((saveObj->pub.objectAttributes & TPMA_OBJECT_stClear) != 0 ? + FWTPM_CTX_SAVED_STCLEAR : (UINT32)TRANSIENT_FIRST)); TPM2_Packet_AppendU32(rsp, seqHi); TPM2_Packet_AppendU32(rsp, seqLo); TPM2_Packet_AppendU32(rsp, savedMarker); @@ -5797,28 +5967,45 @@ static TPM_RC FwCmd_ContextSave(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } else { - /* Object: HMAC + AES-CFB protected handle reference. The object - * remains in its live slot for this fwTPM context model. */ - byte objPlain[sizeof(UINT32)]; - byte wrappedBuf[AES_BLOCK_SIZE + sizeof(UINT32) + - WC_SHA256_DIGEST_SIZE]; - int wrappedSz = 0; - XMEMCPY(objPlain, &saveHandle, sizeof(UINT32)); - rc = FwWrapContextBlob(ctx, ctx->contextSeqCounter, - FWTPM_CTX_TYPE_OBJECT, - objPlain, (int)sizeof(objPlain), - wrappedBuf, (int)sizeof(wrappedBuf), &wrappedSz); + FWTPM_DECLARE_BUF(objPlain, FWTPM_OBJ_CTX_PLAIN_MAX); + FWTPM_DECLARE_BUF(wrappedBuf, FWTPM_OBJ_CTX_WRAPPED_MAX); + int plainSz = 0, wrappedSz = 0; + + FWTPM_ALLOC_BUF(objPlain, FWTPM_OBJ_CTX_PLAIN_MAX); + FWTPM_ALLOC_BUF(wrappedBuf, FWTPM_OBJ_CTX_WRAPPED_MAX); if (rc == 0) { - blobSz = 4 + 4 + (UINT16)wrappedSz; + rc = FwSerializeObjectContext(ctx, saveObj, objPlain, + FWTPM_OBJ_CTX_PLAIN_MAX, &plainSz); + } + if (rc == 0) { + rc = FwWrapContextBlob(ctx, ctx->contextSeqCounter, + FWTPM_CTX_TYPE_OBJECT, objPlain, plainSz, wrappedBuf, + FWTPM_OBJ_CTX_WRAPPED_MAX, &wrappedSz); + } + if (rc == 0 && rsp->pos + 10 + wrappedSz > rsp->size) { + rc = TPM_RC_SIZE; + } + if (rc == 0) { + blobSz = 8 + (UINT16)wrappedSz; TPM2_Packet_AppendU16(rsp, blobSz); - tmp32 = TPM2_Packet_SwapU32(FWTPM_CTX_MAGIC); - TPM2_Packet_AppendBytes(rsp, (byte*)&tmp32, 4); - tmp32 = TPM2_Packet_SwapU32(FWTPM_CTX_VER); - TPM2_Packet_AppendBytes(rsp, (byte*)&tmp32, 4); + TPM2_Packet_AppendU32(rsp, FWTPM_CTX_MAGIC); + TPM2_Packet_AppendU32(rsp, FWTPM_CTX_VER); TPM2_Packet_AppendBytes(rsp, wrappedBuf, wrappedSz); } - TPM2_ForceZero(objPlain, sizeof(objPlain)); - TPM2_ForceZero(wrappedBuf, sizeof(wrappedBuf)); + #ifdef WOLFTPM_SMALL_STACK + if (objPlain != NULL) + #endif + { + wc_ForceZero(objPlain, FWTPM_OBJ_CTX_PLAIN_MAX); + FWTPM_FREE_BUF(objPlain); + } + #ifdef WOLFTPM_SMALL_STACK + if (wrappedBuf != NULL) + #endif + { + wc_ForceZero(wrappedBuf, FWTPM_OBJ_CTX_WRAPPED_MAX); + FWTPM_FREE_BUF(wrappedBuf); + } } if (rc == 0) { @@ -5881,7 +6068,8 @@ static TPM_RC FwCmd_ContextLoad(FWTPM_CTX* ctx, TPM2_Packet* cmd, break; } } - if (liveIdx < 0) { + if (liveIdx < 0 || hierarchy != TPM_RH_NULL || + ctx->contextLiveHandle[liveIdx] != savedHandle) { rc = TPM_RC_INTEGRITY; } } @@ -5935,6 +6123,11 @@ static TPM_RC FwCmd_ContextLoad(FWTPM_CTX* ctx, TPM2_Packet* cmd, rc = TPM_RC_INTEGRITY; } } + /* The returned handle must identify the authenticated session, + * not caller-supplied outer metadata. */ + if (rc == 0 && restored.handle != savedHandle) { + rc = TPM_RC_INTEGRITY; + } /* Validate deserialized session fields */ if (rc == 0) { if (restored.sessionType != TPM_SE_HMAC && @@ -5970,45 +6163,47 @@ static TPM_RC FwCmd_ContextLoad(FWTPM_CTX* ctx, TPM2_Packet* cmd, } TPM2_ForceZero(&restored, sizeof(restored)); } - else if (savedHandle == (UINT32)TRANSIENT_FIRST) { - /* Object context: verify integrity + decrypt, then resolve the - * handle from the authenticated payload only. */ - int expectedWrapSz = AES_BLOCK_SIZE + (int)sizeof(UINT32) + - WC_SHA256_DIGEST_SIZE; - byte objPlain[sizeof(UINT32)]; - int objPlainSz = 0; - UINT32 origHandle = 0; + else if (savedHandle == (UINT32)TRANSIENT_FIRST || + savedHandle == FWTPM_CTX_SAVED_STCLEAR) { + FWTPM_DECLARE_BUF(objPlain, FWTPM_OBJ_CTX_PLAIN_MAX); + FWTPM_DECLARE_BUF(wrappedBuf, FWTPM_OBJ_CTX_WRAPPED_MAX); + int plainSz = 0; - /* Return the same error as an HMAC failure so the expected - * wrapped blob length is not disclosed to the caller */ - if ((int)dataLen != expectedWrapSz) { + if (dataLen > FWTPM_OBJ_CTX_WRAPPED_MAX || + dataLen < AES_BLOCK_SIZE + WC_SHA256_DIGEST_SIZE + 1) { rc = TPM_RC_INTEGRITY; } if (rc == 0) { - byte wrappedBuf[AES_BLOCK_SIZE + sizeof(UINT32) + - WC_SHA256_DIGEST_SIZE]; - TPM2_Packet_ParseBytes(cmd, wrappedBuf, (int)dataLen); + FWTPM_ALLOC_BUF(objPlain, FWTPM_OBJ_CTX_PLAIN_MAX); + FWTPM_ALLOC_BUF(wrappedBuf, FWTPM_OBJ_CTX_WRAPPED_MAX); + } + if (rc == 0) { + TPM2_Packet_ParseBytes(cmd, wrappedBuf, dataLen); rc = FwUnwrapContextBlob(ctx, loadSeq, FWTPM_CTX_TYPE_OBJECT, - wrappedBuf, (int)dataLen, objPlain, (int)sizeof(objPlain), - &objPlainSz); - TPM2_ForceZero(wrappedBuf, sizeof(wrappedBuf)); + wrappedBuf, dataLen, objPlain, FWTPM_OBJ_CTX_PLAIN_MAX, + &plainSz); if (rc != 0 && rc != TPM_RC_INTEGRITY) { rc = TPM_RC_INTEGRITY; } } - if (rc == 0 && objPlainSz != (int)sizeof(objPlain)) { - rc = TPM_RC_INTEGRITY; - } if (rc == 0) { - XMEMCPY(&origHandle, objPlain, sizeof(UINT32)); - if (FwFindObject(ctx, origHandle) == NULL) { - rc = TPM_RC_HANDLE; - } + rc = FwRestoreObjectContext(ctx, objPlain, plainSz, hierarchy, + savedHandle, &savedHandle); } - if (rc == 0) { - savedHandle = origHandle; + #ifdef WOLFTPM_SMALL_STACK + if (objPlain != NULL) + #endif + { + wc_ForceZero(objPlain, FWTPM_OBJ_CTX_PLAIN_MAX); + FWTPM_FREE_BUF(objPlain); + } + #ifdef WOLFTPM_SMALL_STACK + if (wrappedBuf != NULL) + #endif + { + wc_ForceZero(wrappedBuf, FWTPM_OBJ_CTX_WRAPPED_MAX); + FWTPM_FREE_BUF(wrappedBuf); } - TPM2_ForceZero(objPlain, sizeof(objPlain)); } else if (savedHandle == FWTPM_CTX_SAVED_SEQUENCE) { FWTPM_DECLARE_BUF(wrappedBuf, FWTPM_SEQ_CTX_WRAPPED_MAX); diff --git a/src/fwtpm/include.am b/src/fwtpm/include.am index a0186ac8..c41a3b37 100644 --- a/src/fwtpm/include.am +++ b/src/fwtpm/include.am @@ -16,8 +16,10 @@ src_fwtpm_fwtpm_server_SOURCES = \ src/tpm2_packet.c \ src/tpm2_crypto.c \ src/tpm2_param_enc.c -src_fwtpm_fwtpm_server_CFLAGS = -DWOLFTPM_FWTPM $(AM_CFLAGS) -src_fwtpm_fwtpm_server_CPPFLAGS = -DWOLFTPM_FWTPM $(AM_CPPFLAGS) +src_fwtpm_fwtpm_server_CFLAGS = -DWOLFTPM_FWTPM $(FWTPM_SERVER_CFLAGS) \ + $(AM_CFLAGS) +src_fwtpm_fwtpm_server_CPPFLAGS = -DWOLFTPM_FWTPM $(FWTPM_SERVER_CFLAGS) \ + $(AM_CPPFLAGS) src_fwtpm_fwtpm_server_LDADD = $(LIB_STATIC_ADD) if BUILD_FWTPM_TIS src_fwtpm_fwtpm_server_SOURCES += src/fwtpm/fwtpm_tis.c \ diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index 2a37d1ec..f80d33fc 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -17343,7 +17343,7 @@ static void test_fwtpm_context_save(void) static void test_fwtpm_context_load_replay_rejected(void) { FWTPM_CTX ctx; - int rspSize = 0, ctxSz, pos; + int rspSize = 0, ctxSz, pos, i; UINT32 keyH, sessH; byte savedObj[MAX_CONTEXT_SIZE]; byte savedSess[MAX_CONTEXT_SIZE]; @@ -17394,6 +17394,27 @@ static void test_fwtpm_context_load_replay_rejected(void) AssertIntGT(ctxSz, 0); memcpy(savedSess, gRsp + TPM2_HEADER_SIZE, ctxSz); + /* Outer metadata must match the protected session and replay record. + * Rejection must preserve the original context for a valid load. */ + for (i = 0; i < 3; i++) { + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_ContextLoad); + XMEMCPY(gCmd + pos, savedSess, ctxSz); pos += ctxSz; + PutU32BE(gCmd + 2, (UINT32)pos); + if (i == 0) { + PutU32BE(gCmd + TPM2_HEADER_SIZE + 8, sessH + 42); + } + else if (i == 1) { + PutU32BE(gCmd + TPM2_HEADER_SIZE + 8, POLICY_SESSION_FIRST); + } + else { + PutU32BE(gCmd + TPM2_HEADER_SIZE + 12, TPM_RH_OWNER); + } + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_INTEGRITY); + AssertIntEQ(ctx.contextLiveCount, 1); + } + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_ContextLoad); memcpy(gCmd + pos, savedSess, ctxSz); pos += ctxSz; PutU32BE(gCmd + 2, (UINT32)pos); @@ -17509,6 +17530,8 @@ static void test_fwtpm_context_object_roundtrip(void) int rspSize = 0, ctxSz, pos; UINT32 keyH, loadedH; byte savedObj[MAX_CONTEXT_SIZE]; + FWTPM_Object original; + int slot; memset(&ctx, 0, sizeof(ctx)); AssertIntEQ(fwtpm_test_startup(&ctx), 0); @@ -17527,6 +17550,14 @@ static void test_fwtpm_context_object_roundtrip(void) AssertIntGT(ctxSz, 0); memcpy(savedObj, gRsp + TPM2_HEADER_SIZE, ctxSz); + original = ctx.objects[keyH - TRANSIENT_FIRST]; + FlushHandle(&ctx, keyH); + /* Occupy the original slot: loading must restore the saved object into + * another slot, rather than alias this replacement object. */ + ctx.objects[0].used = 1; + ctx.objects[0].handle = keyH; + ctx.objects[0].pub.type = TPM_ALG_KEYEDHASH; + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_ContextLoad); memcpy(gCmd + pos, savedObj, ctxSz); pos += ctxSz; PutU32BE(gCmd + 2, (UINT32)pos); @@ -17534,7 +17565,16 @@ static void test_fwtpm_context_object_roundtrip(void) FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); loadedH = GetU32BE(gRsp + TPM2_HEADER_SIZE); - AssertIntEQ(loadedH, keyH); + AssertIntNE(loadedH, keyH); + slot = (int)(loadedH - TRANSIENT_FIRST); + AssertIntLE(slot, FWTPM_MAX_OBJECTS - 1); + AssertIntEQ(ctx.objects[slot].privKeySize, original.privKeySize); + AssertIntEQ(XMEMCMP(ctx.objects[slot].privKey, original.privKey, + original.privKeySize), 0); + AssertIntEQ(ctx.objects[slot].name.size, original.name.size); + AssertIntEQ(XMEMCMP(ctx.objects[slot].name.name, original.name.name, + original.name.size), 0); + AssertIntEQ(ctx.objects[0].pub.type, TPM_ALG_KEYEDHASH); /* Object is still usable: a ContextSave of the returned handle succeeds. */ BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 14, TPM_CC_ContextSave); @@ -17543,12 +17583,169 @@ static void test_fwtpm_context_object_roundtrip(void) FWTPM_ProcessCommand(&ctx, gCmd, 14, gRsp, &rspSize, 0); AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); - FlushHandle(&ctx, keyH); + /* Fill the remaining slots. A failed load must not displace an object. */ + for (slot = 0; slot < FWTPM_MAX_OBJECTS; slot++) { + ctx.objects[slot].used = 1; + ctx.objects[slot].handle = TRANSIENT_FIRST + (UINT32)slot; + } + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_ContextLoad); + XMEMCPY(gCmd + pos, savedObj, ctxSz); pos += ctxSz; + PutU32BE(gCmd + 2, pos); + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_OBJECT_MEMORY); + + /* Authentication and hierarchy checks apply even when slots are full. */ + ctx.shDisabled = 1; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_HIERARCHY); + ctx.shDisabled = 0; + ctx.ownerSeed[0] ^= 1; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_INTEGRITY); + ctx.ownerSeed[0] ^= 1; + gCmd[TPM2_HEADER_SIZE + 15] ^= 1; /* outer hierarchy */ + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_INTEGRITY); + gCmd[TPM2_HEADER_SIZE + 15] ^= 1; + gCmd[pos - 1] ^= 1; /* ciphertext MAC */ + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_INTEGRITY); + + wc_ForceZero(&original, sizeof(original)); FWTPM_Cleanup(&ctx); printf("Test fwTPM:\tContextLoad object roundtrip:\tPassed\n"); } #endif /* !FWTPM_NO_CONTEXT */ +#ifndef FWTPM_NO_CONTEXT +/* A legacy-provenance object (hierarchy 0) saves and loads, and its context + * depends on both the storage and endorsement hierarchies. */ +static void test_fwtpm_context_legacy_hierarchy(void) +{ + FWTPM_CTX ctx; + int rspSize = 0, ctxSz, pos; + UINT32 keyH, loadedH; + byte savedObj[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); + ctx.objects[keyH - TRANSIENT_FIRST].hierarchy = 0; + + BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 14, TPM_CC_ContextSave); + PutU32BE(gCmd + 10, keyH); + FWTPM_ProcessCommand(&ctx, gCmd, 14, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + ctxSz = rspSize - TPM2_HEADER_SIZE; + AssertIntGT(ctxSz, 0); + memcpy(savedObj, gRsp + TPM2_HEADER_SIZE, ctxSz); + FlushHandle(&ctx, keyH); + + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_ContextLoad); + XMEMCPY(gCmd + pos, savedObj, ctxSz); pos += ctxSz; + PutU32BE(gCmd + 2, (UINT32)pos); + ctx.ehDisabled = 1; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_HIERARCHY); + ctx.ehDisabled = 0; + ctx.endorsementSeed[0] ^= 1; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_INTEGRITY); + ctx.endorsementSeed[0] ^= 1; + ctx.ownerSeed[0] ^= 1; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_INTEGRITY); + ctx.ownerSeed[0] ^= 1; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + loadedH = GetU32BE(gRsp + TPM2_HEADER_SIZE); + AssertIntEQ(ctx.objects[loadedH - TRANSIENT_FIRST].hierarchy, 0); + + FWTPM_Cleanup(&ctx); + fwtpm_pass("ContextLoad legacy hierarchy object:", 0); +} +#endif /* !FWTPM_NO_CONTEXT */ + +#if !defined(FWTPM_NO_CONTEXT) && !defined(NO_RSA) && defined(WOLFSSL_KEY_GEN) +/* The largest RSA key must fit, retain non-empty authorization, and perform a + * private operation after every live copy has been flushed. */ +static void test_fwtpm_context_rsa_max(void) +{ + static const byte message[] = "restored RSA context"; + FWTPM_CTX ctx; + FWTPM_Object* obj; + RsaKey key; + byte saved[MAX_CONTEXT_SIZE]; + byte encrypted[MAX_RSA_KEY_BYTES]; + int pos, ctxSz, rspSize = 0, cipherSz; + UINT32 handle; + + XMEMSET(&ctx, 0, sizeof(ctx)); + XMEMSET(&key, 0, sizeof(key)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + obj = &ctx.objects[0]; + obj->used = 1; + obj->handle = TRANSIENT_FIRST; + obj->hierarchy = TPM_RH_OWNER; + obj->pub.type = TPM_ALG_RSA; + obj->pub.nameAlg = TPM_ALG_SHA256; + obj->pub.objectAttributes = TPMA_OBJECT_decrypt | TPMA_OBJECT_userWithAuth | + TPMA_OBJECT_noDA; + obj->pub.parameters.rsaDetail.keyBits = MAX_RSA_KEY_BITS; + obj->pub.parameters.rsaDetail.symmetric.algorithm = TPM_ALG_NULL; + obj->pub.parameters.rsaDetail.scheme.scheme = TPM_ALG_NULL; + obj->authValue.size = 4; + XMEMCPY(obj->authValue.buffer, "pass", 4); + AssertIntEQ(FwGenerateRsaKey(&ctx.rng, MAX_RSA_KEY_BITS, WC_RSA_EXPONENT, + &obj->pub.unique.rsa, obj->privKey, sizeof(obj->privKey), + &obj->privKeySize), 0); + AssertIntEQ(FwComputePublicName(&obj->pub, &obj->name), 0); + AssertIntEQ(FwImportRsaKey(obj, &key), 0); + cipherSz = wc_RsaPublicEncrypt_ex(message, sizeof(message) - 1, encrypted, + sizeof(encrypted), &key, &ctx.rng, WC_RSA_OAEP_PAD, + WC_HASH_TYPE_SHA256, WC_MGF1SHA256, NULL, 0); + AssertIntEQ(cipherSz, MAX_RSA_KEY_BITS / 8); + wc_FreeRsaKey(&key); + + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 14, TPM_CC_ContextSave); + PutU32BE(gCmd + pos, obj->handle); pos += 4; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + ctxSz = rspSize - TPM2_HEADER_SIZE; + AssertIntLE(ctxSz, sizeof(saved)); + XMEMCPY(saved, gRsp + TPM2_HEADER_SIZE, ctxSz); + FlushHandle(&ctx, obj->handle); + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_ContextLoad); + XMEMCPY(gCmd + pos, saved, ctxSz); pos += ctxSz; + PutU32BE(gCmd + 2, pos); + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + handle = GetU32BE(gRsp + TPM2_HEADER_SIZE); + + pos = BuildCmdHeader(gCmd, TPM_ST_SESSIONS, 0, TPM_CC_RSA_Decrypt); + PutU32BE(gCmd + pos, handle); pos += 4; + pos = AppendPwAuth(gCmd, pos, (const byte*)"pass", 4); + PutU16BE(gCmd + pos, (UINT16)cipherSz); pos += 2; + XMEMCPY(gCmd + pos, encrypted, cipherSz); pos += cipherSz; + PutU16BE(gCmd + pos, TPM_ALG_OAEP); pos += 2; + PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; + PutU16BE(gCmd + pos, 0); pos += 2; + PutU32BE(gCmd + 2, pos); + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(GetU16BE(gRsp + TPM2_HEADER_SIZE + 4), sizeof(message) - 1); + AssertIntEQ(XMEMCMP(gRsp + TPM2_HEADER_SIZE + 6, message, + sizeof(message) - 1), 0); + FWTPM_Cleanup(&ctx); + fwtpm_pass("ContextLoad restores maximum RSA key and auth:", 0); +} +#endif + /* The context type is bound into the blob MAC, so a blob wrapped for one * domain must not verify when unwrapped as the other. */ #ifndef FWTPM_NO_CONTEXT @@ -19831,6 +20028,17 @@ int fwtpm_unit_tests(int argc, char *argv[]) return 0; } +#ifndef FWTPM_NO_CONTEXT + if (argc == 2 && XSTRCMP(argv[1], "object-context") == 0) { + test_fwtpm_context_object_roundtrip(); + test_fwtpm_context_legacy_hierarchy(); +#if !defined(NO_RSA) && defined(WOLFSSL_KEY_GEN) + test_fwtpm_context_rsa_max(); +#endif + return 0; + } +#endif + if (argc == 2 && XSTRCMP(argv[1], "clear-seed") == 0) { test_fwtpm_clear(); return 0; @@ -20172,6 +20380,10 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_contextload_forged_object_blob_rejected(); test_fwtpm_contextload_short_blob_rejected(); test_fwtpm_context_object_roundtrip(); + test_fwtpm_context_legacy_hierarchy(); +#if !defined(NO_RSA) && defined(WOLFSSL_KEY_GEN) + test_fwtpm_context_rsa_max(); +#endif test_fwtpm_context_blob_domain_separation(); test_fwtpm_reset_command_client_flushes_transient(); #endif /* !FWTPM_NO_CONTEXT */ diff --git a/wolftpm/tpm2_types.h b/wolftpm/tpm2_types.h index 67464969..b46c536f 100644 --- a/wolftpm/tpm2_types.h +++ b/wolftpm/tpm2_types.h @@ -865,7 +865,15 @@ typedef int64_t INT64; #define PCR_SELECT_MAX ((IMPLEMENTATION_PCR+7)/8) #endif #ifndef MAX_CONTEXT_SIZE -#define MAX_CONTEXT_SIZE 2048 + /* Object contexts include the public and private key, not a live handle. + * Leave room for the largest classical key and for PQC public areas. */ + #ifdef WOLFTPM_PQC + #define MAX_CONTEXT_SIZE 8192 + #elif MAX_RSA_KEY_BITS > 2048 + #define MAX_CONTEXT_SIZE 4096 + #else + #define MAX_CONTEXT_SIZE 2048 + #endif #endif #ifndef MAX_DIGEST_BUFFER #define MAX_DIGEST_BUFFER 1024 From d5038fc926662f5cb88ef45ea2249ca9007a5edd Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 26 Oct 2026 15:42:11 -0700 Subject: [PATCH 05/12] Normalize ECC signature scalars across signing wrappers --- .gitignore | 2 + src/tpm2_wrap.c | 83 +++++------ tests/include.am | 17 +++ tests/signature_padding_tests.c | 248 ++++++++++++++++++++++++++++++++ wolftpm/tpm2_wrap.h | 6 + 5 files changed, 316 insertions(+), 40 deletions(-) create mode 100644 tests/signature_padding_tests.c diff --git a/.gitignore b/.gitignore index 4220a7da..9dc8ca2e 100644 --- a/.gitignore +++ b/.gitignore @@ -78,6 +78,8 @@ tests/fwtpm_hal_unit.log tests/fwtpm_check.sh.log tests/packet_auth.test tests/packet_auth.log +tests/signature_padding.test +tests/signature_padding.log tests/spdm_config.test tests/spdm_config.log tests/spdm_ctrl.test diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index cbf0b7ea..097fc830 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -5528,6 +5528,40 @@ int wolfTPM2_NVDeleteKey(WOLFTPM2_DEV* dev, TPM_HANDLE primaryHandle, return rc; } +/* Wrapper signatures use fixed-width R || S even when the TPM encodes a + * scalar without leading zero octets. Validate all lengths before writing. */ +static int wolfTPM2_ExtractEccSignature(const WOLFTPM2_KEY* key, + const TPMS_SIGNATURE_ECDSA* ecc, byte* sig, int* sigSz) +{ + int curveSize; + + if (key == NULL || ecc == NULL || sig == NULL || sigSz == NULL) { + return BAD_FUNC_ARG; + } + if (key->pub.publicArea.type != TPM_ALG_ECC) { + return BAD_FUNC_ARG; + } + curveSize = wolfTPM2_GetCurveSize( + key->pub.publicArea.parameters.eccDetail.curveID); + if (curveSize <= 0) { + return BAD_FUNC_ARG; + } + if (2 * curveSize > *sigSz || + ecc->signatureR.size > curveSize || + ecc->signatureS.size > curveSize || + ecc->signatureR.size > sizeof(ecc->signatureR.buffer) || + ecc->signatureS.size > sizeof(ecc->signatureS.buffer)) { + return BUFFER_E; + } + XMEMSET(sig, 0, 2 * curveSize); + XMEMCPY(sig + curveSize - ecc->signatureR.size, + ecc->signatureR.buffer, ecc->signatureR.size); + XMEMCPY(sig + 2 * curveSize - ecc->signatureS.size, + ecc->signatureS.buffer, ecc->signatureS.size); + *sigSz = 2 * curveSize; + return TPM_RC_SUCCESS; +} + /* sigAlg: TPM_ALG_RSASSA, TPM_ALG_RSAPSS, TPM_ALG_ECDSA or TPM_ALG_ECDAA */ /* hashAlg: TPM_ALG_SHA1, TPM_ALG_SHA256, TPM_ALG_SHA384 or TPM_ALG_SHA512 */ int wolfTPM2_SignHashScheme(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, @@ -5607,27 +5641,12 @@ int wolfTPM2_SignHashScheme(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, } if (key->pub.publicArea.type == TPM_ALG_ECC) { - TPMS_SIGNATURE_ECDSA* ecdsa = &signOut.signature.signature.ecdsa; - - /* Assemble R and S into signature (R then S) */ - sigOutSz = curveSize * 2; - if (sigOutSz > *sigSz || - ecdsa->signatureR.size > curveSize || - ecdsa->signatureS.size > curveSize) { - #ifdef DEBUG_WOLFTPM - printf("TPM2_Sign: ECC result buffer too small %d -> %d\n", - sigOutSz, *sigSz); - #endif - return BUFFER_E; + rc = wolfTPM2_ExtractEccSignature(key, + &signOut.signature.signature.ecdsa, sig, sigSz); + if (rc != TPM_RC_SUCCESS) { + return rc; } - /* Left-pad R */ - XMEMSET(sig, 0, curveSize - ecdsa->signatureR.size); - XMEMCPY(sig + curveSize - ecdsa->signatureR.size, - ecdsa->signatureR.buffer, ecdsa->signatureR.size); - /* Left-pad S */ - XMEMSET(sig + curveSize, 0, curveSize - ecdsa->signatureS.size); - XMEMCPY(sig + curveSize + (curveSize - ecdsa->signatureS.size), - ecdsa->signatureS.buffer, ecdsa->signatureS.size); + sigOutSz = *sigSz; } else if (key->pub.publicArea.type == TPM_ALG_RSA) { /* RSA signature size and buffer (with padding depending on scheme) */ @@ -6138,16 +6157,8 @@ int wolfTPM2_SignSequenceComplete(WOLFTPM2_DEV* dev, /* Extract signature based on algorithm */ if (signSeqCompleteOut.signature.sigAlg == TPM_ALG_ECDSA || signSeqCompleteOut.signature.sigAlg == TPM_ALG_ECDAA) { - int rSz = signSeqCompleteOut.signature.signature.ecdsa.signatureR.size; - int sSz = signSeqCompleteOut.signature.signature.ecdsa.signatureS.size; - if (*sigSz >= (rSz + sSz)) { - XMEMCPY(sig, signSeqCompleteOut.signature.signature.ecdsa.signatureR.buffer, rSz); - XMEMCPY(sig + rSz, signSeqCompleteOut.signature.signature.ecdsa.signatureS.buffer, sSz); - *sigSz = rSz + sSz; - } - else { - rc = BUFFER_E; - } + rc = wolfTPM2_ExtractEccSignature(key, + &signSeqCompleteOut.signature.signature.ecdsa, sig, sigSz); } else if (signSeqCompleteOut.signature.sigAlg == TPM_ALG_RSASSA || signSeqCompleteOut.signature.sigAlg == TPM_ALG_RSAPSS) { @@ -6517,16 +6528,8 @@ int wolfTPM2_SignDigest(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, /* Extract signature based on algorithm */ if (signDigestOut.signature.sigAlg == TPM_ALG_ECDSA || signDigestOut.signature.sigAlg == TPM_ALG_ECDAA) { - int rSz = signDigestOut.signature.signature.ecdsa.signatureR.size; - int sSz = signDigestOut.signature.signature.ecdsa.signatureS.size; - if (*sigSz >= (rSz + sSz)) { - XMEMCPY(sig, signDigestOut.signature.signature.ecdsa.signatureR.buffer, rSz); - XMEMCPY(sig + rSz, signDigestOut.signature.signature.ecdsa.signatureS.buffer, sSz); - *sigSz = rSz + sSz; - } - else { - rc = BUFFER_E; - } + rc = wolfTPM2_ExtractEccSignature(key, + &signDigestOut.signature.signature.ecdsa, sig, sigSz); } else if (signDigestOut.signature.sigAlg == TPM_ALG_RSASSA || signDigestOut.signature.sigAlg == TPM_ALG_RSAPSS) { diff --git a/tests/include.am b/tests/include.am index a9c4cddb..8061060e 100644 --- a/tests/include.am +++ b/tests/include.am @@ -103,3 +103,20 @@ tests_spdm_config_test_LDADD = $(spdm_config_support_objects) \ tests_spdm_config_test_DEPENDENCIES = src/libwolftpm.la endif endif + +if BUILD_EXAMPLES +if BUILD_WRAPPER +# Run the real signature wrappers and response decoder against wire fixtures. +check_PROGRAMS += tests/signature_padding.test +noinst_PROGRAMS += tests/signature_padding.test +tests_signature_padding_test_SOURCES = tests/signature_padding_tests.c +tests_signature_padding_test_CFLAGS = $(AM_CFLAGS) +signature_padding_tpm2_object = \ + $(filter %/tpm2.lo %-tpm2.lo,$(src_libwolftpm_la_OBJECTS)) +signature_padding_support_objects = \ + $(filter-out $(signature_padding_tpm2_object),$(src_libwolftpm_la_OBJECTS)) +tests_signature_padding_test_LDADD = $(signature_padding_support_objects) \ + $(src_libwolftpm_la_LIBADD) $(LIB_STATIC_ADD) +tests_signature_padding_test_DEPENDENCIES = src/libwolftpm.la +endif +endif diff --git a/tests/signature_padding_tests.c b/tests/signature_padding_tests.c new file mode 100644 index 00000000..b6bb110d --- /dev/null +++ b/tests/signature_padding_tests.c @@ -0,0 +1,248 @@ +/* signature_padding_tests.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include + +#include +#include + +/* Use the existing unit-test assertion convention. */ +#define Fail(description, result) do { \ + printf("\nERROR - %s line %d failed with:", __FILE__, __LINE__); \ + printf("\n expected: "); printf description; \ + printf("\n result: "); printf result; printf("\n\n"); \ + fflush(stdout); \ + abort(); \ +} while (0) +#define Assert(test, description, result) if (!(test)) Fail(description, result) +#define AssertInt(x, y, op, er) do { \ + int _x = (int)(x); \ + int _y = (int)(y); \ + Assert(_x op _y, ("%s " #op " %s", #x, #y), \ + ("%d(0x%x) " #er " %d(0x%x)", _x, _x, _y, _y)); \ +} while (0) +#define AssertIntEQ(x, y) AssertInt(x, y, ==, !=) + +static TPMT_SIGNATURE wireSignature; +static TPM_CC expectedCommand; +static TPM_RC wireError; + +/* Exercise the real command builders, response parser and wrappers without a + * TPM or socket. The response may have shorter ECC scalars than the curve. */ +#if defined(WOLFTPM_LINUX_DEV) + #define TPM2_LINUX_SendCommand test_signature_send +#elif defined(WOLFTPM_LINUX_DEV_AUTODETECT) + #define TPM2_LINUX_AUTODETECT_SendCommand test_signature_send +#elif defined(WOLFTPM_SWTPM) + #define TPM2_SWTPM_SendCommand test_signature_send +#elif defined(WOLFTPM_WINAPI) + #define TPM2_WinApi_SendCommand test_signature_send +#else + #define TPM2_TIS_SendCommand test_signature_send +#endif +#include "../src/tpm2.c" + +int test_signature_send(TPM2_CTX* ctx, TPM2_Packet* packet) +{ + TPM2_Packet rsp; + UINT32 code; + int i, authCount = 1; + + code = ((UINT32)packet->buf[6] << 24) | + ((UINT32)packet->buf[7] << 16) | + ((UINT32)packet->buf[8] << 8) | packet->buf[9]; + AssertIntEQ(code, expectedCommand); + TPM2_Packet_Init(ctx, &rsp); + if (wireError != TPM_RC_SUCCESS) { + TPM2_Packet_Finalize(&rsp, TPM_ST_NO_SESSIONS, wireError); + } + else { + #ifdef WOLFTPM_MLDSA_SIGN + if (code == TPM_CC_SignSequenceComplete) { + authCount = 2; + } + #endif + TPM2_Packet_AppendU32(&rsp, 8 + + wireSignature.signature.ecdsa.signatureR.size + + wireSignature.signature.ecdsa.signatureS.size); + TPM2_Packet_AppendSignature(&rsp, &wireSignature); + for (i = 0; i < authCount; i++) { + TPM2_Packet_AppendU16(&rsp, 0); /* nonce */ + TPM2_Packet_AppendU8(&rsp, 0); /* attributes */ + TPM2_Packet_AppendU16(&rsp, 0); /* password response */ + } + TPM2_Packet_Finalize(&rsp, TPM_ST_SESSIONS, TPM_RC_SUCCESS); + } + packet->size = rsp.pos; + return TPM_RC_SUCCESS; +} + +#if !defined(WOLFTPM_LINUX_DEV) && !defined(WOLFTPM_SWTPM) && \ + !defined(WOLFTPM_WINAPI) +#ifdef WOLFTPM_ADV_IO +static int test_signature_io(TPM2_CTX* ctx, INT32 read, UINT32 addr, + BYTE* buf, UINT16 sz, void* user) +{ + (void)read; + (void)addr; + (void)buf; +#else +static int test_signature_io(TPM2_CTX* ctx, const BYTE* tx, BYTE* rx, + UINT16 sz, void* user) +{ + (void)tx; + (void)rx; +#endif + (void)ctx; + (void)sz; + (void)user; + return TPM_RC_FAILURE; +} +#endif /* callback transports */ + +static int sign_fixture(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, int method, + byte* sig, int* sigSz) +{ + byte digest[TPM_SHA256_DIGEST_SIZE]; + XMEMSET(digest, 0x5A, sizeof(digest)); + if (method == 0) { + expectedCommand = TPM_CC_Sign; + return wolfTPM2_SignHashScheme(dev, key, digest, sizeof(digest), + sig, sigSz, wireSignature.sigAlg, TPM_ALG_SHA256); + } +#ifdef WOLFTPM_MLDSA_SIGN + if (method == 1) { + expectedCommand = TPM_CC_SignDigest; + return wolfTPM2_SignDigest(dev, key, digest, sizeof(digest), NULL, 0, + sig, sigSz); + } + expectedCommand = TPM_CC_SignSequenceComplete; + return wolfTPM2_SignSequenceComplete(dev, TRANSIENT_FIRST + 1, + key, NULL, 0, sig, sigSz); +#else + return BAD_FUNC_ARG; +#endif +} + +static void test_signature_case(int method, TPM_ECC_CURVE curve, + UINT16 scheme, int rSize, int sSize, int capacity, int expectedRc) +{ + WOLFTPM2_DEV dev; + WOLFTPM2_KEY key; + byte sig[2 * MAX_ECC_KEY_BYTES + 16]; + byte expected[sizeof(sig)]; + int i, rc, sigSz = capacity; + int curveSize = wolfTPM2_GetCurveSize(curve); + + XMEMSET(&dev, 0, sizeof(dev)); + XMEMSET(&key, 0, sizeof(key)); + XMEMSET(&wireSignature, 0, sizeof(wireSignature)); + XMEMSET(sig, 0xA5, sizeof(sig)); + XMEMSET(expected, 0xA5, sizeof(expected)); +#if defined(WOLFTPM_LINUX_DEV) || defined(WOLFTPM_SWTPM) || \ + defined(WOLFTPM_WINAPI) + rc = TPM2_Init_minimal(&dev.ctx); +#else + rc = TPM2_Init_ex(&dev.ctx, test_signature_io, NULL, 0); +#endif + AssertIntEQ(rc, TPM_RC_SUCCESS); + key.handle.hndl = TRANSIENT_FIRST; + key.pub.publicArea.type = TPM_ALG_ECC; + key.pub.publicArea.parameters.eccDetail.curveID = curve; + wireSignature.sigAlg = scheme; + wireSignature.signature.ecdsa.hash = TPM_ALG_SHA256; + wireSignature.signature.ecdsa.signatureR.size = (UINT16)rSize; + wireSignature.signature.ecdsa.signatureS.size = (UINT16)sSize; + for (i = 0; i < rSize; i++) { + wireSignature.signature.ecdsa.signatureR.buffer[i] = (byte)(i + 1); + } + for (i = 0; i < sSize; i++) { + wireSignature.signature.ecdsa.signatureS.buffer[i] = (byte)(i + 0x61); + } + if (expectedRc == TPM_RC_SUCCESS) { + XMEMSET(expected, 0, 2 * curveSize); + XMEMCPY(expected + curveSize - rSize, + wireSignature.signature.ecdsa.signatureR.buffer, rSize); + XMEMCPY(expected + 2 * curveSize - sSize, + wireSignature.signature.ecdsa.signatureS.buffer, sSize); + } + rc = sign_fixture(&dev, &key, method, sig, &sigSz); + AssertIntEQ(rc, expectedRc); + AssertIntEQ(sigSz, expectedRc == TPM_RC_SUCCESS ? 2 * curveSize : capacity); + AssertIntEQ(XMEMCMP(sig, expected, sizeof(sig)), 0); + TPM2_Cleanup(&dev.ctx); +} + +int main(void) +{ + static const TPM_ECC_CURVE curves[] = { + TPM_ECC_NIST_P256, TPM_ECC_NIST_P384, TPM_ECC_NIST_P521 + }; + int method, methods = 1, i, j, curveSize; + UINT16 scheme; + +#ifdef WOLFTPM_MLDSA_SIGN + methods = MAX_SESSION_NUM > 1 ? 3 : 2; +#endif + for (method = 0; method < methods; method++) { + for (i = 0; i < (int)(sizeof(curves) / sizeof(curves[0])); i++) { + curveSize = wolfTPM2_GetCurveSize(curves[i]); + if (curveSize > (int)sizeof( + wireSignature.signature.ecdsa.signatureR.buffer)) { + continue; + } + for (j = 0; j < 2; j++) { + scheme = j == 0 ? TPM_ALG_ECDSA : TPM_ALG_ECDAA; + test_signature_case(method, curves[i], scheme, + curveSize - 1, curveSize - 2, 2 * curveSize, + TPM_RC_SUCCESS); + test_signature_case(method, curves[i], scheme, + curveSize, curveSize, 2 * curveSize, TPM_RC_SUCCESS); + test_signature_case(method, curves[i], scheme, + curveSize - 1, curveSize - 2, 2 * curveSize - 1, BUFFER_E); + test_signature_case(method, curves[i], scheme, + curveSize - 1, curveSize - 2, -1, BUFFER_E); + if (curveSize < (int)sizeof( + wireSignature.signature.ecdsa.signatureR.buffer)) { + test_signature_case(method, curves[i], scheme, + curveSize + 1, curveSize - 2, 2 * curveSize, BUFFER_E); + test_signature_case(method, curves[i], scheme, + curveSize - 2, curveSize + 1, 2 * curveSize, BUFFER_E); + } + } + } + test_signature_case(method, (TPM_ECC_CURVE)0x7FFF, TPM_ALG_ECDSA, + 1, 1, sizeof(wireSignature.signature.ecdsa.signatureR.buffer) * 2, + BAD_FUNC_ARG); + wireError = TPM_RC_FAILURE; + test_signature_case(method, TPM_ECC_NIST_P256, TPM_ALG_ECDSA, + 1, 1, 64, TPM_RC_FAILURE); + wireError = TPM_RC_SUCCESS; + } + printf("PASS: fixed-width ECC signatures and error bounds across %d wrappers\n", + methods); + return 0; +} diff --git a/wolftpm/tpm2_wrap.h b/wolftpm/tpm2_wrap.h index 9d66ff15..8b19603e 100644 --- a/wolftpm/tpm2_wrap.h +++ b/wolftpm/tpm2_wrap.h @@ -2341,6 +2341,8 @@ WOLFTPM_API int wolfTPM2_SignHash(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, /*! \ingroup wolfTPM2_Wrappers \brief Advanced helper function to sign arbitrary data using a TPM key, and specify the signature scheme and hashing algorithm + \note ECC signatures contain fixed-width R followed by S, each padded + to the signing key curve size. \return TPM_RC_SUCCESS: successful \return TPM_RC_FAILURE: generic failure (check TPM IO and TPM return code) @@ -2516,6 +2518,8 @@ WOLFTPM_API int wolfTPM2_SignSequenceUpdate(WOLFTPM2_DEV* dev, /*! \ingroup wolfTPM2_Wrappers \brief Post-Quantum Cryptography: Complete a signing sequence + \note ECC signatures contain fixed-width R followed by S, each padded + to the signing key curve size. \return 0 on success, negative on error \param dev Device structure \param sequenceHandle Sequence handle from SignSequenceStart @@ -2616,6 +2620,8 @@ WOLFTPM_API int wolfTPM2_VerifySequenceComplete(WOLFTPM2_DEV* dev, /*! \ingroup wolfTPM2_Wrappers \brief Post-Quantum Cryptography: Sign a digest with context support + \note ECC signatures contain fixed-width R followed by S, each padded + to the signing key curve size. \return 0 on success, negative on error \param dev Device structure \param key Signing key From 0635bed746ad259a7ed685de5082f4b7a0b81dff Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 26 Oct 2026 15:42:10 -0700 Subject: [PATCH 06/12] Include fwTPM sequence handles in capability queries --- src/fwtpm/fwtpm_command.c | 28 ++++++++- tests/fwtpm_unit_tests.c | 119 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 2 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 9f07b739..7ded545e 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -1727,6 +1727,12 @@ static int FwSelectCapabilityHandle(const FWTPM_CTX* ctx, UINT32 handleClass, if (handleClass == HR_TRANSIENT) { slotCount = FWTPM_MAX_OBJECTS; + #ifndef FWTPM_NO_HASH_CMDS + slotCount += FWTPM_MAX_HASH_SEQ; + #endif + #ifdef WOLFTPM_MLDSA + slotCount += FWTPM_MAX_SIGN_SEQ; + #endif } else if (handleClass == HR_PERSISTENT) { slotCount = FWTPM_MAX_PERSISTENT; @@ -1745,8 +1751,26 @@ static int FwSelectCapabilityHandle(const FWTPM_CTX* ctx, UINT32 handleClass, used = 0; candidate = 0; if (handleClass == HR_TRANSIENT) { - used = ctx->objects[idx].used; - candidate = ctx->objects[idx].handle; + if (idx < FWTPM_MAX_OBJECTS) { + used = ctx->objects[idx].used; + candidate = ctx->objects[idx].handle; + } + #ifndef FWTPM_NO_HASH_CMDS + else if (idx < FWTPM_MAX_OBJECTS + FWTPM_MAX_HASH_SEQ) { + used = ctx->hashSeq[idx - FWTPM_MAX_OBJECTS].used; + candidate = ctx->hashSeq[idx - FWTPM_MAX_OBJECTS].handle; + } + #endif + #ifdef WOLFTPM_MLDSA + else { + int seqIdx = idx - FWTPM_MAX_OBJECTS; + #ifndef FWTPM_NO_HASH_CMDS + seqIdx -= FWTPM_MAX_HASH_SEQ; + #endif + used = ctx->signSeq[seqIdx].used; + candidate = ctx->signSeq[seqIdx].handle; + } + #endif } else if (handleClass == HR_PERSISTENT) { used = ctx->persistent[idx].used; diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index f80d33fc..1838b9b4 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -1796,6 +1796,122 @@ static void test_fwtpm_getcap_handles_ordered(void) (void)remove(FWTPM_NV_FILE); fwtpm_pass("GetCapability(HANDLES) ordered paging:", 0); } +#if !defined(FWTPM_NO_HASH_CMDS) || defined(WOLFTPM_MLDSA) +/* All transient pools participate in one ordered capability page. Hash/HMAC/ + * event sequences share hashSeq; sign and verify sequences share signSeq. */ +static void test_fwtpm_getcap_sequence_handles(void) +{ + FWTPM_CTX ctx; + static const UINT32 expected[] = { + #ifndef FWTPM_NO_HASH_CMDS + TRANSIENT_FIRST + 5u, + #endif + #ifdef WOLFTPM_MLDSA + TRANSIENT_FIRST + 10u, + #endif + TRANSIENT_FIRST + 16u, + #if defined(WOLFTPM_MLDSA) && FWTPM_MAX_SIGN_SEQ >= 2 + TRANSIENT_FIRST + 24u, + #endif + #if !defined(FWTPM_NO_HASH_CMDS) && FWTPM_MAX_HASH_SEQ >= 2 + TRANSIENT_FIRST + 32u, + #endif + #if !defined(FWTPM_NO_HASH_CMDS) && FWTPM_MAX_HASH_SEQ >= 3 + TRANSIENT_FIRST + 48u, + #endif + }; + UINT32 count = sizeof(expected) / sizeof(expected[0]); + UINT32 i, cursor, handle; + byte moreData; + int pos, rspSize = 0; + + (void)remove(FWTPM_NV_FILE); + XMEMSET(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); +#ifndef FWTPM_NO_HASH_CMDS + /* First verify a real sequence allocation and flush through commands. */ + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 14, + TPM_CC_HashSequenceStart); + PutU16BE(gCmd + pos, 0); pos += 2; + PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + handle = GetU32BE(gRsp + TPM2_HEADER_SIZE); + AssertIntEQ(getcap_handle_page(&ctx, TRANSIENT_FIRST, &moreData), handle); + AssertIntEQ(moreData, 0); + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 14, TPM_CC_FlushContext); + PutU32BE(gCmd + pos, handle); pos += 4; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + check_empty_handle_class(&ctx, TRANSIENT_FIRST); +#endif + + /* Metadata-only fixtures interleave pools independently of array order. */ + ctx.objects[0].used = 1; + ctx.objects[0].handle = TRANSIENT_FIRST + 16u; +#ifndef FWTPM_NO_HASH_CMDS + ctx.hashSeq[0].used = 1; + ctx.hashSeq[0].handle = TRANSIENT_FIRST + 5u; +#if FWTPM_MAX_HASH_SEQ >= 2 + ctx.hashSeq[1].used = 1; + ctx.hashSeq[1].isHmac = 1; + ctx.hashSeq[1].handle = TRANSIENT_FIRST + 32u; +#endif +#if FWTPM_MAX_HASH_SEQ >= 3 + ctx.hashSeq[2].used = 1; + ctx.hashSeq[2].hashAlg = TPM_ALG_NULL; + ctx.hashSeq[2].handle = TRANSIENT_FIRST + 48u; +#endif +#endif +#ifdef WOLFTPM_MLDSA + ctx.signSeq[0].used = 1; + ctx.signSeq[0].handle = TRANSIENT_FIRST + 10u; +#if FWTPM_MAX_SIGN_SEQ >= 2 + ctx.signSeq[1].used = 1; + ctx.signSeq[1].isVerifySeq = 1; + ctx.signSeq[1].handle = TRANSIENT_FIRST + 24u; +#endif +#endif + check_handle_zero_count(&ctx, TRANSIENT_FIRST); + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 22, TPM_CC_GetCapability); + PutU32BE(gCmd + pos, TPM_CAP_HANDLES); pos += 4; + PutU32BE(gCmd + pos, TRANSIENT_FIRST); pos += 4; + PutU32BE(gCmd + pos, count); pos += 4; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE + 5), count); + AssertIntEQ(gRsp[TPM2_HEADER_SIZE], 0); + for (i = 0; i < count; i++) { + AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE + 9 + 4 * i), expected[i]); + } + cursor = TRANSIENT_FIRST; + for (i = 0; i < count; i++) { + handle = getcap_handle_page(&ctx, cursor, &moreData); + AssertIntEQ(handle, expected[i]); + AssertIntEQ(moreData, i + 1 < count); + cursor = handle + 1; + } + check_empty_handle_class(&ctx, cursor); +#ifndef FWTPM_NO_HASH_CMDS + ctx.hashSeq[0].used = 0; +#else + ctx.signSeq[0].used = 0; +#endif + AssertIntEQ(getcap_handle_page(&ctx, TRANSIENT_FIRST, &moreData), + expected[1]); + /* No crypto contexts were initialized for the metadata-only fixtures. */ + XMEMSET(ctx.objects, 0, sizeof(ctx.objects)); +#ifndef FWTPM_NO_HASH_CMDS + XMEMSET(ctx.hashSeq, 0, sizeof(ctx.hashSeq)); +#endif +#ifdef WOLFTPM_MLDSA + XMEMSET(ctx.signSeq, 0, sizeof(ctx.signSeq)); +#endif + FWTPM_Cleanup(&ctx); + (void)remove(FWTPM_NV_FILE); + fwtpm_pass("GetCapability transient sequence handles:", 0); +} +#endif /* !FWTPM_NO_HASH_CMDS || WOLFTPM_MLDSA */ #endif /* handle capability test has at least two slots */ /* ================================================================== */ @@ -20153,6 +20269,9 @@ int fwtpm_unit_tests(int argc, char *argv[]) FWTPM_MAX_SESSIONS >= 2 || \ (!defined(FWTPM_NO_NV) && FWTPM_MAX_NV_INDICES >= 2) test_fwtpm_getcap_handles_ordered(); +#if !defined(FWTPM_NO_HASH_CMDS) || defined(WOLFTPM_MLDSA) + test_fwtpm_getcap_sequence_handles(); +#endif #endif /* handle capability test has at least two slots */ test_fwtpm_total_commands(); From 201d8e3089a801681f49e42a65f8fd8839931525 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 26 Oct 2026 13:03:50 -0700 Subject: [PATCH 07/12] Include entity authorization in fwTPM parameter encryption --- src/fwtpm/fwtpm_command.c | 34 +++-- tests/fwtpm_unit_tests.c | 282 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 303 insertions(+), 13 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 7ded545e..8be32f05 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -513,25 +513,33 @@ static void FwResetPolicyData(FWTPM_Session* sess); #endif #ifndef FWTPM_NO_PARAM_ENC -/* Build the parameter-encryption key = sessionKey || authValue, where authValue - * is the bind authValue when the encrypt session authorizes its own bind entity - * (TPM 2.0 Part 1 "Symmetric Encrypt" - the encrypt key keeps the authorized - * entity's authValue even when bound). authHandle is the handle the session - * authorizes (0 for a secondary encrypt-only session). */ +/* Parameter encryption uses sessionKey || the authorized entity's authValue + * regardless of session binding (TPM 2.0 Part 1 Sec.19.1). A secondary + * encrypt-only session has no authorized entity and uses sessionKey alone. + * TPM_RH_NULL denotes that case; zero is the valid PCR 0 handle. */ static int FwBuildParamEncKey(FWTPM_CTX* ctx, FWTPM_Session* sess, TPM_HANDLE authHandle, byte* keyBuf, int keyBufMax, int* keyBufSz) { + const byte* authValue = NULL; + int authValueSz = 0; int sz = sess->sessionKey.size; - if (sz < 0 || sz > keyBufMax) + + if (sz > (int)sizeof(sess->sessionKey.buffer) || sz > keyBufMax) + return TPM_RC_FAILURE; + if (authHandle != TPM_RH_NULL) { + FwLookupEntityAuth(ctx, authHandle, &authValue, &authValueSz); + } + if (authValueSz < 0 || authValueSz > keyBufMax - sz || + (authValueSz > 0 && authValue == NULL)) { return TPM_RC_FAILURE; + } XMEMCPY(keyBuf, sess->sessionKey.buffer, sz); - if (FwSessionIsBoundToEntity(ctx, sess, authHandle) && - sess->bindAuth.size > 0 && sz + sess->bindAuth.size <= keyBufMax) { - XMEMCPY(keyBuf + sz, sess->bindAuth.buffer, sess->bindAuth.size); - sz += sess->bindAuth.size; + if (authValueSz > 0) { + XMEMCPY(keyBuf + sz, authValue, authValueSz); + sz += authValueSz; } *keyBufSz = sz; - return 0; + return TPM_RC_SUCCESS; } /* Decrypt first TPM2B parameter of incoming command (param encryption) */ @@ -21063,9 +21071,9 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, TPM_RC rc = TPM_RC_SUCCESS; #ifndef FWTPM_NO_PARAM_ENC FWTPM_Session* decSess = NULL; /* Session with the decrypt attribute */ - TPM_HANDLE decAuthHandle = 0; /* Handle the decrypt session authorizes */ + TPM_HANDLE decAuthHandle = TPM_RH_NULL; /* Entity decrypt session authorizes */ FWTPM_Session* encSess = NULL; /* Session with the encrypt attribute */ - TPM_HANDLE encAuthHandle = 0; /* Handle the encrypt session authorizes */ + TPM_HANDLE encAuthHandle = TPM_RH_NULL; /* Entity encrypt session authorizes */ int doEncCmd = 0; /* Decrypt incoming encrypted param */ int doEncRsp = 0; /* Encrypt outgoing response param */ #endif diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index 1838b9b4..7e3d4d74 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -16688,6 +16688,273 @@ static void ComputeAuthHmac(const byte* key, UINT16 keySz, wc_HmacFree(&hmac); } +/* An authorizing session always includes the current entity authValue in + * parameter encryption, irrespective of its bind target (Part 1 Sec.19.1). + * Use non-empty, distinct auth/session/bind keys and check both directions. */ +static void test_fwtpm_param_enc_entity_auth(void) +{ + FWTPM_CTX ctx; + FWTPM_Object* obj; + FWTPM_Session* sess; + byte nonceCaller[16]; + byte nonceTPM[TPM_MAX_DIGEST_SIZE]; + byte cipher[16]; + byte expected[TPM_SHA256_DIGEST_SIZE]; + byte data[TPM_SHA256_DIGEST_SIZE]; + byte pHash[TPM_SHA256_DIGEST_SIZE]; + byte authHmac[TPM_SHA256_DIGEST_SIZE]; + byte encKey[TPM_MAX_DIGEST_SIZE * 2]; + byte input[4 + sizeof(TPM2B_NAME) + 2 + sizeof(cipher) + 2]; + UINT16 nonceSz, encKeySz, hmacKeySz; + UINT32 sessionH; + UINT8 attrs = TPMA_SESSION_continueSession | TPMA_SESSION_decrypt | + TPMA_SESSION_encrypt; + int mode, binding, pos, off, authStart, paramStart, rspSize; + Hmac hmac; + + XMEMSET(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + obj = &ctx.objects[0]; + obj->used = 1; + obj->handle = TRANSIENT_FIRST; + obj->hierarchy = TPM_RH_OWNER; + obj->pub.type = TPM_ALG_KEYEDHASH; + obj->pub.nameAlg = TPM_ALG_SHA256; + obj->pub.objectAttributes = TPMA_OBJECT_sign | TPMA_OBJECT_userWithAuth | + TPMA_OBJECT_noDA; + obj->pub.parameters.keyedHashDetail.scheme.scheme = TPM_ALG_HMAC; + obj->pub.parameters.keyedHashDetail.scheme.details.hmac.hashAlg = + TPM_ALG_SHA256; + obj->authValue.size = 4; + XMEMCPY(obj->authValue.buffer, "pass", 4); + obj->privKeySize = 3; + XMEMCPY(obj->privKey, "key", 3); + AssertIntEQ(FwComputePublicName(&obj->pub, &obj->name), 0); + XMEMSET(nonceCaller, 0x5C, sizeof(nonceCaller)); + XMEMSET(cipher, 0x31, sizeof(cipher)); + AssertIntEQ(wc_HmacInit(&hmac, NULL, INVALID_DEVID), 0); + AssertIntEQ(wc_HmacSetKey(&hmac, WC_SHA256, obj->privKey, + obj->privKeySize), 0); + AssertIntEQ(wc_HmacUpdate(&hmac, cipher, sizeof(cipher)), 0); + AssertIntEQ(wc_HmacFinal(&hmac, expected), 0); + wc_HmacFree(&hmac); + + for (mode = 0; mode < 2; mode++) { + #ifdef NO_AES + if (mode == 1) continue; + #endif + for (binding = 0; binding < 4; binding++) { + sessionH = StartParamEncSessionHelper(&ctx, TPM_SE_HMAC, + TPM_ALG_XOR, nonceTPM, &nonceSz); + AssertIntNE(sessionH, 0); + sess = &ctx.sessions[0]; + AssertIntEQ(sess->handle, sessionH); + if (mode == 1) { + sess->symmetric.algorithm = TPM_ALG_AES; + sess->symmetric.keyBits.aes = 128; + sess->symmetric.mode.aes = TPM_ALG_CFB; + } + /* unbound/unsalted, salted, bound here, bound elsewhere */ + if (binding > 0) { + sess->sessionKey.size = TPM_SHA256_DIGEST_SIZE; + XMEMSET(sess->sessionKey.buffer, 0xA3, sess->sessionKey.size); + } + if (binding >= 2) { + sess->isBound = 1; + sess->bindName = obj->name; + sess->bindAuth = obj->authValue; + if (binding == 3) { + sess->bindName.name[2] ^= 1; + sess->bindAuth.buffer[0] ^= 1; + } + } + encKeySz = sess->sessionKey.size; + XMEMCPY(encKey, sess->sessionKey.buffer, encKeySz); + XMEMCPY(encKey + encKeySz, obj->authValue.buffer, + obj->authValue.size); + encKeySz += obj->authValue.size; + hmacKeySz = binding == 2 ? sess->sessionKey.size : encKeySz; + XMEMSET(cipher, 0x31, sizeof(cipher)); + if (mode == 0) { + AssertIntEQ(TPM2_ParamEnc_XOR(TPM_ALG_SHA256, encKey, + encKeySz, nonceCaller, sizeof(nonceCaller), nonceTPM, + nonceSz, cipher, sizeof(cipher)), 0); + } + #ifndef NO_AES + else { + AssertIntEQ(TPM2_ParamEnc_AESCFB(TPM_ALG_SHA256, 128, encKey, + encKeySz, nonceCaller, sizeof(nonceCaller), nonceTPM, + nonceSz, cipher, sizeof(cipher), 1), 0); + } + #endif + off = 0; + PutU32BE(input + off, TPM_CC_HMAC); off += 4; + XMEMCPY(input + off, obj->name.name, obj->name.size); + off += obj->name.size; + PutU16BE(input + off, sizeof(cipher)); off += 2; + XMEMCPY(input + off, cipher, sizeof(cipher)); off += sizeof(cipher); + PutU16BE(input + off, TPM_ALG_SHA256); off += 2; + AssertIntEQ(wc_Sha256Hash(input, off, pHash), 0); + ComputeAuthHmac(encKey, hmacKeySz, pHash, nonceCaller, + sizeof(nonceCaller), nonceTPM, nonceSz, NULL, 0, attrs, + authHmac); + pos = BuildCmdHeader(gCmd, TPM_ST_SESSIONS, 0, TPM_CC_HMAC); + PutU32BE(gCmd + pos, obj->handle); pos += 4; + authStart = pos; pos += 4; + PutU32BE(gCmd + pos, sessionH); pos += 4; + PutU16BE(gCmd + pos, sizeof(nonceCaller)); pos += 2; + XMEMCPY(gCmd + pos, nonceCaller, sizeof(nonceCaller)); + pos += sizeof(nonceCaller); + gCmd[pos++] = attrs; + PutU16BE(gCmd + pos, sizeof(authHmac)); pos += 2; + XMEMCPY(gCmd + pos, authHmac, sizeof(authHmac)); + pos += sizeof(authHmac); + PutU32BE(gCmd + authStart, pos - authStart - 4); + paramStart = 4 + obj->name.size; + XMEMCPY(gCmd + pos, input + paramStart, off - paramStart); + pos += off - paramStart; + PutU32BE(gCmd + 2, pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(GetU16BE(gRsp + TPM2_HEADER_SIZE + 4), sizeof(data)); + XMEMCPY(data, gRsp + TPM2_HEADER_SIZE + 6, sizeof(data)); + off = RspSessOffset(gRsp, 0); + nonceSz = GetU16BE(gRsp + off); + XMEMCPY(nonceTPM, gRsp + off + 2, nonceSz); + if (mode == 0) { + AssertIntEQ(TPM2_ParamEnc_XOR(TPM_ALG_SHA256, encKey, + encKeySz, nonceTPM, nonceSz, nonceCaller, + sizeof(nonceCaller), data, sizeof(data)), 0); + } + #ifndef NO_AES + else { + AssertIntEQ(TPM2_ParamEnc_AESCFB(TPM_ALG_SHA256, 128, encKey, + encKeySz, nonceTPM, nonceSz, nonceCaller, + sizeof(nonceCaller), data, sizeof(data), 0), 0); + } + #endif + AssertIntEQ(XMEMCMP(data, expected, sizeof(data)), 0); + FlushHandle(&ctx, sessionH); + } + } + wc_ForceZero(encKey, sizeof(encKey)); + FWTPM_Cleanup(&ctx); + fwtpm_pass("Parameter encryption includes entity auth:", 0); +} + +/* PCR_FIRST is zero, which must not be confused with an encryption-only + * session having no authorized entity. Verify encrypted PCR0 event data. */ +static void test_fwtpm_param_enc_pcr_zero(void) +{ + FWTPM_CTX ctx; + FWTPM_Session* sess; + byte nonceCaller[16], nonceTPM[TPM_MAX_DIGEST_SIZE]; + byte plain[16], cipher[16], expected[TPM_SHA256_DIGEST_SIZE]; + byte key[2 * TPM_MAX_DIGEST_SIZE], input[4 + 4 + 2 + sizeof(cipher)]; + byte cpHash[TPM_SHA256_DIGEST_SIZE], mac[TPM_SHA256_DIGEST_SIZE]; + UINT16 nonceSz, keySz, hmacKeySz, alg; + UINT32 sessionH, count, i; + UINT8 attrs = TPMA_SESSION_continueSession | TPMA_SESSION_decrypt; + int mode, bound, off, pos, authStart, rspSize, digestSz, found; + + (void)remove(FWTPM_NV_FILE); + XMEMSET(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + ctx.pcrAuth[0].size = 4; + XMEMCPY(ctx.pcrAuth[0].buffer, "pass", 4); + XMEMSET(nonceCaller, 0x5C, sizeof(nonceCaller)); + XMEMSET(plain, 0x31, sizeof(plain)); + AssertIntEQ(wc_Sha256Hash(plain, sizeof(plain), expected), 0); + for (mode = 0; mode < 2; mode++) { + #ifdef NO_AES + if (mode == 1) continue; + #endif + for (bound = 0; bound < 2; bound++) { + sessionH = StartParamEncSessionHelper(&ctx, TPM_SE_HMAC, + TPM_ALG_XOR, nonceTPM, &nonceSz); + AssertIntNE(sessionH, 0); + sess = &ctx.sessions[0]; + AssertIntEQ(sess->handle, sessionH); + sess->sessionKey.size = TPM_SHA256_DIGEST_SIZE; + XMEMSET(sess->sessionKey.buffer, 0xA3, sess->sessionKey.size); + if (mode == 1) { + sess->symmetric.algorithm = TPM_ALG_AES; + sess->symmetric.keyBits.aes = 128; + sess->symmetric.mode.aes = TPM_ALG_CFB; + } + if (bound) { + sess->isBound = 1; + sess->bindHandle = PCR_FIRST; + sess->bindName.size = 4; + PutU32BE(sess->bindName.name, PCR_FIRST); + sess->bindAuth = ctx.pcrAuth[0]; + } + keySz = sess->sessionKey.size; + XMEMCPY(key, sess->sessionKey.buffer, keySz); + XMEMCPY(key + keySz, ctx.pcrAuth[0].buffer, ctx.pcrAuth[0].size); + keySz += ctx.pcrAuth[0].size; + hmacKeySz = bound ? sess->sessionKey.size : keySz; + XMEMCPY(cipher, plain, sizeof(cipher)); + if (mode == 0) { + AssertIntEQ(TPM2_ParamEnc_XOR(TPM_ALG_SHA256, key, keySz, + nonceCaller, sizeof(nonceCaller), nonceTPM, nonceSz, + cipher, sizeof(cipher)), 0); + } + #ifndef NO_AES + else { + AssertIntEQ(TPM2_ParamEnc_AESCFB(TPM_ALG_SHA256, 128, + key, keySz, nonceCaller, sizeof(nonceCaller), nonceTPM, + nonceSz, cipher, sizeof(cipher), 1), 0); + } + #endif + PutU32BE(input, TPM_CC_PCR_Event); + PutU32BE(input + 4, PCR_FIRST); /* PCR name is its four-byte handle */ + PutU16BE(input + 8, sizeof(cipher)); + XMEMCPY(input + 10, cipher, sizeof(cipher)); + AssertIntEQ(wc_Sha256Hash(input, sizeof(input), cpHash), 0); + ComputeAuthHmac(key, hmacKeySz, cpHash, nonceCaller, + sizeof(nonceCaller), nonceTPM, nonceSz, NULL, 0, attrs, mac); + pos = BuildCmdHeader(gCmd, TPM_ST_SESSIONS, 0, TPM_CC_PCR_Event); + PutU32BE(gCmd + pos, PCR_FIRST); pos += 4; + authStart = pos; pos += 4; + PutU32BE(gCmd + pos, sessionH); pos += 4; + PutU16BE(gCmd + pos, sizeof(nonceCaller)); pos += 2; + XMEMCPY(gCmd + pos, nonceCaller, sizeof(nonceCaller)); + pos += sizeof(nonceCaller); + gCmd[pos++] = attrs; + PutU16BE(gCmd + pos, sizeof(mac)); pos += 2; + XMEMCPY(gCmd + pos, mac, sizeof(mac)); pos += sizeof(mac); + PutU32BE(gCmd + authStart, (UINT32)(pos - authStart - 4)); + XMEMCPY(gCmd + pos, input + 8, sizeof(input) - 8); + pos += sizeof(input) - 8; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + off = TPM2_HEADER_SIZE + 4; + count = GetU32BE(gRsp + off); off += 4; + found = 0; + for (i = 0; i < count; i++) { + alg = GetU16BE(gRsp + off); off += 2; + digestSz = TPM2_GetHashDigestSize(alg); + AssertIntGT(digestSz, 0); + if (alg == TPM_ALG_SHA256) { + AssertIntEQ(XMEMCMP(gRsp + off, expected, sizeof(expected)), 0); + found++; + } + off += digestSz; + } + AssertIntEQ(found, 1); + FlushHandle(&ctx, sessionH); + } + } + wc_ForceZero(key, sizeof(key)); + FWTPM_Cleanup(&ctx); + (void)remove(FWTPM_NV_FILE); + fwtpm_pass("PCR0 parameter encryption includes auth:", 0); +} + /* A command that changes the bound entity's authValue must use the same * request-time bound-session key decision for its response HMAC. */ static void test_fwtpm_bound_auth_change_response_hmac(void) @@ -20155,6 +20422,19 @@ int fwtpm_unit_tests(int argc, char *argv[]) } #endif +#if !defined(FWTPM_NO_HASH_CMDS) && !defined(FWTPM_NO_PARAM_ENC) && \ + FWTPM_MAX_SESSIONS >= 2 + if (argc == 2 && XSTRCMP(argv[1], "pcr-zero-param-auth") == 0) { + test_fwtpm_param_enc_pcr_zero(); + return 0; + } + if (argc == 2 && XSTRCMP(argv[1], "param-auth") == 0) { + test_fwtpm_param_enc_entity_auth(); + test_fwtpm_param_enc_pcr_zero(); + return 0; + } +#endif + if (argc == 2 && XSTRCMP(argv[1], "clear-seed") == 0) { test_fwtpm_clear(); return 0; @@ -20514,6 +20794,8 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_hashcheck_ticket_generated_prefix(); #if !defined(FWTPM_NO_PARAM_ENC) && FWTPM_MAX_SESSIONS >= 2 test_fwtpm_param_enc_session_selection(); + test_fwtpm_param_enc_entity_auth(); + test_fwtpm_param_enc_pcr_zero(); test_fwtpm_param_enc_session_bound_to_auth(); test_fwtpm_bound_auth_change_response_hmac(); test_fwtpm_sequence_complete_response_hmac(); From 211d2bc93f46a24435978bbcc9331bf1cda9da29 Mon Sep 17 00:00:00 2001 From: David Garske Date: Tue, 27 Oct 2026 13:04:00 -0700 Subject: [PATCH 08/12] Verify HMACs of all fwTPM sessions in the authorization area --- src/fwtpm/fwtpm_command.c | 28 ++++++--- tests/fwtpm_unit_tests.c | 127 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 10 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 8be32f05..f0838a4a 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -21939,9 +21939,12 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, } /* HMAC session command verification per TPM 2.0 Part 1 Section 19.6. - * Compute cpHash and verify the command HMAC for each HMAC session. */ - for (hj = 0; hj < cmdAuthCnt && hj < (int)entry->authHandleCnt; hj++) { + * Compute cpHash and verify the command HMAC for each HMAC session, + * including audit/encrypt/decrypt sessions that authorize no handle. */ + for (hj = 0; hj < cmdAuthCnt; hj++) { + int isAuthSess = (hj < (int)entry->authHandleCnt); if (cmdAuths[hj].sess != NULL && (cmdAuths[hj].cmdHmacSize > 0 || + (!isAuthSess && cmdAuths[hj].sess->sessionKey.size > 0) || (cmdAuths[hj].sess->sessionType == TPM_SE_POLICY && (cmdAuths[hj].sess->isPasswordPolicy || cmdAuths[hj].sess->isAuthValuePolicy)))) { @@ -21962,7 +21965,7 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, #ifndef FWTPM_NO_PARAM_ENC /* Part 1 Sec.19.6.5: the first session's HMAC also covers the * nonceTPM of a different decrypt and encrypt session */ - if (hj == 0) { + if (hj == 0 && isAuthSess) { if (decSess != NULL && decSess != hSess) { nonceDecrypt = &decSess->nonceTPM; } @@ -21983,15 +21986,19 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, return TPM_RC_SUCCESS; } - /* Look up entity authValue for HMAC key */ - entityH = cmdHandles[hj]; - FwLookupEntityAuth(ctx, entityH, &authVal, &authValSz); + /* Look up entity authValue for HMAC key. A session that + * authorizes no handle uses only its session key. */ + entityH = TPM_RH_NULL; + if (isAuthSess) { + entityH = cmdHandles[hj]; + FwLookupEntityAuth(ctx, entityH, &authVal, &authValSz); + } /* PolicyPassword with no sessionKey (unsalted/unbound): * HMAC field contains plaintext authValue per spec Section 19.6.13. * Use fixed-length FwCtAuthCompare so the compare trip count can't * leak the auth value length (matches the TPM_RS_PW path). */ - if (hSess->sessionType == TPM_SE_POLICY && + if (isAuthSess && hSess->sessionType == TPM_SE_POLICY && hSess->isPasswordPolicy && hSess->sessionKey.size == 0) { if (FwCtAuthCompare(cmdAuths[hj].cmdHmac, @@ -22032,7 +22039,7 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, authVal = NULL; authValSz = 0; } - cmdAuths[hj].omitAuthValue = + cmdAuths[hj].omitAuthValue = isAuthSess && hSess->sessionType == TPM_SE_HMAC && FwSessionIsBoundToEntity(ctx, hSess, entityH); if (cmdAuths[hj].omitAuthValue) { @@ -22043,7 +22050,8 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, /* An empty HMAC is valid only for an empty HMAC key, or an empty * authValue under PolicyPassword (a password, not an HMAC) */ if (cmdAuths[hj].cmdHmacSize == 0 && authValSz == 0 && - (hSess->sessionKey.size == 0 || hSess->isPasswordPolicy)) { + (hSess->sessionKey.size == 0 || + (isAuthSess && hSess->isPasswordPolicy))) { TPM2_ForceZero(cpHash, sizeof(cpHash)); continue; } @@ -22079,7 +22087,7 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, "0x%x (CC=0x%x)\n", entityH, cmdCode); #endif #ifndef FWTPM_NO_DA - if (FwAuthSlotDAUse(ctx, entityH, 0, hSess, + if (isAuthSess && FwAuthSlotDAUse(ctx, entityH, 0, hSess, cmdAuths[hj].cmdHmacSize, &daHandle) != 0) { authRc = TPM_RC_AUTH_FAIL; if (FwDaRegisterFailure(ctx, daHandle)) { diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index 7e3d4d74..4f5bad53 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -16955,6 +16955,132 @@ static void test_fwtpm_param_enc_pcr_zero(void) fwtpm_pass("PCR0 parameter encryption includes auth:", 0); } +/* Command with nHandles owner handles, an optional password authorization, + * an HMAC session and a 2-byte parameter. Returns the command size. */ +static int BuildHmacSessionCmd(UINT32 cc, int nHandles, int withPw, + UINT32 sessionH, const byte* nonceCaller, int nonceCallerSz, + UINT8 attrs, const byte* hmac, int hmacSz) +{ + int pos, authStart, h; + pos = BuildCmdHeader(gCmd, TPM_ST_SESSIONS, 0, cc); + for (h = 0; h < nHandles; h++) { + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; + } + authStart = pos; pos += 4; + if (withPw) { + PutU32BE(gCmd + pos, TPM_RS_PW); pos += 4; + PutU16BE(gCmd + pos, 0); pos += 2; + gCmd[pos++] = attrs; + PutU16BE(gCmd + pos, 0); pos += 2; + } + PutU32BE(gCmd + pos, sessionH); pos += 4; + PutU16BE(gCmd + pos, (UINT16)nonceCallerSz); pos += 2; + XMEMCPY(gCmd + pos, nonceCaller, nonceCallerSz); pos += nonceCallerSz; + gCmd[pos++] = attrs; + PutU16BE(gCmd + pos, (UINT16)hmacSz); pos += 2; + XMEMCPY(gCmd + pos, hmac, hmacSz); pos += hmacSz; + PutU32BE(gCmd + authStart, (UINT32)(pos - authStart - 4)); + PutU16BE(gCmd + pos, 16); pos += 2; + PutU32BE(gCmd + 2, (UINT32)pos); + return pos; +} + +/* Every advertised command must verify the command HMAC of each HMAC session + * in its authorization area (Part 1 Sec.19.6), whether the session is the + * only one or follows a password authorization. A wrong HMAC is never + * accepted and leaves the session and its nonce unchanged. */ +static void test_fwtpm_session_hmac_sweep(void) +{ + FWTPM_CTX ctx; + FWTPM_Session* sess; + UINT32 tpmaList[512]; + UINT32 count, i, cc, sessionH; + byte nonceCaller[16]; + byte nonceTPM[TPM_MAX_DIGEST_SIZE]; + byte cpBuf[4 + 4 * 7 + 2]; + byte pHash[TPM_SHA256_DIGEST_SIZE]; + byte authHmac[TPM_SHA256_DIGEST_SIZE]; + UINT16 nonceSz; + UINT8 attrs = TPMA_SESSION_continueSession; + int pos, rspSize, h, nHandles, withPw, cpSz; + + (void)remove(FWTPM_NV_FILE); + XMEMSET(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_GetCapability); + PutU32BE(gCmd + pos, TPM_CAP_COMMANDS); pos += 4; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, 512); pos += 4; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + count = GetU32BE(gRsp + TPM2_HEADER_SIZE + 5); + AssertTrue(count <= (UINT32)(sizeof(tpmaList) / sizeof(tpmaList[0]))); + for (i = 0; i < count; i++) { + tpmaList[i] = GetU32BE(gRsp + TPM2_HEADER_SIZE + 9 + (int)(i * 4)); + } + + sessionH = StartParamEncSessionHelper(&ctx, TPM_SE_HMAC, TPM_ALG_XOR, + nonceTPM, &nonceSz); + AssertIntNE(sessionH, 0); + sess = &ctx.sessions[0]; + AssertIntEQ(sess->handle, sessionH); + sess->sessionKey.size = TPM_SHA256_DIGEST_SIZE; + XMEMSET(sess->sessionKey.buffer, 0xA3, sess->sessionKey.size); + XMEMSET(nonceCaller, 0x5C, sizeof(nonceCaller)); + + /* A rejected command changes no state, so one context serves them all */ + for (i = 0; i < count; i++) { + cc = TpmaCcToCmdCode(tpmaList[i]); + nHandles = (int)((tpmaList[i] >> 25) & 0x7); + cpSz = 0; + PutU32BE(cpBuf + cpSz, cc); cpSz += 4; + for (h = 0; h < nHandles; h++) { + /* Permanent handles are their own names */ + PutU32BE(cpBuf + cpSz, TPM_RH_OWNER); cpSz += 4; + } + PutU16BE(cpBuf + cpSz, 16); cpSz += 2; + AssertIntEQ(wc_Sha256Hash(cpBuf, cpSz, pHash), 0); + ComputeAuthHmac(sess->sessionKey.buffer, sess->sessionKey.size, + pHash, nonceCaller, sizeof(nonceCaller), nonceTPM, nonceSz, + NULL, 0, attrs, authHmac); + authHmac[0] ^= 0x01; + for (withPw = 0; withPw <= 1; withPw++) { + pos = BuildHmacSessionCmd(cc, nHandles, withPw, sessionH, + nonceCaller, sizeof(nonceCaller), attrs, authHmac, + sizeof(authHmac)); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + if (GetRspRC(gRsp) == TPM_RC_SUCCESS) { + printf("Session HMAC not verified: CC 0x%x\n", (unsigned)cc); + } + AssertIntNE(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(sess->handle, sessionH); + AssertIntEQ(sess->nonceTPM.size, nonceSz); + AssertIntEQ(XMEMCMP(sess->nonceTPM.buffer, nonceTPM, nonceSz), 0); + } + } + + /* Positive control: the same construction with the correct HMAC */ + PutU32BE(cpBuf, TPM_CC_GetRandom); + PutU16BE(cpBuf + 4, 16); + AssertIntEQ(wc_Sha256Hash(cpBuf, 6, pHash), 0); + ComputeAuthHmac(sess->sessionKey.buffer, sess->sessionKey.size, pHash, + nonceCaller, sizeof(nonceCaller), nonceTPM, nonceSz, NULL, 0, attrs, + authHmac); + pos = BuildHmacSessionCmd(TPM_CC_GetRandom, 0, 0, sessionH, nonceCaller, + sizeof(nonceCaller), attrs, authHmac, sizeof(authHmac)); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + FlushHandle(&ctx, sessionH); + FWTPM_Cleanup(&ctx); + (void)remove(FWTPM_NV_FILE); + fwtpm_pass("Session HMAC verified for every command:", 0); +} + /* A command that changes the bound entity's authValue must use the same * request-time bound-session key decision for its response HMAC. */ static void test_fwtpm_bound_auth_change_response_hmac(void) @@ -20796,6 +20922,7 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_param_enc_session_selection(); test_fwtpm_param_enc_entity_auth(); test_fwtpm_param_enc_pcr_zero(); + test_fwtpm_session_hmac_sweep(); test_fwtpm_param_enc_session_bound_to_auth(); test_fwtpm_bound_auth_change_response_hmac(); test_fwtpm_sequence_complete_response_hmac(); From c40d63fac44a037119679d4f86ba1142f37fe045 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 26 Oct 2026 15:42:10 -0700 Subject: [PATCH 09/12] Fix fwTPM Hash and StirRandom parameter handling --- src/fwtpm/fwtpm_command.c | 68 ++++----- tests/fwtpm_unit_tests.c | 292 +++++++++++++++++++++++++++++++++++--- 2 files changed, 306 insertions(+), 54 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index f0838a4a..67195e45 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -1591,19 +1591,20 @@ static TPM_RC FwCmd_StirRandom(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, TPM_RC rc = TPM_RC_SUCCESS; UINT16 inDataSize = 0; - (void)cmdTag; - - if (cmdSize < TPM2_HEADER_SIZE + 2) { - rc = TPM_RC_COMMAND_SIZE; + if (cmdTag == TPM_ST_SESSIONS) { + rc = FwSkipAuthArea(cmd, cmdSize); + } + if (rc == 0 && cmdSize - cmd->pos < 2) { + rc = TPM_RC_INSUFFICIENT | TPM_RC_P | TPM_RC_1; } if (rc == 0) { TPM2_Packet_ParseU16(cmd, &inDataSize); if (inDataSize > MAX_SYM_DATA) { - rc = TPM_RC_SIZE; + rc = TPM_RC_SIZE | TPM_RC_P | TPM_RC_1; } else if (cmd->pos + inDataSize > cmdSize) { - rc = TPM_RC_COMMAND_SIZE; + rc = TPM_RC_INSUFFICIENT | TPM_RC_P | TPM_RC_1; } } @@ -1627,7 +1628,7 @@ static TPM_RC FwCmd_StirRandom(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, } if (rc == 0) { - FwRspFinalize(rsp, TPM_ST_NO_SESSIONS, TPM_RC_SUCCESS); + FwRspNoParams(rsp, cmdTag); } return rc; @@ -10403,47 +10404,46 @@ static TPM_RC FwCmd_Hash(FWTPM_CTX* ctx, TPM2_Packet* cmd, FWTPM_ALLOC_BUF(dataBuf, FWTPM_MAX_DATA_BUF); - (void)ctx; - if (cmdSize < TPM2_HEADER_SIZE + 2) { - rc = TPM_RC_COMMAND_SIZE; + if (rc == 0 && cmdTag == TPM_ST_SESSIONS) { + rc = FwSkipAuthArea(cmd, cmdSize); + } + if (rc == 0 && cmdSize - cmd->pos < 2) { + rc = TPM_RC_INSUFFICIENT | TPM_RC_P | TPM_RC_1; } - /* No handles, no auth area for Hash */ - - /* Parse data (TPM2B_MAX_BUFFER) */ + /* Unmarshal data, hashAlg and hierarchy in parameter order so each + * failure identifies its own field, matching reference TPM diagnostics. */ if (rc == 0) { TPM2_Packet_ParseU16(cmd, &dataSize); if (dataSize > (UINT16)FWTPM_MAX_DATA_BUF) { - rc = TPM_RC_SIZE; + rc = TPM_RC_SIZE | TPM_RC_P | TPM_RC_1; + } + else if (cmdSize - cmd->pos < dataSize) { + rc = TPM_RC_INSUFFICIENT | TPM_RC_P | TPM_RC_1; } - } - if (rc == 0 && cmd->pos + dataSize > cmdSize) { - rc = TPM_RC_COMMAND_SIZE; } if (rc == 0) { TPM2_Packet_ParseBytes(cmd, dataBuf, dataSize); - - /* Parse hashAlg */ - TPM2_Packet_ParseU16(cmd, (UINT16*)&hashAlg); - - /* Parse hierarchy used for the validation ticket. */ - TPM2_Packet_ParseU32(cmd, &hierarchy); - if (hierarchy != TPM_RH_OWNER && - hierarchy != TPM_RH_ENDORSEMENT && - hierarchy != TPM_RH_PLATFORM && hierarchy != TPM_RH_NULL) { - rc = TPM_RC_VALUE; + if (cmdSize - cmd->pos < 2) { + rc = TPM_RC_INSUFFICIENT | TPM_RC_P | TPM_RC_2; } - + } + if (rc == 0) { + TPM2_Packet_ParseU16(cmd, (UINT16*)&hashAlg); wcHash = FwGetWcHashType(hashAlg); - if (wcHash == WC_HASH_TYPE_NONE) { - rc = TPM_RC_HASH; + digestSz = wc_HashGetDigestSize(wcHash); + if (digestSz <= 0) { + rc = TPM_RC_HASH | TPM_RC_P | TPM_RC_2; + } + else if (cmdSize - cmd->pos < 4) { + rc = TPM_RC_INSUFFICIENT | TPM_RC_P | TPM_RC_3; } } - if (rc == 0) { - digestSz = TPM2_GetHashDigestSize(hashAlg); - if (digestSz <= 0) { - rc = TPM_RC_HASH; + TPM2_Packet_ParseU32(cmd, &hierarchy); + if (hierarchy != TPM_RH_OWNER && hierarchy != TPM_RH_ENDORSEMENT && + hierarchy != TPM_RH_PLATFORM && hierarchy != TPM_RH_NULL) { + rc = TPM_RC_VALUE | TPM_RC_P | TPM_RC_3; } } diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index 4f5bad53..51466102 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -813,6 +813,89 @@ static void test_fwtpm_stirrandom(void) fwtpm_pass("StirRandom:", 0); } +/* Compare wire errors with reference TPM parameter locations. These are + * parameter-unmarshalling diagnostics, distinct from command-header errors. */ +static TPM_RC parameter_error_reply(FWTPM_CTX* ctx, int commandSize) +{ + int rspSize = 0; + PutU32BE(gCmd + 2, (UINT32)commandSize); + AssertIntEQ(FWTPM_ProcessCommand(ctx, gCmd, commandSize, gRsp, + &rspSize, 0), TPM_RC_SUCCESS); + return (TPM_RC)GetRspRC(gRsp); +} + +static void test_fwtpm_parameter_error_locations(void) +{ + FWTPM_CTX ctx; + int pos, rspSize = 0; +#ifndef FWTPM_NO_HASH_CMDS + static const UINT16 invalidHashes[] = { TPM_ALG_NULL, 0x7FFF, + #ifdef NO_SHA + TPM_ALG_SHA1, + #endif + #ifndef WOLFSSL_SM3 + TPM_ALG_SM3_256, + #endif + }; + UINT32 i; +#endif + + XMEMSET(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_StirRandom); + PutU16BE(gCmd + pos, MAX_SYM_DATA + 1); pos += 2; + AssertIntEQ(parameter_error_reply(&ctx, pos), + TPM_RC_SIZE | TPM_RC_P | TPM_RC_1); + + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_StirRandom); + AssertIntEQ(parameter_error_reply(&ctx, pos), + TPM_RC_INSUFFICIENT | TPM_RC_P | TPM_RC_1); + PutU16BE(gCmd + pos, 1); pos += 2; + AssertIntEQ(parameter_error_reply(&ctx, pos), + TPM_RC_INSUFFICIENT | TPM_RC_P | TPM_RC_1); + +#ifndef FWTPM_NO_HASH_CMDS + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_Hash); + PutU16BE(gCmd + pos, FWTPM_MAX_DATA_BUF + 1); pos += 2; + AssertIntEQ(parameter_error_reply(&ctx, pos), + TPM_RC_SIZE | TPM_RC_P | TPM_RC_1); + + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_Hash); + AssertIntEQ(parameter_error_reply(&ctx, pos), + TPM_RC_INSUFFICIENT | TPM_RC_P | TPM_RC_1); + PutU16BE(gCmd + pos, 1); pos += 2; + AssertIntEQ(parameter_error_reply(&ctx, pos), + TPM_RC_INSUFFICIENT | TPM_RC_P | TPM_RC_1); + PutU16BE(gCmd + pos - 2, 0); + AssertIntEQ(parameter_error_reply(&ctx, pos), + TPM_RC_INSUFFICIENT | TPM_RC_P | TPM_RC_2); + PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; + AssertIntEQ(parameter_error_reply(&ctx, pos), + TPM_RC_INSUFFICIENT | TPM_RC_P | TPM_RC_3); + PutU32BE(gCmd + pos, TPM_RH_LOCKOUT); pos += 4; + AssertIntEQ(parameter_error_reply(&ctx, pos), + TPM_RC_VALUE | TPM_RC_P | TPM_RC_3); + + for (i = 0; i < sizeof(invalidHashes) / sizeof(invalidHashes[0]); i++) { + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_Hash); + PutU16BE(gCmd + pos, 0); pos += 2; + PutU16BE(gCmd + pos, invalidHashes[i]); pos += 2; + PutU32BE(gCmd + pos, TPM_RH_NULL); pos += 4; + AssertIntEQ(parameter_error_reply(&ctx, pos), + TPM_RC_HASH | TPM_RC_P | TPM_RC_2); + } +#endif + + /* Header commandSize mismatch remains a format-zero command error. */ + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_StirRandom); + PutU16BE(gCmd + pos, 0); pos += 2; + PutU32BE(gCmd + 2, (UINT32)pos + 1); + AssertIntEQ(FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0), 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_COMMAND_SIZE); + FWTPM_Cleanup(&ctx); + fwtpm_pass("Hash and StirRandom parameter errors:", 0); +} + /* ================================================================== */ /* 5. GetCapability */ /* ================================================================== */ @@ -10911,6 +10994,177 @@ static UINT32 StartSessionHelper(FWTPM_CTX* ctx, UINT8 sessType) return GetU32BE(gRsp + TPM2_HEADER_SIZE); } +/* HMAC-SHA256 over a parameter hash, nonces and session attributes. */ +static void ComputeAuthHmac(const byte* key, UINT16 keySz, + const byte* pHash, const byte* nonceFirst, UINT16 nonceFirstSz, + const byte* nonceSecond, UINT16 nonceSecondSz, const byte* nonceExtra, + UINT16 nonceExtraSz, UINT8 attribs, byte* out) +{ + Hmac hmac; + AssertIntEQ(wc_HmacInit(&hmac, NULL, INVALID_DEVID), 0); + AssertIntEQ(wc_HmacSetKey(&hmac, WC_SHA256, key, keySz), 0); + AssertIntEQ(wc_HmacUpdate(&hmac, pHash, WC_SHA256_DIGEST_SIZE), 0); + AssertIntEQ(wc_HmacUpdate(&hmac, nonceFirst, nonceFirstSz), 0); + AssertIntEQ(wc_HmacUpdate(&hmac, nonceSecond, nonceSecondSz), 0); + if (nonceExtraSz > 0) { + AssertIntEQ(wc_HmacUpdate(&hmac, nonceExtra, nonceExtraSz), 0); + } + AssertIntEQ(wc_HmacUpdate(&hmac, &attribs, 1), 0); + AssertIntEQ(wc_HmacFinal(&hmac, out), 0); + wc_HmacFree(&hmac); +} + +/* A Hash/StirRandom audit session authorizes no entity: its HMAC key is + * the unbound, unsalted session key, which is empty in this fixture. */ +static int BuildNoHandleAuditCmd(FWTPM_CTX* ctx, TPM_CC code, UINT32 sessH, + const byte* params, int paramsSz, const byte* nonce) +{ + FWTPM_Session* sess = NULL; + byte cc[4], cpHash[WC_SHA256_DIGEST_SIZE], mac[WC_SHA256_DIGEST_SIZE]; + wc_Sha256 sha; + UINT8 attrs = TPMA_SESSION_audit | TPMA_SESSION_continueSession; + int i, pos, authStart; + + for (i = 0; i < FWTPM_MAX_SESSIONS; i++) { + if (ctx->sessions[i].used && ctx->sessions[i].handle == sessH) { + sess = &ctx->sessions[i]; + } + } + AssertNotNull(sess); + AssertIntEQ(sess->sessionKey.size, 0); + PutU32BE(cc, code); + AssertIntEQ(wc_InitSha256(&sha), 0); + AssertIntEQ(wc_Sha256Update(&sha, cc, sizeof(cc)), 0); + AssertIntEQ(wc_Sha256Update(&sha, params, paramsSz), 0); + AssertIntEQ(wc_Sha256Final(&sha, cpHash), 0); + wc_Sha256Free(&sha); + ComputeAuthHmac(NULL, 0, cpHash, nonce, WC_SHA256_DIGEST_SIZE, + sess->nonceTPM.buffer, sess->nonceTPM.size, NULL, 0, attrs, mac); + + pos = BuildCmdHeader(gCmd, TPM_ST_SESSIONS, 0, code); + authStart = pos; pos += 4; + PutU32BE(gCmd + pos, sessH); pos += 4; + PutU16BE(gCmd + pos, WC_SHA256_DIGEST_SIZE); pos += 2; + XMEMCPY(gCmd + pos, nonce, WC_SHA256_DIGEST_SIZE); + pos += WC_SHA256_DIGEST_SIZE; + gCmd[pos++] = attrs; + PutU16BE(gCmd + pos, sizeof(mac)); pos += 2; + XMEMCPY(gCmd + pos, mac, sizeof(mac)); pos += sizeof(mac); + PutU32BE(gCmd + authStart, (UINT32)(pos - authStart - 4)); + XMEMCPY(gCmd + pos, params, paramsSz); pos += paramsSz; + PutU32BE(gCmd + 2, (UINT32)pos); + return pos; +} + +static void CheckNoHandleAuditReply(TPM_CC code, int rspSize, + const byte* nonceCaller, const byte* expectedParams, int expectedSz) +{ + byte prefix[8], rpHash[WC_SHA256_DIGEST_SIZE]; + byte expectedMac[WC_SHA256_DIGEST_SIZE]; + wc_Sha256 sha; + UINT16 nonceSz, macSz; + UINT8 attrs; + int authPos; + + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(GetU16BE(gRsp), TPM_ST_SESSIONS); + AssertIntEQ(GetU32BE(gRsp + 2), rspSize); + AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE), expectedSz); + AssertIntEQ(XMEMCMP(gRsp + TPM2_HEADER_SIZE + 4, + expectedParams, expectedSz), 0); + authPos = TPM2_HEADER_SIZE + 4 + expectedSz; + nonceSz = GetU16BE(gRsp + authPos); + AssertIntEQ(nonceSz, WC_SHA256_DIGEST_SIZE); + attrs = gRsp[authPos + 2 + nonceSz]; + macSz = GetU16BE(gRsp + authPos + 3 + nonceSz); + AssertIntEQ(macSz, sizeof(expectedMac)); + AssertIntEQ(rspSize, authPos + 5 + nonceSz + macSz); + PutU32BE(prefix, TPM_RC_SUCCESS); + PutU32BE(prefix + 4, code); + AssertIntEQ(wc_InitSha256(&sha), 0); + AssertIntEQ(wc_Sha256Update(&sha, prefix, sizeof(prefix)), 0); + AssertIntEQ(wc_Sha256Update(&sha, gRsp + TPM2_HEADER_SIZE + 4, + expectedSz), 0); + AssertIntEQ(wc_Sha256Final(&sha, rpHash), 0); + wc_Sha256Free(&sha); + ComputeAuthHmac(NULL, 0, rpHash, gRsp + authPos + 2, nonceSz, + nonceCaller, WC_SHA256_DIGEST_SIZE, NULL, 0, attrs, expectedMac); + AssertIntEQ(XMEMCMP(gRsp + authPos + 5 + nonceSz, + expectedMac, sizeof(expectedMac)), 0); +} + +static void test_fwtpm_session_parameter_area(TPM_CC code) +{ + FWTPM_CTX ctx; + byte params[16], nonce[WC_SHA256_DIGEST_SIZE]; + byte expectedParams[2 + TPM_MAX_DIGEST_SIZE + 8]; + UINT32 sessH; + int pos, paramsSz, expectedSz, rspSize = 0; + + (void)remove(FWTPM_NV_FILE); + XMEMSET(&ctx, 0, sizeof(ctx)); + XMEMSET(params, 0, sizeof(params)); + XMEMSET(nonce, 0x42, sizeof(nonce)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + PutU16BE(params, 8); + XMEMSET(params + 2, 0xAB, 8); + paramsSz = 10; +#ifndef FWTPM_NO_HASH_CMDS + if (code == TPM_CC_Hash) { + PutU16BE(params + paramsSz, TPM_ALG_SHA256); paramsSz += 2; + PutU32BE(params + paramsSz, TPM_RH_NULL); paramsSz += 4; + } +#endif + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, code); + XMEMCPY(gCmd + pos, params, paramsSz); pos += paramsSz; + AssertIntEQ(parameter_error_reply(&ctx, pos), TPM_RC_SUCCESS); + expectedSz = (int)GetU32BE(gRsp + 2) - TPM2_HEADER_SIZE; + AssertIntLE(expectedSz, sizeof(expectedParams)); + XMEMCPY(expectedParams, gRsp + TPM2_HEADER_SIZE, expectedSz); + sessH = StartSessionHelper(&ctx, TPM_SE_HMAC); + AssertIntNE(sessH, 0); + pos = BuildNoHandleAuditCmd(&ctx, code, sessH, params, paramsSz, nonce); + AssertIntEQ(FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0), 0); + CheckNoHandleAuditReply(code, rspSize, nonce, expectedParams, expectedSz); + + /* The same locations apply after the authorization area: missing and + * oversized data are parameter1, then Hash's hash and hierarchy are2/3. */ + paramsSz = 0; + pos = BuildNoHandleAuditCmd(&ctx, code, sessH, params, paramsSz, nonce); + AssertIntEQ(parameter_error_reply(&ctx, pos), + TPM_RC_INSUFFICIENT | TPM_RC_P | TPM_RC_1); + PutU16BE(params, code == TPM_CC_StirRandom ? + MAX_SYM_DATA + 1 : FWTPM_MAX_DATA_BUF + 1); + pos = BuildNoHandleAuditCmd(&ctx, code, sessH, params, 2, nonce); + AssertIntEQ(parameter_error_reply(&ctx, pos), + TPM_RC_SIZE | TPM_RC_P | TPM_RC_1); +#ifndef FWTPM_NO_HASH_CMDS + if (code == TPM_CC_Hash) { + PutU16BE(params, 0); + pos = BuildNoHandleAuditCmd(&ctx, code, sessH, params, 2, nonce); + AssertIntEQ(parameter_error_reply(&ctx, pos), + TPM_RC_INSUFFICIENT | TPM_RC_P | TPM_RC_2); + PutU16BE(params + 2, TPM_ALG_NULL); + PutU32BE(params + 4, TPM_RH_NULL); + pos = BuildNoHandleAuditCmd(&ctx, code, sessH, params, 8, nonce); + AssertIntEQ(parameter_error_reply(&ctx, pos), + TPM_RC_HASH | TPM_RC_P | TPM_RC_2); + PutU16BE(params + 2, TPM_ALG_SHA256); + pos = BuildNoHandleAuditCmd(&ctx, code, sessH, params, 4, nonce); + AssertIntEQ(parameter_error_reply(&ctx, pos), + TPM_RC_INSUFFICIENT | TPM_RC_P | TPM_RC_3); + PutU32BE(params + 4, TPM_RH_LOCKOUT); + pos = BuildNoHandleAuditCmd(&ctx, code, sessH, params, 8, nonce); + AssertIntEQ(parameter_error_reply(&ctx, pos), + TPM_RC_VALUE | TPM_RC_P | TPM_RC_3); + } +#endif + FWTPM_Cleanup(&ctx); + (void)remove(FWTPM_NV_FILE); + fwtpm_pass(code == TPM_CC_StirRandom ? "StirRandom session parameters:" : + "Hash session parameters:", 0); +} + #ifdef WOLFTPM_FWTPM_UNIT_TEST int FwSessionIsBoundToEntity(FWTPM_CTX* ctx, const FWTPM_Session* sess, TPM_HANDLE handle); @@ -16668,26 +16922,6 @@ static void test_fwtpm_param_enc_session_selection(void) fwtpm_pass("Param-enc follows decrypt/encrypt session:", 0); } -/* HMAC-SHA256 over a parameter hash, nonces and session attributes. */ -static void ComputeAuthHmac(const byte* key, UINT16 keySz, - const byte* pHash, const byte* nonceFirst, UINT16 nonceFirstSz, - const byte* nonceSecond, UINT16 nonceSecondSz, const byte* nonceExtra, - UINT16 nonceExtraSz, UINT8 attribs, byte* out) -{ - Hmac hmac; - AssertIntEQ(wc_HmacInit(&hmac, NULL, INVALID_DEVID), 0); - AssertIntEQ(wc_HmacSetKey(&hmac, WC_SHA256, key, keySz), 0); - AssertIntEQ(wc_HmacUpdate(&hmac, pHash, WC_SHA256_DIGEST_SIZE), 0); - AssertIntEQ(wc_HmacUpdate(&hmac, nonceFirst, nonceFirstSz), 0); - AssertIntEQ(wc_HmacUpdate(&hmac, nonceSecond, nonceSecondSz), 0); - if (nonceExtraSz > 0) { - AssertIntEQ(wc_HmacUpdate(&hmac, nonceExtra, nonceExtraSz), 0); - } - AssertIntEQ(wc_HmacUpdate(&hmac, &attribs, 1), 0); - AssertIntEQ(wc_HmacFinal(&hmac, out), 0); - wc_HmacFree(&hmac); -} - /* An authorizing session always includes the current entity authValue in * parameter encryption, irrespective of its bind target (Part 1 Sec.19.1). * Use non-empty, distinct auth/session/bind keys and check both directions. */ @@ -20476,6 +20710,19 @@ int fwtpm_unit_tests(int argc, char *argv[]) #endif { printf("fwTPM Unit Tests\n"); + if (argc == 2 && XSTRCMP(argv[1], "session-parameter-errors") == 0) { + test_fwtpm_session_parameter_area(TPM_CC_StirRandom); + #ifndef FWTPM_NO_HASH_CMDS + test_fwtpm_session_parameter_area(TPM_CC_Hash); + #endif + return 0; + } + if (argc == 2 && XSTRCMP(argv[1], "parameter-errors") == 0) { + (void)remove(FWTPM_NV_FILE); + test_fwtpm_parameter_error_locations(); + return 0; + } + /* Remove stale NV state to ensure clean test runs */ (void)remove(FWTPM_NV_FILE); @@ -20650,6 +20897,11 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_getrandom(); test_fwtpm_getrandom_zero(); test_fwtpm_stirrandom(); + test_fwtpm_parameter_error_locations(); + test_fwtpm_session_parameter_area(TPM_CC_StirRandom); +#ifndef FWTPM_NO_HASH_CMDS + test_fwtpm_session_parameter_area(TPM_CC_Hash); +#endif /* GetCapability */ test_fwtpm_getcap_algorithms(); From fa179ace578cb404180db025b5ae557fe82be69c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 26 Oct 2026 13:05:10 -0700 Subject: [PATCH 10/12] Report fwTPM capabilities with durable clock and orderly state --- src/fwtpm/README.md | 16 + src/fwtpm/fwtpm.c | 58 +++ src/fwtpm/fwtpm_command.c | 254 ++++++++++- src/fwtpm/fwtpm_io.c | 67 ++- src/fwtpm/fwtpm_main.c | 29 ++ src/fwtpm/fwtpm_nv.c | 107 ++++- src/fwtpm/fwtpm_tis.c | 9 + src/fwtpm/fwtpm_tis_shm.c | 42 +- tests/fwtpm_hal_unit_tests.c | 9 + tests/fwtpm_unit_tests.c | 863 ++++++++++++++++++++++++++++++++++- wolftpm/fwtpm/fwtpm.h | 48 ++ wolftpm/fwtpm/fwtpm_tis.h | 5 +- 12 files changed, 1449 insertions(+), 58 deletions(-) diff --git a/src/fwtpm/README.md b/src/fwtpm/README.md index 047a0712..09b1b515 100644 --- a/src/fwtpm/README.md +++ b/src/fwtpm/README.md @@ -367,3 +367,19 @@ the v1.85 spec defines them generically: only. Classical digest signing (RSASSA, RSAPSS, ECDSA) over these new commands is not supported; use the existing `TPM2_Sign` / `TPM2_VerifySignature` commands for those schemes. + +Clock persistence requires a clock HAL returning milliseconds since the current +power-on. The standalone server registers a monotonic host clock. Server loops +checkpoint Clock while idle. Embedded integrations must call `FWTPM_Clock_Update()` at least every `FWTPM_CLOCK_POLL_MS` milliseconds; +custom transport waits, reads and writes must return within that interval. +`TPM_PT_CLOCK_UPDATE` reports the maximum NV checkpoint interval. Clock's `safe` flag stays clear after +an unclean shutdown until the prior boot's potentially reported range has passed +and the new value is committed. Orderly NV counters checkpoint on modulo-2 +rollover and at shutdown, and round forward on an unclean startup. +An orderly shutdown preserves Clock's safety flag separately; shutdown and +library cleanup cannot make an unsafe Clock safe before recovery. + +Existing NV journals remain readable. Legacy shutdown markers lack a Clock +safety flag and restore Clock conservatively as unsafe. Older firmware rejects +journals containing the extended orderly-shutdown marker; downgrades need a +compatible journal conversion. diff --git a/src/fwtpm/fwtpm.c b/src/fwtpm/fwtpm.c index 4c33737e..18c2d45a 100644 --- a/src/fwtpm/fwtpm.c +++ b/src/fwtpm/fwtpm.c @@ -36,6 +36,15 @@ #include #include +/* Clock resumes from its NV checkpoint and becomes safe one checkpoint + * interval later, unless the NV state kept it safe. */ +static void FwClockRestoreSafe(FWTPM_CTX* ctx) +{ + ctx->clockUnsafeUntil = ctx->clockNvValue > + ~(UINT64)0 - FWTPM_CLOCK_UPDATE_MS ? ~(UINT64)0 : + ctx->clockNvValue + FWTPM_CLOCK_UPDATE_MS; +} + int FWTPM_Init(FWTPM_CTX* ctx) { int rc; @@ -134,6 +143,8 @@ int FWTPM_Init(FWTPM_CTX* ctx) /* Initialize NV storage - loads existing state or creates fresh seeds */ #ifndef FWTPM_NO_NV if (rc == 0) { + /* A compaction during the load cannot establish Clock safety. */ + ctx->clockUnsafeUntil = ~(UINT64)0; rc = FWTPM_NV_Init(ctx); } #else @@ -152,6 +163,7 @@ int FWTPM_Init(FWTPM_CTX* ctx) if (rc == 0) { ctx->pcrAllocatedBanks = FWTPM_PCR_ALLOC_DEFAULT; ctx->endorsementProofVersion = 1; + ctx->clockSafe = 1; } #ifndef FWTPM_NO_DA if (rc == 0) { @@ -163,6 +175,13 @@ int FWTPM_Init(FWTPM_CTX* ctx) #endif #endif + /* The NV load keeps clockSafe only from a committed orderly marker. + * Fresh state has reported no Clock yet, so it is safe. */ + if (rc == TPM_RC_SUCCESS) { + ctx->clockNvValue = ctx->clockOffset; + FwClockRestoreSafe(ctx); + } + if (rc != 0) { if (rngInit) { wc_FreeRng(&ctx->rng); @@ -232,6 +251,45 @@ UINT64 FWTPM_Clock_GetMs(FWTPM_CTX* ctx) return now + ctx->clockOffset; } +int FWTPM_Clock_PowerOn(FWTPM_CTX* ctx) +{ + UINT64 halNow = 0; + if (ctx == NULL) { + return BAD_FUNC_ARG; + } + if (ctx->clockHal.get_ms != NULL) { + halNow = ctx->clockHal.get_ms(ctx->clockHal.ctx); + } + /* Powered-off time is not counted, whatever the HAL epoch. Unsigned + * wrap is intended, as in ClockSet. */ + ctx->clockOffset = ctx->clockNvValue - halNow; +#ifndef FWTPM_NO_NV + ctx->clockSafe = ctx->nvOrderly && ctx->clockSafe; +#else + /* Without NV, Clock restarts from its boot value and can roll back. */ + ctx->clockSafe = 0; +#endif + FwClockRestoreSafe(ctx); + return TPM_RC_SUCCESS; +} + +int FWTPM_Clock_Update(FWTPM_CTX* ctx) +{ + if (ctx == NULL) return BAD_FUNC_ARG; +#if !defined(FWTPM_NO_CLOCK) && !defined(FWTPM_NO_NV) + if (ctx->wasStarted && ctx->powerOn) { + UINT64 now = FWTPM_Clock_GetMs(ctx); + if (now >= ctx->clockNvValue && + (now - ctx->clockNvValue >= + FWTPM_CLOCK_UPDATE_MS - FWTPM_CLOCK_POLL_MS || + (!ctx->clockSafe && now >= ctx->clockUnsafeUntil))) { + return FWTPM_NV_SaveClock(ctx); + } + } +#endif + return TPM_RC_SUCCESS; +} + #ifndef FWTPM_NO_PP int FWTPM_PP_SetHAL(FWTPM_CTX* ctx, int (*get_pp)(void* halCtx), void* halCtx) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 67195e45..37db3c88 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -1108,6 +1108,8 @@ static TPM_RC FwCmd_Startup(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, int oldNvDataAllocated = 0; #endif + int wasOrderly = ctx->nvOrderly; + (void)cmdTag; if (cmdSize < TPM2_HEADER_SIZE + 2) { @@ -1250,7 +1252,8 @@ static TPM_RC FwCmd_Startup(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, oldAttrs = nv->nvPublic.attributes; oldWritten = nv->written; attrs = oldAttrs; - clearData = (attrs & TPMA_NV_CLEAR_STCLEAR) != 0; + clearData = (attrs & TPMA_NV_CLEAR_STCLEAR) != 0 && + ((attrs & TPMA_NV_TPM_NT) >> 4) != TPM_NT_COUNTER; if (clearData) { if (!oldNvDataAllocated) { @@ -1296,6 +1299,26 @@ static TPM_RC FwCmd_Startup(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, #endif } + #ifndef FWTPM_NO_NV + if (rc == 0 && !wasOrderly) { + for (i = 0; i < FWTPM_MAX_NV_INDICES; i++) { + FWTPM_NvIndex* nv = &ctx->nvIndices[i]; + if (nv->inUse && nv->written && + (nv->nvPublic.attributes & TPMA_NV_ORDERLY) != 0 && + ((nv->nvPublic.attributes & TPMA_NV_TPM_NT) >> 4) == + TPM_NT_COUNTER) { + UINT64 count = FwLoadU64BE(nv->data) | FWTPM_ORDERLY_COUNT; + FwStoreU64BE(nv->data, count); + if (count > ctx->nvCounterMax) { + ctx->nvCounterMax = count; + } + } + } + } + #else + (void)wasOrderly; + #endif + if (rc == 0) { oldResetCount = ctx->resetCount; oldRestartCount = ctx->restartCount; @@ -1361,6 +1384,7 @@ static TPM_RC FwCmd_Shutdown(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, TPM_RC rc = TPM_RC_SUCCESS; UINT16 shutdownType = 0; int oldShutdownState; + int oldOrderly; (void)cmdTag; @@ -1395,10 +1419,13 @@ static TPM_RC FwCmd_Shutdown(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, ctx->orderly = 1; #endif oldShutdownState = ctx->shutdownStateSaved; + oldOrderly = ctx->nvOrderly; + ctx->nvOrderly = 1; ctx->shutdownStateSaved = (shutdownType == TPM_SU_STATE); rc = FWTPM_NV_Save(ctx); if (rc != TPM_RC_SUCCESS) { ctx->shutdownStateSaved = oldShutdownState; + ctx->nvOrderly = oldOrderly; return rc; } @@ -2074,20 +2101,93 @@ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd, ((UINT32)'M' << 8) | (UINT32)0 }, { TPM_PT_VENDOR_STRING_3, 0 }, { TPM_PT_VENDOR_STRING_4, 0 }, + { TPM_PT_VENDOR_TPM_TYPE, 0 }, { TPM_PT_FIRMWARE_VERSION_1, FWTPM_VERSION_MAJOR }, { TPM_PT_FIRMWARE_VERSION_2, FWTPM_VERSION_MINOR }, { TPM_PT_INPUT_BUFFER, FWTPM_MAX_COMMAND_SIZE }, + { TPM_PT_HR_TRANSIENT_MIN, FWTPM_MAX_OBJECTS }, + #ifndef FWTPM_NO_NV + { TPM_PT_HR_PERSISTENT_MIN, FWTPM_MAX_PERSISTENT }, + #else + { TPM_PT_HR_PERSISTENT_MIN, 0 }, + #endif + { TPM_PT_HR_LOADED_MIN, FWTPM_MAX_SESSIONS }, + { TPM_PT_ACTIVE_SESSIONS_MAX, + #ifndef FWTPM_NO_CONTEXT + FWTPM_MAX_SESSIONS + + sizeof(ctx->contextLive) / sizeof(ctx->contextLive[0]) + #else + FWTPM_MAX_SESSIONS + #endif + }, { TPM_PT_PCR_COUNT, IMPLEMENTATION_PCR }, + { TPM_PT_PCR_SELECT_MIN, PCR_SELECT_MIN }, + { TPM_PT_CONTEXT_GAP_MAX, 0xFFFFFFFFu }, + #ifndef FWTPM_NO_NV + { TPM_PT_NV_COUNTERS_MAX, FWTPM_MAX_NV_INDICES }, + { TPM_PT_NV_INDEX_MAX, FWTPM_MAX_NV_DATA }, + #else + { TPM_PT_NV_COUNTERS_MAX, 0 }, + { TPM_PT_NV_INDEX_MAX, 0 }, + #endif + /* Object, session, persistent and index slots are separate. */ + { TPM_PT_MEMORY, 0 }, + #if !defined(FWTPM_NO_CLOCK) && !defined(FWTPM_NO_NV) + { TPM_PT_CLOCK_UPDATE, FWTPM_CLOCK_UPDATE_MS }, + #else + { TPM_PT_CLOCK_UPDATE, 0 }, + #endif + #ifndef FWTPM_NO_CONTEXT + /* FwWrapContextBlob uses SHA-256 HMAC and AES-256-CFB. */ + { TPM_PT_CONTEXT_HASH, TPM_ALG_SHA256 }, + { TPM_PT_CONTEXT_SYM, TPM_ALG_AES }, + { TPM_PT_CONTEXT_SYM_SIZE, AES_256_KEY_SIZE * 8 }, + #else + { TPM_PT_CONTEXT_HASH, TPM_ALG_NULL }, + { TPM_PT_CONTEXT_SYM, TPM_ALG_NULL }, + { TPM_PT_CONTEXT_SYM_SIZE, 0 }, + #endif + { TPM_PT_ORDERLY_COUNT, FWTPM_ORDERLY_COUNT }, { TPM_PT_MAX_COMMAND_SIZE, FWTPM_MAX_COMMAND_SIZE }, { TPM_PT_MAX_RESPONSE_SIZE, FWTPM_MAX_COMMAND_SIZE }, - #if defined(WOLFSSL_SHA512) + #if defined(WOLFSSL_SHA512) || (defined(WOLFSSL_SHA3) && \ + !defined(WOLFSSL_NOSHA3_512)) { TPM_PT_MAX_DIGEST, TPM_SHA512_DIGEST_SIZE }, - #elif defined(WOLFSSL_SHA384) + #elif defined(WOLFSSL_SHA384) || (defined(WOLFSSL_SHA3) && \ + !defined(WOLFSSL_NOSHA3_384)) { TPM_PT_MAX_DIGEST, TPM_SHA384_DIGEST_SIZE }, #else { TPM_PT_MAX_DIGEST, TPM_SHA256_DIGEST_SIZE }, #endif + #ifndef FWTPM_NO_CONTEXT + /* Include the TPMS_CONTEXT metadata and size field. The + * response buffer also bounds any saved sequence context. */ + { TPM_PT_MAX_OBJECT_CONTEXT, + MAX_CONTEXT_SIZE + 18 < FWTPM_MAX_COMMAND_SIZE - + TPM2_HEADER_SIZE ? MAX_CONTEXT_SIZE + 18 : + FWTPM_MAX_COMMAND_SIZE - TPM2_HEADER_SIZE }, + { TPM_PT_MAX_SESSION_CONTEXT, + 18 + 8 + AES_BLOCK_SIZE + sizeof(FWTPM_Session) + + WC_SHA256_DIGEST_SIZE }, + #else + { TPM_PT_MAX_OBJECT_CONTEXT, 0 }, + { TPM_PT_MAX_SESSION_CONTEXT, 0 }, + #endif + /* fwTPM does not claim a platform-specific profile. */ + { TPM_PT_PS_FAMILY_INDICATOR, 0 }, + { TPM_PT_PS_LEVEL, 0 }, + { TPM_PT_PS_REVISION, 0 }, + { TPM_PT_PS_DAY_OF_YEAR, 0 }, + { TPM_PT_PS_YEAR, 0 }, + { TPM_PT_SPLIT_MAX, 0 }, { TPM_PT_TOTAL_COMMANDS, 0 }, /* patched to FwGetCmdCount() at emission */ + { TPM_PT_LIBRARY_COMMANDS, 0 }, /* patched at emission */ + { TPM_PT_VENDOR_COMMANDS, 0 }, /* patched at emission */ + #ifndef FWTPM_NO_NV + { TPM_PT_NV_BUFFER_MAX, MAX_NV_BUFFER_SIZE }, + #else + { TPM_PT_NV_BUFFER_MAX, 0 }, + #endif /* TPMA_MODES (TPM_PT_MODES): bit 0 = FIPS_140_2 (spec * v1.38+), bit 1 = FIPS_140_3 (spec v1.83+). Mirror the * underlying wolfCrypt module's FIPS status so callers @@ -2105,6 +2205,7 @@ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd, 0 #endif }, + { TPM_PT_MAX_CAP_BUFFER, MAX_CAP_BUFFER }, #ifdef WOLFTPM_V185 /* v1.85 Part 2 Sec.8.13 TPMA_ML_PARAMETER_SET: bits 0-5 for * MLKEM-512/768/1024 and MLDSA-44/65/87. Each bit is gated @@ -2160,11 +2261,16 @@ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd, #endif { TPM_PT_PERMANENT, 0 }, /* patched at emission */ { TPM_PT_STARTUP_CLEAR, 0 }, /* patched at emission */ + { TPM_PT_HR_NV_INDEX, 0 }, { TPM_PT_HR_LOADED, 0 }, - { TPM_PT_HR_LOADED_AVAIL, FWTPM_MAX_OBJECTS }, + { TPM_PT_HR_LOADED_AVAIL, 0 }, + { TPM_PT_HR_ACTIVE, 0 }, + { TPM_PT_HR_ACTIVE_AVAIL, 0 }, { TPM_PT_HR_TRANSIENT_AVAIL, 0 }, { TPM_PT_HR_PERSISTENT, 0 }, - { TPM_PT_HR_PERSISTENT_AVAIL, FWTPM_MAX_PERSISTENT }, + { TPM_PT_HR_PERSISTENT_AVAIL, 0 }, + { TPM_PT_NV_COUNTERS, 0 }, + { TPM_PT_NV_COUNTERS_AVAIL, 0 }, #ifndef FWTPM_NO_DA { TPM_PT_LOCKOUT_COUNTER, 0 }, /* patched: daFailedTries */ { TPM_PT_MAX_AUTH_FAIL, 0 }, /* patched: daMaxTries */ @@ -2177,6 +2283,52 @@ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd, int avail = 0; int numOut; UINT32 propertyGroup = property / (UINT32)PT_GROUP; + UINT32 vendorCmdCount = 0; + UINT32 loadedSessions = 0, activeSessions; + UINT32 activeMax = FWTPM_MAX_SESSIONS; + UINT32 transientCount = 0, persistentCount = 0; + UINT32 persistentMax = 0, nvIndexCount = 0; + UINT32 nvIndexMax = 0, nvCounterCount = 0; + int cmdIdx; + + for (cmdIdx = 0; cmdIdx < FWTPM_MAX_SESSIONS; cmdIdx++) { + if (ctx->sessions[cmdIdx].used) { + loadedSessions++; + } + } + activeSessions = loadedSessions; + #ifndef FWTPM_NO_CONTEXT + activeSessions += (UINT32)ctx->contextLiveCount; + activeMax += sizeof(ctx->contextLive) / sizeof(ctx->contextLive[0]); + #endif + for (cmdIdx = 0; cmdIdx < FWTPM_MAX_OBJECTS; cmdIdx++) { + if (ctx->objects[cmdIdx].used) { + transientCount++; + } + } + #ifndef FWTPM_NO_NV + persistentMax = FWTPM_MAX_PERSISTENT; + nvIndexMax = FWTPM_MAX_NV_INDICES; + for (cmdIdx = 0; cmdIdx < FWTPM_MAX_PERSISTENT; cmdIdx++) { + if (ctx->persistent[cmdIdx].used) { + persistentCount++; + } + } + for (cmdIdx = 0; cmdIdx < FWTPM_MAX_NV_INDICES; cmdIdx++) { + if (ctx->nvIndices[cmdIdx].inUse) { + nvIndexCount++; + if (((ctx->nvIndices[cmdIdx].nvPublic.attributes & + TPMA_NV_TPM_NT) >> 4) == TPM_NT_COUNTER) { + nvCounterCount++; + } + } + } + #endif + for (cmdIdx = 0; cmdIdx < FwGetCmdCount(); cmdIdx++) { + if ((FwGetCmdCcAt(cmdIdx) & (UINT32)CC_VEND) != 0) { + vendorCmdCount++; + } + } for (startIdx = 0; startIdx < totalProps; startIdx++) { if (allProps[startIdx].prop >= property && @@ -2203,6 +2355,30 @@ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd, UINT32 val = allProps[startIdx + i].val; if (prop == TPM_PT_TOTAL_COMMANDS) val = (UINT32)FwGetCmdCount(); + else if (prop == TPM_PT_LIBRARY_COMMANDS) + val = (UINT32)FwGetCmdCount() - vendorCmdCount; + else if (prop == TPM_PT_VENDOR_COMMANDS) + val = vendorCmdCount; + else if (prop == TPM_PT_HR_NV_INDEX) + val = nvIndexCount; + else if (prop == TPM_PT_HR_LOADED) + val = loadedSessions; + else if (prop == TPM_PT_HR_LOADED_AVAIL) + val = FWTPM_MAX_SESSIONS - loadedSessions; + else if (prop == TPM_PT_HR_ACTIVE) + val = activeSessions; + else if (prop == TPM_PT_HR_ACTIVE_AVAIL) + val = activeMax - activeSessions; + else if (prop == TPM_PT_HR_TRANSIENT_AVAIL) + val = FWTPM_MAX_OBJECTS - transientCount; + else if (prop == TPM_PT_HR_PERSISTENT) + val = persistentCount; + else if (prop == TPM_PT_HR_PERSISTENT_AVAIL) + val = persistentMax - persistentCount; + else if (prop == TPM_PT_NV_COUNTERS) + val = nvCounterCount; + else if (prop == TPM_PT_NV_COUNTERS_AVAIL) + val = nvIndexMax - nvIndexCount; else if (prop == TPM_PT_PERMANENT) { val = 0; if (ctx->ownerAuth.size > 0) @@ -3838,11 +4014,12 @@ static TPM_RC FwCmd_ReadClock(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, * Uses clock HAL (if set) + clockOffset. clockOffset is persisted to NV * and can be advanced via ClockSet. */ clockMs = FWTPM_Clock_GetMs(ctx); - TPM2_Packet_AppendU64(rsp, clockMs); /* time */ + TPM2_Packet_AppendU64(rsp, ctx->clockHal.get_ms != NULL ? + ctx->clockHal.get_ms(ctx->clockHal.ctx) : 0); /* time */ TPM2_Packet_AppendU64(rsp, clockMs); /* clock */ TPM2_Packet_AppendU32(rsp, ctx->resetCount); TPM2_Packet_AppendU32(rsp, ctx->restartCount); - TPM2_Packet_AppendU8(rsp, 1); /* safe = YES */ + TPM2_Packet_AppendU8(rsp, (UINT8)ctx->clockSafe); FwRspFinalize(rsp, TPM_ST_NO_SESSIONS, TPM_RC_SUCCESS); return TPM_RC_SUCCESS; /* always succeeds */ @@ -4005,6 +4182,25 @@ static void FwFlushAllObjects(FWTPM_CTX* ctx) /* --- Session management helpers --- */ +/* Saved sessions retain their handles even after their RAM slots are free. */ +static int FwSessionHandleInUse(const FWTPM_CTX* ctx, TPM_HANDLE handle) +{ + int i; + for (i = 0; i < FWTPM_MAX_SESSIONS; i++) { + if (ctx->sessions[i].used && ctx->sessions[i].handle == handle) { + return 1; + } + } +#ifndef FWTPM_NO_CONTEXT + for (i = 0; i < ctx->contextLiveCount; i++) { + if (ctx->contextLiveHandle[i] == handle) { + return 1; + } + } +#endif + return 0; +} + /** \brief Allocate a free session slot for HMAC, policy, or trial session. */ static FWTPM_Session* FwAllocSession(FWTPM_CTX* ctx, TPM_SE sessionType, TPM_HANDLE* outHandle) @@ -4012,17 +4208,14 @@ static FWTPM_Session* FwAllocSession(FWTPM_CTX* ctx, TPM_SE sessionType, int i; for (i = 0; i < FWTPM_MAX_SESSIONS; i++) { if (!ctx->sessions[i].used) { + TPM_HANDLE handle = sessionType == TPM_SE_HMAC ? + HMAC_SESSION_FIRST : POLICY_SESSION_FIRST; + while (FwSessionHandleInUse(ctx, handle)) { + handle++; + } XMEMSET(&ctx->sessions[i], 0, sizeof(FWTPM_Session)); ctx->sessions[i].used = 1; - if (sessionType == TPM_SE_HMAC) { - ctx->sessions[i].handle = HMAC_SESSION_FIRST + - (TPM_HANDLE)i; - } - else { - /* Policy and Trial sessions both use policy handle space */ - ctx->sessions[i].handle = POLICY_SESSION_FIRST + - (TPM_HANDLE)i; - } + ctx->sessions[i].handle = handle; ctx->sessions[i].sessionType = sessionType; if (outHandle != NULL) { *outHandle = ctx->sessions[i].handle; @@ -6469,6 +6662,9 @@ static TPM_RC FwCmd_Clear(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, else { ctx->clockOffset = 0; } + ctx->clockNvValue = 0; + ctx->clockUnsafeUntil = 0; + ctx->clockSafe = 1; ctx->resetCount = 0; ctx->restartCount = 0; #ifndef FWTPM_NO_POLICY @@ -14554,13 +14750,14 @@ static TPM_RC FwCmd_PolicyCounterTimer(FWTPM_CTX* ctx, TPM2_Packet* cmd, int p = 0; UINT64 t = FWTPM_Clock_GetMs(ctx); /* time (8 bytes big-endian) */ - FwStoreU64BE(timeInfo + p, t); p += 8; + FwStoreU64BE(timeInfo + p, ctx->clockHal.get_ms != NULL ? + ctx->clockHal.get_ms(ctx->clockHal.ctx) : 0); p += 8; /* clockInfo.clock (8 bytes) */ FwStoreU64BE(timeInfo + p, t); p += 8; /* resetCount(4) + restartCount(4) + safe(1) */ FwStoreU32BE(timeInfo + p, ctx->resetCount); p += 4; FwStoreU32BE(timeInfo + p, ctx->restartCount); p += 4; - timeInfo[p++] = 1; /* safe = YES */ + timeInfo[p++] = (byte)ctx->clockSafe; if ((UINT32)offset + operandBSz > (UINT32)p) { rc = TPM_RC_RANGE; @@ -15817,7 +16014,9 @@ static TPM_RC FwCmd_NV_Increment(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } - if (rc == 0) { + if (rc == 0 && (!oldWritten || + (oldAttrs & TPMA_NV_ORDERLY) == 0 || + (counter & FWTPM_ORDERLY_COUNT) == 0)) { rc = FWTPM_NV_SaveNvIndex(ctx, (int)(nv - ctx->nvIndices)); } @@ -16601,7 +16800,7 @@ static void FwAppendClockInfo(FWTPM_CTX* ctx, TPM2_Packet* pkt, TPM2_Packet_AppendU64(pkt, FWTPM_Clock_GetMs(ctx)); TPM2_Packet_AppendU32(pkt, ctx->resetCount + resetObfuscation); TPM2_Packet_AppendU32(pkt, ctx->restartCount + restartObfuscation); - TPM2_Packet_AppendU8(pkt, YES); /* safe */ + TPM2_Packet_AppendU8(pkt, (UINT8)ctx->clockSafe); /* safe */ } static TPM_RC FwAppendAttestCommonHeader(FWTPM_CTX* ctx, TPM2_Packet* pkt, @@ -17236,7 +17435,8 @@ static TPM_RC FwCmd_GetTime(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* attested.time: TPMS_TIME_INFO (time + clockInfo) + * firmwareVersion */ - TPM2_Packet_AppendU64(&attestPkt, FWTPM_Clock_GetMs(ctx)); /* time */ + TPM2_Packet_AppendU64(&attestPkt, ctx->clockHal.get_ms != NULL ? + ctx->clockHal.get_ms(ctx->clockHal.ctx) : 0); /* time */ FwAppendClockInfo(ctx, &attestPkt, 0, 0); TPM2_Packet_AppendU64(&attestPkt, ((UINT64)FWTPM_VERSION_MAJOR << 32) | @@ -21244,12 +21444,14 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, /* Any structurally valid command after Shutdown(STATE), except a * pre-startup capability query, invalidates the saved state before * session parsing or authorization can mutate TPM state. */ - if (ctx->shutdownStateSaved && cmdCode != TPM_CC_Startup && + if ((ctx->shutdownStateSaved || ctx->nvOrderly) && + cmdCode != TPM_CC_Startup && cmdCode != TPM_CC_GetCapability) { + int oldSavedState = ctx->shutdownStateSaved; ctx->shutdownStateSaved = 0; rc = FWTPM_NV_SaveFlags(ctx); if (rc != TPM_RC_SUCCESS) { - ctx->shutdownStateSaved = 1; + ctx->shutdownStateSaved = oldSavedState; *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, rc); TPM2_ForceZero(cmdAuths, sizeof(cmdAuths)); @@ -21257,6 +21459,12 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, } } + rc = FWTPM_Clock_Update(ctx); + if (rc != TPM_RC_SUCCESS) { + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, rc); + return TPM_RC_SUCCESS; + } + /* For TPM_ST_SESSIONS commands, parse auth area to detect param encryption * and track all auth sessions for response auth generation. * We peek without advancing cmdPkt.pos so handler re-parses normally. */ diff --git a/src/fwtpm/fwtpm_io.c b/src/fwtpm/fwtpm_io.c index af325f69..e91218b8 100644 --- a/src/fwtpm/fwtpm_io.c +++ b/src/fwtpm/fwtpm_io.c @@ -87,11 +87,32 @@ int FWTPM_IO_IsStopRequested(void) #define FWTPM_MAX_COMMAND_CLIENTS 8 #define FWTPM_IO_CONNECTION_CLOSED (-1) -static int SocketSend(SOCKET_T fd, const void* buf, int sz) +/* Bound each blocking transfer, including clients that stop mid-packet. */ +static int SocketSetTimeouts(SOCKET_T fd) +{ +#ifdef _WIN32 + DWORD timeout = FWTPM_CLOCK_POLL_MS; +#else + struct timeval timeout; + timeout.tv_sec = FWTPM_CLOCK_POLL_MS / 1000; + timeout.tv_usec = (FWTPM_CLOCK_POLL_MS % 1000) * 1000; +#endif + if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, + (const char*)&timeout, sizeof(timeout)) != 0 || + setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, + (const char*)&timeout, sizeof(timeout)) != 0) { + return TPM_RC_FAILURE; + } + return TPM_RC_SUCCESS; +} + +static int SocketSend(FWTPM_CTX* ctx, SOCKET_T fd, const void* buf, int sz) { const char* ptr = (const char*)buf; int remaining = sz; while (remaining > 0) { + int rc = FWTPM_Clock_Update(ctx); + if (rc != TPM_RC_SUCCESS && rc != TPM_RC_NV_RATE) return rc; #ifdef _WIN32 int sent = send(fd, ptr, remaining, 0); #else @@ -120,11 +141,13 @@ static int SocketSend(SOCKET_T fd, const void* buf, int sz) return TPM_RC_SUCCESS; } -static int SocketRecv(SOCKET_T fd, void* buf, int sz) +static int SocketRecv(FWTPM_CTX* ctx, SOCKET_T fd, void* buf, int sz) { char* ptr = (char*)buf; int remaining = sz; while (remaining > 0) { + int rc = FWTPM_Clock_Update(ctx); + if (rc != TPM_RC_SUCCESS && rc != TPM_RC_NV_RATE) return rc; #ifdef _WIN32 int got = recv(fd, ptr, remaining, 0); #else @@ -167,7 +190,7 @@ static int TransportSend(FWTPM_CTX* ctx, int type, SOCKET_T fd, if (fd == FWTPM_INVALID_FD) { return TPM_RC_FAILURE; } - return SocketSend(fd, buf, sz); + return SocketSend(ctx, fd, buf, sz); } static int TransportRecv(FWTPM_CTX* ctx, int type, SOCKET_T fd, @@ -179,7 +202,7 @@ static int TransportRecv(FWTPM_CTX* ctx, int type, SOCKET_T fd, if (fd == FWTPM_INVALID_FD) { return TPM_RC_FAILURE; } - return SocketRecv(fd, buf, sz); + return SocketRecv(ctx, fd, buf, sz); } static SOCKET_T CreateListenSocket(int port) @@ -259,6 +282,9 @@ static int HandlePlatformCommand(FWTPM_CTX* ctx, SOCKET_T clientFd) switch (cmd) { case FWTPM_TCP_SIGNAL_POWER_ON: + if (!ctx->powerOn) { + FWTPM_Clock_PowerOn(ctx); + } ctx->powerOn = 1; #ifdef DEBUG_WOLFTPM printf("fwTPM: Platform POWER_ON\n"); @@ -372,8 +398,12 @@ static int HandleMssimSignal(FWTPM_CTX* ctx, SOCKET_T clientFd, UINT32 tssCmd) /* 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) + if (tssCmd == FWTPM_TCP_SIGNAL_POWER_ON) { + if (!ctx->powerOn) { + FWTPM_Clock_PowerOn(ctx); + } ctx->powerOn = 1; + } #ifdef DEBUG_WOLFTPM printf("fwTPM: Cmd-port signal %u (ack)\n", tssCmd); #endif @@ -640,13 +670,21 @@ static int CustomServerLoop(FWTPM_CTX* ctx) ctx->running = 1; while (ctx->running) { + rc = FWTPM_Clock_Update(ctx); + if (rc == TPM_RC_NV_RATE) { + rc = TPM_RC_SUCCESS; /* retried on the next pass */ + } + if (rc != TPM_RC_SUCCESS) { + break; + } if (g_stopRequested) { ctx->running = 0; break; } events = ctx->ioHal.wait(ctx->ioHal.ctx, - FWTPM_IO_WAIT_TIMEOUT_MS); + FWTPM_IO_WAIT_TIMEOUT_MS < (int)FWTPM_CLOCK_POLL_MS ? + FWTPM_IO_WAIT_TIMEOUT_MS : (int)FWTPM_CLOCK_POLL_MS); if (g_stopRequested) { ctx->running = 0; break; @@ -867,6 +905,13 @@ int FWTPM_IO_ServerLoop(FWTPM_CTX* ctx) * the command connection open while sending POWER_ON on the platform port. * A sequential accept/handle loop would deadlock here. */ while (ctx->running) { + rc = FWTPM_Clock_Update(ctx); + if (rc == TPM_RC_NV_RATE) { + rc = TPM_RC_SUCCESS; /* retried on the next pass */ + } + if (rc != TPM_RC_SUCCESS) { + break; + } /* Propagate async stop request into ctx state from normal * control flow (signal handler only sets the volatile flag). */ if (g_stopRequested) { @@ -894,7 +939,7 @@ int FWTPM_IO_ServerLoop(FWTPM_CTX* ctx) if (platFd > maxFd) maxFd = platFd; } - tv.tv_sec = 30; + tv.tv_sec = FWTPM_CLOCK_POLL_MS / 1000; tv.tv_usec = 0; /* Windows ignores the first argument and its SOCKET does not fit an * int; everywhere else it is the descriptor bound and does. */ @@ -919,6 +964,10 @@ int FWTPM_IO_ServerLoop(FWTPM_CTX* ctx) if (FD_ISSET(ctx->io.platListenFd, &readFds)) { SOCKET_T newFd = accept(ctx->io.platListenFd, NULL, NULL); if (newFd != FWTPM_INVALID_FD) { + if (SocketSetTimeouts(newFd) != TPM_RC_SUCCESS) { + CloseSocket(newFd); + continue; + } if (platFd != FWTPM_INVALID_FD) { #ifdef DEBUG_WOLFTPM printf("fwTPM: platform connection replaced\n"); @@ -953,6 +1002,10 @@ int FWTPM_IO_ServerLoop(FWTPM_CTX* ctx) if (FD_ISSET(ctx->io.listenFd, &readFds)) { SOCKET_T newFd = accept(ctx->io.listenFd, NULL, NULL); if (newFd != FWTPM_INVALID_FD) { + if (SocketSetTimeouts(newFd) != TPM_RC_SUCCESS) { + CloseSocket(newFd); + continue; + } for (i = 0; i < FWTPM_MAX_COMMAND_CLIENTS; i++) { if (cmdFds[i] == FWTPM_INVALID_FD) { cmdFds[i] = newFd; diff --git a/src/fwtpm/fwtpm_main.c b/src/fwtpm/fwtpm_main.c index 56ef4358..f434235a 100644 --- a/src/fwtpm/fwtpm_main.c +++ b/src/fwtpm/fwtpm_main.c @@ -46,6 +46,27 @@ #include #include +#ifndef FWTPM_NO_CLOCK + #ifdef _WIN32 + #include + #else + #include + #endif + +static UINT64 FwtpmHostClockMs(void* userCtx) +{ + UINT64 now; +#ifdef _WIN32 + now = (UINT64)GetTickCount64(); +#else + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) return 0; + now = (UINT64)ts.tv_sec * 1000u + (UINT64)ts.tv_nsec / 1000000u; +#endif + return userCtx == NULL ? now : now - *(const UINT64*)userCtx; +} +#endif /* !FWTPM_NO_CLOCK */ + /* Signal handler does only async-signal-safe work: it calls * FWTPM_IO_RequestStop(), which sets a volatile sig_atomic_t. The server * loop polls that flag and clears ctx->running from normal control flow. */ @@ -243,6 +264,9 @@ int main(int argc, char* argv[]) { int rc, rcCleanup; static FWTPM_CTX ctx; +#ifndef FWTPM_NO_CLOCK + UINT64 bootMs; +#endif int i; int clearNv = 0; #ifdef WOLFTPM_SPDM_RESPONDER @@ -312,6 +336,11 @@ int main(int argc, char* argv[]) } /* Initialize fwTPM */ +#ifndef FWTPM_NO_CLOCK + bootMs = FwtpmHostClockMs(NULL); + rc = FWTPM_Clock_SetHAL(&ctx, FwtpmHostClockMs, &bootMs); + if (rc != TPM_RC_SUCCESS) return rc; +#endif rc = FWTPM_Init(&ctx); if (rc != 0) { fprintf(stderr, "FWTPM_Init failed: %d\n", rc); diff --git a/src/fwtpm/fwtpm_nv.c b/src/fwtpm/fwtpm_nv.c index 10ab68eb..8ca1a846 100644 --- a/src/fwtpm/fwtpm_nv.c +++ b/src/fwtpm/fwtpm_nv.c @@ -1154,8 +1154,10 @@ static int FwNvTagIsDelete(UINT16 tag) } /* Append a single TLV entry to the journal */ -static int FwNvAppendEntry(FWTPM_CTX* ctx, UINT16 tag, - const byte* value, UINT16 valueLen) +/* compacted (optional) is set when the entry was committed by a compaction + * of the live context instead of being appended. */ +static int FwNvAppendEntryEx(FWTPM_CTX* ctx, UINT16 tag, + const byte* value, UINT16 valueLen, int* compacted) { FWTPM_NV_HAL* hal; word32 entrySize = TLV_HDR_SIZE + valueLen; @@ -1164,6 +1166,9 @@ static int FwNvAppendEntry(FWTPM_CTX* ctx, UINT16 tag, word32 savedWritePos; int rc; + if (compacted != NULL) { + *compacted = 0; + } #ifdef FWTPM_NO_NV (void)ctx; (void)tag; @@ -1207,6 +1212,9 @@ static int FwNvAppendEntry(FWTPM_CTX* ctx, UINT16 tag, return rc; } if (!FwNvTagIsDelete(tag) || ctx->nvDeleteHandle != 0) { + if (compacted != NULL) { + *compacted = 1; + } return TPM_RC_SUCCESS; } if (ctx->nvWritePos + entrySize + reserve > hal->maxSize) { @@ -1259,6 +1267,12 @@ static int FwNvAppendEntry(FWTPM_CTX* ctx, UINT16 tag, return rc; } +static int FwNvAppendEntry(FWTPM_CTX* ctx, UINT16 tag, + const byte* value, UINT16 valueLen) +{ + return FwNvAppendEntryEx(ctx, tag, value, valueLen, NULL); +} + /* ========================================================================= */ /* Journal Load (Init) */ /* ========================================================================= */ @@ -1605,6 +1619,8 @@ static int FwNvProcessEntry(FWTPM_CTX* ctx, UINT16 tag, (proofVersion == 1) ? 1 : 0; ctx->restartCount = restartCount; ctx->shutdownStateSaved = shutdownState ? 1 : 0; + ctx->nvOrderly = 0; + ctx->clockSafe = 0; #ifndef FWTPM_NO_NV if (counterMax > ctx->nvCounterMax) { ctx->nvCounterMax = counterMax; @@ -1615,8 +1631,25 @@ static int FwNvProcessEntry(FWTPM_CTX* ctx, UINT16 tag, } case FWTPM_NV_TAG_SHUTDOWN_STATE: - if (valueLen == 1) { - ctx->shutdownStateSaved = value[0] ? 1 : 0; + /* Legacy markers do not preserve Clock safety. */ + if (valueLen == 1 && value[0] <= 1) { + /* Legacy orderly Shutdown(STATE) marker. */ + ctx->shutdownStateSaved = value[0] != 0; + ctx->nvOrderly = value[0] != 0; + ctx->clockSafe = 0; + } + else if (valueLen == 2 && value[0] <= 1 && value[1] == 1) { + /* Extended marker also distinguishes clean Shutdown(CLEAR). + * Older readers reject this length instead of resuming it. */ + ctx->shutdownStateSaved = value[0] != 0; + ctx->nvOrderly = 1; + ctx->clockSafe = 0; + } + else if (valueLen == 3 && value[0] <= 1 && value[1] == 1 && + value[2] <= 1) { + ctx->shutdownStateSaved = value[0] != 0; + ctx->nvOrderly = 1; + ctx->clockSafe = value[2] != 0; } else { rc = TPM_RC_FAILURE; @@ -1631,6 +1664,7 @@ static int FwNvProcessEntry(FWTPM_CTX* ctx, UINT16 tag, } if (rc == TPM_RC_SUCCESS && vPos == vMax) { ctx->clockOffset = (UINT64)lo | ((UINT64)hi << 32); + ctx->clockNvValue = ctx->clockOffset; } else { rc = TPM_RC_FAILURE; @@ -1947,6 +1981,7 @@ static int FwNvGenFreshState(FWTPM_CTX* ctx) /* PCR auth/policy defaults */ ctx->pcrAllocatedBanks = FWTPM_PCR_ALLOC_DEFAULT; + ctx->clockSafe = 1; /* Save initial state (compact write) */ rc = FWTPM_NV_Save(ctx); @@ -2357,10 +2392,13 @@ int FWTPM_NV_Save(FWTPM_CTX* ctx) byte* buf = NULL; word32 bufSz = FWTPM_NV_MAX_ENTRY; word32 pos; + UINT64 clockValue = 0; + int clockSafe; if (ctx == NULL) { return BAD_FUNC_ARG; } + clockSafe = ctx->clockSafe; #ifdef FWTPM_NO_NV return TPM_RC_SUCCESS; @@ -2537,13 +2575,24 @@ int FWTPM_NV_Save(FWTPM_CTX* ctx) rc = FwNvAppendEntry(ctx, FWTPM_NV_TAG_FLAGS, buf, (UINT16)pos); } - /* --- Clock offset --- */ - if (rc == 0 && ctx->clockOffset != 0) { + /* --- Clock checkpoint --- */ + if (rc == 0) { + /* An unsafe Shutdown must retain NV Clock (Part 1, 33.3.3). + * Other snapshots may establish safety after the recovery interval. */ + if (ctx->nvOrderly && !clockSafe) { + clockValue = ctx->clockNvValue; + } + else { + clockValue = FWTPM_Clock_GetMs(ctx); + if (clockValue >= ctx->clockUnsafeUntil) { + clockSafe = 1; + } + } pos = 0; FwNvMarshalU32(buf, &pos, bufSz, - (UINT32)(ctx->clockOffset & 0xFFFFFFFF)); + (UINT32)(clockValue & 0xFFFFFFFF)); FwNvMarshalU32(buf, &pos, bufSz, - (UINT32)(ctx->clockOffset >> 32)); + (UINT32)(clockValue >> 32)); rc = FwNvAppendEntry(ctx, FWTPM_NV_TAG_CLOCK, buf, (UINT16)pos); } @@ -2678,10 +2727,13 @@ int FWTPM_NV_Save(FWTPM_CTX* ctx) } /* Commit the orderly-state marker after every snapshot entry. */ - if (rc == 0 && ctx->shutdownStateSaved) { - const byte stateSaved = 1; + if (rc == 0 && ctx->nvOrderly) { + byte stateSaved[3]; + stateSaved[0] = ctx->shutdownStateSaved ? 1 : 0; + stateSaved[1] = 1; + stateSaved[2] = clockSafe ? 1 : 0; rc = FwNvAppendEntry(ctx, FWTPM_NV_TAG_SHUTDOWN_STATE, - &stateSaved, sizeof(stateSaved)); + stateSaved, sizeof(stateSaved)); } /* Append-only compaction defers its checkpoint until every entry is @@ -2702,6 +2754,11 @@ int FWTPM_NV_Save(FWTPM_CTX* ctx) printf("fwTPM: NV saved (compact, %d bytes)\n", (int)ctx->nvWritePos); #endif + if (rc == TPM_RC_SUCCESS) { + ctx->clockNvValue = clockValue; + ctx->clockSafe = clockSafe; + } + ctx->nvCompacting = 0; TPM2_ForceZero(buf, bufSz); XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); @@ -2852,6 +2909,7 @@ int FWTPM_NV_SaveFlags(FWTPM_CTX* ctx) return TPM_RC_SUCCESS; #else int rc; + int oldOrderly; byte buf[1 + 12 + 4 + 4 + 1 + 4 + 1 + 8]; word32 pos = 0; @@ -2890,7 +2948,14 @@ int FWTPM_NV_SaveFlags(FWTPM_CTX* ctx) FwNvMarshalU32(buf, &pos, sizeof(buf), (UINT32)(ctx->nvCounterMax >> 32)); + /* Compaction consumes current RAM state instead of appending this + * entry. It must see the dirty state before it writes a snapshot. */ + oldOrderly = ctx->nvOrderly; + ctx->nvOrderly = 0; rc = FwNvAppendEntry(ctx, FWTPM_NV_TAG_FLAGS, buf, (UINT16)pos); + if (rc != TPM_RC_SUCCESS) { + ctx->nvOrderly = oldOrderly; + } return rc; #endif /* FWTPM_NO_NV */ } @@ -2900,17 +2965,31 @@ int FWTPM_NV_SaveClock(FWTPM_CTX* ctx) int rc; byte buf[8]; /* UINT64 as two U32 (lo, hi) */ word32 pos = 0; + UINT64 clockValue; + int compacted = 0; if (ctx == NULL) { return BAD_FUNC_ARG; } + clockValue = FWTPM_Clock_GetMs(ctx); FwNvMarshalU32(buf, &pos, sizeof(buf), - (UINT32)(ctx->clockOffset & 0xFFFFFFFF)); + (UINT32)(clockValue & 0xFFFFFFFF)); FwNvMarshalU32(buf, &pos, sizeof(buf), - (UINT32)(ctx->clockOffset >> 32)); + (UINT32)(clockValue >> 32)); - rc = FwNvAppendEntry(ctx, FWTPM_NV_TAG_CLOCK, buf, (UINT16)pos); + rc = FwNvAppendEntryEx(ctx, FWTPM_NV_TAG_CLOCK, buf, (UINT16)pos, + &compacted); + if (rc == TPM_RC_FAILURE || rc < 0) { + rc = TPM_RC_NV_UNAVAILABLE; + } + /* A compaction already committed its own Clock checkpoint and safety. */ + if (rc == TPM_RC_SUCCESS && !compacted) { + ctx->clockNvValue = clockValue; + if (clockValue >= ctx->clockUnsafeUntil) { + ctx->clockSafe = 1; + } + } return rc; } diff --git a/src/fwtpm/fwtpm_tis.c b/src/fwtpm/fwtpm_tis.c index e6f4730a..e19af4c1 100644 --- a/src/fwtpm/fwtpm_tis.c +++ b/src/fwtpm/fwtpm_tis.c @@ -511,6 +511,15 @@ int FWTPM_TIS_ServerLoop(FWTPM_CTX* ctx) printf("fwTPM TIS: Server ready, waiting for register accesses...\n"); while (ctx->running) { + rc = FWTPM_Clock_Update(ctx); + if (rc == TPM_RC_NV_RATE) { + rc = TPM_RC_SUCCESS; /* retried on the next pass */ + } + if (rc != TPM_RC_SUCCESS) { + retRc = rc; + break; + } + /* Propagate async stop request (from signal handler) into * ctx state, matching FWTPM_IO_ServerLoop behavior */ if (FWTPM_IO_IsStopRequested()) { diff --git a/src/fwtpm/fwtpm_tis_shm.c b/src/fwtpm/fwtpm_tis_shm.c index 8f31858c..4fe17903 100644 --- a/src/fwtpm/fwtpm_tis_shm.c +++ b/src/fwtpm/fwtpm_tis_shm.c @@ -28,6 +28,10 @@ * Server: This file provides FWTPM_TIS_HAL callbacks for fwtpm_tis.c */ +#if defined(__linux__) && !defined(_GNU_SOURCE) + #define _GNU_SOURCE /* sem_clockwait */ +#endif + #ifdef HAVE_CONFIG_H #include #endif @@ -51,6 +55,7 @@ #include #include +#include /* Internal context for POSIX shm transport */ typedef struct { @@ -179,18 +184,33 @@ static int TisShmInit(void* ctx, FWTPM_TIS_REGS** regs) static int TisShmWaitRequest(void* ctx) { FWTPM_TIS_SHM_CTX* shm = (FWTPM_TIS_SHM_CTX*)ctx; - - if (sem_wait(shm->semCmd) != 0) { - if (errno == EINTR) { - return -1; /* caller should continue loop */ - } - #ifdef DEBUG_WOLFTPM - printf("fwTPM TIS: sem_wait error %d (%s)\n", - errno, strerror(errno)); - #endif - return -2; /* fatal error */ + /* The poll bound must hold when the realtime clock moves, which a + * sem_timedwait deadline does not guarantee. */ +#if defined(__GLIBC__) && \ + (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 30)) + struct timespec deadline; + if (clock_gettime(CLOCK_MONOTONIC, &deadline) != 0) return -2; + deadline.tv_sec += FWTPM_CLOCK_POLL_MS / 1000; + deadline.tv_nsec += (FWTPM_CLOCK_POLL_MS % 1000) * 1000000L; + if (deadline.tv_nsec >= 1000000000L) { + deadline.tv_sec++; + deadline.tv_nsec -= 1000000000L; } - return 0; + if (sem_clockwait(shm->semCmd, CLOCK_MONOTONIC, &deadline) == 0) return 0; + return errno == EINTR || errno == ETIMEDOUT ? -1 : -2; +#else + struct timespec delay; + unsigned int waited; + delay.tv_sec = 0; + delay.tv_nsec = 1000000L; + for (waited = 0; waited < FWTPM_CLOCK_POLL_MS; waited++) { + if (sem_trywait(shm->semCmd) == 0) return 0; + if (errno == EINTR) return -1; + if (errno != EAGAIN) return -2; + if (nanosleep(&delay, NULL) != 0) return -1; + } + return -1; +#endif } static int TisShmSignalResponse(void* ctx) diff --git a/tests/fwtpm_hal_unit_tests.c b/tests/fwtpm_hal_unit_tests.c index 5c3f942b..c797a89a 100644 --- a/tests/fwtpm_hal_unit_tests.c +++ b/tests/fwtpm_hal_unit_tests.c @@ -95,13 +95,16 @@ static sem_t* TestSemOpen(const char* name, int oflag, ...) void TestSetDefaultHAL(FWTPM_CTX* ctx); UINT32 TestByteArrayToU32(const BYTE* data); +int TestClockUpdate(FWTPM_CTX* ctx); #define TPM2_ForceZero TestForceZero #define FWTPM_ProcessCommand TestProcessCommand #define FWTPM_IO_IsStopRequested TestStopRequested #define FWTPM_TIS_SetDefaultHAL TestSetDefaultHAL #define TPM2_Packet_ByteArrayToU32 TestByteArrayToU32 +#define FWTPM_Clock_Update TestClockUpdate #include "../src/fwtpm/fwtpm_tis.c" +#undef FWTPM_Clock_Update #undef TPM2_Packet_ByteArrayToU32 #undef FWTPM_TIS_SetDefaultHAL #undef FWTPM_IO_IsStopRequested @@ -130,6 +133,12 @@ void TestSetDefaultHAL(FWTPM_CTX* ctx) (void)ctx; } +int TestClockUpdate(FWTPM_CTX* ctx) +{ + (void)ctx; + return 0; +} + UINT32 TestByteArrayToU32(const BYTE* data) { return ((UINT32)data[0] << 24) | ((UINT32)data[1] << 16) | diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index 51466102..cdda7717 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -2213,6 +2213,7 @@ static void test_fwtpm_total_commands(void) UINT32 advertised[512]; UINT32 count, props, i, total = 0; int rc, rspSize, cmdSz, found = 0; + UINT32 vendorCount = 0; memset(&ctx, 0, sizeof(ctx)); AssertIntEQ(fwtpm_test_startup(&ctx), 0); @@ -2249,6 +2250,25 @@ static void test_fwtpm_total_commands(void) AssertIntEQ(found, 1); AssertIntEQ(total, count); + for (i = 0; i < count; i++) { + if ((advertised[i] & (UINT32)CC_VEND) != 0) { + vendorCount++; + } + } + cmdSz = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 22, + TPM_CC_GetCapability); + PutU32BE(gCmd + cmdSz, TPM_CAP_TPM_PROPERTIES); cmdSz += 4; + PutU32BE(gCmd + cmdSz, TPM_PT_LIBRARY_COMMANDS); cmdSz += 4; + PutU32BE(gCmd + cmdSz, 2); cmdSz += 4; + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE + 5), 2); + AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE + 9), TPM_PT_LIBRARY_COMMANDS); + AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE + 13), total - vendorCount); + AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE + 17), TPM_PT_VENDOR_COMMANDS); + AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE + 21), vendorCount); + FWTPM_Cleanup(&ctx); fwtpm_pass("TPM_PT_TOTAL_COMMANDS matches CAP_COMMANDS:", 0); } @@ -15356,7 +15376,7 @@ static void test_fwtpm_state_change_nv_failure(void) PutU32BE(gCmd + 2, (UINT32)pos); rspSize = 0; FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_FAILURE); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_NV_UNAVAILABLE); AssertIntEQ((int)ctx.clockOffset, 0); #endif @@ -20703,6 +20723,793 @@ static void test_fwtpm_rsa_hash_matrix(void) #endif +#if defined(FWTPM_NO_NV) && !defined(FWTPM_NO_CLOCK) +static UINT64 no_nv_clock_ms(void* userCtx) +{ + return *(UINT64*)userCtx; +} + +static byte no_nv_clock_safe(FWTPM_CTX* ctx) +{ + int pos, rspSize = 0; + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, TPM2_HEADER_SIZE, + TPM_CC_ReadClock); + FWTPM_ProcessCommand(ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + return gRsp[TPM2_HEADER_SIZE + 24]; +} + +/* Without NV, a power cycle restarts Clock from its boot value, so Clock is + * no longer safe even after an orderly Shutdown. */ +static void test_fwtpm_clock_power_cycle_no_nv(void) +{ + FWTPM_CTX ctx; + UINT64 now = 0; + int pos, rspSize = 0; + + XMEMSET(&ctx, 0, sizeof(ctx)); + AssertIntEQ(FWTPM_Clock_SetHAL(&ctx, no_nv_clock_ms, &now), 0); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + now = 1u << 22; + AssertIntEQ(no_nv_clock_safe(&ctx), YES); + + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 12, TPM_CC_Shutdown); + PutU16BE(gCmd + pos, TPM_SU_CLEAR); pos += 2; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + ctx.powerOn = 0; + ctx.wasStarted = 0; + now = 0; + AssertIntEQ(FWTPM_Clock_PowerOn(&ctx), TPM_RC_SUCCESS); + ctx.powerOn = 1; + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 12, TPM_CC_Startup); + PutU16BE(gCmd + pos, TPM_SU_CLEAR); pos += 2; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(no_nv_clock_safe(&ctx), NO); + + FWTPM_Cleanup(&ctx); + fwtpm_pass("Clock power cycle without NV:", 0); +} +#endif + +#ifndef FWTPM_NO_NV +static int gPersistenceWriteError = TPM_RC_FAILURE; + +static int persistence_fail_write(void* userCtx, word32 off, + const byte* data, word32 size) +{ + (void)userCtx; + (void)off; + (void)data; + (void)size; + return gPersistenceWriteError; +} + +/* Release volatile resources without FWTPM_Cleanup's NV snapshot. */ +static void test_fwtpm_power_cut(FWTPM_CTX* ctx) +{ + FWTPM_ResetCommandClient(ctx); + wc_FreeRng(&ctx->rng); + wolfCrypt_Cleanup(); + wc_ForceZero(ctx, sizeof(*ctx)); +} + +#ifndef FWTPM_NO_CLOCK +static UINT64 persistence_clock_ms(void* userCtx) +{ + return *(UINT64*)userCtx; +} + +static UINT64 persistence_read_clock(FWTPM_CTX* ctx, byte* safe) +{ + int pos, rspSize = 0; + UINT64 value; + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, TPM2_HEADER_SIZE, + TPM_CC_ReadClock); + FWTPM_ProcessCommand(ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + value = ((UINT64)GetU32BE(gRsp + TPM2_HEADER_SIZE + 8) << 32) | + GetU32BE(gRsp + TPM2_HEADER_SIZE + 12); + *safe = gRsp[TPM2_HEADER_SIZE + 24]; + return value; +} + +static void test_fwtpm_clock_checkpoint_recovery(void) +{ + FWTPM_CTX ctx; + UINT64 now = 0, saved; + byte safe; + int pos, rspSize = 0; + int (*savedWrite)(void*, word32, const byte*, word32); + + (void)remove(FWTPM_NV_FILE); + XMEMSET(&ctx, 0, sizeof(ctx)); + AssertIntEQ(FWTPM_Clock_SetHAL(&ctx, persistence_clock_ms, &now), 0); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + AssertIntEQ(ctx.clockSafe, YES); + now = 1u << 22; + saved = persistence_read_clock(&ctx, &safe); + AssertTrue(saved >= now); + AssertIntEQ(safe, YES); + test_fwtpm_power_cut(&ctx); + + now = 0; + AssertIntEQ(FWTPM_Clock_SetHAL(&ctx, persistence_clock_ms, &now), 0); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + AssertTrue(persistence_read_clock(&ctx, &safe) >= saved); + AssertIntEQ(safe, NO); + now = 1u << 22; + savedWrite = ctx.nvHal.write; + ctx.nvHal.write = persistence_fail_write; + AssertIntEQ(FWTPM_Clock_Update(&ctx), TPM_RC_NV_UNAVAILABLE); + gPersistenceWriteError = TPM_RC_NV_RATE; + AssertIntEQ(FWTPM_Clock_Update(&ctx), TPM_RC_NV_RATE); + gPersistenceWriteError = TPM_RC_FAILURE; + AssertIntEQ(ctx.clockNvValue, saved); + AssertIntEQ(ctx.clockSafe, NO); + ctx.nvHal.write = savedWrite; + saved = persistence_read_clock(&ctx, &safe); + AssertIntEQ(safe, YES); + + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 12, TPM_CC_Shutdown); + PutU16BE(gCmd + pos, TPM_SU_CLEAR); pos += 2; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + test_fwtpm_power_cut(&ctx); + now = 0; + AssertIntEQ(FWTPM_Clock_SetHAL(&ctx, persistence_clock_ms, &now), 0); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + AssertTrue(persistence_read_clock(&ctx, &safe) >= saved); + AssertIntEQ(safe, YES); + FWTPM_Cleanup(&ctx); + (void)remove(FWTPM_NV_FILE); + fwtpm_pass("Clock checkpoints and safe recovery:", 0); +} + +static TPM_RC persistence_lifecycle(FWTPM_CTX* ctx, UINT32 command, + UINT16 type) +{ + int pos, rspSize = 0; + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 12, command); + PutU16BE(gCmd + pos, type); + AssertIntEQ(FWTPM_ProcessCommand(ctx, gCmd, 12, gRsp, &rspSize, 0), 0); + return GetRspRC(gRsp); +} + +/* Platform power cycles in the same context: the HAL restarts at zero and + * Clock resumes from NV, never rolling back. Only an orderly Shutdown keeps + * Clock safe. */ +static void test_fwtpm_clock_power_cycle(void) +{ + FWTPM_CTX ctx; + UINT64 now = 0, saved; + byte safe; + int orderly; + + (void)remove(FWTPM_NV_FILE); + XMEMSET(&ctx, 0, sizeof(ctx)); + AssertIntEQ(FWTPM_Clock_SetHAL(&ctx, persistence_clock_ms, &now), 0); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + for (orderly = 0; orderly <= 1; orderly++) { + now = 1u << 22; + AssertIntEQ(FWTPM_Clock_Update(&ctx), TPM_RC_SUCCESS); + (void)persistence_read_clock(&ctx, &safe); + AssertIntEQ(safe, YES); + if (orderly) { + AssertIntEQ(persistence_lifecycle(&ctx, TPM_CC_Shutdown, + TPM_SU_CLEAR), TPM_RC_SUCCESS); + } + saved = ctx.clockNvValue; + ctx.powerOn = 0; + ctx.wasStarted = 0; + + now = 0; + AssertIntEQ(FWTPM_Clock_PowerOn(&ctx), TPM_RC_SUCCESS); + ctx.powerOn = 1; + AssertIntEQ(persistence_lifecycle(&ctx, TPM_CC_Startup, + TPM_SU_CLEAR), TPM_RC_SUCCESS); + AssertTrue(persistence_read_clock(&ctx, &safe) >= saved); + AssertIntEQ(safe, orderly ? YES : NO); + } + FWTPM_Cleanup(&ctx); + (void)remove(FWTPM_NV_FILE); + fwtpm_pass("Clock across platform power cycles:", 0); +} + +static void persistence_clock_boot(FWTPM_CTX* ctx, UINT64* now, int flash) +{ +#ifdef WOLFTPM_FWTPM_NV_APPEND_ONLY + if (flash) { + FWTPM_NV_HAL hal; + AssertIntEQ(flash_boot(ctx, &hal, FNV_PROG, 1), 0); + AssertIntEQ(FWTPM_Clock_SetHAL(ctx, persistence_clock_ms, now), 0); + return; + } +#else + (void)flash; +#endif + XMEMSET(ctx, 0, sizeof(*ctx)); + AssertIntEQ(FWTPM_Clock_SetHAL(ctx, persistence_clock_ms, now), 0); + AssertIntEQ(FWTPM_Init(ctx), 0); +} + +/* A Clock checkpoint that compacts the journal keeps the Clock and safety the + * compaction committed, including a retained unsafe orderly Clock. */ +static void test_fwtpm_clock_compaction(void) +{ + FWTPM_CTX ctx; + UINT64 now = 0, checkpoint; + + (void)remove(FWTPM_NV_FILE); + XMEMSET(&ctx, 0, sizeof(ctx)); + AssertIntEQ(FWTPM_Clock_SetHAL(&ctx, persistence_clock_ms, &now), 0); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + test_fwtpm_power_cut(&ctx); + persistence_clock_boot(&ctx, &now, 0); + AssertIntEQ(persistence_lifecycle(&ctx, TPM_CC_Startup, TPM_SU_CLEAR), + TPM_RC_SUCCESS); + AssertIntEQ(ctx.clockSafe, NO); + + now = 1u << 24; + ctx.nvWritePos = ctx.nvHal.maxSize; + AssertIntEQ(FWTPM_NV_SaveClock(&ctx), TPM_RC_SUCCESS); + AssertTrue(ctx.clockNvValue == FWTPM_Clock_GetMs(&ctx)); + AssertIntEQ(ctx.clockSafe, YES); + + test_fwtpm_power_cut(&ctx); + now = 0; + persistence_clock_boot(&ctx, &now, 0); + AssertIntEQ(persistence_lifecycle(&ctx, TPM_CC_Startup, TPM_SU_CLEAR), + TPM_RC_SUCCESS); + AssertIntEQ(persistence_lifecycle(&ctx, TPM_CC_Shutdown, TPM_SU_CLEAR), + TPM_RC_SUCCESS); + AssertIntEQ(ctx.nvOrderly, YES); + AssertIntEQ(ctx.clockSafe, NO); + checkpoint = ctx.clockNvValue; + now = 1u << 25; + ctx.nvWritePos = ctx.nvHal.maxSize; + AssertIntEQ(FWTPM_NV_SaveClock(&ctx), TPM_RC_SUCCESS); + AssertTrue(ctx.clockNvValue == checkpoint); + AssertIntEQ(ctx.clockSafe, NO); + + FWTPM_Cleanup(&ctx); + (void)remove(FWTPM_NV_FILE); + fwtpm_pass("Clock checkpoint during compaction:", 0); +} + +/* Part 1, 33.3.3: Shutdown must not turn an unsafe Clock into a safe one. */ +static void test_fwtpm_clock_unsafe_shutdown(void) +{ + FWTPM_CTX ctx; + UINT64 now, highWater, checkpoint; + byte safe; + UINT16 shutdownType; + int flash, flashCount = 1; + int (*savedWrite)(void*, word32, const byte*, word32); +#ifdef WOLFTPM_FWTPM_NV_APPEND_ONLY + byte torn[FNV_PROG]; +#endif + +#ifdef WOLFTPM_FWTPM_NV_APPEND_ONLY + flashCount = 2; +#endif + for (flash = 0; flash < flashCount; flash++) { + for (shutdownType = TPM_SU_CLEAR; shutdownType <= TPM_SU_STATE; + shutdownType++) { + (void)remove(FWTPM_NV_FILE); + #ifdef WOLFTPM_FWTPM_NV_APPEND_ONLY + XMEMSET(gFlashNv, 0xFF, sizeof(gFlashNv)); + #endif + now = 0; + persistence_clock_boot(&ctx, &now, flash); + AssertIntEQ(persistence_lifecycle(&ctx, TPM_CC_Startup, + TPM_SU_CLEAR), 0); + now = FWTPM_CLOCK_UPDATE_MS; + (void)persistence_read_clock(&ctx, &safe); + AssertIntEQ(safe, YES); + now += 1234; + highWater = persistence_read_clock(&ctx, &safe); + test_fwtpm_power_cut(&ctx); + + now = 0; + persistence_clock_boot(&ctx, &now, flash); + AssertIntEQ(persistence_lifecycle(&ctx, TPM_CC_Startup, + TPM_SU_CLEAR), 0); + AssertTrue(persistence_read_clock(&ctx, &safe) < highWater); + AssertIntEQ(safe, NO); + + /* Full saves and journal compaction before recovery remain unsafe. */ + now = 100; + AssertIntEQ(FWTPM_NV_Save(&ctx), 0); + AssertIntEQ(ctx.clockSafe, NO); + now = 200; + ctx.nvWritePos = ctx.nvHal.maxSize; + AssertIntEQ(FWTPM_NV_SaveFlags(&ctx), 0); + AssertIntEQ(ctx.clockSafe, NO); + checkpoint = ctx.clockNvValue; + now = 300; + + /* A failed shutdown cannot promote safety or commit its marker. */ + savedWrite = ctx.nvHal.write; + ctx.nvHal.write = persistence_fail_write; + AssertIntNE(persistence_lifecycle(&ctx, TPM_CC_Shutdown, + shutdownType), 0); + AssertIntEQ(ctx.clockSafe, NO); + AssertIntEQ(ctx.nvOrderly, NO); + AssertIntEQ(ctx.shutdownStateSaved, NO); + AssertTrue(ctx.clockNvValue == checkpoint); + ctx.nvHal.write = savedWrite; + + AssertIntEQ(persistence_lifecycle(&ctx, TPM_CC_Shutdown, + shutdownType), 0); + AssertIntEQ(ctx.clockSafe, NO); + AssertTrue(ctx.clockNvValue == checkpoint); + #ifdef WOLFTPM_FWTPM_NV_APPEND_ONLY + if (flash) { + /* A torn trailing header forces compaction during Init. */ + XMEMSET(torn, 0xFF, sizeof(torn)); + torn[0] = 0x5A; + AssertIntEQ(flash_write(NULL, ctx.nvWritePos, torn, + sizeof(torn)), 0); + } + #endif + test_fwtpm_power_cut(&ctx); + now = 0; + persistence_clock_boot(&ctx, &now, flash); + /* Recovered compaction must survive another immediate reset. */ + test_fwtpm_power_cut(&ctx); + persistence_clock_boot(&ctx, &now, flash); + AssertIntEQ(ctx.nvOrderly, YES); + AssertIntEQ(ctx.shutdownStateSaved, shutdownType == TPM_SU_STATE); + AssertIntEQ(ctx.clockSafe, NO); + AssertTrue(ctx.clockNvValue == checkpoint); + AssertIntEQ(persistence_lifecycle(&ctx, TPM_CC_Startup, + shutdownType), 0); + AssertTrue(persistence_read_clock(&ctx, &safe) < highWater); + AssertIntEQ(safe, NO); + + /* Library cleanup also preserves an unsafe, non-orderly state. */ + AssertIntEQ(FWTPM_Cleanup(&ctx), 0); + persistence_clock_boot(&ctx, &now, flash); + AssertIntEQ(ctx.nvOrderly, NO); + AssertIntEQ(ctx.clockSafe, NO); + AssertIntEQ(persistence_lifecycle(&ctx, TPM_CC_Startup, + TPM_SU_CLEAR), 0); + now = FWTPM_CLOCK_UPDATE_MS; + savedWrite = ctx.nvHal.write; + ctx.nvHal.write = persistence_fail_write; + AssertIntEQ(FWTPM_Clock_Update(&ctx), TPM_RC_NV_UNAVAILABLE); + AssertIntEQ(ctx.clockSafe, NO); + ctx.nvHal.write = savedWrite; + /* A failed write-once append requires restart before retrying. */ + test_fwtpm_power_cut(&ctx); + now = 0; + persistence_clock_boot(&ctx, &now, flash); + AssertIntEQ(persistence_lifecycle(&ctx, TPM_CC_Startup, + TPM_SU_CLEAR), 0); + AssertIntEQ(ctx.clockSafe, NO); + now = FWTPM_CLOCK_UPDATE_MS; + AssertTrue(persistence_read_clock(&ctx, &safe) >= highWater); + AssertIntEQ(safe, YES); + AssertIntEQ(persistence_lifecycle(&ctx, TPM_CC_Shutdown, + shutdownType), 0); + test_fwtpm_power_cut(&ctx); + now = 0; + persistence_clock_boot(&ctx, &now, flash); + AssertIntEQ(ctx.clockSafe, YES); + AssertIntEQ(persistence_lifecycle(&ctx, TPM_CC_Startup, + shutdownType), 0); + (void)persistence_read_clock(&ctx, &safe); + AssertIntEQ(safe, YES); + AssertIntEQ(FWTPM_Cleanup(&ctx), 0); + } + } + (void)remove(FWTPM_NV_FILE); + fwtpm_pass("Unsafe Clock survives shutdown and cleanup:", 0); +} + +static void test_fwtpm_clock_shutdown_marker(void) +{ + FWTPM_CTX ctx; + byte marker[3] = {1, 1, 0}; + int state, safe; + + /* Legacy markers say nothing about Clock safety. */ + AssertIntEQ(NvLoadCraftedEntry(&ctx, FWTPM_NV_TAG_SHUTDOWN_STATE, + marker, 1), 0); + AssertIntEQ(ctx.nvOrderly, YES); + AssertIntEQ(ctx.shutdownStateSaved, YES); + AssertIntEQ(ctx.clockSafe, NO); + for (state = 0; state <= 1; state++) { + marker[0] = (byte)state; + AssertIntEQ(NvLoadCraftedEntry(&ctx, FWTPM_NV_TAG_SHUTDOWN_STATE, + marker, 2), 0); + AssertIntEQ(ctx.nvOrderly, YES); + AssertIntEQ(ctx.shutdownStateSaved, state); + AssertIntEQ(ctx.clockSafe, NO); + for (safe = 0; safe <= 1; safe++) { + marker[2] = (byte)safe; + AssertIntEQ(NvLoadCraftedEntry(&ctx, FWTPM_NV_TAG_SHUTDOWN_STATE, + marker, sizeof(marker)), 0); + AssertIntEQ(ctx.nvOrderly, YES); + AssertIntEQ(ctx.shutdownStateSaved, state); + AssertIntEQ(ctx.clockSafe, safe); + } + } + marker[2] = 2; + AssertIntEQ(NvLoadCraftedEntry(&ctx, FWTPM_NV_TAG_SHUTDOWN_STATE, + marker, sizeof(marker)), TPM_RC_FAILURE); + fwtpm_pass("Clock safety in current and legacy NV markers:", 0); +} +#endif /* !FWTPM_NO_CLOCK */ + +static TPM_RC persistence_increment(FWTPM_CTX* ctx, UINT32 index) +{ + int pos, rspSize = 0; + pos = BuildCmdHeader(gCmd, TPM_ST_SESSIONS, 0, TPM_CC_NV_Increment); + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; + PutU32BE(gCmd + pos, index); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU32BE(gCmd + 2, pos); + FWTPM_ProcessCommand(ctx, gCmd, pos, gRsp, &rspSize, 0); + return (TPM_RC)GetRspRC(gRsp); +} + +static UINT64 persistence_counter(FWTPM_CTX* ctx) +{ + return ((UINT64)GetU32BE(ctx->nvIndices[0].data) << 32) | + GetU32BE(ctx->nvIndices[0].data + 4); +} + +static void test_fwtpm_orderly_counter_recovery(void) +{ + FWTPM_CTX ctx, probe; + UINT32 index = 0x01500003; + UINT32 attrs = TPMA_NV_OWNERWRITE | TPMA_NV_OWNERREAD | TPMA_NV_NO_DA | + TPMA_NV_ORDERLY | TPMA_NV_CLEAR_STCLEAR | + ((UINT32)TPM_NT_COUNTER << 4); + int pos, rspSize = 0; + int (*savedWrite)(void*, word32, const byte*, word32); + + (void)remove(FWTPM_NV_FILE); + XMEMSET(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + pos = BuildNvDefineCmd(gCmd, index, 8, attrs); + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(persistence_increment(&ctx, index), TPM_RC_SUCCESS); /* first write persists */ + AssertIntEQ(persistence_increment(&ctx, index), TPM_RC_SUCCESS); /* even value persists */ + AssertIntEQ(persistence_increment(&ctx, index), TPM_RC_SUCCESS); /* odd value remains in RAM */ + AssertIntEQ(persistence_counter(&ctx), 3); + XMEMSET(&probe, 0, sizeof(probe)); + AssertIntEQ(FWTPM_Init(&probe), 0); + AssertIntEQ(persistence_counter(&probe), 2); + test_fwtpm_power_cut(&probe); + savedWrite = ctx.nvHal.write; + ctx.nvHal.write = persistence_fail_write; + AssertIntNE(persistence_increment(&ctx, index), TPM_RC_SUCCESS); + AssertIntEQ(persistence_counter(&ctx), 3); + ctx.nvHal.write = savedWrite; + test_fwtpm_power_cut(&ctx); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + AssertIntEQ(persistence_counter(&ctx), 3); + AssertIntEQ(persistence_increment(&ctx, index), TPM_RC_SUCCESS); + AssertIntEQ(persistence_counter(&ctx), 4); + test_fwtpm_power_cut(&ctx); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + AssertIntEQ(persistence_counter(&ctx), 5); + test_fwtpm_power_cut(&ctx); /* no use: another loss must not advance it */ + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + AssertIntEQ(persistence_counter(&ctx), 5); + + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 12, TPM_CC_Shutdown); + PutU16BE(gCmd + pos, TPM_SU_CLEAR); pos += 2; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + test_fwtpm_power_cut(&ctx); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + AssertIntEQ(persistence_counter(&ctx), 5); + AssertIntEQ(persistence_increment(&ctx, index), TPM_RC_SUCCESS); + AssertIntEQ(persistence_counter(&ctx), 6); + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 12, TPM_CC_Shutdown); + PutU16BE(gCmd + pos, TPM_SU_CLEAR); pos += 2; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + /* Force dirty-marker persistence to compact the journal. The new + * snapshot must be dirty before the next odd increment is acknowledged. */ + ctx.nvWritePos = ctx.nvHal.maxSize; + AssertIntEQ(persistence_increment(&ctx, index), TPM_RC_SUCCESS); + AssertIntEQ(persistence_counter(&ctx), 7); + test_fwtpm_power_cut(&ctx); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + AssertIntEQ(persistence_counter(&ctx), 7); + FWTPM_Cleanup(&ctx); + (void)remove(FWTPM_NV_FILE); + fwtpm_pass("Orderly counter modulo and recovery:", 0); +} +#endif /* !FWTPM_NO_NV */ + +/* Resource managers need current counts as well as the fixed limits. */ +static UINT32 resource_property(FWTPM_CTX* ctx, UINT32 property) +{ + int pos, rspSize = 0; + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 22, TPM_CC_GetCapability); + PutU32BE(gCmd + pos, TPM_CAP_TPM_PROPERTIES); pos += 4; + PutU32BE(gCmd + pos, property); pos += 4; + PutU32BE(gCmd + pos, 1); pos += 4; + FWTPM_ProcessCommand(ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE + 5), 1); + AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE + 9), property); + return GetU32BE(gRsp + TPM2_HEADER_SIZE + 13); +} + +static void test_fwtpm_resource_properties(void) +{ + FWTPM_CTX ctx; + UINT32 sessions[FWTPM_MAX_SESSIONS]; + UINT32 activeMax = FWTPM_MAX_SESSIONS; + int i; +#if !defined(FWTPM_NO_CONTEXT) || !defined(FWTPM_NO_NV) || \ + defined(HAVE_ECC) || (!defined(NO_RSA) && defined(WOLFSSL_KEY_GEN)) + int pos, rspSize = 0; +#endif +#ifndef FWTPM_NO_CONTEXT + UINT32 saved; +#endif +#if defined(HAVE_ECC) || (!defined(NO_RSA) && defined(WOLFSSL_KEY_GEN)) + UINT32 object; +#endif + + (void)remove(FWTPM_NV_FILE); + XMEMSET(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); +#ifndef FWTPM_NO_CONTEXT + activeMax += sizeof(ctx.contextLive) / sizeof(ctx.contextLive[0]); +#endif + AssertIntEQ(resource_property(&ctx, TPM_PT_HR_LOADED), 0); + AssertIntEQ(resource_property(&ctx, TPM_PT_HR_LOADED_AVAIL), + FWTPM_MAX_SESSIONS); + AssertIntEQ(resource_property(&ctx, TPM_PT_HR_ACTIVE), 0); + AssertIntEQ(resource_property(&ctx, TPM_PT_HR_ACTIVE_AVAIL), activeMax); + AssertIntEQ(resource_property(&ctx, TPM_PT_HR_TRANSIENT_AVAIL), + FWTPM_MAX_OBJECTS); + for (i = 0; i < FWTPM_MAX_SESSIONS; i++) { + sessions[i] = StartSessionHelper(&ctx, TPM_SE_HMAC); + AssertIntNE(sessions[i], 0); + AssertIntEQ(resource_property(&ctx, TPM_PT_HR_LOADED), i + 1); + AssertIntEQ(resource_property(&ctx, TPM_PT_HR_ACTIVE), i + 1); + AssertIntEQ(resource_property(&ctx, TPM_PT_HR_LOADED_AVAIL), + FWTPM_MAX_SESSIONS - i - 1); + AssertIntEQ(resource_property(&ctx, TPM_PT_HR_ACTIVE_AVAIL), + activeMax - i - 1); + } +#ifndef FWTPM_NO_CONTEXT + saved = sessions[0]; + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 14, TPM_CC_ContextSave); + PutU32BE(gCmd + pos, saved); pos += 4; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(resource_property(&ctx, TPM_PT_HR_LOADED), + FWTPM_MAX_SESSIONS - 1); + AssertIntEQ(resource_property(&ctx, TPM_PT_HR_LOADED_AVAIL), 1); + AssertIntEQ(resource_property(&ctx, TPM_PT_HR_ACTIVE), FWTPM_MAX_SESSIONS); + sessions[0] = StartSessionHelper(&ctx, TPM_SE_HMAC); + AssertIntNE(sessions[0], 0); + AssertIntNE(sessions[0], saved); + AssertIntEQ(resource_property(&ctx, TPM_PT_HR_ACTIVE), + FWTPM_MAX_SESSIONS + 1); + FlushHandle(&ctx, saved); +#endif + for (i = 0; i < FWTPM_MAX_SESSIONS; i++) { + FlushHandle(&ctx, sessions[i]); + } + AssertIntEQ(resource_property(&ctx, TPM_PT_HR_LOADED), 0); + AssertIntEQ(resource_property(&ctx, TPM_PT_HR_ACTIVE), 0); + AssertIntEQ(resource_property(&ctx, TPM_PT_HR_ACTIVE_AVAIL), activeMax); + +#if defined(HAVE_ECC) || (!defined(NO_RSA) && defined(WOLFSSL_KEY_GEN)) +#ifdef HAVE_ECC + object = CreatePrimaryHelper(&ctx, TPM_ALG_ECC); +#else + object = CreatePrimaryHelper(&ctx, TPM_ALG_RSA); +#endif + AssertIntNE(object, 0); + AssertIntEQ(resource_property(&ctx, TPM_PT_HR_TRANSIENT_AVAIL), + FWTPM_MAX_OBJECTS - 1); +#ifndef FWTPM_NO_NV + pos = BuildCmdHeader(gCmd, TPM_ST_SESSIONS, 0, TPM_CC_EvictControl); + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; + PutU32BE(gCmd + pos, object); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU32BE(gCmd + pos, PERSISTENT_FIRST); pos += 4; + PutU32BE(gCmd + 2, pos); + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(resource_property(&ctx, TPM_PT_HR_PERSISTENT), 1); + AssertIntEQ(resource_property(&ctx, TPM_PT_HR_PERSISTENT_AVAIL), + FWTPM_MAX_PERSISTENT - 1); +#endif + FlushHandle(&ctx, object); + AssertIntEQ(resource_property(&ctx, TPM_PT_HR_TRANSIENT_AVAIL), + FWTPM_MAX_OBJECTS); +#endif + +#ifndef FWTPM_NO_NV + AssertIntEQ(resource_property(&ctx, TPM_PT_HR_NV_INDEX), 0); + AssertIntEQ(resource_property(&ctx, TPM_PT_NV_COUNTERS), 0); + AssertIntEQ(resource_property(&ctx, TPM_PT_NV_COUNTERS_AVAIL), + FWTPM_MAX_NV_INDICES); + pos = BuildNvDefineCmd(gCmd, NV_INDEX_FIRST + 1, 8, + TPMA_NV_OWNERREAD | TPMA_NV_OWNERWRITE | TPMA_NV_NO_DA); + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(resource_property(&ctx, TPM_PT_HR_NV_INDEX), 1); + AssertIntEQ(resource_property(&ctx, TPM_PT_NV_COUNTERS), 0); + pos = BuildNvDefineCmd(gCmd, NV_INDEX_FIRST + 2, 8, + TPMA_NV_OWNERREAD | TPMA_NV_OWNERWRITE | TPMA_NV_NO_DA | + TPMA_NV_ORDERLY | ((UINT32)TPM_NT_COUNTER << 4)); + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(resource_property(&ctx, TPM_PT_HR_NV_INDEX), 2); + AssertIntEQ(resource_property(&ctx, TPM_PT_NV_COUNTERS), 1); + AssertIntEQ(resource_property(&ctx, TPM_PT_NV_COUNTERS_AVAIL), + FWTPM_MAX_NV_INDICES - 2); +#else + AssertIntEQ(resource_property(&ctx, TPM_PT_HR_PERSISTENT), 0); + AssertIntEQ(resource_property(&ctx, TPM_PT_HR_PERSISTENT_AVAIL), 0); + AssertIntEQ(resource_property(&ctx, TPM_PT_HR_NV_INDEX), 0); + AssertIntEQ(resource_property(&ctx, TPM_PT_NV_COUNTERS), 0); + AssertIntEQ(resource_property(&ctx, TPM_PT_NV_COUNTERS_AVAIL), 0); +#endif + FWTPM_Cleanup(&ctx); + (void)remove(FWTPM_NV_FILE); + fwtpm_pass("Current resource capability counts:", 0); +} + +#ifndef FWTPM_NO_CONTEXT +/* The active-session limit includes saved sessions. Saving a session frees its + * RAM slot but must not make its handle available to StartAuthSession. */ +static void test_fwtpm_active_session_capacity(void) +{ + FWTPM_CTX ctx; + byte saved[MAX_CONTEXT_SIZE]; + UINT32 handles[FWTPM_MAX_OBJECTS + 2 * FWTPM_MAX_SESSIONS]; + UINT32 h; + int i, j, pos, rspSize = 0, savedSz = 0; + int savedCount = FWTPM_MAX_OBJECTS + FWTPM_MAX_SESSIONS; + + XMEMSET(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + for (i = 0; i < savedCount + FWTPM_MAX_SESSIONS; i++) { + h = StartSessionHelper(&ctx, TPM_SE_HMAC); + AssertIntNE(h, 0); + for (j = 0; j < i; j++) { + AssertIntNE(h, handles[j]); + } + handles[i] = h; + if (i < savedCount) { + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 14, + TPM_CC_ContextSave); + PutU32BE(gCmd + pos, h); pos += 4; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + if (i == 0) { + savedSz = rspSize - TPM2_HEADER_SIZE; + AssertIntLE(savedSz, sizeof(saved)); + XMEMCPY(saved, gRsp + TPM2_HEADER_SIZE, savedSz); + } + } + } + AssertIntEQ(StartSessionHelper(&ctx, TPM_SE_HMAC), 0); + FlushHandle(&ctx, handles[savedCount]); + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_ContextLoad); + XMEMCPY(gCmd + pos, saved, savedSz); pos += savedSz; + PutU32BE(gCmd + 2, pos); + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE), handles[0]); + FWTPM_Cleanup(&ctx); + fwtpm_pass("Active and saved session capacity:", 0); +} +#endif + +/* Every defined fixed property must be queryable by its exact tag. The + * reserved PT_FIXED+21 slot is omitted; zero-valued properties are present. */ +static void test_fwtpm_fixed_properties_complete(void) +{ + FWTPM_CTX ctx; + UINT32 prop, last, value, count; + int pos, rspSize, i; + + XMEMSET(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); +#ifdef WOLFTPM_V185 + last = TPM_PT_ML_PARAMETER_SETS; +#else + last = TPM_PT_MAX_CAP_BUFFER; +#endif + for (prop = TPM_PT_FAMILY_INDICATOR; prop <= last; prop++) { + if (prop == PT_FIXED + 21) continue; + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 22, + TPM_CC_GetCapability); + PutU32BE(gCmd + pos, TPM_CAP_TPM_PROPERTIES); pos += 4; + PutU32BE(gCmd + pos, prop); pos += 4; + PutU32BE(gCmd + pos, 1); pos += 4; + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE + 5), 1); + AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE + 9), prop); + AssertIntEQ(gRsp[TPM2_HEADER_SIZE], prop < last); + value = GetU32BE(gRsp + TPM2_HEADER_SIZE + 13); + if (prop == TPM_PT_HR_TRANSIENT_MIN) { + AssertIntEQ(value, FWTPM_MAX_OBJECTS); + } + else if (prop == TPM_PT_HR_LOADED_MIN) { + AssertIntEQ(value, FWTPM_MAX_SESSIONS); + } + else if (prop == TPM_PT_HR_PERSISTENT_MIN) { + #ifndef FWTPM_NO_NV + AssertIntEQ(value, FWTPM_MAX_PERSISTENT); + #else + AssertIntEQ(value, 0); + #endif + } + else if (prop == TPM_PT_MAX_DIGEST) { + #if defined(WOLFSSL_SHA512) || (defined(WOLFSSL_SHA3) && \ + !defined(WOLFSSL_NOSHA3_512)) + AssertIntEQ(value, TPM_SHA512_DIGEST_SIZE); + #elif defined(WOLFSSL_SHA384) || (defined(WOLFSSL_SHA3) && \ + !defined(WOLFSSL_NOSHA3_384)) + AssertIntEQ(value, TPM_SHA384_DIGEST_SIZE); + #else + AssertIntEQ(value, TPM_SHA256_DIGEST_SIZE); + #endif + } + else if (prop == TPM_PT_CLOCK_UPDATE) { + #if !defined(FWTPM_NO_CLOCK) && !defined(FWTPM_NO_NV) + AssertIntEQ(value, FWTPM_CLOCK_UPDATE_MS); + #else + AssertIntEQ(value, 0); + #endif + } + else if (prop == TPM_PT_CONTEXT_SYM) { + #ifndef FWTPM_NO_CONTEXT + AssertIntEQ(value, TPM_ALG_AES); + #else + AssertIntEQ(value, TPM_ALG_NULL); + #endif + } + else if (prop == TPM_PT_CONTEXT_SYM_SIZE) { + #ifndef FWTPM_NO_CONTEXT + AssertIntEQ(value, sizeof(ctx.ctxProtectKey) * 8); + #else + AssertIntEQ(value, 0); + #endif + } + } + /* Bulk enumeration stays sorted, with only the reserved hole. */ + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 22, TPM_CC_GetCapability); + PutU32BE(gCmd + pos, TPM_CAP_TPM_PROPERTIES); pos += 4; + PutU32BE(gCmd + pos, PT_FIXED); pos += 4; + PutU32BE(gCmd + pos, MAX_TPM_PROPERTIES); pos += 4; + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + count = GetU32BE(gRsp + TPM2_HEADER_SIZE + 5); + AssertIntEQ(count, last - PT_FIXED); + AssertIntEQ(rspSize, TPM2_HEADER_SIZE + 9 + count * 8); + for (i = 0; i < (int)count; i++) { + prop = PT_FIXED + (UINT32)i + (i >= 21 ? 1 : 0); + AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE + 9 + i * 8), prop); + } + FWTPM_Cleanup(&ctx); + fwtpm_pass("Complete fixed property capabilities:", 0); +} + #ifndef NO_MAIN_DRIVER int main(int argc, char *argv[]) #else @@ -20740,6 +21547,32 @@ int fwtpm_unit_tests(int argc, char *argv[]) #endif } + if (argc == 2 && XSTRCMP(argv[1], "resource-properties") == 0) { + test_fwtpm_resource_properties(); + return 0; + } + + if (argc == 1) { + test_fwtpm_resource_properties(); + test_fwtpm_fixed_properties_complete(); +#ifndef FWTPM_NO_CONTEXT + test_fwtpm_active_session_capacity(); +#endif +#if defined(FWTPM_NO_NV) && !defined(FWTPM_NO_CLOCK) + test_fwtpm_clock_power_cycle_no_nv(); +#endif +#ifndef FWTPM_NO_NV +#ifndef FWTPM_NO_CLOCK + test_fwtpm_clock_checkpoint_recovery(); + test_fwtpm_clock_power_cycle(); + test_fwtpm_clock_compaction(); + test_fwtpm_clock_unsafe_shutdown(); + test_fwtpm_clock_shutdown_marker(); +#endif + test_fwtpm_orderly_counter_recovery(); +#endif + } + #ifdef WOLFTPM_FWTPM_UNIT_TEST if (argc == 2 && XSTRCMP(argv[1], "custom-io-hal") == 0) { test_fwtpm_custom_io_hal(); @@ -20808,6 +21641,34 @@ int fwtpm_unit_tests(int argc, char *argv[]) } #endif +#ifndef FWTPM_NO_NV + if (argc == 2 && XSTRCMP(argv[1], "clock-persistence") == 0) { +#ifndef FWTPM_NO_CLOCK + test_fwtpm_clock_checkpoint_recovery(); + test_fwtpm_clock_power_cycle(); + test_fwtpm_clock_compaction(); + test_fwtpm_clock_unsafe_shutdown(); + test_fwtpm_clock_shutdown_marker(); +#endif + return 0; + } + if (argc == 2 && XSTRCMP(argv[1], "counter-persistence") == 0) { + test_fwtpm_orderly_counter_recovery(); + return 0; + } +#endif +#ifndef FWTPM_NO_CONTEXT + if (argc == 2 && XSTRCMP(argv[1], "session-capacity") == 0) { + test_fwtpm_active_session_capacity(); + return 0; + } +#endif + if (argc == 2 && XSTRCMP(argv[1], "fixed-properties") == 0) { + test_fwtpm_fixed_properties_complete(); + test_fwtpm_total_commands(); + return 0; + } + if (argc == 2 && XSTRCMP(argv[1], "clear-seed") == 0) { test_fwtpm_clear(); return 0; diff --git a/wolftpm/fwtpm/fwtpm.h b/wolftpm/fwtpm/fwtpm.h index acc06293..4de684fd 100644 --- a/wolftpm/fwtpm/fwtpm.h +++ b/wolftpm/fwtpm/fwtpm.h @@ -364,6 +364,12 @@ #endif #endif +/* Maximum NV Clock lag. The host polls at least once per second; checkpoint + * one poll early so scheduler wakeups stay within the advertised bound. */ +#define FWTPM_CLOCK_UPDATE_MS (1u << 22) +#define FWTPM_CLOCK_POLL_MS 1000u +#define FWTPM_ORDERLY_COUNT 1u + /* Compile-time build date parsed from __DATE__ ("Mmm DD YYYY") */ #define FWTPM_BUILD_YEAR \ (((__DATE__[7] - '0') * 1000) + ((__DATE__[8] - '0') * 100) + \ @@ -985,6 +991,11 @@ typedef struct FWTPM_CTX { /* Transient authorization methods for the command being dispatched. */ byte activeCmdAuthIsPolicy[FWTPM_MAX_CMD_AUTHS]; #endif + + UINT64 clockNvValue; /* Last durable Clock checkpoint */ + UINT64 clockUnsafeUntil; /* Prior boot's potentially reported range */ + int clockSafe; /* Clock cannot repeat a reported value */ + int nvOrderly; /* Final clean snapshot marker was committed */ } FWTPM_CTX; /* fwTPM SPDM mode bits - used with FWTPM_CTX.spdmMode. */ @@ -1077,6 +1088,43 @@ WOLFTPM_API int FWTPM_Clock_SetHAL(FWTPM_CTX* ctx, */ WOLFTPM_API UINT64 FWTPM_Clock_GetMs(FWTPM_CTX* ctx); +/*! + \ingroup wolfTPM_fwTPM + \brief Resume Clock after a platform power cycle that keeps the same + FWTPM_CTX. Clock restarts from its last NV checkpoint, so powered-off + time is not counted and Clock cannot roll back. Without an orderly + Shutdown, Clock reports unsafe until the recovery interval has passed. + Call it on power-on, before Startup. + + \return TPM_RC_SUCCESS on success + \return BAD_FUNC_ARG if ctx is NULL + + \param ctx pointer to an initialized FWTPM_CTX + + \sa FWTPM_Clock_GetMs + \sa FWTPM_Clock_Update +*/ +WOLFTPM_API int FWTPM_Clock_PowerOn(FWTPM_CTX* ctx); + +/*! + \ingroup wolfTPM_fwTPM + \brief Checkpoint Clock to NV when due. Command dispatch and socket loops + call this automatically. Embedded hosts must also call it at least every + FWTPM_CLOCK_POLL_MS milliseconds while idle, using a milliseconds-since- + power-on clock HAL. HAL wait, receive and send callbacks must return + within that interval. + + \return TPM_RC_SUCCESS on success or when no update is due + \return BAD_FUNC_ARG if ctx is NULL + \return TPM_RC_NV_UNAVAILABLE when storage cannot be written + \return TPM_RC_NV_RATE when storage temporarily limits writes + \return other NV error if the checkpoint cannot be committed + \param ctx pointer to an initialized FWTPM_CTX + \sa FWTPM_Clock_SetHAL + \sa FWTPM_Clock_GetMs +*/ +WOLFTPM_API int FWTPM_Clock_Update(FWTPM_CTX* ctx); + /*! \ingroup wolfTPM_fwTPM \brief Register the physical-presence HAL. get_pp must return non-zero only diff --git a/wolftpm/fwtpm/fwtpm_tis.h b/wolftpm/fwtpm/fwtpm_tis.h index 52f70f12..b99ce158 100644 --- a/wolftpm/fwtpm/fwtpm_tis.h +++ b/wolftpm/fwtpm/fwtpm_tis.h @@ -179,8 +179,9 @@ typedef struct FWTPM_TIS_HAL { * Returns 0 on success. */ int (*init)(void* ctx, FWTPM_TIS_REGS** regs); - /* Block until a register access request is available. Returns 0 when a - * request is ready; -1 is reserved for a transient/interrupted wait and + /* Wait at most FWTPM_CLOCK_POLL_MS for a register access, so the server + * can checkpoint Clock while idle. Returns 0 when a request is ready; + * -1 indicates timeout or a transient/interrupted wait and * makes the server retry without examining errno. Any other negative * value is fatal and ends the server loop. */ int (*wait_request)(void* ctx); From 6087cfe7db0bc6a1fb05696d2c96a2be3b4dbfc6 Mon Sep 17 00:00:00 2001 From: David Garske Date: Wed, 28 Oct 2026 15:57:09 -0700 Subject: [PATCH 11/12] Add tpm2-pkcs11 CI test against fwTPM, direct and through tpm2-abrmd --- .github/workflows/fwtpm-test.yml | 75 +++++++++++++++ scripts/tpm2_pkcs11_test.sh | 154 +++++++++++++++++++++++++++++++ 2 files changed, 229 insertions(+) create mode 100755 scripts/tpm2_pkcs11_test.sh diff --git a/.github/workflows/fwtpm-test.yml b/.github/workflows/fwtpm-test.yml index 480c8fa1..6c03cb57 100644 --- a/.github/workflows/fwtpm-test.yml +++ b/.github/workflows/fwtpm-test.yml @@ -560,6 +560,81 @@ jobs: # 77 is the autotools "skip" code; treat it as a pass. [ "${rc:-0}" -eq 0 ] || [ "${rc:-0}" -eq 77 ] + # ---------------------------------------------------------------- + # tpm2-pkcs11 against fwtpm_server, directly and through tpm2-abrmd. + # wolfSSL allows a zero ECDSA hash so an all-zero digest is signed, as on + # the reference TPM. + # ---------------------------------------------------------------- + fwtpm-tpm2-pkcs11: + if: github.event_name != 'pull_request' || github.event.pull_request.draft == false + runs-on: ubuntu-latest + container: + image: ghcr.io/wolfssl/wolftpm-ci:v1.1 + defaults: + run: + shell: bash + strategy: + fail-fast: false + matrix: + include: + - name: classic + wolftpm_config: --enable-fwtpm --enable-swtpm + wolfssl_config: --enable-wolftpm --enable-pkcallbacks --enable-keygen + - name: v185 + wolftpm_config: --enable-fwtpm --enable-swtpm --enable-v185 + wolfssl_config: --enable-wolftpm --enable-pkcallbacks --enable-keygen --enable-dilithium --enable-mlkem --enable-experimental + steps: + - name: Checkout wolfTPM + uses: actions/checkout@v4 + with: + submodules: true + + - name: Install tpm2-pkcs11, tpm2-abrmd and OpenSC + run: | + apt-get update + DEBIAN_FRONTEND=noninteractive apt-get install -y \ + libtpm2-pkcs11-1 libtpm2-pkcs11-tools tpm2-abrmd \ + libtss2-tcti-tabrmd0 opensc dbus + + - name: Setup wolfSSL + uses: ./.github/actions/setup-wolfssl + with: + configure-flags: ${{ matrix.wolfssl_config }} + cflags: -DWC_RSA_NO_PADDING -DWC_ALLOW_ECC_ZERO_HASH + + - name: Build wolfTPM + run: | + ./autogen.sh + ./configure ${{ matrix.wolftpm_config }} + make -j"$(nproc)" + + - name: Run tpm2-pkcs11 tests + run: | + ./src/fwtpm/fwtpm_server --clear > /tmp/fwtpm_pkcs11.log 2>&1 & + server_pid=$! + trap 'kill $server_pid $abrmd_pid 2>/dev/null || true' EXIT + sleep 1 + kill -0 "$server_pid" + tpm2_startup -c -T mssim:host=localhost,port=2321 + + echo "=== direct (mssim) ===" + scripts/tpm2_pkcs11_test.sh + + echo "=== through tpm2-abrmd ===" + export DBUS_SESSION_BUS_ADDRESS=$(dbus-daemon --session --fork \ + --print-address) + tpm2-abrmd --session --allow-root \ + --tcti=mssim:host=localhost,port=2321 > /tmp/abrmd.log 2>&1 & + abrmd_pid=$! + sleep 2 + kill -0 "$abrmd_pid" + TPM2_PKCS11_TCTI=tabrmd:bus_type=session scripts/tpm2_pkcs11_test.sh + + - name: Print logs on failure + if: failure() + run: | + cat /tmp/fwtpm_pkcs11.log /tmp/abrmd.log 2>/dev/null || true + # ---------------------------------------------------------------- # tpm2-tools compatibility test against IBM SW TPM # Validates that tpm2_tools_test.sh works on a reference TPM. diff --git a/scripts/tpm2_pkcs11_test.sh b/scripts/tpm2_pkcs11_test.sh new file mode 100755 index 00000000..1404a482 --- /dev/null +++ b/scripts/tpm2_pkcs11_test.sh @@ -0,0 +1,154 @@ +#!/bin/bash +# tpm2_pkcs11_test.sh +# Run tpm2-pkcs11 (tpm2_ptool + OpenSC pkcs11-tool) against a running TPM, +# either directly or through tpm2-abrmd. +# +# Usage: +# scripts/tpm2_pkcs11_test.sh +# +# Environment: +# TPM2_PKCS11_TCTI TCTI for tpm2-pkcs11 and tpm2_ptool +# (default: mssim:host=localhost,port=2321; +# use tabrmd:bus_type=session behind tpm2-abrmd) +# PKCS11_MODULE path to libtpm2_pkcs11.so (default: searched) +# +# Requirements: tpm2-pkcs11 (libtpm2_pkcs11.so, tpm2_ptool), OpenSC +# pkcs11-tool, openssl +# +# Exit: 0 if all tests pass, 77 if a requirement is missing (SKIP), 1 on failure + +TPM2_PKCS11_TCTI="${TPM2_PKCS11_TCTI:-mssim:host=localhost,port=2321}" +export TPM2_PKCS11_TCTI +export TPM2TOOLS_TCTI="$TPM2_PKCS11_TCTI" + +if [ -z "$PKCS11_MODULE" ]; then + PKCS11_MODULE=$(find /usr/lib /usr/local/lib -name 'libtpm2_pkcs11.so*' \ + 2>/dev/null | head -1) +fi +for tool in tpm2_ptool tpm2_flushcontext pkcs11-tool openssl; do + if ! command -v "$tool" >/dev/null 2>&1; then + echo "SKIP: $tool not installed" + exit 77 + fi +done +if [ -z "$PKCS11_MODULE" ] || [ ! -f "$PKCS11_MODULE" ]; then + echo "SKIP: libtpm2_pkcs11.so not found" + exit 77 +fi + +PIN=1234 +SOPIN=5678 +LABEL=wolftpm +TMP=$(mktemp -d) +export TPM2_PKCS11_STORE="$TMP/store" +mkdir -p "$TPM2_PKCS11_STORE" +trap 'rm -rf "$TMP"' EXIT + +PASS=0 +FAIL=0 + +run_test() { + local name="$1" + shift + if "$@" > "$TMP/out.log" 2>&1; then + echo " [PASS] $name" + PASS=$((PASS+1)) + else + echo " [FAIL] $name" + sed 's/^/ /' "$TMP/out.log" + FAIL=$((FAIL+1)) + fi +} + +# Without a resource manager, tpm2-tools and tpm2-pkcs11 leave objects and +# sessions loaded when they exit. Flush them so later steps have free slots. +flush_tpm() { + case "$TPM2_PKCS11_TCTI" in + tabrmd*) ;; + *) + tpm2_flushcontext -t > /dev/null 2>&1 + tpm2_flushcontext -l > /dev/null 2>&1 + ;; + esac +} + +ptool() { + tpm2_ptool "$@" + local rc=$? + flush_tpm + return $rc +} + +p11() { + pkcs11-tool --module "$PKCS11_MODULE" --token-label "$LABEL" \ + --login --pin "$PIN" "$@" + local rc=$? + flush_tpm + return $rc +} + +echo "=== tpm2-pkcs11 tests (TCTI: $TPM2_PKCS11_TCTI) ===" + +run_test "ptool init" ptool init +run_test "ptool addtoken" ptool addtoken --pid=1 --label="$LABEL" \ + --userpin="$PIN" --sopin="$SOPIN" +run_test "ptool addkey ecc256" ptool addkey --label="$LABEL" \ + --userpin="$PIN" --algorithm=ecc256 --key-label=ecc --id=01 +run_test "ptool addkey rsa2048" ptool addkey --label="$LABEL" \ + --userpin="$PIN" --algorithm=rsa2048 --key-label=rsa --id=02 + +run_test "list objects" p11 --list-objects + +# Keys are selected by CKA_ID (the ASCII bytes of the tpm2_ptool --id); +# a label match can return another key +# Export public keys for software verification +run_test "read ECC public key" p11 --read-object --type pubkey --id 3031 \ + --output-file "$TMP/ecc.der" +run_test "read RSA public key" p11 --read-object --type pubkey --id 3032 \ + --output-file "$TMP/rsa.der" + +# ECDSA (CKM_ECDSA_SHA256) sign, verified by OpenSSL +printf 'wolfTPM tpm2-pkcs11 test' > "$TMP/msg.bin" +run_test "ECDSA-SHA256 sign" p11 --sign --mechanism ECDSA-SHA256 --id 3031 \ + --signature-format openssl --input-file "$TMP/msg.bin" \ + --output-file "$TMP/ecc.sig" +run_test "ECDSA-SHA256 verify (OpenSSL)" openssl dgst -sha256 \ + -verify "$TMP/ecc.der" -keyform DER -signature "$TMP/ecc.sig" \ + "$TMP/msg.bin" + +# CKM_ECDSA of an all-zero digest, as for an empty message padded to the +# hash length +head -c 32 /dev/zero > "$TMP/zero.bin" +run_test "ECDSA sign of an all-zero digest" p11 --sign --mechanism ECDSA \ + --id 3031 --input-file "$TMP/zero.bin" --output-file "$TMP/zero.sig" + +# RSA-PKCS#1 v1.5 sign, verified by OpenSSL +run_test "RSA-SHA256 sign" p11 --sign --mechanism SHA256-RSA-PKCS --id 3032 \ + --input-file "$TMP/msg.bin" --output-file "$TMP/rsa.sig" +run_test "RSA-SHA256 verify (OpenSSL)" openssl dgst -sha256 \ + -verify "$TMP/rsa.der" -keyform DER -signature "$TMP/rsa.sig" \ + "$TMP/msg.bin" + +# RSA-OAEP (SHA-256) encrypted by OpenSSL, decrypted by the TPM +printf 'oaep plaintext' > "$TMP/pt.bin" +run_test "RSA-OAEP encrypt (OpenSSL)" openssl pkeyutl -encrypt -pubin \ + -keyform DER -inkey "$TMP/rsa.der" -in "$TMP/pt.bin" -out "$TMP/ct.bin" \ + -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 \ + -pkeyopt rsa_mgf1_md:sha256 +run_test "RSA-OAEP decrypt" p11 --decrypt --mechanism RSA-PKCS-OAEP \ + --hash-algorithm SHA256 --mgf MGF1-SHA256 --id 3032 \ + --input-file "$TMP/ct.bin" --output-file "$TMP/dec.bin" +run_test "RSA-OAEP plaintext matches" cmp "$TMP/pt.bin" "$TMP/dec.bin" + +# RSA-OAEP with SHA-1 and MGF1-SHA-1 +run_test "RSA-OAEP-SHA1 encrypt (OpenSSL)" openssl pkeyutl -encrypt -pubin \ + -keyform DER -inkey "$TMP/rsa.der" -in "$TMP/pt.bin" -out "$TMP/ct1.bin" \ + -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha1 \ + -pkeyopt rsa_mgf1_md:sha1 +run_test "RSA-OAEP-SHA1 decrypt" p11 --decrypt --mechanism RSA-PKCS-OAEP \ + --hash-algorithm SHA-1 --mgf MGF1-SHA1 --id 3032 \ + --input-file "$TMP/ct1.bin" --output-file "$TMP/dec1.bin" +run_test "RSA-OAEP-SHA1 plaintext matches" cmp "$TMP/pt.bin" "$TMP/dec1.bin" + +echo "=== tpm2-pkcs11 results: $PASS passed, $FAIL failed ===" +[ "$FAIL" -eq 0 ] From a341fd29d21f1d7eaaa9e29d124cbe5ead2fb711 Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 29 Oct 2026 08:01:19 -0700 Subject: [PATCH 12/12] Report the fwTPM Sign digest size error on parameter 1 --- src/fwtpm/fwtpm_command.c | 2 +- tests/fwtpm_unit_tests.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 37db3c88..feef0959 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -10022,7 +10022,7 @@ static TPM_RC FwCmd_Sign(FWTPM_CTX* ctx, TPM2_Packet* cmd, * size (matches FwCmd_SignDigest). */ if (rc == 0 && sigHashAlg != TPM_ALG_NULL && digest.size != (UINT16)TPM2_GetHashDigestSize(sigHashAlg)) { - rc = TPM_RC_SIZE; + rc = TPM_RC_SIZE | TPM_RC_P | TPM_RC_1; } /* Parse validation ticket (TPMT_TK_HASHCHECK) */ diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index cdda7717..d851b685 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -3240,7 +3240,7 @@ static void test_fwtpm_sign_digest_size_mismatch(void) rspSize = 0; rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); AssertIntEQ(rc, TPM_RC_SUCCESS); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_SIZE); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SIZE | TPM_RC_P | TPM_RC_1); cmdSz = BuildSignCmd(gCmd, keyHandle, digest, TPM_SHA256_DIGEST_SIZE, TPM_ALG_RSASSA, TPM_ALG_SHA256); @@ -3265,7 +3265,7 @@ static void test_fwtpm_sign_digest_size_mismatch(void) rspSize = 0; rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); AssertIntEQ(rc, TPM_RC_SUCCESS); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_SIZE); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SIZE | TPM_RC_P | TPM_RC_1); FWTPM_Cleanup(&ctx); fwtpm_pass("Sign digest-size mismatch rejected:", 0);