diff --git a/.gitignore b/.gitignore index a248c34b3..fb9e0b3b9 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,7 @@ examples/pcr/extend examples/pcr/policy examples/pcr/policy_sign examples/pcr/reset +examples/pcr/allocate examples/timestamp/clock_set examples/management/flush examples/management/tpmclear diff --git a/CMakeLists.txt b/CMakeLists.txt index 866c07c37..c1f958e75 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -794,6 +794,7 @@ if (WOLFTPM_EXAMPLES AND BUILD_WOLFTPM_LIB) add_tpm_example(quote pcr/quote.c) add_tpm_example(read_pcr pcr/read_pcr.c) add_tpm_example(reset pcr/reset.c) + add_tpm_example(allocate pcr/allocate.c) add_tpm_example(pkcs7 pkcs7/pkcs7.c) add_tpm_example(seal seal/seal.c) add_tpm_example(unseal seal/unseal.c) diff --git a/ChangeLog.md b/ChangeLog.md index 990e30530..a7ce02035 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -2,6 +2,12 @@ ## Unreleased +* Added `wolfTPM2_AllocatePCRBanks` for changing which PCR banks a TPM allocates. + - Added `examples/pcr/allocate` to report and re-provision the banks. + - Fixed the fwTPM applying the allocation immediately instead of at the next + `Startup(CLEAR)` per TPM 2.0 Part 3 22.5, accepting a selection that would + leave it with no PCR banks, ignoring the `pcrSelect` bitmap, and reporting + no allocated banks after a restart against an existing NV file. * Added SPDM transport-bound TPM policies (PR #594). - Added `TPM2_PolicyTransportSPDM` client support and fwTPM enforcement. - Added SPDM-bound NV policy examples and tests. diff --git a/examples/README.md b/examples/README.md index d7f4e7b33..6fda0cdc5 100644 --- a/examples/README.md +++ b/examples/README.md @@ -55,6 +55,7 @@ More information about how to test and use PCR attestation can be found in the i `./examples/pcr/quote` `./examples/pcr/extend` `./examples/pcr/reset` +`./examples/pcr/allocate` ### Remote Attestation challenge diff --git a/examples/bench/bench.c b/examples/bench/bench.c index 37e96b8f8..3c3678335 100644 --- a/examples/bench/bench.c +++ b/examples/bench/bench.c @@ -43,16 +43,44 @@ #define TPM2_BENCH_DURATION_KEYGEN_SEC 15 static int gUseBase2 = 1; +/* Fixed-iteration mode (-iter=N). Zero keeps the default duration mode, where + * each algorithm runs for a wall-clock budget instead of a set count. */ +static int gBenchIter = 0; +/* Per-iteration spread, so a single slow outlier is visible rather than + * averaged away. Rejection sampling makes ML-DSA signing vary run to run. */ +static double gIterPrev, gIterMin, gIterMax; + static inline void bench_stats_start(int* count, double* start) { *count = 0; *start = gettime_secs(1); + gIterPrev = *start; + gIterMin = 0; + gIterMax = 0; } static inline int bench_stats_check(double start, int* count, double maxDurSec) { + double now, each; + (*count)++; - return ((gettime_secs(0) - start) < maxDurSec); + now = gettime_secs(0); + each = now - gIterPrev; + gIterPrev = now; + if (*count == 1) { + gIterMin = each; + gIterMax = each; + } + else if (each > gIterMax) { + gIterMax = each; + } + else if (each < gIterMin) { + gIterMin = each; + } + if (gBenchIter > 0) { + return (*count < gBenchIter); + } + return ((now - start) < maxDurSec); } /* countSz is number of bytes that 1 count represents. Normally bench_size, @@ -105,8 +133,14 @@ static void bench_stats_sym_finish(const char* desc, int count, int countSz, } /* format and print to terminal */ - printf("%-16s %5.0f %s took %5.3f seconds, %8.3f %s/s\n", + printf("%-16s %5.0f %s took %5.3f seconds, %8.3f %s/s", desc, blocks, blockType, total, persec, blockType); + if (gBenchIter > 0) { + printf(", %d ops, avg %5.3f ms, min %5.3f ms, max %5.3f ms", + count, (count > 0) ? (total / count) * 1000 : 0, + gIterMin * 1000, gIterMax * 1000); + } + printf("\n"); } static void bench_stats_asym_finish(const char* algo, int strength, @@ -121,15 +155,21 @@ static void bench_stats_asym_finish(const char* algo, int strength, milliEach = each * 1000; /* milliseconds */ printf("%-6s %5d %-9s %6d ops took %5.3f sec, avg %5.3f ms," - " %.3f ops/sec\n", algo, strength, desc, + " %.3f ops/sec", algo, strength, desc, count, total, milliEach, opsSec); + if (gBenchIter > 0) { + printf(", min %5.3f ms, max %5.3f ms", + gIterMin * 1000, gIterMax * 1000); + } + printf("\n"); } /* True if rc means the TPM does not implement the operation (so the bench * can skip it instead of aborting). Masks parameter bits on FMT1 codes. */ static int bench_unsupported(int rc) { - return ((rc & 0xBF) == TPM_RC_SCHEME) || WOLFTPM_IS_COMMAND_UNAVAILABLE(rc); + return ((rc & 0xBF) == TPM_RC_SCHEME) || + WOLFTPM_IS_COMMAND_UNAVAILABLE_OR_DISABLED(rc); } /* Print timing on success, "Skipped" if the op was not implemented. Returns @@ -145,6 +185,14 @@ static int bench_asym_done(const char* algo, int strength, const char* desc, printf("%-6s %5d %-9s Skipped (not supported)\n", algo, strength, desc); return 0; } + /* A post-quantum signature can exceed the TPM's input buffer, which the + * verify guard reports before sending. That is a property of the part, + * not a benchmark failure, so record it and carry on. */ + if (rc == BUFFER_E) { + printf("%-6s %5d %-9s Skipped (signature exceeds TPM input buffer)\n", + algo, strength, desc); + return 0; + } return rc; } @@ -203,9 +251,10 @@ static int bench_sym_aes(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* storageKey, XMEMSET(iv, 0, sizeof(iv)); rc = wolfTPM2_EncryptDecrypt(dev, &aesKey, in, out, inOutSz, iv, sizeof(iv), isDecrypt); - if (WOLFTPM_IS_COMMAND_UNAVAILABLE(rc)) { - printf("Encrypt/Decrypt unavailable\n"); - break; + if (bench_unsupported(rc)) { + printf("%-16s Skipped (not supported)\n", desc); + rc = 0; + goto exit; } if (rc != 0) goto exit; } while (bench_stats_check(start, &count, maxDuration)); @@ -368,6 +417,9 @@ static void usage(void) printf("* -aes/xor: Use Parameter Encryption\n"); printf("* -maxdur=[ms]: Maximum runtime for each algorithm in milliseconds " "(default %d)\n", TPM2_BENCH_DURATION_SEC*1000); + printf("* -iter=[n]: Run each algorithm exactly n times and report the\n"); + printf(" average with the per-iteration min and max, instead of\n"); + printf(" running for a duration. Overrides -maxdur.\n"); } /******************************************************************************/ @@ -398,6 +450,9 @@ int TPM2_Wrapper_BenchArgs(void* userCtx, int argc, char *argv[]) double maxDuration = TPM2_BENCH_DURATION_SEC; double maxKeyGenDurSec = TPM2_BENCH_DURATION_KEYGEN_SEC; + /* Static, so a previous call with -iter must not leak into this one */ + gBenchIter = 0; + if (argc >= 2) { if (XSTRCMP(argv[1], "-?") == 0 || XSTRCMP(argv[1], "-h") == 0 || @@ -417,6 +472,15 @@ int TPM2_Wrapper_BenchArgs(void* userCtx, int argc, char *argv[]) const char* maxStr = argv[argc-1] + XSTRLEN("-maxdur="); maxKeyGenDurSec = maxDuration = XATOI(maxStr) / 1000.0; } + else if (XSTRNCMP(argv[argc-1], "-iter=", XSTRLEN("-iter=")) == 0) { + const char* iterStr = argv[argc-1] + XSTRLEN("-iter="); + gBenchIter = XATOI(iterStr); + if (gBenchIter <= 0) { + printf("Iteration count must be greater than zero\n"); + usage(); + return BAD_FUNC_ARG; + } + } else { printf("Warning: Unrecognized option: %s\n", argv[argc-1]); } diff --git a/examples/native/native_test.c b/examples/native/native_test.c index 11d7c0adf..ad9580bbb 100644 --- a/examples/native/native_test.c +++ b/examples/native/native_test.c @@ -339,6 +339,7 @@ int TPM2_Native_Test(void* userCtx) int TPM2_Native_TestArgs(void* userCtx, int argc, char *argv[]) { int rc; + int isAllocated = 0; TPM2_CTX tpm2Ctx; union { @@ -709,6 +710,19 @@ int TPM2_Native_TestArgs(void* userCtx, int argc, char *argv[]) } printf("TPM2_ReadClock: success\n"); + /* TEST_WRAP_DIGEST is SHA-1 on some targets, and a TPM that allocates no + * SHA-1 bank would abort every PCR step below. Check once, up front. */ + rc = TPM2_IsPcrBankAllocated(TEST_WRAP_DIGEST, 0, &isAllocated); + if (rc != TPM_RC_SUCCESS) { + printf("TPM2_IsPcrBankAllocated failed 0x%x: %s\n", rc, + TPM2_GetRCString(rc)); + goto exit; + } + if (!isAllocated) { + printf("PCR tests skipped (no 0x%x PCR bank allocated)\n", + (unsigned int)TEST_WRAP_DIGEST); + goto pcr_tests_done; + } /* PCR Read */ for (i=0; i +#endif + +#include + +#ifndef WOLFTPM2_NO_WRAPPER + +#include +#include +#include + +#include + +/******************************************************************************/ +/* --- BEGIN TPM2.0 PCR Allocate example tool -- */ +/******************************************************************************/ + +/* Names for the banks a TPM commonly reports; anything else prints as hex. */ +static const char* bank_name(TPM_ALG_ID alg) +{ + switch (alg) { + case TPM_ALG_SHA1: return "SHA-1"; + case TPM_ALG_SHA256: return "SHA-256"; + case TPM_ALG_SHA384: return "SHA-384"; + case TPM_ALG_SHA512: return "SHA-512"; + default: return NULL; + } +} + +static void usage(void) +{ + printf("Expected usage:\n"); + printf("./examples/pcr/allocate [-sha1] [-sha256] [-sha384] [-sha512]\n"); + printf(" [-restore]\n"); + printf("* no algorithm flags: report the current allocation and exit\n"); + printf("* -shaN: include that bank in the new allocation (repeatable)\n"); + printf("* -restore: put the original selection back before exiting\n"); + printf("Demo usage without parameters, reports the PCR banks.\n"); + printf("\n"); + printf("WARNING: the algorithm flags REPLACE the allocation. Banks not\n"); + printf("named are deallocated, every PolicyPCR digest changes, and blobs\n"); + printf("sealed to PCR values become unsealable. Many TPMs support only\n"); + printf("one active bank at a time.\n"); + printf("\n"); + printf("The new allocation takes effect at the next TPM reset, so power\n"); + printf("cycle the TPM (or restart the simulator) and re-run to confirm.\n"); +} + +/* Read the TPM's own bank list. That list, not a fixed table of algorithms, + * is what the TPM implements, and its bitmaps are what -restore replays. */ +static int read_banks(TPML_PCR_SELECTION* banks) +{ + int rc; + GetCapability_In in; + GetCapability_Out out; + + XMEMSET(&in, 0, sizeof(in)); + XMEMSET(&out, 0, sizeof(out)); + in.capability = TPM_CAP_PCRS; + in.property = 0; + in.propertyCount = HASH_COUNT; + rc = TPM2_GetCapability(&in, &out); + if (rc != TPM_RC_SUCCESS) { + printf("TPM2_GetCapability(TPM_CAP_PCRS) failed 0x%x: %s\n", rc, + TPM2_GetRCString(rc)); + return rc; + } + if (out.capabilityData.capability != TPM_CAP_PCRS) { + return TPM_RC_VALUE; + } + if (out.moreData == YES) { + printf("The TPM reports more PCR banks than this build can hold" + " (HASH_COUNT %d)\n", (int)HASH_COUNT); + return TPM_RC_SIZE; + } + XMEMCPY(banks, &out.capabilityData.data.assignedPCR, sizeof(*banks)); + return TPM_RC_SUCCESS; +} + +/* Print every bank the TPM reports, with the PCRs selected in each. */ +static void print_banks(TPML_PCR_SELECTION* banks) +{ + const char* name; + word32 i; + int j, any; + + printf("PCR banks:\n"); + printf(" %-10s %-10s %s\n", "Bank", "Allocated", "pcrSelect"); + for (i = 0; i < banks->count; i++) { + any = 0; + for (j = 0; j < (int)banks->pcrSelections[i].sizeofSelect; j++) { + if (banks->pcrSelections[i].pcrSelect[j] != 0) { + any = 1; + } + } + name = bank_name(banks->pcrSelections[i].hash); + if (name != NULL) { + printf(" %-10s %-10s ", name, any ? "yes" : "no"); + } + else { + printf(" 0x%04X %-10s ", banks->pcrSelections[i].hash, + any ? "yes" : "no"); + } + for (j = 0; j < (int)banks->pcrSelections[i].sizeofSelect; j++) { + printf("%02X", banks->pcrSelections[i].pcrSelect[j]); + } + printf("\n"); + } +} + +/* Replay a previously captured selection verbatim, bitmaps included, so a + * partial bank comes back partial rather than expanded to every PCR. */ +static int restore_banks(WOLFTPM2_DEV* dev, TPML_PCR_SELECTION* banks) +{ + int rc; + PCR_Allocate_In in; + PCR_Allocate_Out out; + + XMEMSET(&in, 0, sizeof(in)); + XMEMSET(&out, 0, sizeof(out)); + in.authHandle = TPM_RH_PLATFORM; + XMEMCPY(&in.pcrAllocation, banks, sizeof(in.pcrAllocation)); + + rc = wolfTPM2_SetAuthPassword(dev, 0, NULL); + if (rc != TPM_RC_SUCCESS) { + return rc; + } + dev->session[0].sessionAttributes = 0; + rc = TPM2_PCR_Allocate(&in, &out); + if (rc == TPM_RC_SUCCESS && out.allocationSuccess != YES) { + rc = BUFFER_E; + } + return rc; +} + +static void print_alloc_out(PCR_Allocate_Out* allocOut) +{ + printf("TPM reported: allocationSuccess %s, maxPCR %u, sizeNeeded %u," + " sizeAvailable %u\n", + (allocOut->allocationSuccess == YES) ? "YES" : "NO", + (unsigned int)allocOut->maxPCR, + (unsigned int)allocOut->sizeNeeded, + (unsigned int)allocOut->sizeAvailable); +} + +int TPM2_PCR_Allocate_Test(void* userCtx, int argc, char *argv[]) +{ + int i, rc = -1; + int doRestore = 0; + int algCount = 0; + TPM_ALG_ID algs[HASH_COUNT]; + TPML_PCR_SELECTION origBanks; + PCR_Allocate_Out allocOut; + WOLFTPM2_DEV dev; + + XMEMSET(algs, 0, sizeof(algs)); + XMEMSET(&origBanks, 0, sizeof(origBanks)); + XMEMSET(&allocOut, 0, sizeof(allocOut)); + + for (i = 1; i < argc; i++) { + if (XSTRCMP(argv[i], "-?") == 0 || + XSTRCMP(argv[i], "-h") == 0 || + XSTRCMP(argv[i], "--help") == 0) { + usage(); + return 0; + } + else if (XSTRCMP(argv[i], "-restore") == 0) { + doRestore = 1; + } + else if (XSTRCMP(argv[i], "-sha1") == 0 || + XSTRCMP(argv[i], "-sha256") == 0 || + XSTRCMP(argv[i], "-sha384") == 0 || + XSTRCMP(argv[i], "-sha512") == 0) { + /* Repeatable, so bound the array before writing */ + if (algCount >= (int)(sizeof(algs)/sizeof(algs[0]))) { + printf("Too many algorithm flags (max %d)\n", + (int)(sizeof(algs)/sizeof(algs[0]))); + usage(); + return -1; + } + if (XSTRCMP(argv[i], "-sha1") == 0) { + algs[algCount++] = TPM_ALG_SHA1; + } + else if (XSTRCMP(argv[i], "-sha256") == 0) { + algs[algCount++] = TPM_ALG_SHA256; + } + else if (XSTRCMP(argv[i], "-sha384") == 0) { + algs[algCount++] = TPM_ALG_SHA384; + } + else { + algs[algCount++] = TPM_ALG_SHA512; + } + } + else { + printf("Incorrect arguments\n"); + usage(); + return -1; + } + } + + printf("Demo how to inspect and change the TPM PCR banks\n"); + rc = wolfTPM2_Init(&dev, TPM2_IoCb, userCtx); + if (rc != TPM_RC_SUCCESS) { + printf("wolfTPM2_Init failed 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + return rc; + } + printf("wolfTPM2_Init: success\n"); + + rc = read_banks(&origBanks); + if (rc != TPM_RC_SUCCESS) { + goto exit; + } + print_banks(&origBanks); + if (algCount == 0) { + printf("No algorithm flags given, nothing changed.\n"); + goto exit; + } + + printf("\nWARNING: replacing the PCR bank allocation. Banks not listed are\n" + "deallocated, PCR values are zeroed at the next reset, and blobs\n" + "sealed to PCR values become unsealable.\n\n"); + + rc = wolfTPM2_AllocatePCRBanks(&dev, algs, algCount, &allocOut); + if (rc == TPM_RC_HASH) { + printf("The TPM does not implement one of the requested banks.\n"); + printf("Nothing was sent to the TPM and the allocation is unchanged.\n"); + goto exit; + } + if (WOLFTPM_RC_IS(rc, TPM_RC_PCR)) { /* mask any vendor/layer bits */ + printf("The TPM refused that PCR bank selection (TPM_RC_PCR).\n"); + printf("Parts that keep one bank active reject a multi-bank request" + " outright - try a single -shaN flag.\n"); + goto exit; + } + /* Only meaningful once the TPM answered with parameters */ + if (rc == TPM_RC_SUCCESS || rc == BUFFER_E) { + print_alloc_out(&allocOut); + } + if (rc == BUFFER_E) { + if (allocOut.sizeNeeded > 0) { + printf("The TPM does not have room for that bank set (needed %u," + " available %u).\n", (unsigned int)allocOut.sizeNeeded, + (unsigned int)allocOut.sizeAvailable); + printf("Try a single -shaN flag.\n"); + } + else { + /* No sizing reported means the TPM rejected the selection itself, + * not that it ran out of room */ + printf("The TPM rejected that bank selection and reported no" + " sizing.\n"); + } + goto exit; + } + if (rc != TPM_RC_SUCCESS) { + printf("wolfTPM2_AllocatePCRBanks failed 0x%x: %s\n", rc, + TPM2_GetRCString(rc)); + goto exit; + } + + printf("PCR allocation staged. It takes effect at the next TPM reset\n" + "(Startup(CLEAR) after a _TPM_Init) - power cycle the TPM, or\n" + "restart the simulator process, then re-run to confirm.\n"); + + if (doRestore && origBanks.count > 0) { + printf("\nRestoring the original selection...\n"); + rc = restore_banks(&dev, &origBanks); + if (rc != TPM_RC_SUCCESS) { + printf("Restore failed 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + goto exit; + } + printf("Original allocation staged.\n"); + } + +exit: + + wolfTPM2_Cleanup(&dev); + + return rc; +} + +/******************************************************************************/ +/* --- END TPM2.0 PCR Allocate example tool -- */ +/******************************************************************************/ +#endif /* !WOLFTPM2_NO_WRAPPER */ + +#ifndef NO_MAIN_DRIVER +int main(int argc, char *argv[]) +{ + int rc = -1; + +#ifndef WOLFTPM2_NO_WRAPPER + rc = TPM2_PCR_Allocate_Test(NULL, argc, argv); + /* TPM rc is wider than an exit status (0x100 truncates to 0) */ + if (rc != 0) { + rc = 1; + } +#else + printf("Wrapper code not compiled in\n"); + (void)argc; + (void)argv; +#endif /* !WOLFTPM2_NO_WRAPPER */ + + return rc; +} +#endif diff --git a/examples/pcr/include.am b/examples/pcr/include.am index 3d21bd64b..4fedb532d 100644 --- a/examples/pcr/include.am +++ b/examples/pcr/include.am @@ -7,7 +7,8 @@ noinst_PROGRAMS += examples/pcr/quote \ examples/pcr/extend \ examples/pcr/policy \ examples/pcr/policy_sign \ - examples/pcr/reset + examples/pcr/reset \ + examples/pcr/allocate noinst_HEADERS += examples/pcr/quote.h \ examples/pcr/pcr.h @@ -29,6 +30,10 @@ examples_pcr_reset_SOURCES = examples/pcr/reset.c examples_pcr_reset_LDADD = src/libwolftpm.la $(LIB_STATIC_ADD) examples_pcr_reset_DEPENDENCIES = src/libwolftpm.la +examples_pcr_allocate_SOURCES = examples/pcr/allocate.c +examples_pcr_allocate_LDADD = src/libwolftpm.la $(LIB_STATIC_ADD) +examples_pcr_allocate_DEPENDENCIES = src/libwolftpm.la + examples_pcr_policy_SOURCES = examples/pcr/policy.c \ examples/tpm_test_keys.c examples_pcr_policy_LDADD = src/libwolftpm.la $(LIB_STATIC_ADD) @@ -47,14 +52,16 @@ dist_example_pcr_DATA = \ examples/pcr/extend.c \ examples/pcr/policy.c \ examples/pcr/policy_sign.c \ - examples/pcr/reset.c + examples/pcr/reset.c \ + examples/pcr/allocate.c DISTCLEANFILES+= examples/pcr/.libs/quote \ examples/pcr/.libs/read_pcr \ examples/pcr/.libs/policy \ examples/pcr/.libs/policy_sign \ examples/pcr/.libs/extend \ - examples/pcr/.libs/reset + examples/pcr/.libs/reset \ + examples/pcr/.libs/allocate EXTRA_DIST+= examples/pcr/README.md \ examples/pcr/demo.sh \ diff --git a/examples/pcr/pcr.h b/examples/pcr/pcr.h index 87bc67588..967af1f37 100644 --- a/examples/pcr/pcr.h +++ b/examples/pcr/pcr.h @@ -29,6 +29,7 @@ int TPM2_PCR_Read_Test(void* userCtx, int argc, char *argv[]); int TPM2_PCR_Extend_Test(void* userCtx, int argc, char *argv[]); int TPM2_PCR_Reset_Test(void* userCtx, int argc, char *argv[]); +int TPM2_PCR_Allocate_Test(void* userCtx, int argc, char *argv[]); int TPM2_PCR_Policy_Test(void* userCtx, int argc, char *argv[]); int TPM2_PCR_PolicySign_Example(void* userCtx, int argc, char *argv[]); diff --git a/examples/pqc/pqc_ctrl.c b/examples/pqc/pqc_ctrl.c index 2e41c1bd5..c4d9c1e26 100644 --- a/examples/pqc/pqc_ctrl.c +++ b/examples/pqc/pqc_ctrl.c @@ -430,6 +430,15 @@ static int do_mldsa(WOLFTPM2_DEV* dev, TPMI_MLDSA_PARAMETER_SET ps) if (rc != TPM_RC_SUCCESS) goto exit; rc = wolfTPM2_VerifySequenceComplete(dev, seq, &key, NULL, 0, sig, sigSz, &validation); + if (rc == BUFFER_E) { + /* Same oversize case do_hash_mldsa() reports: signing worked but the + * TPM cannot take the signature back, so skip rather than fail. */ + printf("SKIP ML-DSA-%-3s signed %d bytes, not verified: " + "signature exceeds this TPM's input buffer\n", + mldsaName(ps), sigSz); + rc = TPM_RC_SUCCESS; + goto exit_quiet; + } if (rc != TPM_RC_SUCCESS) goto exit; seq = 0; /* Complete consumed the sequence object */ @@ -448,6 +457,9 @@ static int do_mldsa(WOLFTPM2_DEV* dev, TPMI_MLDSA_PARAMETER_SET ps) printf("FAIL ML-DSA-%-3s 0x%x: %s\n", mldsaName(ps), rc, wolfTPM2_GetRCString(rc)); } +exit_quiet: + /* The size guard runs before the sequence is consumed, so seq is still + * live on BUFFER_E and must be flushed here like any other exit. */ if (seq != 0) { flushCtx.flushHandle = seq; (void)TPM2_FlushContext(&flushCtx); @@ -493,6 +505,16 @@ static int do_hash_mldsa(WOLFTPM2_DEV* dev, TPMI_MLDSA_PARAMETER_SET ps) rc = wolfTPM2_VerifyDigestSignature(dev, &key, digest, (int)sizeof(digest), sig, sigSz, NULL, 0, &validation); + if (rc == BUFFER_E) { + /* Too large for this TPM to accept back for on-TPM verification. + * Signing worked but nothing checked it, so skip, not pass. Only + * BUFFER_E means oversize; a query error still reports FAIL. */ + printf("SKIP HashML-DSA-%-3s signed %d bytes, not verified: " + "signature exceeds this TPM's input buffer\n", + mldsaName(ps), sigSz); + rc = TPM_RC_SUCCESS; + goto exit_quiet; + } if (rc != TPM_RC_SUCCESS) goto exit; if (validation.tag != TPM_ST_DIGEST_VERIFIED) { @@ -510,6 +532,7 @@ static int do_hash_mldsa(WOLFTPM2_DEV* dev, TPMI_MLDSA_PARAMETER_SET ps) printf("FAIL HashML-DSA-%-3s 0x%x: %s\n", mldsaName(ps), rc, wolfTPM2_GetRCString(rc)); } +exit_quiet: wolfTPM2_UnloadHandle(dev, &key.handle); XFREE(sig, NULL, DYNAMIC_TYPE_TMP_BUFFER); return rc; diff --git a/examples/run_examples.sh b/examples/run_examples.sh index d33807ef1..85ae00626 100755 --- a/examples/run_examples.sh +++ b/examples/run_examples.sh @@ -890,6 +890,28 @@ if [ $WOLFCRYPT_ENABLE -eq 1 ] && [ $NO_FILESYSTEM -eq 0 ]; then # Keeping keyblob.bin for tests later fi +# PCR bank allocation. Only the query-only form runs by default: changing the +# allocation is persistent platform configuration that invalidates PCR policies +# and sealed objects, and the two commands are not transactional - an interrupt +# between them leaves a new allocation staged. Strict assertions live in +# tests/fwtpm_check.sh where the NV image is disposable. +echo -e "PCR bank allocation" +./examples/pcr/allocate >> $TPMPWD/run.out 2>&1 +RESULT=$? +[ $RESULT -ne 0 ] && echo -e "pcr allocate query failed! $RESULT" && exit 1 +if test $ENABLE_DESTRUCTIVE_TESTS -eq 1 +then + ALLOC_OUT=$(./examples/pcr/allocate -sha256 -restore 2>&1) + echo "$ALLOC_OUT" >> $TPMPWD/run.out + if echo "$ALLOC_OUT" | grep -q "Original allocation staged"; then + : # platform auth available and the round trip worked + elif echo "$ALLOC_OUT" | grep -qiE "hierarchy|BAD_AUTH|auth"; then + echo -e " PCR allocate needs platform auth, skipping" + else + echo -e "pcr allocate failed!" && echo "$ALLOC_OUT" && exit 1 + fi +fi + # PCR Quote Tests echo -e "PCR Quote tests" ./examples/pcr/reset 16 >> $TPMPWD/run.out 2>&1 diff --git a/examples/wrap/encrypt_decrypt.c b/examples/wrap/encrypt_decrypt.c index 7f7ef84e9..0f5a37fbe 100644 --- a/examples/wrap/encrypt_decrypt.c +++ b/examples/wrap/encrypt_decrypt.c @@ -151,14 +151,14 @@ int TPM2_EncryptDecrypt_Example(void* userCtx, int argc, char* argv[]) XMEMSET(iv, 0, sizeof(iv)); rc = wolfTPM2_EncryptDecrypt(&dev, &aesKey, message, cipher, (word32)sizeof(message), iv, (word32)sizeof(iv), WOLFTPM2_ENCRYPT); - if (rc != 0 && !WOLFTPM_IS_COMMAND_UNAVAILABLE(rc)) { + if (rc != 0 && !WOLFTPM_IS_COMMAND_UNAVAILABLE_OR_DISABLED(rc)) { printf("wolfTPM2_EncryptDecrypt (encrypt) failed 0x%x: %s\n", rc, wolfTPM2_GetRCString(rc)); goto exit; } - if (WOLFTPM_IS_COMMAND_UNAVAILABLE(rc)) { - printf("Encrypt/Decrypt: not supported (export controls)\n"); + if (WOLFTPM_IS_COMMAND_UNAVAILABLE_OR_DISABLED(rc)) { + printf("Encrypt/Decrypt: not supported or disabled\n"); rc = 0; /* not an error condition */ goto exit; } @@ -167,14 +167,14 @@ int TPM2_EncryptDecrypt_Example(void* userCtx, int argc, char* argv[]) XMEMSET(iv, 0, sizeof(iv)); rc = wolfTPM2_EncryptDecrypt(&dev, &aesKey, cipher, plain, (word32)sizeof(cipher), iv, (word32)sizeof(iv), WOLFTPM2_DECRYPT); - if (rc != 0 && !WOLFTPM_IS_COMMAND_UNAVAILABLE(rc)) { + if (rc != 0 && !WOLFTPM_IS_COMMAND_UNAVAILABLE_OR_DISABLED(rc)) { printf("wolfTPM2_EncryptDecrypt (decrypt) failed 0x%x: %s\n", rc, wolfTPM2_GetRCString(rc)); goto exit; } - if (WOLFTPM_IS_COMMAND_UNAVAILABLE(rc)) { - printf("Encrypt/Decrypt: not supported (export controls)\n"); + if (WOLFTPM_IS_COMMAND_UNAVAILABLE_OR_DISABLED(rc)) { + printf("Encrypt/Decrypt: not supported or disabled\n"); rc = 0; /* not an error condition */ goto exit; } diff --git a/examples/wrap/wrap_test.c b/examples/wrap/wrap_test.c index e4b03e1f7..cb082d7e8 100644 --- a/examples/wrap/wrap_test.c +++ b/examples/wrap/wrap_test.c @@ -70,6 +70,7 @@ int TPM2_Wrapper_Test(void* userCtx) int TPM2_Wrapper_TestArgs(void* userCtx, int argc, char *argv[]) { int rc, i; + int isSupported = 0; WOLFTPM2_DEV dev; WOLFTPM2_CAPS caps; WOLFTPM2_KEY ekKey; @@ -683,19 +684,27 @@ int TPM2_Wrapper_TestArgs(void* userCtx, int argc, char *argv[]) printf("ECC DH Test %s\n", rc == 0 ? "Passed" : "Failed"); /* ECC Public Key Signature Verify Test/Example */ - rc = wolfTPM2_LoadEccPublicKey(&dev, &publicKey, TPM_ECC_NIST_P256, - kEccTestPubQX, sizeof(kEccTestPubQX), - kEccTestPubQY, sizeof(kEccTestPubQY)); + /* Vector below uses a SHA-1 digest, which many current TPMs lack. */ + rc = wolfTPM2_IsAlgSupported(&dev, TPM_ALG_SHA1, &isSupported); if (rc != 0) goto exit; + if (!isSupported) { + printf("ECC Verify Test Skipped (TPM does not implement SHA-1)\n"); + } + else { + rc = wolfTPM2_LoadEccPublicKey(&dev, &publicKey, TPM_ECC_NIST_P256, + kEccTestPubQX, sizeof(kEccTestPubQX), + kEccTestPubQY, sizeof(kEccTestPubQY)); + if (rc != 0) goto exit; - rc = wolfTPM2_VerifyHashScheme(&dev, &publicKey, - kEccTestSigRS, sizeof(kEccTestSigRS), - kEccTestMsg, sizeof(kEccTestMsg), TPM_ALG_ECDSA, TPM_ALG_SHA1); - if (rc != 0) goto exit; + rc = wolfTPM2_VerifyHashScheme(&dev, &publicKey, + kEccTestSigRS, sizeof(kEccTestSigRS), + kEccTestMsg, sizeof(kEccTestMsg), TPM_ALG_ECDSA, TPM_ALG_SHA1); + if (rc != 0) goto exit; - rc = wolfTPM2_UnloadHandle(&dev, &publicKey.handle); - if (rc != 0) goto exit; - printf("ECC Verify Test Passed\n"); + rc = wolfTPM2_UnloadHandle(&dev, &publicKey.handle); + if (rc != 0) goto exit; + printf("ECC Verify Test Passed\n"); + } /*------------------------------------------------------------------------*/ /* ECC KEY LOADING TESTS */ @@ -973,7 +982,7 @@ int TPM2_Wrapper_TestArgs(void* userCtx, int argc, char *argv[]) XMEMCPY(aesIv, TEST_AES_IV, (word32)sizeof(TEST_AES_IV)); rc = wolfTPM2_EncryptDecrypt(&dev, &aesKey, message.buffer, cipher.buffer, message.size, aesIv, (word32)sizeof(aesIv), WOLFTPM2_ENCRYPT); - if (rc != 0 && !WOLFTPM_IS_COMMAND_UNAVAILABLE(rc)) goto exit; + if (rc != 0 && !WOLFTPM_IS_COMMAND_UNAVAILABLE_OR_DISABLED(rc)) goto exit; XMEMSET(plain.buffer, 0, sizeof(plain.buffer)); plain.size = message.size; @@ -990,7 +999,7 @@ int TPM2_Wrapper_TestArgs(void* userCtx, int argc, char *argv[]) XMEMCMP(cipher.buffer, TEST_AES_VERIFY, cipher.size) == 0) { printf("Encrypt/Decrypt (known key) test success\n"); } - else if (WOLFTPM_IS_COMMAND_UNAVAILABLE(rc)) { + else if (WOLFTPM_IS_COMMAND_UNAVAILABLE_OR_DISABLED(rc)) { printf("Encrypt/Decrypt: Is not a supported feature due to export controls\n"); } else { @@ -1020,7 +1029,7 @@ int TPM2_Wrapper_TestArgs(void* userCtx, int argc, char *argv[]) XMEMSET(aesIv, 0, sizeof(aesIv)); rc = wolfTPM2_EncryptDecrypt(&dev, &aesKey, message.buffer, cipher.buffer, message.size, aesIv, (word32)sizeof(aesIv), WOLFTPM2_ENCRYPT); - if (rc != 0 && !WOLFTPM_IS_COMMAND_UNAVAILABLE(rc)) goto exit; + if (rc != 0 && !WOLFTPM_IS_COMMAND_UNAVAILABLE_OR_DISABLED(rc)) goto exit; XMEMSET(plain.buffer, 0, sizeof(plain.buffer)); plain.size = message.size; @@ -1035,7 +1044,7 @@ int TPM2_Wrapper_TestArgs(void* userCtx, int argc, char *argv[]) XMEMCMP(message.buffer, plain.buffer, message.size) == 0) { printf("Encrypt/Decrypt test success\n"); } - else if (WOLFTPM_IS_COMMAND_UNAVAILABLE(rc)) { + else if (WOLFTPM_IS_COMMAND_UNAVAILABLE_OR_DISABLED(rc)) { printf("Encrypt/Decrypt: Is not a supported feature due to export controls\n"); } else { diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 3d38b8bf0..023c6dfb2 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -1017,6 +1017,13 @@ static TPM_RC FwCmd_Startup(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, XMEMSET(ctx->pcrDigest[i][b], 0, TPM_MAX_DIGEST_SIZE); } } + /* A staged TPM2_PCR_Allocate takes effect here, and only here */ + if (ctx->pcrAllocPending) { + ctx->pcrAllocatedBanks = ctx->pcrAllocatedBanksPending; + ctx->pcrAllocatedBanksPending = 0; + ctx->pcrAllocPending = 0; + (void)FWTPM_NV_Save(ctx); + } ctx->globalNvWriteLock = 0; /* shEnable/ehEnable/phEnableNV re-enable on TPM Reset only; * phEnable re-enables on every startup (handled below). */ @@ -3081,8 +3088,8 @@ static TPM_RC FwCmd_PCR_Event(FWTPM_CTX* ctx, TPM2_Packet* cmd, } /* --- TPM2_PCR_Allocate (CC 0x012B) --- */ -/* Allocate PCR banks. Per spec Section 22.5, takes effect after next Startup(CLEAR). - * For software TPM, we always succeed. */ +/* Allocate PCR banks. Per spec Section 22.5 the selection is recorded and takes + * effect at the next Startup(CLEAR), so it is staged rather than applied. */ static TPM_RC FwCmd_PCR_Allocate(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, TPM2_Packet* rsp, UINT16 cmdTag) { @@ -3092,6 +3099,9 @@ static TPM_RC FwCmd_PCR_Allocate(FWTPM_CTX* ctx, TPM2_Packet* cmd, UINT32 c; UINT8 newBanks = 0; int paramSzPos, paramStart; + int anySelected = 0; + int b; + UINT8 selByte = 0; UINT32 sizeNeeded = 0; UINT32 sizeAvailable; @@ -3122,10 +3132,18 @@ static TPM_RC FwCmd_PCR_Allocate(FWTPM_CTX* ctx, TPM2_Packet* cmd, rc = TPM_RC_COMMAND_SIZE; break; } - cmd->pos += sizeOfSelect; /* skip pcrSelect bytes */ + /* An all-zero pcrSelect deallocates the bank, so read it */ + anySelected = 0; + for (b = 0; b < (int)sizeOfSelect; b++) { + selByte = 0; + TPM2_Packet_ParseU8(cmd, &selByte); + if (selByte != 0) { + anySelected = 1; + } + } bank = FwGetPcrBankIndex(hashAlg); - if (bank >= 0) { + if (bank >= 0 && anySelected) { newBanks |= (UINT8)(1 << bank); sizeNeeded += (UINT32)(IMPLEMENTATION_PCR * TPM2_GetHashDigestSize(hashAlg)); @@ -3138,15 +3156,27 @@ static TPM_RC FwCmd_PCR_Allocate(FWTPM_CTX* ctx, TPM2_Packet* cmd, #ifdef DEBUG_WOLFTPM printf("fwTPM: PCR_Allocate(banks=0x%02x)\n", newBanks); #endif - ctx->pcrAllocatedBanks = newBanks; + sizeAvailable = (UINT32)(IMPLEMENTATION_PCR * FWTPM_PCR_BANKS * + TPM_MAX_DIGEST_SIZE); + + /* Storing an empty result would persist "no banks at all" to NV */ + if (newBanks == 0) { + paramStart = FwRspParamsBegin(rsp, cmdTag, ¶mSzPos); + TPM2_Packet_AppendU8(rsp, 0); /* allocationSuccess = NO */ + TPM2_Packet_AppendU32(rsp, (UINT32)IMPLEMENTATION_PCR); + TPM2_Packet_AppendU32(rsp, sizeNeeded); + TPM2_Packet_AppendU32(rsp, sizeAvailable); + FwRspParamsEnd(rsp, cmdTag, paramSzPos, paramStart); + return rc; + } + + ctx->pcrAllocatedBanksPending = newBanks; + ctx->pcrAllocPending = 1; rc = FWTPM_NV_Save(ctx); if (rc != TPM_RC_SUCCESS) { return rc; } - sizeAvailable = (UINT32)(IMPLEMENTATION_PCR * FWTPM_PCR_BANKS * - TPM_MAX_DIGEST_SIZE); - paramStart = FwRspParamsBegin(rsp, cmdTag, ¶mSzPos); TPM2_Packet_AppendU8(rsp, 1); /* allocationSuccess = YES */ TPM2_Packet_AppendU32(rsp, (UINT32)IMPLEMENTATION_PCR); diff --git a/src/fwtpm/fwtpm_nv.c b/src/fwtpm/fwtpm_nv.c index 925f33648..8db5689ea 100644 --- a/src/fwtpm/fwtpm_nv.c +++ b/src/fwtpm/fwtpm_nv.c @@ -1345,6 +1345,12 @@ static int FwNvProcessEntry(FWTPM_CTX* ctx, UINT16 tag, FwNvUnmarshalU16(value, &vPos, vMax, &ctx->pcrPolicyAlg[idx]); } + /* Absent in records written before staging existed */ + if (vPos < vMax) { + FwNvUnmarshalU8(value, &vPos, vMax, &ctx->pcrAllocPending); + FwNvUnmarshalU8(value, &vPos, vMax, + &ctx->pcrAllocatedBanksPending); + } break; } @@ -1816,6 +1822,14 @@ int FWTPM_NV_Init(FWTPM_CTX* ctx) return BAD_FUNC_ARG; } + /* The journal only carries a PCR_AUTH record when the allocation is + * non-default or a PCR has auth set, so a replay would otherwise leave + * this at zero - no banks allocated - on every restart. Seed the default; + * FwNvGenFreshState and a replayed record both override it. */ + ctx->pcrAllocatedBanks = FWTPM_PCR_ALLOC_DEFAULT; + ctx->pcrAllocatedBanksPending = 0; + ctx->pcrAllocPending = 0; + #ifdef WOLFTPM_FWTPM_NV_APPEND_ONLY ctx->nvRebuild = 0; #endif @@ -2103,7 +2117,8 @@ int FWTPM_NV_Save(FWTPM_CTX* ctx) break; } } - if (hasPcrAuth || ctx->pcrAllocatedBanks != FWTPM_PCR_ALLOC_DEFAULT) { + if (hasPcrAuth || ctx->pcrAllocPending || + ctx->pcrAllocatedBanks != FWTPM_PCR_ALLOC_DEFAULT) { word32 needed = 1 + IMPLEMENTATION_PCR * (2 + 64 + 2 + 64 + 2); if (needed > bufSz) { byte* newBuf; @@ -2129,6 +2144,10 @@ int FWTPM_NV_Save(FWTPM_CTX* ctx) FwNvMarshalU16(buf, &pos, bufSz, ctx->pcrPolicyAlg[i]); } + /* Appended last so an older record still parses */ + FwNvMarshalU8(buf, &pos, bufSz, ctx->pcrAllocPending); + FwNvMarshalU8(buf, &pos, bufSz, + ctx->pcrAllocatedBanksPending); rc = FwNvAppendEntry(ctx, FWTPM_NV_TAG_PCR_AUTH, buf, (UINT16)pos); } @@ -2446,6 +2465,9 @@ int FWTPM_NV_SavePcrAuth(FWTPM_CTX* ctx) FwNvMarshalDigest(buf, &pos, bufSz, &ctx->pcrPolicy[i]); FwNvMarshalU16(buf, &pos, bufSz, ctx->pcrPolicyAlg[i]); } + /* Appended last so an older record still parses */ + FwNvMarshalU8(buf, &pos, bufSz, ctx->pcrAllocPending); + FwNvMarshalU8(buf, &pos, bufSz, ctx->pcrAllocatedBanksPending); rc = FwNvAppendEntry(ctx, FWTPM_NV_TAG_PCR_AUTH, buf, (UINT16)pos); diff --git a/src/tpm2.c b/src/tpm2.c index 153121398..b72dbbc78 100644 --- a/src/tpm2.c +++ b/src/tpm2.c @@ -7919,6 +7919,94 @@ void TPM2_PrintPublicArea(const TPM2B_PUBLIC* pub) } #endif /* DEBUG_WOLFTPM */ +/* TPM_CAP_ALGS returns algorithms with ID >= property, so a match at index 0 + * means implemented. Fails closed: *isSupported is 0 on any error. */ +int TPM2_IsAlgSupported(TPM_ALG_ID alg, int* isSupported) +{ + int rc; + GetCapability_In in; + GetCapability_Out out; + TPML_ALG_PROPERTY* algs; + + if (isSupported == NULL) { + return BAD_FUNC_ARG; + } + *isSupported = 0; + + XMEMSET(&in, 0, sizeof(in)); + XMEMSET(&out, 0, sizeof(out)); + in.capability = TPM_CAP_ALGS; + in.property = alg; + in.propertyCount = 1; + rc = TPM2_GetCapability(&in, &out); + if (rc != TPM_RC_SUCCESS) { + return rc; /* query failure, distinct from "not supported" */ + } + /* union - confirm the capability asked for */ + if (out.capabilityData.capability != TPM_CAP_ALGS) { + return TPM_RC_VALUE; + } + + algs = &out.capabilityData.data.algorithms; + if (algs->count >= 1 && algs->algProperties[0].alg == alg) { + *isSupported = 1; + } + return TPM_RC_SUCCESS; +} + +/* Implementing a hash and allocating a bank for it are separate: a TPM may + * offer SHA-1 while allocating no SHA-1 bank, and a selection naming an + * unallocated bank is rejected. Ask before TPM2_SetupPCRSel(). Fails closed. */ +int TPM2_IsPcrBankAllocated(TPM_ALG_ID hashAlg, int pcrIndex, int* isAllocated) +{ + int rc; + word32 i; + GetCapability_In in; + GetCapability_Out out; + TPML_PCR_SELECTION* banks; + + if (isAllocated == NULL) { + return BAD_FUNC_ARG; + } + *isAllocated = 0; + if (pcrIndex < 0) { + return BAD_FUNC_ARG; + } + + XMEMSET(&in, 0, sizeof(in)); + XMEMSET(&out, 0, sizeof(out)); + in.capability = TPM_CAP_PCRS; + in.property = 0; + in.propertyCount = HASH_COUNT; /* all assigned banks */ + rc = TPM2_GetCapability(&in, &out); + if (rc != TPM_RC_SUCCESS) { + return rc; + } + if (out.capabilityData.capability != TPM_CAP_PCRS) { + return TPM_RC_VALUE; + } + + banks = &out.capabilityData.data.assignedPCR; + /* HASH_COUNT is this build's TPML capacity, not the TPM's bank count, so a + * TPM with more banks returns a partial page. Reporting "not allocated" + * from a truncated list would be a false negative. */ + if (out.moreData == YES) { + return TPM_RC_SIZE; + } + for (i = 0; i < banks->count; i++) { + if (banks->pcrSelections[i].hash != hashAlg) { + continue; + } + if ((pcrIndex / 8) < (int)banks->pcrSelections[i].sizeofSelect && + (banks->pcrSelections[i].pcrSelect[pcrIndex / 8] & + (1 << (pcrIndex % 8))) != 0) { + *isAllocated = 1; + break; + } + } + return TPM_RC_SUCCESS; +} + /******************************************************************************/ /* --- END Helpful API's -- */ /******************************************************************************/ diff --git a/src/tpm2_asn.c b/src/tpm2_asn.c index c09433279..f3c8be768 100644 --- a/src/tpm2_asn.c +++ b/src/tpm2_asn.c @@ -50,7 +50,7 @@ int TPM2_ASN_GetLength_ex(const uint8_t* input, word32* inOutIdx, int* len, *len = 0; /* default length */ if ((idx + 1) > maxIdx) { - return TPM_RC_INSUFFICIENT; + return TPM_RC_ASN_INSUFFICIENT; } b = input[idx++]; @@ -58,7 +58,7 @@ int TPM2_ASN_GetLength_ex(const uint8_t* input, word32* inOutIdx, int* len, word32 bytes = b & 0x7F; /* DER does not allow BER indefinite-length (0x80 => bytes == 0) */ if (bytes == 0 || bytes > 3 || (idx + bytes) > maxIdx) { - return TPM_RC_INSUFFICIENT; + return TPM_RC_ASN_INSUFFICIENT; } while (bytes--) { b = input[idx++]; @@ -69,7 +69,7 @@ int TPM2_ASN_GetLength_ex(const uint8_t* input, word32* inOutIdx, int* len, length = b; if (check && (idx + length) > maxIdx) { - return TPM_RC_INSUFFICIENT; + return TPM_RC_ASN_INSUFFICIENT; } *inOutIdx = idx; @@ -93,7 +93,7 @@ int TPM2_ASN_GetLength(const uint8_t* input, word32* inOutIdx, int* len, \param inOutIdx Current position in buffer, updated to new position \param len Decoded length value \param maxIdx Maximum allowed index in buffer - \return Length on success, TPM_RC_VALUE on tag mismatch, TPM_RC_INSUFFICIENT on buffer error + \return Length on success, TPM_RC_ASN_VALUE on tag mismatch, TPM_RC_ASN_INSUFFICIENT on buffer error */ static int TPM2_ASN_GetHeader(const uint8_t* input, byte tag, word32* inOutIdx, int* len, word32 maxIdx) @@ -103,14 +103,14 @@ static int TPM2_ASN_GetHeader(const uint8_t* input, byte tag, word32* inOutIdx, int length; if ((idx + 1) > maxIdx) - return TPM_RC_INSUFFICIENT; + return TPM_RC_ASN_INSUFFICIENT; b = input[idx++]; if (b != tag) - return TPM_RC_VALUE; + return TPM_RC_ASN_VALUE; if (TPM2_ASN_GetLength(input, &idx, &length, maxIdx) < 0) - return TPM_RC_VALUE; + return TPM_RC_ASN_VALUE; *len = length; *inOutIdx = idx; @@ -164,7 +164,7 @@ int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, byte sigParamTag = 0; if (input == NULL || x509 == NULL) { - rc = TPM_RC_VALUE; + rc = TPM_RC_ASN_VALUE; } /* Decode outer SEQUENCE */ @@ -193,14 +193,14 @@ int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, if (rc >= 0) { if (len <= 0 || idx >= (word32)inputSz) { - rc = TPM_RC_VALUE; + rc = TPM_RC_ASN_VALUE; } } if (rc >= 0) { /* check version tag is INTEGER */ if (input[idx] != TPM2_ASN_INTEGER) { - rc = TPM_RC_VALUE; + rc = TPM_RC_ASN_VALUE; } } @@ -282,7 +282,7 @@ int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, if (outerSigAlgSz != tbsSigAlgSz || XMEMCMP(input + outerSigAlgBegin, input + tbsSigAlgBegin, outerSigAlgSz) != 0) { - rc = TPM_RC_VALUE; + rc = TPM_RC_ASN_VALUE; } } } @@ -299,13 +299,13 @@ int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, if (sigParamTag != TPM2_ASN_TAG_NULL && sigParamTag != (TPM2_ASN_SEQUENCE | TPM2_ASN_CONSTRUCTED)) { - rc = TPM_RC_VALUE; + rc = TPM_RC_ASN_VALUE; } else { rc = TPM2_ASN_GetHeader(input, sigParamTag, &idx, &len, sigAlgEnd); if (rc >= 0 && sigParamTag == TPM2_ASN_TAG_NULL && len != 0) { - rc = TPM_RC_VALUE; + rc = TPM_RC_ASN_VALUE; } if (rc >= 0) { idx += len; @@ -313,7 +313,7 @@ int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, } } if (rc >= 0 && idx != sigAlgEnd) { - rc = TPM_RC_VALUE; + rc = TPM_RC_ASN_VALUE; } } diff --git a/src/tpm2_tis.c b/src/tpm2_tis.c index 0a02df578..cdd6bcfb0 100644 --- a/src/tpm2_tis.c +++ b/src/tpm2_tis.c @@ -417,22 +417,140 @@ int TPM2_TIS_Status(TPM2_CTX* ctx, byte* status) sizeof(*status)); } +/* Budget for the wait loops below. An iteration count makes the real timeout + * depend on host speed, so a >20 s RSA key generation times out intermittently. + * Use TPM_TIMEOUT_MS of real time where a monotonic clock exists; elsewhere + * keep counting exactly as before so no port gains a requirement. */ +typedef struct TPM2_TIS_TIMEOUT { +#ifdef WOLFTPM_HAVE_MONOTONIC_MS + word32 start; + word32 last; /* most recent tick that differed from the one before */ + int stagnant; /* consecutive polls with no clock progress */ +#endif +#if defined(DEBUG_WOLFTPM) && !defined(WOLFTPM_NO_STD_HEADERS) && \ + defined(WOLFTPM_HAVE_MONOTONIC_MS) + #define WOLFTPM_TIS_PROGRESS + word32 secs; /* whole seconds already marked with a dot */ +#endif + int tries; +} TPM2_TIS_TIMEOUT; + +static void TPM2_TIS_TimeoutStart(TPM2_TIS_TIMEOUT* to) +{ + XMEMSET(to, 0, sizeof(*to)); + to->tries = TPM_TIMEOUT_TRIES; +#ifdef WOLFTPM_HAVE_MONOTONIC_MS + /* A zero tick is a real timestamp (boot, or the word32 wrapping through + * 0), so it must not be treated as an unreadable clock. */ + to->start = XTPM_GET_TIMEMS(); + to->last = to->start; +#endif +} + +/* Returns 1 once the budget is spent, 0 while there is still time. */ +static int TPM2_TIS_TimeoutExpired(TPM2_TIS_TIMEOUT* to) +{ +#ifdef WOLFTPM_HAVE_MONOTONIC_MS + word32 now, elapsed; +#endif + + if (to->tries > 0) { + to->tries--; + } +#ifdef WOLFTPM_HAVE_MONOTONIC_MS + now = XTPM_GET_TIMEMS(); + /* unsigned subtraction stays correct across the word32 wrap */ + elapsed = (word32)(now - to->start); + + /* A wait never legitimately spans hours, so an implausible elapsed means + * the tick source returned garbage (a failed read yields zero, which the + * subtraction turns into a huge value). Treat it as no progress rather + * than expiring on one bad sample. */ + if (elapsed > (word32)(TPM_TIMEOUT_MS) * 10u) { + now = to->last; + #ifdef WOLFTPM_TIS_PROGRESS + elapsed = (word32)(now - to->start); + #endif + } + else if (elapsed >= TPM_TIMEOUT_MS) { + return 1; + } + + /* A clock that stops advancing - at startup or part way through - would + * otherwise never reach the deadline and the wait would hang. Bound it by + * consecutive polls that saw no progress, independent of the iteration + * cap, so this holds when TPM_TIMEOUT_TRIES is zero ("no cap") too. */ + if (now == to->last) { + if (++to->stagnant >= TPM_TIMEOUT_STAGNANT_POLLS) { + return 1; + } + } + else { + to->last = now; + to->stagnant = 0; + } + +#ifdef WOLFTPM_TIS_PROGRESS + /* Waits of a minute or more are normal for key generation, so show a dot + * per second rather than let a slow command look like a hang. */ + if (elapsed / 1000u > to->secs) { + to->secs = elapsed / 1000u; + printf("."); + fflush(stdout); + } +#endif + return 0; +#else + return (to->tries <= 0) ? 1 : 0; +#endif +} + +#ifdef WOLFTPM_TIS_PROGRESS +/* End the dot line, if any were printed. */ +static void TPM2_TIS_TimeoutDone(TPM2_TIS_TIMEOUT* to) +{ + if (to->secs > 0) { + printf("\n"); + fflush(stdout); + } +} +#else + #define TPM2_TIS_TimeoutDone(to) (void)(to) +#endif + +#ifdef WOLFTPM_DEBUG_TIMEOUT +/* Elapsed ms where a clock exists, else polls taken. */ +static word32 TPM2_TIS_TimeoutSpent(TPM2_TIS_TIMEOUT* to) +{ +#ifdef WOLFTPM_HAVE_MONOTONIC_MS + return (word32)(XTPM_GET_TIMEMS() - to->start); +#else + return (word32)(TPM_TIMEOUT_TRIES - to->tries); +#endif +} +#endif + int TPM2_TIS_WaitForStatus(TPM2_CTX* ctx, byte status, byte status_mask) { int rc; - int timeout = TPM_TIMEOUT_TRIES; + int expired = 0; + TPM2_TIS_TIMEOUT to; byte reg = 0; + TPM2_TIS_TimeoutStart(&to); do { rc = TPM2_TIS_Status(ctx, ®); if (rc == TPM_RC_SUCCESS && (reg & status) == status_mask) break; XTPM_WAIT(); - } while (rc == TPM_RC_SUCCESS && --timeout > 0); + expired = TPM2_TIS_TimeoutExpired(&to); + } while (rc == TPM_RC_SUCCESS && !expired); + TPM2_TIS_TimeoutDone(&to); #ifdef WOLFTPM_DEBUG_TIMEOUT - printf("TIS_WaitForStatus: Timeout %d\n", TPM_TIMEOUT_TRIES - timeout); + printf("TIS_WaitForStatus: spent %u\n", + (unsigned int)TPM2_TIS_TimeoutSpent(&to)); #endif - if (timeout <= 0) + if (expired) return TPM_RC_TIMEOUT; return rc; } @@ -458,7 +576,10 @@ int TPM2_TIS_GetBurstCount(TPM2_CTX* ctx, word16* burstCount) #endif { - int timeout = TPM_TIMEOUT_TRIES; + int expired = 0; + TPM2_TIS_TIMEOUT to; + + TPM2_TIS_TimeoutStart(&to); *burstCount = 0; do { rc = TPM2_TIS_Read(ctx, TPM_BURST_COUNT(ctx->locality), @@ -469,16 +590,19 @@ int TPM2_TIS_GetBurstCount(TPM2_CTX* ctx, word16* burstCount) if (rc == TPM_RC_SUCCESS && *burstCount > 0) break; XTPM_WAIT(); - } while (rc == TPM_RC_SUCCESS && --timeout > 0); + expired = TPM2_TIS_TimeoutExpired(&to); + } while (rc == TPM_RC_SUCCESS && !expired); + TPM2_TIS_TimeoutDone(&to); #ifdef WOLFTPM_DEBUG_TIMEOUT - printf("TIS_GetBurstCount: Timeout %d\n", TPM_TIMEOUT_TRIES - timeout); + printf("TIS_GetBurstCount: spent %u\n", + (unsigned int)TPM2_TIS_TimeoutSpent(&to)); #endif if (*burstCount > MAX_SPI_FRAMESIZE) *burstCount = MAX_SPI_FRAMESIZE; - if (timeout <= 0) + if (expired) return TPM_RC_TIMEOUT; } diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index ca7ff76a1..266e0ecdf 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -1250,15 +1250,9 @@ int wolfTPM2_GetCapabilities(WOLFTPM2_DEV* dev, WOLFTPM2_CAPS* cap) * Returns TPM_RC_SUCCESS with *isSupported set to 1 (supported) or 0 (not * supported); on any failure a non-zero rc is returned and *isSupported is set * to 0 so a caller that ignores the rc fails closed. - * Queries TPM_CAP_ALGS: the TPM returns algorithms with ID >= property, so a - * match at index 0 for a single-property query means it is implemented. */ + * Delegates to TPM2_IsAlgSupported(); dev is validated but unused. */ int wolfTPM2_IsAlgSupported(WOLFTPM2_DEV* dev, TPM_ALG_ID alg, int* isSupported) { - int rc; - GetCapability_In in; - GetCapability_Out out; - TPML_ALG_PROPERTY* algs; - if (isSupported == NULL) { return BAD_FUNC_ARG; } @@ -1267,29 +1261,7 @@ int wolfTPM2_IsAlgSupported(WOLFTPM2_DEV* dev, TPM_ALG_ID alg, int* isSupported) if (dev == NULL) { return BAD_FUNC_ARG; } - XMEMSET(&in, 0, sizeof(in)); - XMEMSET(&out, 0, sizeof(out)); - in.capability = TPM_CAP_ALGS; - in.property = alg; - in.propertyCount = 1; - rc = TPM2_GetCapability(&in, &out); - if (rc != TPM_RC_SUCCESS) { - return rc; /* query failure, distinct from "not supported" */ - } - /* capabilityData.data is a union - confirm the TPM answered with the - * capability we asked for before reading the algorithm member, so a - * non-conforming response cannot be reinterpreted as an algorithm - * property. */ - if (out.capabilityData.capability != TPM_CAP_ALGS) { - return TPM_RC_VALUE; - } - /* The TPM returns algorithms with ID >= property; a match at index 0 - * means the requested algorithm is implemented. */ - algs = &out.capabilityData.data.algorithms; - if (algs->count >= 1 && algs->algProperties[0].alg == alg) { - *isSupported = 1; - } - return TPM_RC_SUCCESS; + return TPM2_IsAlgSupported(alg, isSupported); } int wolfTPM2_GetHandles(TPM_HANDLE handle, TPML_HANDLE* handles) @@ -5788,6 +5760,124 @@ int wolfTPM2_SignHash(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, } +/* Reject a signature the TPM cannot accept as a parameter. Some parts stop + * responding until a hardware reset rather than erroring, so this must not be + * left to the TPM. Two limits apply: TPM_PT_INPUT_BUFFER bounds the parameter + * and TPM_PT_MAX_COMMAND_SIZE bounds the whole command, so the capability read + * is skipped only when the signature plus overhead fits inside + * TPM_MIN_INPUT_BUFFER, the floor every conformant TPM meets. That still + * covers every ECC signature and RSA up to 4096. Above it the limit must be + * established, so this fails closed. Returns TPM_RC_SUCCESS if it fits, + * BUFFER_E if it provably does not, and the query error otherwise so callers + * can tell the two apart. */ + +/* Bytes that share the command with the signature: 10-byte header, up to two + * 4-byte handles, a 4-byte auth-area size, a password session (~9) or an HMAC + * session with a nonce and digest (~75), the digest TPM2B (up to 66), and the + * TPMT_SIGNATURE tag/alg/size fields (~8). Rounded up with margin. Only used + * to reserve room against TPM_PT_MAX_COMMAND_SIZE, so an over-estimate can + * reject a signature that would just fit; override if that ever bites. */ +#ifndef TPM_SIG_CMD_OVERHEAD +#define TPM_SIG_CMD_OVERHEAD 176 +#endif + +/* extraSz is the caller's other variable-length parameters (digest, context), + * which the fixed overhead estimate does not cover. */ +static int wolfTPM2_CheckSigInputBuffer(int sigSz, int extraSz) +{ + int rc; + GetCapability_In in; + GetCapability_Out out; + TPML_TAGGED_TPM_PROPERTY* props; + UINT32 inputBuffer; + UINT32 cmdSz; + + if (sigSz < 0 || extraSz < 0) { + return BUFFER_E; + } + cmdSz = (UINT32)sigSz + (UINT32)extraSz + TPM_SIG_CMD_OVERHEAD; + /* Overhead is included: a signature that fits the parameter floor could + * still overflow a TPM whose command limit equals that floor. */ + if (cmdSz <= TPM_MIN_INPUT_BUFFER) { + return TPM_RC_SUCCESS; + } + + XMEMSET(&in, 0, sizeof(in)); + XMEMSET(&out, 0, sizeof(out)); + in.capability = TPM_CAP_TPM_PROPERTIES; + in.property = TPM_PT_INPUT_BUFFER; + in.propertyCount = 1; + rc = TPM2_GetCapability(&in, &out); + if (rc != TPM_RC_SUCCESS) { + #ifdef DEBUG_WOLFTPM + printf("Signature size check: cannot read TPM_PT_INPUT_BUFFER " + "(0x%x), refusing a %d byte signature\n", rc, sigSz); + #endif + return rc; /* query failure, distinct from a genuine oversize */ + } + /* union - confirm the capability and property asked for */ + props = &out.capabilityData.data.tpmProperties; + if (out.capabilityData.capability != TPM_CAP_TPM_PROPERTIES || + props->count == 0 || + props->tpmProperty[0].property != TPM_PT_INPUT_BUFFER) { + #ifdef DEBUG_WOLFTPM + printf("Signature size check: unexpected capability response, " + "refusing a %d byte signature\n", sigSz); + #endif + return TPM_RC_VALUE; /* not an oversize; the TPM answered wrongly */ + } + + inputBuffer = props->tpmProperty[0].value; + if (inputBuffer == 0) { + return TPM_RC_VALUE; /* nonsensical limit, treat as unreadable */ + } + if ((UINT32)sigSz > inputBuffer) { + #ifdef DEBUG_WOLFTPM + printf("Signature size check: signature %d bytes exceeds the TPM's " + "%u byte input buffer\n", sigSz, (unsigned int)inputBuffer); + #endif + return BUFFER_E; + } + + /* The whole command has to fit too, and that is the limit this guard + * exists to respect, so an unreadable or malformed answer fails closed + * rather than letting the oversized command through. */ + XMEMSET(&in, 0, sizeof(in)); + XMEMSET(&out, 0, sizeof(out)); + in.capability = TPM_CAP_TPM_PROPERTIES; + in.property = TPM_PT_MAX_COMMAND_SIZE; + in.propertyCount = 1; + rc = TPM2_GetCapability(&in, &out); + if (rc != TPM_RC_SUCCESS) { + #ifdef DEBUG_WOLFTPM + printf("Signature size check: cannot read TPM_PT_MAX_COMMAND_SIZE " + "(0x%x), refusing a %d byte signature\n", rc, sigSz); + #endif + return rc; + } + props = &out.capabilityData.data.tpmProperties; + if (out.capabilityData.capability != TPM_CAP_TPM_PROPERTIES || + props->count == 0 || + props->tpmProperty[0].property != TPM_PT_MAX_COMMAND_SIZE || + props->tpmProperty[0].value == 0) { + #ifdef DEBUG_WOLFTPM + printf("Signature size check: unexpected command-size response, " + "refusing a %d byte signature\n", sigSz); + #endif + return TPM_RC_VALUE; + } + if (cmdSz > props->tpmProperty[0].value) { + #ifdef DEBUG_WOLFTPM + printf("Signature size check: command %u bytes exceeds the TPM's " + "%u byte command limit\n", (unsigned int)cmdSz, + (unsigned int)props->tpmProperty[0].value); + #endif + return BUFFER_E; + } + + 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_VerifyHashTicket(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, @@ -5805,6 +5895,11 @@ int wolfTPM2_VerifyHashTicket(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, return BAD_FUNC_ARG; } + rc = wolfTPM2_CheckSigInputBuffer(sigSz, digestSz); + if (rc != TPM_RC_SUCCESS) { + return rc; + } + if (key->pub.publicArea.type == TPM_ALG_ECC) { if (sigAlg == TPM_ALG_NULL) sigAlg = key->pub.publicArea.parameters.eccDetail.scheme.scheme; @@ -6234,6 +6329,15 @@ int wolfTPM2_VerifySequenceComplete(WOLFTPM2_DEV* dev, return BAD_FUNC_ARG; } + /* Before the sequence is advanced: bailing out after SequenceUpdate + * would leave the sequence slot allocated. The data is hashed by the TPM + * rather than carried in the verify command, so only the signature and + * the fixed overhead count here. */ + rc = wolfTPM2_CheckSigInputBuffer(sigSz, 0); + if (rc != TPM_RC_SUCCESS) { + return rc; + } + /* Validate per-key-type sigSz BEFORE the internal SequenceUpdate * call. Otherwise we advance the TPM-side sequence and then bail out * before Complete, leaving the slot allocated until the caller @@ -6521,6 +6625,11 @@ int wolfTPM2_VerifyDigestSignature(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, return BAD_FUNC_ARG; } + rc = wolfTPM2_CheckSigInputBuffer(sigSz, digestSz + contextSz); + if (rc != TPM_RC_SUCCESS) { + return rc; + } + XMEMSET(&verifyDigestSigIn, 0, sizeof(verifyDigestSigIn)); verifyDigestSigIn.keyHandle = key->handle.hndl; verifyDigestSigIn.digest.size = (UINT16)digestSz; @@ -7413,6 +7522,172 @@ int wolfTPM2_ExtendPCR(WOLFTPM2_DEV* dev, int pcrIndex, int hashAlg, return rc; } +int wolfTPM2_AllocatePCRBanks_ex(WOLFTPM2_DEV* dev, WOLFTPM2_SESSION* session, + const TPM_ALG_ID* hashAlgs, int hashAlgCount, PCR_Allocate_Out* allocOut) +{ + int rc; + int i, wanted; + word32 j, selIdx; + byte pcrArray[PCR_LAST - PCR_FIRST + 1]; + GetCapability_In capIn; + GetCapability_Out capOut; + TPML_PCR_SELECTION* banks; + PCR_Allocate_In in; + PCR_Allocate_Out out; + TPM2_AUTH_SESSION saveSess; + + if (allocOut != NULL) { + XMEMSET(allocOut, 0, sizeof(*allocOut)); + } + if (dev == NULL || hashAlgs == NULL) { + return BAD_FUNC_ARG; + } + /* A zero-length selection is legal and leaves the TPM with no PCR banks */ + if (hashAlgCount <= 0 || hashAlgCount > (int)HASH_COUNT) { + return BAD_FUNC_ARG; + } + + for (i = 0; i < hashAlgCount; i++) { + if (hashAlgs[i] == TPM_ALG_NULL || hashAlgs[i] == TPM_ALG_ERROR) { + return BAD_FUNC_ARG; + } + for (j = 0; j < (word32)i; j++) { + if (hashAlgs[j] == hashAlgs[i]) { + return BAD_FUNC_ARG; + } + } + } + + /* The bank list is the authority on what can be allocated, not + * TPM_CAP_ALGS - parts advertise hashes there that have no PCR bank */ + XMEMSET(&capIn, 0, sizeof(capIn)); + XMEMSET(&capOut, 0, sizeof(capOut)); + capIn.capability = TPM_CAP_PCRS; + capIn.property = 0; + capIn.propertyCount = HASH_COUNT; + rc = TPM2_GetCapability(&capIn, &capOut); + if (rc != TPM_RC_SUCCESS) { + return rc; + } + if (capOut.capabilityData.capability != TPM_CAP_PCRS) { + return TPM_RC_VALUE; + } + /* A truncated list would mean mirroring an incomplete selection, which + * silently deallocates the banks that did not fit the page. */ + if (capOut.moreData == YES) { + return TPM_RC_SIZE; + } + banks = &capOut.capabilityData.data.assignedPCR; + + /* An unimplemented bank is silently ignored (Part 3 22.5), which would + * allocate nothing and still report success */ + for (i = 0; i < hashAlgCount; i++) { + wanted = 0; + for (j = 0; j < banks->count; j++) { + if (banks->pcrSelections[j].hash == hashAlgs[i]) { + wanted = 1; + break; + } + } + if (!wanted) { + return TPM_RC_HASH; + } + } + + for (i = 0; i < (int)sizeof(pcrArray); i++) { + pcrArray[i] = (byte)(PCR_FIRST + i); + } + + XMEMSET(&in, 0, sizeof(in)); + XMEMSET(&out, 0, sizeof(out)); + in.authHandle = TPM_RH_PLATFORM; + + /* Mirror the bank list, giving dropped banks an all-zero bitmap. Part 3 + * 22.5 says an omitted bank is deallocated, but Infineon parts answer + * TPM_RC_PCR unless each deallocation is spelled out. */ + for (j = 0; j < banks->count; j++) { + wanted = 0; + for (i = 0; i < hashAlgCount; i++) { + if (hashAlgs[i] == banks->pcrSelections[j].hash) { + wanted = 1; + break; + } + } + if (wanted) { + TPM2_SetupPCRSelArray(&in.pcrAllocation, + banks->pcrSelections[j].hash, pcrArray, + (word32)sizeof(pcrArray)); + } + else { + selIdx = in.pcrAllocation.count; + if (selIdx >= HASH_COUNT) { + return TPM_RC_VALUE; /* the TPM reported more banks than fit */ + } + in.pcrAllocation.pcrSelections[selIdx].hash = + banks->pcrSelections[j].hash; + in.pcrAllocation.pcrSelections[selIdx].sizeofSelect = + banks->pcrSelections[j].sizeofSelect; + in.pcrAllocation.count++; + } + } + + /* Platform auth setup below overwrites session[0] */ + XMEMCPY(&saveSess, &dev->session[0], sizeof(saveSess)); + + if (session == NULL) { + rc = wolfTPM2_SetAuthPassword(dev, 0, NULL); + if (rc == TPM_RC_SUCCESS) { + dev->session[0].sessionAttributes = 0; + } + } + else { + rc = wolfTPM2_SetAuthSession(dev, 0, session, + (TPMA_SESSION_continueSession)); + } + if (rc == TPM_RC_SUCCESS) { + rc = TPM2_PCR_Allocate(&in, &out); + if (rc != TPM_RC_SUCCESS) { + #ifdef DEBUG_WOLFTPM + printf("TPM2_PCR_Allocate failed 0x%x: %s\n", rc, + TPM2_GetRCString(rc)); + #endif + } + } + + /* A continuing session gets a fresh nonceTPM from the response; hand it + * back before slot 0 is overwritten or the caller's next use of the + * session computes an invalid HMAC (see wolfTPM2_UnsetAuthSession). */ + if (session != NULL) { + XMEMCPY(&session->nonceTPM, &dev->session[0].nonceTPM, + sizeof(TPM2B_NONCE)); + } + + /* Restore previous session[0] state and clear the stack copy */ + XMEMCPY(&dev->session[0], &saveSess, sizeof(dev->session[0])); + TPM2_ForceZero(&saveSess, sizeof(saveSess)); + + if (allocOut != NULL) { + XMEMCPY(allocOut, &out, sizeof(*allocOut)); + } + if (rc != TPM_RC_SUCCESS) { + return rc; + } + /* No room for the set - fail rather than make the caller check a field */ + if (out.allocationSuccess != YES) { + return BUFFER_E; + } + /* Staged: applied at the next Startup(CLEAR), which needs a _TPM_Init + * only a power cycle provides - no command does it */ + return TPM_RC_SUCCESS; +} + +int wolfTPM2_AllocatePCRBanks(WOLFTPM2_DEV* dev, const TPM_ALG_ID* hashAlgs, + int hashAlgCount, PCR_Allocate_Out* allocOut) +{ + return wolfTPM2_AllocatePCRBanks_ex(dev, NULL, hashAlgs, hashAlgCount, + allocOut); +} + int wolfTPM2_UnloadHandle(WOLFTPM2_DEV* dev, WOLFTPM2_HANDLE* handle) { int rc; @@ -8635,7 +8910,7 @@ int wolfTPM2_EncryptDecryptBlock(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, * it with TPM_RC_VALUE / TPM_RC_KEY per TPM 2.0 Part 3. */ rc = TPM2_EncryptDecrypt2(&encDecIn, &encDecOut); - if (rc == TPM_RC_COMMAND_CODE) { /* some TPM's may not support command */ + if (WOLFTPM_IS_COMMAND_UNAVAILABLE(rc)) { /* some TPM's lack the command */ /* try to enable support */ rc = wolfTPM2_SetCommand(dev, TPM_CC_EncryptDecrypt2, YES); if (rc == TPM_RC_SUCCESS) { diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index 7373dade3..47ceaf5d1 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -1915,6 +1915,181 @@ static void test_fwtpm_pcr_read(void) fwtpm_pass("PCR_Read(0):", 0); } +/* One TPMS_PCR_SELECTION per algs[] entry; selectAll=0 sends an all-zero + * bitmap, which deallocates that bank */ +static int BuildPcrAllocateCmd(byte* buf, UINT32 authHandle, + const UINT16* algs, UINT32 count, int selectAll) +{ + int pos, i; + UINT32 c; + + pos = BuildCmdHeader(buf, TPM_ST_SESSIONS, 0, TPM_CC_PCR_Allocate); + PutU32BE(buf + pos, authHandle); pos += 4; + /* Auth area: size(4) + sessionHandle(4) + nonce(2) + attrs(1) + hmac(2) */ + PutU32BE(buf + pos, 9); pos += 4; + PutU32BE(buf + pos, TPM_RS_PW); pos += 4; + PutU16BE(buf + pos, 0); pos += 2; + buf[pos++] = 0; + PutU16BE(buf + pos, 0); pos += 2; + PutU32BE(buf + pos, count); pos += 4; + for (c = 0; c < count; c++) { + PutU16BE(buf + pos, algs[c]); pos += 2; + buf[pos++] = (byte)PCR_SELECT_MAX; + for (i = 0; i < PCR_SELECT_MAX; i++) { + buf[pos + i] = selectAll ? 0xFF : 0x00; + } + pos += PCR_SELECT_MAX; + } + PutU32BE(buf + 2, (UINT32)pos); + return pos; +} + +/* 1 if hashAlg has any PCR bits set in TPM_CAP_PCRS */ +static int fwtpm_bank_allocated(FWTPM_CTX* ctx, UINT16 hashAlg) +{ + int rc, rspSize, cmdSz, pos, b, i, found = 0; + UINT32 bankCount; + + cmdSz = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_GetCapability); + PutU32BE(gCmd + cmdSz, TPM_CAP_PCRS); cmdSz += 4; + PutU32BE(gCmd + cmdSz, 0); cmdSz += 4; + PutU32BE(gCmd + cmdSz, HASH_COUNT); cmdSz += 4; + PutU32BE(gCmd + 2, (UINT32)cmdSz); + + rspSize = 0; + rc = FWTPM_ProcessCommand(ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + if (rc != TPM_RC_SUCCESS || GetRspRC(gRsp) != TPM_RC_SUCCESS) { + return -1; + } + + /* header + moreData(1) + capability(4) + count(4) */ + pos = TPM2_HEADER_SIZE + 1 + 4; + bankCount = GetU32BE(gRsp + pos); pos += 4; + for (b = 0; b < (int)bankCount && pos + 3 <= rspSize; b++) { + UINT16 alg = (UINT16)GetU16BE(gRsp + pos); pos += 2; + int sizeOfSelect = gRsp[pos++]; + if (pos + sizeOfSelect > rspSize) { + break; + } + if (alg == hashAlg) { + for (i = 0; i < sizeOfSelect; i++) { + if (gRsp[pos + i] != 0) { + found = 1; + } + } + } + pos += sizeOfSelect; + } + return found; +} + +static void test_fwtpm_pcr_allocate(void) +{ + FWTPM_CTX ctx; + int rc, rspSize, cmdSz; + UINT16 algs[2]; + /* A TPM_ST_SESSIONS response prefixes the parameters with paramSize(4), + * so allocationSuccess sits 4 bytes past the header. */ + + memset(&ctx, 0, sizeof(ctx)); + rc = fwtpm_test_startup(&ctx); + AssertIntEQ(rc, 0); + + algs[0] = TPM_ALG_SHA256; + algs[1] = TPM_ALG_SHA384; + + /* Only the platform hierarchy may re-provision the banks */ + cmdSz = BuildPcrAllocateCmd(gCmd, TPM_RH_OWNER, algs, 1, 1); + rspSize = 0; + rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_AUTH_TYPE); + + /* Oversized selection count is rejected before parsing */ + cmdSz = BuildPcrAllocateCmd(gCmd, TPM_RH_PLATFORM, algs, 1, 1); + PutU32BE(gCmd + TPM2_HEADER_SIZE + 4 + 13, FWTPM_PCR_BANKS * 4 + 1); + rspSize = 0; + rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SIZE); + + /* Both banks selected. Per spec 22.5 the change is staged, so the live + * allocation must not move until the next Startup(CLEAR). */ + cmdSz = BuildPcrAllocateCmd(gCmd, TPM_RH_PLATFORM, algs, 2, 1); + rspSize = 0; + rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(gRsp[TPM2_HEADER_SIZE + 4], 1); /* allocationSuccess */ + AssertIntEQ(fwtpm_bank_allocated(&ctx, TPM_ALG_SHA256), 1); + AssertIntEQ(fwtpm_bank_allocated(&ctx, TPM_ALG_SHA384), 1); + + /* SHA-256 only: staged, so SHA-384 is still allocated right now */ + cmdSz = BuildPcrAllocateCmd(gCmd, TPM_RH_PLATFORM, algs, 1, 1); + rspSize = 0; + rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(gRsp[TPM2_HEADER_SIZE + 4], 1); + AssertIntEQ(fwtpm_bank_allocated(&ctx, TPM_ALG_SHA384), 1); + + /* The restart performs the _TPM_Init and Startup(CLEAR) that apply it. + * Replace, not add: the bank left out of the selection is now gone. + * Without NV nothing survives the restart - the TPM comes back with the + * default banks - so the applied state is only observable with NV. */ +#ifndef FWTPM_NO_NV + FWTPM_Cleanup(&ctx); + memset(&ctx, 0, sizeof(ctx)); + rc = fwtpm_test_startup(&ctx); + AssertIntEQ(rc, 0); + AssertIntEQ(fwtpm_bank_allocated(&ctx, TPM_ALG_SHA256), 1); + AssertIntEQ(fwtpm_bank_allocated(&ctx, TPM_ALG_SHA384), 0); +#endif + + /* An all-zero bitmap deallocates, but must not leave zero banks */ + algs[0] = TPM_ALG_SHA256; + cmdSz = BuildPcrAllocateCmd(gCmd, TPM_RH_PLATFORM, algs, 1, 0); + rspSize = 0; + rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(gRsp[TPM2_HEADER_SIZE + 4], 0); /* allocationSuccess = NO */ + AssertIntEQ(fwtpm_bank_allocated(&ctx, TPM_ALG_SHA256), 1); /* unchanged */ + + /* Same trap: an unimplemented bank must not store "no banks" */ + algs[0] = (UINT16)0x7FFF; + cmdSz = BuildPcrAllocateCmd(gCmd, TPM_RH_PLATFORM, algs, 1, 1); + rspSize = 0; + rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(gRsp[TPM2_HEADER_SIZE + 4], 0); + AssertIntEQ(fwtpm_bank_allocated(&ctx, TPM_ALG_SHA256), 1); + + /* Restore both banks, applied by the restart below */ + algs[0] = TPM_ALG_SHA256; + cmdSz = BuildPcrAllocateCmd(gCmd, TPM_RH_PLATFORM, algs, 2, 1); + rspSize = 0; + rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + FWTPM_Cleanup(&ctx); + + /* Restarting applies the staged change and must not lose the banks. With + * NV the journal carries no PCR_AUTH record while the allocation is the + * default, so a replay that does not seed the default reports no banks at + * all; without NV the defaults are regenerated. Both must end up here. */ + memset(&ctx, 0, sizeof(ctx)); + rc = fwtpm_test_startup(&ctx); + AssertIntEQ(rc, 0); + AssertIntEQ(fwtpm_bank_allocated(&ctx, TPM_ALG_SHA256), 1); + AssertIntEQ(fwtpm_bank_allocated(&ctx, TPM_ALG_SHA384), 1); + + FWTPM_Cleanup(&ctx); + fwtpm_pass("PCR_Allocate:", 0); +} + static void test_fwtpm_pcr_extend_and_read(void) { FWTPM_CTX ctx; @@ -15900,6 +16075,7 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_getcap_flushcontext_chandles(); test_fwtpm_getcap_properties(); test_fwtpm_getcap_pcrs(); + test_fwtpm_pcr_allocate(); test_fwtpm_getcap_paging(); test_fwtpm_getcap_ecc_curves(); #if defined(HAVE_ECC) && !defined(FWTPM_NO_ECDH) && \ diff --git a/tests/unit_tests.c b/tests/unit_tests.c index a139bf885..62a89f93b 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -1408,6 +1408,190 @@ static void test_wolfTPM2_IsAlgSupported(void) #endif /* WOLFTPM_SWTPM */ } +/* TPM2_IsPcrBankAllocated: argument validation always, plus a live query on + * the simulator. Mirrors test_wolfTPM2_IsAlgSupported. */ +static void test_TPM2_IsPcrBankAllocated(void) +{ + int isAllocated = 1; /* seeded true to prove the error paths clear it */ +#if defined(WOLFTPM_SWTPM) + int rc; + WOLFTPM2_DEV dev; +#endif + + /* NULL out-param */ + AssertIntEQ(TPM2_IsPcrBankAllocated(TPM_ALG_SHA256, 0, NULL), + BAD_FUNC_ARG); + /* negative index must fail and must not leave the out-param set */ + AssertIntEQ(TPM2_IsPcrBankAllocated(TPM_ALG_SHA256, -1, &isAllocated), + BAD_FUNC_ARG); + AssertIntEQ(isAllocated, 0); + +#if defined(WOLFTPM_SWTPM) + XMEMSET(&dev, 0, sizeof(dev)); + rc = wolfTPM2_Init(&dev, TPM2_IoCb, NULL); + AssertIntEQ(rc, 0); + + /* Every TPM 2.0 part allocates a SHA2-256 bank covering PCR 0 */ + isAllocated = 0; + AssertIntEQ(TPM2_IsPcrBankAllocated(TPM_ALG_SHA256, 0, &isAllocated), + TPM_RC_SUCCESS); + AssertIntEQ(isAllocated, 1); + + /* A hash no bank uses reports not-allocated, with a success rc because + * the query itself worked - the distinction this API exists to make. */ + isAllocated = 1; + AssertIntEQ(TPM2_IsPcrBankAllocated((TPM_ALG_ID)0x7FFF, 0, &isAllocated), + TPM_RC_SUCCESS); + AssertIntEQ(isAllocated, 0); + + /* An index beyond the PCR count is not allocated in any bank */ + isAllocated = 1; + AssertIntEQ(TPM2_IsPcrBankAllocated(TPM_ALG_SHA256, 250, &isAllocated), + TPM_RC_SUCCESS); + AssertIntEQ(isAllocated, 0); + + wolfTPM2_Cleanup(&dev); + printf("Test PcrBank: %-40s Passed\n", "Args + Query:"); +#else + printf("Test PcrBank: %-40s Passed\n", "Arg Validation:"); +#endif /* WOLFTPM_SWTPM */ +} + +static void test_wolfTPM2_AllocatePCRBanks(void) +{ + WOLFTPM2_DEV dev; + PCR_Allocate_Out allocOut; + TPM_ALG_ID algs[4]; +#if defined(WOLFTPM_SWTPM) + TPM_ALG_ID origAlgs[4]; + WOLFTPM2_SESSION sess; + int origCount = 0; + int isAllocated; + int i; + int rc; +#endif + + XMEMSET(&dev, 0, sizeof(dev)); + algs[0] = TPM_ALG_SHA256; + algs[1] = TPM_ALG_SHA384; + + /* Seeded non-zero to prove a rejection still clears the out-param */ + XMEMSET(&allocOut, 0xFF, sizeof(allocOut)); + AssertIntEQ(wolfTPM2_AllocatePCRBanks(NULL, algs, 1, &allocOut), + BAD_FUNC_ARG); + AssertIntEQ(allocOut.allocationSuccess, 0); + AssertIntEQ((int)allocOut.maxPCR, 0); + AssertIntEQ((int)allocOut.sizeNeeded, 0); + AssertIntEQ((int)allocOut.sizeAvailable, 0); + + AssertIntEQ(wolfTPM2_AllocatePCRBanks(&dev, NULL, 1, &allocOut), + BAD_FUNC_ARG); + /* Zero banks is legal at the TPM; the wrapper must make it unreachable */ + AssertIntEQ(wolfTPM2_AllocatePCRBanks(&dev, algs, 0, &allocOut), + BAD_FUNC_ARG); + AssertIntEQ(wolfTPM2_AllocatePCRBanks(&dev, algs, -1, &allocOut), + BAD_FUNC_ARG); + AssertIntEQ(wolfTPM2_AllocatePCRBanks(&dev, algs, HASH_COUNT + 1, + &allocOut), BAD_FUNC_ARG); + + algs[0] = TPM_ALG_NULL; + AssertIntEQ(wolfTPM2_AllocatePCRBanks(&dev, algs, 1, &allocOut), + BAD_FUNC_ARG); + algs[0] = TPM_ALG_SHA256; + + /* Duplicates would silently halve the requested set */ + algs[1] = TPM_ALG_SHA256; + AssertIntEQ(wolfTPM2_AllocatePCRBanks(&dev, algs, 2, &allocOut), + BAD_FUNC_ARG); + algs[1] = TPM_ALG_SHA384; + + /* _ex applies the same checks before touching session[0] */ + AssertIntEQ(wolfTPM2_AllocatePCRBanks_ex(NULL, NULL, algs, 1, &allocOut), + BAD_FUNC_ARG); + AssertIntEQ(wolfTPM2_AllocatePCRBanks_ex(&dev, NULL, NULL, 1, &allocOut), + BAD_FUNC_ARG); + AssertIntEQ(wolfTPM2_AllocatePCRBanks_ex(&dev, NULL, algs, 0, &allocOut), + BAD_FUNC_ARG); + +#if defined(WOLFTPM_SWTPM) + rc = wolfTPM2_Init(&dev, TPM2_IoCb, NULL); + AssertIntEQ(rc, 0); + + /* Record the current set to restore later - the fwTPM persists this to + * NV, so a leaked change breaks later PCR tests and subsequent runs */ + algs[0] = TPM_ALG_SHA256; + algs[1] = TPM_ALG_SHA384; + for (i = 0; i < 2; i++) { + isAllocated = 0; + AssertIntEQ(TPM2_IsPcrBankAllocated(algs[i], (int)PCR_FIRST, + &isAllocated), TPM_RC_SUCCESS); + if (isAllocated) { + origAlgs[origCount++] = algs[i]; + } + } + AssertIntGT(origCount, 0); + + /* Refused before sending: the TPM would silently ignore it */ + algs[0] = (TPM_ALG_ID)0x7FFF; + AssertIntEQ(wolfTPM2_AllocatePCRBanks(&dev, algs, 1, &allocOut), + TPM_RC_HASH); + isAllocated = 0; + AssertIntEQ(TPM2_IsPcrBankAllocated(origAlgs[0], (int)PCR_FIRST, + &isAllocated), TPM_RC_SUCCESS); + AssertIntEQ(isAllocated, 1); /* unchanged - no command was sent */ + + /* The TPM accepts the request and reports sensible sizing. The resulting + * allocation is deliberately not asserted: it takes effect at the next + * Startup(CLEAR) following a _TPM_Init, which no command can trigger, so + * a correct TPM still reports the old banks here. Replace semantics are + * covered by tests/fwtpm_unit_tests.c across a restart. */ + algs[0] = TPM_ALG_SHA256; + XMEMSET(&allocOut, 0, sizeof(allocOut)); + AssertIntEQ(wolfTPM2_AllocatePCRBanks(&dev, algs, 1, &allocOut), + TPM_RC_SUCCESS); + AssertIntEQ(allocOut.allocationSuccess, YES); + AssertIntGT((int)allocOut.maxPCR, 0); + AssertIntLE((int)allocOut.sizeNeeded, (int)allocOut.sizeAvailable); + + /* Stage the original set again so nothing is left pending for the next + * reset, whichever way this TPM applies the change */ + XMEMSET(&allocOut, 0, sizeof(allocOut)); + AssertIntEQ(wolfTPM2_AllocatePCRBanks(&dev, origAlgs, origCount, + &allocOut), TPM_RC_SUCCESS); + AssertIntEQ(allocOut.allocationSuccess, YES); + + /* allocOut is optional */ + AssertIntEQ(wolfTPM2_AllocatePCRBanks(&dev, origAlgs, origCount, NULL), + TPM_RC_SUCCESS); + + /* _ex with a NULL session is the path the base function takes */ + AssertIntEQ(wolfTPM2_AllocatePCRBanks_ex(&dev, NULL, origAlgs, origCount, + NULL), TPM_RC_SUCCESS); + + /* Slot 0 authorization is a password or a policy session (see + * TPM2_GetCmdAuthCount); an HMAC session there is not an auth session. + * Prove the policy session is wired into the auth area: the TPM must + * evaluate it and answer, not reject the command as unauthorized. + * Authorizing for real needs a platform authPolicy this TPM has not been + * provisioned with, so the result itself is not asserted. */ + XMEMSET(&sess, 0, sizeof(sess)); + rc = wolfTPM2_StartSession(&dev, &sess, NULL, NULL, TPM_SE_POLICY, + TPM_ALG_NULL); + if (rc == TPM_RC_SUCCESS) { + rc = wolfTPM2_AllocatePCRBanks_ex(&dev, &sess, origAlgs, origCount, + NULL); + AssertIntNE(rc, BAD_FUNC_ARG); + AssertIntNE(rc, TPM_RC_AUTH_MISSING); + wolfTPM2_UnloadHandle(&dev, &sess.handle); + } + + wolfTPM2_Cleanup(&dev); + printf("Test PcrAlloc: %-40s Passed\n", "Args + Allocate:"); +#else + printf("Test PcrAlloc: %-40s Passed\n", "Arg Validation:"); +#endif /* WOLFTPM_SWTPM */ +} + /* Success path for wolfTPM2_PolicyOR: satisfy one branch of a real two-branch * OR on a live policy session and confirm the TPM's running policy digest * matches the offline computation. Simulator only. */ @@ -2847,6 +3031,31 @@ static void test_WOLFTPM_IS_COMMAND_UNAVAILABLE(void) AssertIntEQ(0, WOLFTPM_IS_COMMAND_UNAVAILABLE(0x00000343)); /* layer bits */ AssertIntEQ(0, WOLFTPM_IS_COMMAND_UNAVAILABLE(0x000b0142)); /* vendor, non-cc */ + /* A command that exists but is switched off answers TPM_RC_DISABLED, which + * must not be confused with an absent one. Same masking and >= 0 gate. */ + AssertIntNE(0, WOLFTPM_IS_COMMAND_DISABLED((int)TPM_RC_DISABLED)); + AssertIntNE(0, WOLFTPM_IS_COMMAND_DISABLED(0x000b0120)); /* vendor bits */ + AssertIntEQ(0, WOLFTPM_IS_COMMAND_DISABLED((int)TPM_RC_COMMAND_CODE)); + AssertIntEQ(0, WOLFTPM_IS_COMMAND_DISABLED((int)TPM_RC_SUCCESS)); + AssertIntEQ(0, WOLFTPM_IS_COMMAND_DISABLED(-189)); + AssertIntEQ(0, WOLFTPM_IS_COMMAND_DISABLED(-1)); + + /* The combined form accepts either, and nothing else. */ + AssertIntNE(0, + WOLFTPM_IS_COMMAND_UNAVAILABLE_OR_DISABLED((int)TPM_RC_COMMAND_CODE)); + AssertIntNE(0, + WOLFTPM_IS_COMMAND_UNAVAILABLE_OR_DISABLED((int)TPM_RC_DISABLED)); + AssertIntEQ(0, + WOLFTPM_IS_COMMAND_UNAVAILABLE_OR_DISABLED((int)TPM_RC_SUCCESS)); + AssertIntEQ(0, WOLFTPM_IS_COMMAND_UNAVAILABLE_OR_DISABLED(-189)); + + /* The shared base macro underlying all three. Note TPM_RC_VALUE is not + * usable here: tpm2_asn.h defines it as an ASN error (-203), shadowing the + * response code of the same name from tpm2.h in this translation unit. */ + AssertIntNE(0, WOLFTPM_RC_IS((int)TPM_RC_DISABLED, TPM_RC_DISABLED)); + AssertIntEQ(0, WOLFTPM_RC_IS((int)TPM_RC_DISABLED, TPM_RC_COMMAND_CODE)); + AssertIntEQ(0, WOLFTPM_RC_IS(-189, TPM_RC_DISABLED)); + printf("Test TPM Wrapper: %-40s Passed\n", "IsCommandUnavailable:"); } @@ -8570,6 +8779,49 @@ static void test_TPM2_GetHashDigestSize_AllAlgs(void) * only when both families are present (a WOLFTPM_NO_MLDSA or WOLFTPM_NO_MLKEM * build excludes the matching wrapper definitions). CI always builds full * PQC, so coverage is unchanged there. */ +/* The signature-size gate is static, so reach it through wolfTPM2_VerifyHashTicket: + * that caller is built in every configuration and passes sigSz straight to the + * gate, so the gate's own checks are what answer here. */ +static void test_wolfTPM2_SigInputBufferGate(void) +{ + WOLFTPM2_DEV dev; + WOLFTPM2_KEY key; + byte digest[32]; + byte sig[64]; +#if defined(WOLFTPM_SWTPM) + int rc; +#endif + + XMEMSET(&dev, 0, sizeof(dev)); + XMEMSET(&key, 0, sizeof(key)); + XMEMSET(digest, 0, sizeof(digest)); + XMEMSET(sig, 0, sizeof(sig)); + + /* A negative size must be refused as a size problem, not read as huge */ + AssertIntEQ(wolfTPM2_VerifyHashTicket(&dev, &key, sig, -1, digest, + (int)sizeof(digest), TPM_ALG_NULL, TPM_ALG_SHA256, NULL), BUFFER_E); + +#if defined(WOLFTPM_SWTPM) + rc = wolfTPM2_Init(&dev, TPM2_IoCb, NULL); + AssertIntEQ(rc, 0); + + /* Below the parameter floor the gate short-circuits without a TPM query, + * so this gets past it and fails later on the empty key instead */ + AssertIntNE(wolfTPM2_VerifyHashTicket(&dev, &key, sig, (int)sizeof(sig), + digest, (int)sizeof(digest), TPM_ALG_NULL, TPM_ALG_SHA256, NULL), + BUFFER_E); + + /* Larger than any TPM input buffer must be refused up front */ + AssertIntEQ(wolfTPM2_VerifyHashTicket(&dev, &key, sig, 0x7FFFFFFF, digest, + (int)sizeof(digest), TPM_ALG_NULL, TPM_ALG_SHA256, NULL), BUFFER_E); + + wolfTPM2_Cleanup(&dev); + printf("Test TPM Wrapper: %-40s Passed\n", "SigInputBuffer gate:"); +#else + printf("Test TPM Wrapper: %-40s Passed\n", "SigInputBuffer args:"); +#endif +} + #if defined(WOLFTPM_MLDSA) && defined(WOLFTPM_MLKEM) /* Post-Quantum Cryptography (PQC) Unit Tests - TPM 2.0 v185 */ @@ -9527,6 +9779,8 @@ int unit_tests(int argc, char *argv[]) test_wolfTPM2_FirmwareUpgrade_ex_session(); #endif test_wolfTPM2_IsAlgSupported(); + test_TPM2_IsPcrBankAllocated(); + test_wolfTPM2_AllocatePCRBanks(); test_wolfTPM2_PolicyOR_success(); #if defined(WOLFTPM_MLDSA) && defined(WOLFTPM_MLKEM) /* Run non-TPM-dependent tests first */ @@ -9536,6 +9790,9 @@ int unit_tests(int argc, char *argv[]) test_wolfTPM2_PQC(); test_wolfTPM2_VerifySequence_NoLeak(); #endif + /* The gate guards classical RSA/ECC verifies too, so it must not sit + * behind the post-quantum guard */ + test_wolfTPM2_SigInputBufferGate(); test_wolfTPM2_Cleanup(); test_wolfTPM2_Reset_contract(); test_wolfTPM2_thread_local_storage(); diff --git a/wolftpm/fwtpm/fwtpm.h b/wolftpm/fwtpm/fwtpm.h index ec06d8042..dba15498d 100644 --- a/wolftpm/fwtpm/fwtpm.h +++ b/wolftpm/fwtpm/fwtpm.h @@ -795,6 +795,11 @@ typedef struct FWTPM_CTX { TPMI_ALG_HASH pcrPolicyAlg[IMPLEMENTATION_PCR]; /* PCR bank allocation (bitmap: bit 0=SHA-256, bit 1=SHA-384) */ UINT8 pcrAllocatedBanks; /* default: 0x03 = both banks */ + /* TPM2_PCR_Allocate is recorded here and applied at the next + * Startup(CLEAR), per TPM 2.0 Part 3 22.5. Persisted so the change + * survives the power cycle that performs that reset. */ + UINT8 pcrAllocatedBanksPending; + UINT8 pcrAllocPending; /* 1 when a change is staged */ /* Transient object slots */ FWTPM_Object objects[FWTPM_MAX_OBJECTS]; diff --git a/wolftpm/tpm2.h b/wolftpm/tpm2.h index 77ae060ae..08d17278b 100644 --- a/wolftpm/tpm2.h +++ b/wolftpm/tpm2.h @@ -744,6 +744,10 @@ typedef enum { } TPM_PT_T; typedef UINT32 TPM_PT; +/* Smallest TPM_PT_INPUT_BUFFER a conformant TPM may report (TCG Part 2), so a + * parameter at or below it fits without reading the capability. */ +#define TPM_MIN_INPUT_BUFFER 1024 + /* PCR Property Tag */ typedef enum { TPM_PT_PCR_FIRST = 0x00000000, @@ -2201,21 +2205,35 @@ struct wolfTPM_winContext { #define TPM_E_COMMAND_BLOCKED (0x80280400) #endif -/* Mask off vendor/layer high bits so a vendor-decorated TPM_RC_COMMAND_CODE - * (e.g. NS350 returns 0x000b0143 for 0x143) still matches. Gate on >= 0 so a - * propagated negative wolfCrypt error (e.g. -189) is never misread as an - * unavailable command. TPM_E_COMMAND_BLOCKED is a Windows HRESULT (negative), - * matched exactly. */ -#define WOLFTPM_IS_COMMAND_UNAVAILABLE(code) \ - (((code) >= 0 && \ - (((UINT32)(code)) & 0xFFFFu) == (UINT32)TPM_RC_COMMAND_CODE) || \ - (code) == (int)TPM_E_COMMAND_BLOCKED) -#else -#define WOLFTPM_IS_COMMAND_UNAVAILABLE(code) \ - ((code) >= 0 && \ - (((UINT32)(code)) & 0xFFFFu) == (UINT32)TPM_RC_COMMAND_CODE) #endif /* WOLFTPM_WINAPI */ +/* Compare a return code against a TPM_RC, masking off vendor/layer high bits + * so a vendor-decorated code (NS350 returns 0x000b0143 for 0x143) still + * matches. Gate on >= 0 so a propagated negative wolfCrypt error (e.g. -189) + * is never misread as a TPM response code. */ +#define WOLFTPM_RC_IS(code, rc) \ + ((code) >= 0 && (((UINT32)(code)) & 0xFFFFu) == (UINT32)(rc)) + +/* The TPM does not implement this command. TPM_E_COMMAND_BLOCKED is a Windows + * HRESULT (negative), so it is matched exactly rather than masked. */ +#ifdef WOLFTPM_WINAPI + #define WOLFTPM_IS_COMMAND_UNAVAILABLE(code) \ + (WOLFTPM_RC_IS(code, TPM_RC_COMMAND_CODE) || \ + (code) == (int)TPM_E_COMMAND_BLOCKED) +#else + #define WOLFTPM_IS_COMMAND_UNAVAILABLE(code) \ + WOLFTPM_RC_IS(code, TPM_RC_COMMAND_CODE) +#endif + +/* Implemented but switched off, commonly TPM2_EncryptDecrypt for export + * controls; answers TPM_RC_DISABLED not TPM_RC_COMMAND_CODE. */ +#define WOLFTPM_IS_COMMAND_DISABLED(code) \ + WOLFTPM_RC_IS(code, TPM_RC_DISABLED) + +/* Either form of "the TPM will not run this command". */ +#define WOLFTPM_IS_COMMAND_UNAVAILABLE_OR_DISABLED(code) \ + (WOLFTPM_IS_COMMAND_UNAVAILABLE(code) || WOLFTPM_IS_COMMAND_DISABLED(code)) + /* make sure advanced IO is enabled for I2C */ #ifdef WOLFTPM_I2C #undef WOLFTPM_ADV_IO @@ -2993,6 +3011,31 @@ typedef struct { UINT32 sizeNeeded; UINT32 sizeAvailable; } PCR_Allocate_Out; +/*! + \ingroup TPM2_Proprietary + \brief Set which PCR banks the TPM allocates + \note The selection REPLACES the current allocation - banks not named in it + are deallocated. Algorithms the TPM does not implement are silently + ignored (TPM 2.0 Part 3 22.5), so a selection naming only unimplemented + algorithms can leave the TPM with no PCR banks at all. Prefer + wolfTPM2_AllocatePCRBanks, which pre-checks each algorithm and refuses + an empty result. + \note The change takes effect at the next Startup(CLEAR), not on return. + \note Requires an active session (TPM2_SetAuthPassword or + wolfTPM2_SetAuthSession on slot 0) and the platform hierarchy; without + one this returns BAD_FUNC_ARG rather than a TPM response code. + + \return TPM_RC_SUCCESS: the TPM processed the request - check + out->allocationSuccess, which is NO when the TPM lacks the space + \return TPM_RC_HIERARCHY: the platform hierarchy is disabled + \return BAD_FUNC_ARG: check the provided arguments, or no session is set + + \param in pointer to a PCR_Allocate_In struct + \param out pointer to a PCR_Allocate_Out struct + + \sa wolfTPM2_AllocatePCRBanks + \sa TPM2_IsPcrBankAllocated +*/ WOLFTPM_API TPM_RC TPM2_PCR_Allocate(PCR_Allocate_In* in, PCR_Allocate_Out* out); @@ -4135,6 +4178,46 @@ WOLFTPM_API void TPM2_SetupPCRSel(TPML_PCR_SELECTION* pcr, TPM_ALG_ID alg, WOLFTPM_API void TPM2_SetupPCRSelArray(TPML_PCR_SELECTION* pcr, TPM_ALG_ID alg, byte* pcrArray, word32 pcrArraySz); +/*! + \ingroup TPM2_Proprietary + \brief Report whether the TPM implements a given algorithm + + \note Queries TPM_CAP_ALGS. Fails closed: *isSupported is 0 on any error, + so a query failure cannot be mistaken for "supported". + + \return TPM_RC_SUCCESS: query completed; *isSupported is 1 or 0 + \return BAD_FUNC_ARG: isSupported is NULL + + \param alg the algorithm identifier to test (for example TPM_ALG_SHA512) + \param isSupported output, set to 1 if implemented by the TPM, else 0 + + \sa TPM2_IsPcrBankAllocated +*/ +WOLFTPM_API int TPM2_IsAlgSupported(TPM_ALG_ID alg, int* isSupported); + +/*! + \ingroup TPM2_Proprietary + \brief Report whether a PCR index is allocated in a bank of a given hash + + \note Queries TPM_CAP_PCRS. Implementing a hash and allocating a bank for + it are separate: a TPM may offer SHA-1 while allocating no SHA-1 + bank, and a selection naming an unallocated bank is rejected. Ask + before building a selection with TPM2_SetupPCRSel(). Fails closed: + *isAllocated is 0 on any error. + + \return TPM_RC_SUCCESS: query completed; *isAllocated is 1 or 0 + \return BAD_FUNC_ARG: isAllocated is NULL or pcrIndex is negative + + \param hashAlg the PCR bank hash algorithm (for example TPM_ALG_SHA256) + \param pcrIndex the PCR index to test + \param isAllocated output, set to 1 if allocated in that bank, else 0 + + \sa TPM2_SetupPCRSel + \sa TPM2_IsAlgSupported +*/ +WOLFTPM_API int TPM2_IsPcrBankAllocated(TPM_ALG_ID hashAlg, int pcrIndex, + int* isAllocated); + /*! \ingroup TPM2_Proprietary \brief Get a human readable string for any TPM 2.0 return code diff --git a/wolftpm/tpm2_asn.h b/wolftpm/tpm2_asn.h index 0ad05a3d6..1ff698702 100644 --- a/wolftpm/tpm2_asn.h +++ b/wolftpm/tpm2_asn.h @@ -35,11 +35,26 @@ #define MAX_CERT_SZ 2048 #endif -/* ASN Error Codes */ -#define TPM_RC_ASN_PARSE (-201) /* ASN parsing error */ -#define TPM_RC_INSUFFICIENT (-202) /* ASN insufficient data */ -#define TPM_RC_VALUE (-203) /* ASN value error (invalid tag) */ -#define TPM_RC_BUFFER (-204) /* ASN buffer error */ +/* ASN Error Codes. + * Spelled TPM_RC_ASN_* so they cannot shadow the TPM response codes of the + * same short name in tpm2.h: TPM_RC_VALUE there is an enum equal to 0x084 and + * TPM_RC_INSUFFICIENT is 0x09A. Because those are enum constants rather than + * macros, a #ifndef guard cannot see them, so any file including both headers + * silently got the ASN meaning. */ +#define TPM_RC_ASN_PARSE (-201) /* ASN parsing error */ +#define TPM_RC_ASN_INSUFFICIENT (-202) /* ASN insufficient data */ +#define TPM_RC_ASN_VALUE (-203) /* ASN value error (invalid tag) */ +#define TPM_RC_ASN_BUFFER (-204) /* ASN buffer error */ + +/* Deprecated short spellings, kept so existing callers still build. They + * shadow the tpm2.h response codes of the same name, so prefer the + * TPM_RC_ASN_* forms above. Define WOLFTPM_NO_DEPRECATED_ASN_RC to drop them + * and get the tpm2.h meanings instead. */ +#ifndef WOLFTPM_NO_DEPRECATED_ASN_RC + #define TPM_RC_INSUFFICIENT TPM_RC_ASN_INSUFFICIENT + #define TPM_RC_VALUE TPM_RC_ASN_VALUE + #define TPM_RC_BUFFER TPM_RC_ASN_BUFFER +#endif /* ASN.1 Constants */ enum { @@ -78,7 +93,7 @@ typedef struct DecodedX509 { \param inOutIdx Current position in buffer, updated to new position \param len Decoded length value \param maxIdx Maximum allowed index in buffer - \return Length on success, TPM_RC_INSUFFICIENT on buffer error + \return Length on success, TPM_RC_ASN_INSUFFICIENT on buffer error */ WOLFTPM_API int TPM2_ASN_GetLength(const uint8_t* input, word32* inOutIdx, int* len, word32 maxIdx); @@ -91,7 +106,7 @@ WOLFTPM_API int TPM2_ASN_GetLength(const uint8_t* input, word32* inOutIdx, \param len Decoded length value \param maxIdx Maximum allowed index in buffer \param check Flag to enable length validation - \return Length on success, TPM_RC_INSUFFICIENT on buffer error + \return Length on success, TPM_RC_ASN_INSUFFICIENT on buffer error */ WOLFTPM_API int TPM2_ASN_GetLength_ex(const uint8_t* input, word32* inOutIdx, int* len, word32 maxIdx, int check); @@ -104,7 +119,7 @@ WOLFTPM_API int TPM2_ASN_GetLength_ex(const uint8_t* input, word32* inOutIdx, \param inOutIdx Current position in buffer, updated to new position \param tag_len Decoded length value \param tag Expected ASN.1 tag value - \return 0 on success, TPM_RC_INSUFFICIENT on buffer error, TPM_RC_VALUE on tag mismatch + \return 0 on success, TPM_RC_ASN_INSUFFICIENT on buffer error, TPM_RC_ASN_VALUE on tag mismatch */ WOLFTPM_API int TPM2_ASN_DecodeTag(const uint8_t* input, int inputSz, int* inOutIdx, int* tag_len, uint8_t tag); @@ -114,8 +129,8 @@ WOLFTPM_API int TPM2_ASN_DecodeTag(const uint8_t* input, int inputSz, \brief Decodes RSA signature from ASN.1 format \param pInput Pointer to buffer containing ASN.1 encoded RSA signature \param inputSz Size of input buffer - \return Size of decoded signature on success, TPM_RC_VALUE on invalid input, - TPM_RC_INSUFFICIENT on buffer error + \return Size of decoded signature on success, TPM_RC_ASN_VALUE on invalid input, + TPM_RC_ASN_INSUFFICIENT on buffer error */ WOLFTPM_API int TPM2_ASN_RsaDecodeSignature(uint8_t** pInput, int inputSz); @@ -124,7 +139,7 @@ WOLFTPM_API int TPM2_ASN_RsaDecodeSignature(uint8_t** pInput, int inputSz); \param input Buffer containing ASN.1 encoded X.509 certificate \param inputSz Size of input buffer \param x509 Structure to store decoded certificate data - \return 0 on success, TPM_RC_VALUE on invalid input, TPM_RC_INSUFFICIENT on buffer error + \return 0 on success, TPM_RC_ASN_VALUE on invalid input, TPM_RC_ASN_INSUFFICIENT on buffer error */ WOLFTPM_API int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, DecodedX509* x509); @@ -135,8 +150,8 @@ WOLFTPM_API int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, \param input Buffer containing ASN.1 encoded RSA public key \param inputSz Size of input buffer \param pub TPM2B_PUBLIC structure to store decoded key - \return 0 on success, TPM_RC_VALUE on invalid input, - TPM_RC_INSUFFICIENT on buffer error + \return 0 on success, TPM_RC_ASN_VALUE on invalid input, + TPM_RC_ASN_INSUFFICIENT on buffer error */ WOLFTPM_API int TPM2_ASN_DecodeRsaPubKey(uint8_t* input, int inputSz, TPM2B_PUBLIC* pub); @@ -150,7 +165,7 @@ WOLFTPM_API int TPM2_ASN_DecodeRsaPubKey(uint8_t* input, int inputSz, \param pSig Pointer to buffer containing padded signature, updated to point to unpadded data \param sigSz Size of signature buffer, updated with unpadded size - \return 0 on success, TPM_RC_VALUE on invalid padding + \return 0 on success, TPM_RC_ASN_VALUE on invalid padding */ WOLFTPM_API int TPM2_ASN_RsaUnpadPkcsv15(uint8_t** pSig, int* sigSz); diff --git a/wolftpm/tpm2_types.h b/wolftpm/tpm2_types.h index 68463eb1b..a7f139f82 100644 --- a/wolftpm/tpm2_types.h +++ b/wolftpm/tpm2_types.h @@ -716,6 +716,63 @@ typedef int64_t INT64; #endif #endif +/* Monotonic ms tick for the TIS wait loops. Wraps ~49 days, so compare with + * unsigned subtraction. WOLFTPM_HAVE_MONOTONIC_MS is set only where a clock + * exists, port-supplied included; otherwise the iteration counter is kept. + * WOLFTPM_NO_MONOTONIC_MS forces the counter. */ +#ifndef WOLFTPM_NO_MONOTONIC_MS + +/* Without this a port-supplied hook would be silently ignored. */ +#ifdef XTPM_GET_TIMEMS + #define WOLFTPM_HAVE_MONOTONIC_MS +#elif !defined(WOLFTPM_NO_STD_HEADERS) + #if defined(WOLFTPM_ZEPHYR) + #include + #define XTPM_GET_TIMEMS() ((word32)k_uptime_get()) + #define WOLFTPM_HAVE_MONOTONIC_MS + #elif defined(WOLFSSL_ESPIDF) || defined(FREERTOS) + #define XTPM_GET_TIMEMS() \ + ((word32)xTaskGetTickCount() * (word32)portTICK_PERIOD_MS) + #define WOLFTPM_HAVE_MONOTONIC_MS + #elif defined(_WIN32) + #include + #define XTPM_GET_TIMEMS() ((word32)GetTickCount64()) + #define WOLFTPM_HAVE_MONOTONIC_MS + #else + /* Include first, then feature-test: strict C99 may not expose + * CLOCK_MONOTONIC, and __linux__ alone would fail to compile. */ + #include + #if defined(CLOCK_MONOTONIC) && \ + (!defined(_POSIX_TIMERS) || _POSIX_TIMERS > 0) + static inline word32 XTPM_GET_TIMEMS(void) + { + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) { + return 0; + } + return (word32)((word32)ts.tv_sec * 1000u + + (word32)(ts.tv_nsec / 1000000L)); + } + #define WOLFTPM_HAVE_MONOTONIC_MS + #endif + #endif +#endif /* XTPM_GET_TIMEMS */ + +#endif /* !WOLFTPM_NO_MONOTONIC_MS */ + +/* Must cover the slowest single command. RSA-2048 key generation is an + * unbounded prime search: repeated runs on a current part ranged from 26 s to + * over 180 s for the same command, so this is a tail, not an average. */ +#ifndef TPM_TIMEOUT_MS +#define TPM_TIMEOUT_MS 300000 +#endif + +/* Bound for a tick source that stops advancing part way through a wait. Only a + * stopped or broken clock reaches this: a working one advances long before. */ +#ifndef TPM_TIMEOUT_STAGNANT_POLLS +#define TPM_TIMEOUT_STAGNANT_POLLS 1000000 +#endif + #ifndef BUFFER_ALIGNMENT #define BUFFER_ALIGNMENT 4 #endif diff --git a/wolftpm/tpm2_wrap.h b/wolftpm/tpm2_wrap.h index a3166cf3c..ca55514bd 100644 --- a/wolftpm/tpm2_wrap.h +++ b/wolftpm/tpm2_wrap.h @@ -3021,6 +3021,82 @@ WOLFTPM_API int wolfTPM2_SetLocality(WOLFTPM2_DEV* dev, int locality); WOLFTPM_API int wolfTPM2_ExtendPCR(WOLFTPM2_DEV* dev, int pcrIndex, int hashAlg, const byte* digest, int digestLen); +/*! + \ingroup wolfTPM2_Wrappers + \brief Re-provision which PCR banks the TPM has allocated + \note This REPLACES the allocation: hashAlgs is the complete new set and + every bank not named in it is deallocated. Allocating SHA-384 alone on a + SHA-256 TPM removes the SHA-256 bank. Many parts (Infineon SLB9672 and + later) support only one active bank at a time and reject a multi-bank + request outright with TPM_RC_PCR. A TPM that answers but lacks the + space reports it in allocOut instead, which maps to BUFFER_E. + \note Changing banks invalidates every PolicyPCR digest and makes every blob + sealed to PCR values unsealable. The PCR contents are zeroed at the + Startup(CLEAR) and re-allocating the old bank does not bring them back. + \note Success means the change is STAGED. The TPM applies it at the next + Startup(CLEAR) following a _TPM_Init, so the caller must power cycle + the TPM (or restart the simulator process) and re-read the banks with + TPM2_IsPcrBankAllocated to confirm. No TPM command performs that reset. + \note Requires the platform hierarchy. Under an OS the platform firmware has + usually cleared phEnable, in which case the TPM answers TPM_RC_HIERARCHY + no matter what auth is supplied. + + \return TPM_RC_SUCCESS: the new allocation is staged for the next reset + \return BUFFER_E: the TPM has no room for the requested bank set; see + allocOut->sizeNeeded and allocOut->sizeAvailable + \return TPM_RC_HASH: the TPM does not implement one of the requested algs; + nothing was sent and the allocation is unchanged + \return TPM_RC_PCR: the TPM refused the selection, typically because it + keeps a single bank active and more than one was requested + \return TPM_RC_HIERARCHY: the platform hierarchy is disabled + \return TPM_RC_BAD_AUTH: platform auth is not the empty password; use + wolfTPM2_AllocatePCRBanks_ex with a session + \return BAD_FUNC_ARG: check the provided arguments + + \param dev pointer to a TPM2_DEV struct + \param hashAlgs array of TPM_ALG_ID hash algorithms naming the complete new + bank set + \param hashAlgCount number of entries in hashAlgs, 1 to HASH_COUNT; zero is + rejected because it would leave the TPM with no PCR banks + \param allocOut optional, receives the TPM's allocationSuccess, maxPCR, + sizeNeeded and sizeAvailable; zeroed on entry + + \sa wolfTPM2_AllocatePCRBanks_ex + \sa TPM2_IsPcrBankAllocated + \sa wolfTPM2_ExtendPCR +*/ +WOLFTPM_API int wolfTPM2_AllocatePCRBanks(WOLFTPM2_DEV* dev, + const TPM_ALG_ID* hashAlgs, int hashAlgCount, PCR_Allocate_Out* allocOut); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Re-provision the TPM's PCR banks using a caller supplied session + \note Same as wolfTPM2_AllocatePCRBanks, but takes a session for a platform + hierarchy that carries an authPolicy. This must be a POLICY session: + slot 0 authorization is a password or a policy session, so an HMAC + session there is not an authorization session and the TPM answers + TPM_RC_AUTH_MISSING. Passing NULL is identical to + wolfTPM2_AllocatePCRBanks. + \note Session slot 0 is saved and restored around the command, and the + response nonce is copied back into the caller's session so it stays + usable for a following command. + + \return TPM_RC_SUCCESS: the new allocation is staged for the next reset + \return BAD_FUNC_ARG: check the provided arguments + + \param dev pointer to a TPM2_DEV struct + \param session pointer to a WOLFTPM2_SESSION for platform auth, or NULL + \param hashAlgs array of TPM_ALG_ID hash algorithms naming the complete new + bank set + \param hashAlgCount number of entries in hashAlgs, 1 to HASH_COUNT + \param allocOut optional, receives the TPM's allocation result + + \sa wolfTPM2_AllocatePCRBanks +*/ +WOLFTPM_API int wolfTPM2_AllocatePCRBanks_ex(WOLFTPM2_DEV* dev, + WOLFTPM2_SESSION* session, const TPM_ALG_ID* hashAlgs, int hashAlgCount, + PCR_Allocate_Out* allocOut); + /* Newer API's that use WOLFTPM2_NV context and support auth */ /*!