diff --git a/README.md b/README.md index d50a4071..7e57174e 100644 --- a/README.md +++ b/README.md @@ -267,6 +267,10 @@ STMicro ST33TPHF2XSPI TPM2: Caps 0x1a7e2882, Did 0x0000, Vid 0x104a, Rid 0x4e Mfg STM (2), Vendor , Fw 74.8 (1151341959), FIPS 140-2 1, CC-EAL4 0 +STMicro ST33TPHF2XSPI (newer firmware line) +TPM2: Caps 0x30000415, Did 0x0000, Vid 0x104a, Rid 0x4e +Mfg STM (2), Vendor , Fw 1.258 (0x0), FIPS 140-2 1, CC-EAL4 0 + STMicro ST33TPHF2XI2C TPM2: Caps 0x1a7e2882, Did 0x0000, Vid 0x104a, Rid 0x4e Mfg STM (2), Vendor , Fw 74.9 (1151341959), FIPS 140-2 1, CC-EAL4 0 @@ -275,6 +279,13 @@ Microchip ATTPM20 TPM2: Caps 0x30000695, Did 0x3205, Vid 0x1114, Rid 0x 1 Mfg MCHP (3), Vendor , Fw 512.20481 (0), FIPS 140-2 0, CC-EAL4 0 +Note: ST33TPHF2X parts report `TPM_PT_VENDOR_STRING_1..4` as zero or as +non-printable bytes, so the `Vendor` field prints empty. The firmware major version identifies the line +instead: 1.x and 2.x are ST33TPHF2X (SPI and I2C firmware respectively), 9.x is +ST33KTPM2X and 10.x is ST33KTPM2A. See +[examples/firmware/README.md](examples/firmware/README.md) for how this selects +the firmware update format and command codes. + Nations Technologies Inc. Z32H330 TPM 2.0 module Mfg NTZ (0), Vendor Z32H330, Fw 7.51 (419631892), FIPS 140-2 0, CC-EAL4 0 diff --git a/examples/firmware/README.md b/examples/firmware/README.md index f56852fa..73766fd3 100644 --- a/examples/firmware/README.md +++ b/examples/firmware/README.md @@ -131,6 +131,49 @@ The manifest (blob0) is a 33 byte fixed header followed by the firmware digest a The LMS requirement is a generation 9 rule, so both `fwVerMajor` and `fwVerMinor` from TPM capabilities are consulted. The example confirms its choice against the file itself: everything after blob0 is a chain of `[type][length]` records that ends exactly at end of file, and only the correct manifest size lands on the final byte. No manual format selection is needed. +### Identifying the part + +ST33TPHF2X parts report `TPM_PT_VENDOR_STRING_1..4` as zero or as non-printable bytes, so unlike an ST33KTPM they print an empty `Vendor` field. `st33_fw_update` dumps the raw bytes so they can still be compared. What identifies them is the firmware major version, which tracks the part and interface line: + +| `fwVerMajor` | Part line | Example firmware image | +| --- | --- | --- | +| 1 | ST33TPHF2X, SPI firmware line | `TPM_ST33TPHF2XSPI_00010301.fi` | +| 2 | ST33TPHF2X, I2C firmware line | `TPM_ST33TPHF2XI2C_00020200.fi` | +| 9 | ST33KTPM2X | `TPM_ST33KTPM2X_00090200_V1.fi` | +| 10 (`0x000a`) | ST33KTPM2A | `TPM_ST33KTPM2A_000a0200.fi` | +| 11 (`0x000b`) | ST33KTPMQ | (none on hand) | + +The manifest header carries the same version: a zero byte followed by the firmware version the image upgrades to, in the `TPM_PT_FIRMWARE_VERSION_1` layout (`00 | 00 02 02 00` is 2.512). `st33_fw_update` prints both and refuses an image whose major version does not match the running part. + +### Field upgrade command codes + +ST33 implements the field upgrade with one of two command code pairs, and the wrong one is answered with `TPM_RC_COMMAND_CODE` (`0x143`) before the manifest is even parsed: + +| Pair | Start | Data | +| --- | --- | --- | +| Standard TCG | `TPM_CC_FieldUpgradeStart` (`0x0000012F`) | `TPM_CC_FieldUpgradeData` (`0x00000141`) | +| ST33KTPM vendor | `0x2000030C` | `0x2000030D` | + +wolfTPM asks the TPM which pair it implements, by querying `TPM_CAP_COMMANDS` for the two start codes. That is authoritative and needs no table of parts. ST's own reference tool instead infers the pair from version numbers (standard codes when the running firmware minor version is below 256, or when the image targets firmware generation 2), and wolfTPM falls back to that same rule -- `wolfTPM2_ST33_FwUpgradeCommands()` -- only when the TPM will not answer, which is the case once it has entered firmware upgrade mode, or when it lists both pairs. + +The probe matters: an ST33KTPMQ at firmware 11.1 implements **only** the vendor pair even though its minor version is below 256, so the version rule alone would pick the wrong codes for it. + +Nothing needs to be selected by hand. When a caller-supplied policy is used, the `PolicyCommandCode` is bound to whichever start code will actually be sent, and `st33_fw_update` reports whether the codes came from the TPM or were inferred. + +Running `st33_fw_update` with no firmware file also reports which of the four codes the attached part implements, read from `TPM_CAP_COMMANDS`. This is read-only and is the quickest way to diagnose a `TPM_RC_COMMAND_CODE` on a new part: + +```sh +./st33_fw_update +... +Field upgrade command set: + 0x0000012f FieldUpgradeStart (standard): implemented + 0x00000141 FieldUpgradeData (standard): implemented + 0x2000030c FieldUpgradeStartVendor (ST33KTPM): not implemented + 0x2000030d FieldUpgradeDataVendor (ST33KTPM): not implemented +``` + +A firmware major version outside the known families is treated as unknown rather than guessed at: no manifest size is asserted for it, the tool reports that the size is taken from the image, and the block-chain check in `st33_detect_blob0` establishes the real size from the file itself. That way a part from a newer line, such as an ST33KTPMQ, is not refused an image on the strength of a rule that does not apply to it. + ### Updating the firmware The `st33_fw_update` tool automatically detects the firmware format. @@ -154,15 +197,23 @@ Firmware format is auto-detected from TPM firmware version and the file: - Generation 9 below 512: Non-LMS format (177 byte manifest) - Generation 9 at 512 and above: LMS format (2697 byte manifest) -# Run without arguments to display the current firmware information +# Run without arguments to display the current firmware information. +# This capture is an ST33TPHF2XSPI, which implements only the vendor codes. ./st33_fw_update ST33 Firmware Update Tool -TPM2: Caps 0x30000415, Did 0x0003, Vid 0x104a, Rid 0x 1 +TPM2: Caps 0x30000415, Did 0x0000, Vid 0x104a, Rid 0x4e TPM2_Startup pass -Mfg STM (2), Vendor ST33KTPM2X, Fw 9.257 (0x0) -Firmware version details: Major=9, Minor=257, Vendor=0x0 -Hardware: ST33K (generation 9 firmware below 512) -Firmware update: Non-LMS format required (177 byte manifest) +Mfg STM (2), Vendor , Fw 1.258 (0x0) +Vendor string bytes: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +Firmware version details: Major=1, Minor=258, Vendor=0x0 +Part line: ST33TPHF2X (SPI firmware line) +Firmware generation: 1 +Firmware update: Non-LMS format required (321 byte manifest) +Field upgrade command set: + 0x0000012f FieldUpgradeStart (standard): not implemented + 0x00000141 FieldUpgradeData (standard): not implemented + 0x2000030c FieldUpgradeStartVendor (ST33KTPM): implemented + 0x2000030d FieldUpgradeDataVendor (ST33KTPM): implemented # Run with firmware file (format auto-detected from TPM version) ./st33_fw_update TPM_ST33KTPM2X_00090200_V1.fi @@ -172,13 +223,16 @@ TPM2: Caps 0x30000415, Did 0x0003, Vid 0x104a, Rid 0x 1 TPM2_Startup pass Mfg STM (2), Vendor ST33KTPM2X, Fw 9.257 (0x0) Firmware version details: Major=9, Minor=257, Vendor=0x0 -Hardware: ST33K (generation 9 firmware below 512) +Part line: ST33KTPM2X +Firmware generation: 9 below 512 Firmware update: Non-LMS format required (177 byte manifest) Format: Non-LMS (blob0 177 bytes, verified against the block chain) Firmware Update: Total file size: 364290 bytes Manifest (blob0): 177 bytes Firmware data: 364113 bytes + Image targets firmware: 9.512 (ST33KTPM2X) + Command codes: start 0x2000030c, data 0x2000030d ... Firmware update completed successfully. Please reset or power cycle the TPM. @@ -191,7 +245,7 @@ TPM2: Caps 0x30000415, Did 0x0003, Vid 0x104a, Rid 0x 3 TPM2_Startup pass Mfg STM (2), Vendor ST33KTPM2X, Fw 9.512 (0x0) Firmware version details: Major=9, Minor=512, Vendor=0x0 -Hardware: ST33K (generation 9 firmware at 512 and above) +Firmware generation: 9 at 512 and above Firmware update: LMS format required (2697 byte manifest) Format: LMS (blob0 2697 bytes, verified against the block chain) Firmware Update: @@ -209,7 +263,7 @@ TPM2: Caps 0x30000415, Did 0x0003, Vid 0x104a, Rid 0x 1 TPM2_Startup pass Mfg STM (2), Vendor ST33KTPM2X, Fw 9.257 (0x0) Firmware version details: Major=9, Minor=257, Vendor=0x0 -Hardware: ST33K (generation 9 firmware below 512) +Firmware generation: 9 below 512 Firmware update: Non-LMS format required (177 byte manifest) Firmware Update Abandon: Success: Please reset or power cycle TPM diff --git a/examples/firmware/st33_blob0.c b/examples/firmware/st33_blob0.c index 0f59c739..25f07734 100644 --- a/examples/firmware/st33_blob0.c +++ b/examples/firmware/st33_blob0.c @@ -31,15 +31,34 @@ const size_t st33_blob0_sizes[ST33_BLOB0_SIZE_CNT] = { ST33_BLOB0_SIZE_LMS }; -size_t st33_expected_blob0(word32 fwVerMajor, word32 fwVerMinor) +int st33_blob0_family(word32 fwVerMajor) { - if (fwVerMajor < ST33_BLOB0_GENERATION_LMS_CAPABLE) { - return ST33_BLOB0_SIZE_NON_LMS_RSA; + switch (fwVerMajor) { + case 1: /* ST33TPHF2X, SPI firmware line */ + case 2: /* ST33TPHF2X, I2C firmware line */ + case 74: /* ST33TPHF2X, older line both buses (74.8 SPI, 74.9 I2C) */ + return ST33_BLOB0_FAMILY_TPHF2X; + case 9: /* ST33KTPM2X */ + case 10: /* ST33KTPM2A */ + return ST33_BLOB0_FAMILY_KTPM; + default: + return ST33_BLOB0_FAMILY_UNKNOWN; } - if (fwVerMinor < ST33_BLOB0_VERSION_LMS_REQUIRED) { - return ST33_BLOB0_SIZE_NON_LMS; +} + +size_t st33_expected_blob0(word32 fwVerMajor, word32 fwVerMinor) +{ + switch (st33_blob0_family(fwVerMajor)) { + case ST33_BLOB0_FAMILY_TPHF2X: + return ST33_BLOB0_SIZE_NON_LMS_RSA; + case ST33_BLOB0_FAMILY_KTPM: + if (fwVerMinor < ST33_BLOB0_VERSION_LMS_REQUIRED) { + return ST33_BLOB0_SIZE_NON_LMS; + } + return ST33_BLOB0_SIZE_LMS; + default: + return 0; /* unknown family, assert nothing */ } - return ST33_BLOB0_SIZE_LMS; } size_t st33_blob0_candidates(word32 fwVerMajor, word32 fwVerMinor, @@ -47,12 +66,16 @@ size_t st33_blob0_candidates(word32 fwVerMajor, word32 fwVerMinor, { size_t idx; size_t candCnt = 0; + size_t expected; if (cand == NULL) { return 0; } if (haveCaps) { - cand[candCnt++] = st33_expected_blob0(fwVerMajor, fwVerMinor); + expected = st33_expected_blob0(fwVerMajor, fwVerMinor); + if (expected != 0) { + cand[candCnt++] = expected; + } } for (idx = 0; idx < ST33_BLOB0_SIZE_CNT; idx++) { if (candCnt == 0 || cand[0] != st33_blob0_sizes[idx]) { diff --git a/examples/firmware/st33_blob0.h b/examples/firmware/st33_blob0.h index efb33cbe..60dbfcb8 100644 --- a/examples/firmware/st33_blob0.h +++ b/examples/firmware/st33_blob0.h @@ -43,18 +43,27 @@ extern "C" { /* gen 9 at 512 and above: embedded LMS signature */ #define ST33_BLOB0_SIZE_LMS 2697 -/* LMS is a generation 9 rule. Generation 1 parts are always non-LMS no matter - * how high the minor version goes (e.g. 1.771). Mirrors - * ST33_FW_GENERATION_LMS_CAPABLE / ST33_FW_VERSION_LMS_REQUIRED in - * src/tpm2_wrap.c, which is the authority the library validates against. */ -#define ST33_BLOB0_GENERATION_LMS_CAPABLE 9 -#define ST33_BLOB0_VERSION_LMS_REQUIRED 512 +/* The manifest format follows the silicon family, which the firmware major + * version identifies. ST33TPHF2X (majors 1, 2 and the older 74 line) signs + * with SHA-256 + RSA-PSS; ST33KTPM (majors 9 and 10) signs with ECDSA P-384, + * and from minor 512 with LMS. A major outside both lists is unknown, and no + * size is asserted for it. Mirrors src/tpm2_wrap.c, which is the authority + * the library validates against. */ +#define ST33_BLOB0_FAMILY_UNKNOWN 0 +#define ST33_BLOB0_FAMILY_TPHF2X 1 +#define ST33_BLOB0_FAMILY_KTPM 2 +#define ST33_BLOB0_VERSION_LMS_REQUIRED 512 + +/* Silicon family for a firmware major version, ST33_BLOB0_FAMILY_UNKNOWN when + * the major is not one this release knows about. */ +int st33_blob0_family(word32 fwVerMajor); #define ST33_BLOB0_SIZE_CNT 3 extern const size_t st33_blob0_sizes[ST33_BLOB0_SIZE_CNT]; /* Manifest size the running firmware expects for its next update. Takes the - * version fields rather than WOLFTPM2_CAPS so it stays free of the wrapper. */ + * version fields rather than WOLFTPM2_CAPS so it stays free of the wrapper. + * Returns 0 when the family is unknown, meaning no size can be asserted. */ size_t st33_expected_blob0(word32 fwVerMajor, word32 fwVerMinor); /* Fill cand (at least ST33_BLOB0_SIZE_CNT entries) with every known manifest diff --git a/examples/firmware/st33_fw_update.c b/examples/firmware/st33_fw_update.c index 181ab4aa..e8c44374 100644 --- a/examples/firmware/st33_fw_update.c +++ b/examples/firmware/st33_fw_update.c @@ -71,6 +71,88 @@ static void usage(void) "(177 byte manifest)\n"); printf(" - Generation 9 at 512 and above: LMS format " "(2697 byte manifest)\n"); + printf("\nThe field upgrade command codes come from the TPM: its " + "TPM_CAP_COMMANDS list\nsays whether it implements the standard TPM " + "codes or the ST33KTPM vendor\ncodes. Only when that does not settle " + "it, as in firmware upgrade mode, is\nST's version rule used - the " + "standard codes when the running firmware minor\nversion is below 256 " + "or the image targets generation 2.\n"); +} + +/* Field upgrade command codes come from the library, so the codes this tool + * reports, and binds a PolicyCommandCode to, are the ones that will be sent. + * In firmware upgrade mode the capabilities cannot be read, so caps is passed + * as NULL and the image's own target version decides. Returns 1 when the TPM + * decided, 0 when the codes came from the firmware version rule. */ +static int TPM2_ST33_PickOrdinals(const WOLFTPM2_CAPS* caps, int haveCaps, + word16 manifestMajor, TPM_CC* ccStart, TPM_CC* ccData) +{ + int fromTpm = 0; + + (void)wolfTPM2_ST33_GetFwUpgradeCommands(haveCaps ? caps : NULL, + manifestMajor, ccStart, ccData, &fromTpm); + return fromTpm; +} + +/* ST33 vendor product information. Two parts of the same model and firmware + * revision are otherwise indistinguishable over the standard capabilities, so + * this is what tells one physical unit from another. Read-only. + * Layout per TPM2_GetProductInfo: serial 7B, pad 1B, Product ID (PIN) 2B, + * Master Product ID (MPIN) 2B, internal revision 1B, pad 3B, kernel ver 4B. */ +static void TPM2_ST33_PrintProductInfo(void) +{ + uint8_t info[20]; + int rc, i; + + XMEMSET(info, 0, sizeof(info)); + rc = TPM2_GetProductInfo(info, (uint16_t)sizeof(info)); + if (rc != TPM_RC_SUCCESS) { + printf("Product info: unavailable (0x%x: %s)\n", rc, + TPM2_GetRCString(rc)); + return; + } + printf("Serial:"); + for (i = 0; i < 7; i++) { + printf(" %02x", info[i]); + } + printf("\n"); + printf("Product ID (PIN) 0x%02x%02x, Master (MPIN) 0x%02x%02x, " + "internal rev 0x%02x\n", info[8], info[9], info[10], info[11], + info[12]); + printf("Firmware kernel version: %02x %02x %02x %02x\n", + info[16], info[17], info[18], info[19]); +} + +/* Print which of the four field upgrade command codes this part implements. + * Purely diagnostic - it changes no TPM state and drives no upgrade. */ +static void TPM2_ST33_PrintCommandSet(void) +{ + static const TPM_CC fuCmds[4] = { + TPM_CC_FieldUpgradeStart, + TPM_CC_FieldUpgradeData, + TPM_CC_FieldUpgradeStartVendor_ST33, + TPM_CC_FieldUpgradeDataVendor_ST33 + }; + static const char* fuNames[4] = { + "FieldUpgradeStart (standard)", + "FieldUpgradeData (standard)", + "FieldUpgradeStartVendor (ST33KTPM)", + "FieldUpgradeDataVendor (ST33KTPM)" + }; + int i, isImpl, rc; + + printf("Field upgrade command set:\n"); + for (i = 0; i < 4; i++) { + rc = wolfTPM2_ST33_CmdImplemented(fuCmds[i], &isImpl); + if (rc != TPM_RC_SUCCESS) { + printf("\t0x%08x %s: query failed 0x%x: %s\n", + (unsigned int)fuCmds[i], fuNames[i], rc, TPM2_GetRCString(rc)); + } + else { + printf("\t0x%08x %s: %s\n", (unsigned int)fuCmds[i], fuNames[i], + isImpl ? "implemented" : "not implemented"); + } + } } typedef struct { @@ -84,7 +166,16 @@ typedef struct { } fw_info_t; /* Send firmware data blobs directly - used when continuing from upgrade mode */ -static int TPM2_ST33_SendFirmwareData(fw_info_t* fwinfo) +/* The other half of the pair, for the resume-path retry below */ +static TPM_CC TPM2_ST33_AltDataCmd(TPM_CC ccData) +{ + return (ccData == TPM_CC_FieldUpgradeDataVendor_ST33) ? + (TPM_CC)TPM_CC_FieldUpgradeData : + (TPM_CC)TPM_CC_FieldUpgradeDataVendor_ST33; +} + +static int TPM2_ST33_SendFirmwareData(fw_info_t* fwinfo, TPM_CC ccData, + int ccFromTpm) { int rc; uint32_t offset = 0; @@ -135,8 +226,18 @@ static int TPM2_ST33_SendFirmwareData(fw_info_t* fwinfo) XMEMCPY(blob_buf, &fwinfo->firmware_buf[offset], blob_total); /* Send blob to TPM */ - rc = TPM2_ST33_FieldUpgradeCommand(TPM_CC_FieldUpgradeDataVendor_ST33, - blob_buf, blob_total); + rc = TPM2_ST33_FieldUpgradeCommand(ccData, blob_buf, blob_total); + /* Resuming into a TPM already in upgrade mode, the running firmware + * version cannot be read, so the code may have been inferred from the + * image alone. The TPM ignores a command code it does not implement, + * so the first blob can safely be re-sent with the other pair rather + * than forcing an --abandon and a full restart. */ + if (rc == TPM_RC_COMMAND_CODE && blob_count == 0 && !ccFromTpm) { + ccData = TPM2_ST33_AltDataCmd(ccData); + printf("FieldUpgradeData rejected, retrying with 0x%08x\n", + (unsigned int)ccData); + rc = TPM2_ST33_FieldUpgradeCommand(ccData, blob_buf, blob_total); + } if (rc != TPM_RC_SUCCESS) { printf("FieldUpgradeData failed at blob %d, offset %u: 0x%x\n", blob_count, offset, rc); @@ -177,27 +278,48 @@ static int TPM2_ST33_FwData_Cb(uint8_t* data, uint32_t data_req_sz, return data_req_sz; } +/* The firmware major version tracks the part and interface line. ST33TPHF2X + * parts report a binary vendor string, so the name never comes from + * TPM_PT_VENDOR_STRING_1..4 the way it does on an ST33KTPM. */ +static const char* TPM2_ST33_PartLine(word16 fwVerMajor) +{ + switch (fwVerMajor) { + case 1: return "ST33TPHF2X (SPI firmware line)"; + case 2: return "ST33TPHF2X (I2C firmware line)"; + case 74: return "ST33TPHF2X (older firmware line)"; + case 9: return "ST33KTPM2X"; + case 10: return "ST33KTPM2A"; + case 11: return "ST33KTPMQ"; + default: return "unrecognized ST33 firmware line"; + } +} + static void TPM2_ST33_PrintInfo(const WOLFTPM2_CAPS* caps) { + size_t i; + size_t expected; + printf("Mfg %s (%d), Vendor %s, Fw %u.%u (0x%x)\n", caps->mfgStr, caps->mfg, caps->vendorStr, caps->fwVerMajor, caps->fwVerMinor, caps->fwVerVendor); + /* The vendor string is binary on ST33TPHF2X, so %s above shows nothing. + * Print the raw bytes too, they are part of the device identity. */ + printf("Vendor string bytes:"); + for (i = 0; i < sizeof(caps->vendorStr) - 1; i++) { + printf(" %02x", (unsigned char)caps->vendorStr[i]); + } + printf("\n"); printf("Firmware version details: Major=%u, Minor=%u, Vendor=0x%x\n", caps->fwVerMajor, caps->fwVerMinor, caps->fwVerVendor); - if (caps->fwVerMajor < ST33_BLOB0_GENERATION_LMS_CAPABLE) { - printf("Hardware: ST33K (generation 1 firmware)\n"); - printf("Firmware update: Non-LMS format required " - "(%d byte manifest)\n", ST33_BLOB0_SIZE_NON_LMS_RSA); - } - else if (caps->fwVerMinor < ST33_BLOB0_VERSION_LMS_REQUIRED) { - printf("Hardware: ST33K (generation 9 firmware below 512)\n"); - printf("Firmware update: Non-LMS format required " - "(%d byte manifest)\n", ST33_BLOB0_SIZE_NON_LMS); + printf("Part line: %s\n", TPM2_ST33_PartLine(caps->fwVerMajor)); + expected = st33_expected_blob0(caps->fwVerMajor, caps->fwVerMinor); + if (expected == 0) { + printf("Firmware update: manifest size unknown for this firmware " + "line, taken from the image\n"); } else { - printf("Hardware: ST33K (generation 9 firmware at 512 and above)\n"); - printf("Firmware update: LMS format required " - "(%d byte manifest)\n", ST33_BLOB0_SIZE_LMS); + printf("Firmware update: %s format required (%zu byte manifest)\n", + (expected == ST33_BLOB0_SIZE_LMS) ? "LMS" : "Non-LMS", expected); } } @@ -215,6 +337,10 @@ int TPM2_ST33_Firmware_Update(void* userCtx, int argc, char *argv[]) size_t blob0_size; size_t cand[ST33_BLOB0_SIZE_CNT]; size_t candCnt; + word16 manifestMajor = 0, manifestMinor = 0; + TPM_CC ccStart = TPM_CC_FieldUpgradeStartVendor_ST33; + TPM_CC ccData = TPM_CC_FieldUpgradeDataVendor_ST33; + int ccFromTpm = 0; int i; #ifdef WOLFTPM_HAVE_FW_POLICY int policytest = 0; @@ -364,6 +490,10 @@ int TPM2_ST33_Firmware_Update(void* userCtx, int argc, char *argv[]) goto exit; } + /* ST33 specific, so only meaningful once the manufacturer is confirmed */ + TPM2_ST33_PrintProductInfo(); + TPM2_ST33_PrintCommandSet(); + if (abandon) { printf("Firmware Update Abandon:\n"); rc = wolfTPM2_FirmwareUpgradeCancel(&dev); @@ -415,7 +545,9 @@ int TPM2_ST33_Firmware_Update(void* userCtx, int argc, char *argv[]) /* The file parsed, but at a size this TPM will not accept. Say so here: * the library rejects it too, but only with a DEBUG_WOLFTPM diagnostic, * so on a release build the operator would see a bare error code. */ - if (!fwinfo.in_upgrade_mode && blob0_size != cand[0]) { + if (!fwinfo.in_upgrade_mode && + st33_expected_blob0(caps.fwVerMajor, caps.fwVerMinor) != 0 && + blob0_size != cand[0]) { printf("Error: %s is for a different ST33 generation.\n", fi_file); printf(" Its manifest is %zu bytes, but firmware %u.%u running on " "this TPM\n expects %zu bytes. Use the .fi file for this part.\n", @@ -438,15 +570,33 @@ int TPM2_ST33_Firmware_Update(void* userCtx, int argc, char *argv[]) fwinfo.firmware_buf = fwinfo.fi_buf + blob0_size; fwinfo.firmware_bufSz = fwinfo.fi_bufSz - blob0_size; + /* The manifest carries the firmware line it upgrades, which together with + * the running version selects the field upgrade command codes. */ + if (wolfTPM2_ST33_ManifestVersion(fwinfo.manifest_buf, + (uint32_t)fwinfo.manifest_bufSz, &manifestMajor, + &manifestMinor) != TPM_RC_SUCCESS) { + printf("Error: manifest too small to hold a version header\n"); + rc = BAD_FUNC_ARG; + goto exit; + } + ccFromTpm = TPM2_ST33_PickOrdinals(&caps, !fwinfo.in_upgrade_mode, + manifestMajor, &ccStart, &ccData); + printf("Firmware Update:\n"); printf("\tTotal file size: %zu bytes\n", fwinfo.fi_bufSz); printf("\tManifest (blob0): %zu bytes\n", fwinfo.manifest_bufSz); printf("\tFirmware data: %zu bytes\n", fwinfo.firmware_bufSz); + printf("\tImage targets firmware: %u.%u (%s)\n", manifestMajor, + manifestMinor, TPM2_ST33_PartLine(manifestMajor)); + printf("\tCommand codes: start 0x%08x, data 0x%08x (%s)\n", + (unsigned int)ccStart, (unsigned int)ccData, + ccFromTpm ? "from TPM_CAP_COMMANDS" : + "inferred from the firmware version"); if (fwinfo.in_upgrade_mode) { /* Continuing from upgrade mode - just send firmware data */ printf("Sending firmware data (TPM already in upgrade mode)...\n"); - rc = TPM2_ST33_SendFirmwareData(&fwinfo); + rc = TPM2_ST33_SendFirmwareData(&fwinfo, ccData, ccFromTpm); } else { WOLFTPM2_SESSION* startSess = NULL; @@ -456,8 +606,7 @@ int TPM2_ST33_Firmware_Update(void* userCtx, int argc, char *argv[]) * that caller-supplied session instead of the default password auth. */ if (policyMode != ST33_POLICY_NONE) { rc = firmware_policy_session_setup(&dev, &policyCtx, policyHash, - (policyMode == ST33_POLICY_OR), - TPM_CC_FieldUpgradeStartVendor_ST33, &policySession); + (policyMode == ST33_POLICY_OR), ccStart, &policySession); if (rc == 0) { printf("Using caller-supplied policy session\n"); startSess = &policySession; diff --git a/hal/tpm_io_linux.c b/hal/tpm_io_linux.c index 626ab352..54663a1c 100644 --- a/hal/tpm_io_linux.c +++ b/hal/tpm_io_linux.c @@ -72,9 +72,19 @@ #ifdef WOLFTPM_I2C /* I2C - (Only tested with SLB9673 and ST33 I2C) */ + /* Overridable at build time: the bus number is board specific. A + * Raspberry Pi 5 using a bit-banged i2c-gpio overlay for the clock + * stretching these TPMs need, for example, enumerates it well above + * i2c-1. */ + #ifndef TPM2_I2C_ADDR #define TPM2_I2C_ADDR 0x2e + #endif + #ifndef TPM2_I2C_DEV #define TPM2_I2C_DEV "/dev/i2c-1" + #endif + #ifndef TPM2_I2C_HZ #define TPM2_I2C_HZ 400000 /* 400kHz */ + #endif static int i2cOpenFailed = 0; static int i2cDevFd = -1; #else diff --git a/src/tpm2.c b/src/tpm2.c index ea8fdca3..a7550f2c 100644 --- a/src/tpm2.c +++ b/src/tpm2.c @@ -6664,7 +6664,7 @@ int TPM2_IFX_FieldUpgradeCommand(TPM_CC cc, uint8_t* data, uint32_t size) #if defined(WOLFTPM_ST33) || defined(WOLFTPM_AUTODETECT) /* ST33 Firmware Update Vendor Command Functions */ -int TPM2_ST33_FieldUpgradeStart(TPM_HANDLE sessionHandle, +int TPM2_ST33_FieldUpgradeStart_ex(TPM_HANDLE sessionHandle, TPM_CC cc, uint8_t* data, uint32_t size) { int rc; @@ -6687,8 +6687,7 @@ int TPM2_ST33_FieldUpgradeStart(TPM_HANDLE sessionHandle, TPM2_Packet_AppendBytes(&packet, data, size); - TPM2_Packet_Finalize(&packet, TPM_ST_SESSIONS, - TPM_CC_FieldUpgradeStartVendor_ST33); + TPM2_Packet_Finalize(&packet, TPM_ST_SESSIONS, cc); rc = TPM2_SendCommand(ctx, &packet); @@ -6697,6 +6696,13 @@ int TPM2_ST33_FieldUpgradeStart(TPM_HANDLE sessionHandle, return rc; } +int TPM2_ST33_FieldUpgradeStart(TPM_HANDLE sessionHandle, + uint8_t* data, uint32_t size) +{ + return TPM2_ST33_FieldUpgradeStart_ex(sessionHandle, + TPM_CC_FieldUpgradeStartVendor_ST33, data, size); +} + int TPM2_ST33_FieldUpgradeCommand(TPM_CC cc, uint8_t* data, uint32_t size) { int rc; @@ -6946,6 +6952,16 @@ const char* TPM2_GetRCString(int rc) return "Success"; } + /* Format-zero codes carry the vendor bit (T, bit 10). This has to be + * checked before the format-zero decode below, which masks with + * RC_MAX_FM0 and would otherwise report a vendor code as whichever + * standard code shares its low bits - an ST33 field upgrade rejection of + * 0x501 reads as TPM_RC_FAILURE. Format-one codes have no vendor bit, and + * negative values are wolfCrypt errors, not TPM response codes. */ + if (rc > 0 && (rc & RC_FMT1) == 0 && (rc & 0x400)) { /* bit 10 */ + return "Vendor defined response code"; + } + if ((rc & RC_WARN) == RC_WARN && (rc & RC_FMT1) == 0) { int rc_warn = rc & RC_MAX_WARN; @@ -7094,10 +7110,6 @@ const char* TPM2_GetRCString(int rc) } } - else if (rc & 0x400) { /* bit 10 */ - return "Vendor defined response code"; - } - return "Unknown"; } diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index 2f402c37..bf2a065a 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -1006,8 +1006,10 @@ static int wolfTPM2_GetCapabilities_NoDev(WOLFTPM2_CAPS* cap) rc = wolfTPM2_ParseCapabilities(cap, &out.capabilityData.data.tpmProperties); #if defined(WOLFTPM_SLB9672) || defined(WOLFTPM_SLB9673) - /* Get vendor specific information */ - if (rc == 0) { + /* Get vendor specific information. These properties only exist on + * Infineon parts - probing them on another manufacturer's TPM just + * produces misleading TPM_RC_VALUE errors. */ + if (rc == 0 && cap->mfg == TPM_MFG_INFINEON) { int rc_ifx; rc_ifx = tpm2_ifx_cap_vendor_get(cap, TPM_PT_VENDOR_FIX_FU_OPERATION_MODE, &cap->opMode, sizeof(cap->opMode)); @@ -11949,12 +11951,30 @@ int wolfTPM2_FirmwareUpgradeCancel(WOLFTPM2_DEV* dev) /* Maximum size of firmware chunks for ST33 */ #define ST33_FW_MAX_CHUNK_SZ 2048 /* Must be large enough for firmware blobs */ -/* ST33 firmware version threshold for LMS requirement. LMS is a generation 9 - * feature: on those parts a minor version >= 512 requires the LMS format - * (e.g. 9.512), below that the ECDSA format (e.g. 9.257). Generation 1 parts - * are always non-LMS no matter how high the minor version goes (e.g. 1.771). */ -#define ST33_FW_GENERATION_LMS_CAPABLE 9 -#define ST33_FW_VERSION_LMS_REQUIRED 512 +/* The manifest format follows the silicon family, which the firmware major + * version identifies. ST33TPHF2X (majors 1, 2 and the older 74 line) signs + * with SHA-256 + RSA-PSS; ST33KTPM (majors 9 and 10) signs with ECDSA P-384, + * and from minor 512 with LMS. A major outside both lists is unknown and no + * size is asserted for it, rather than guessing an upgrade into a rejection. */ +#define ST33_FW_FAMILY_UNKNOWN 0 +#define ST33_FW_FAMILY_TPHF2X 1 +#define ST33_FW_FAMILY_KTPM 2 +#define ST33_FW_VERSION_LMS_REQUIRED 512 + +static int tpm2_st33_family(word16 fwVerMajor) +{ + switch (fwVerMajor) { + case 1: /* ST33TPHF2X, SPI firmware line */ + case 2: /* ST33TPHF2X, I2C firmware line */ + case 74: /* ST33TPHF2X, older line both buses (74.8 SPI, 74.9 I2C) */ + return ST33_FW_FAMILY_TPHF2X; + case 9: /* ST33KTPM2X */ + case 10: /* ST33KTPM2A */ + return ST33_FW_FAMILY_KTPM; + default: + return ST33_FW_FAMILY_UNKNOWN; + } +} /* ST33 manifest (blob0) sizes determine firmware format. The manifest is a * 33 byte fixed header followed by the firmware digest and the signature over @@ -11966,16 +11986,181 @@ int wolfTPM2_FirmwareUpgradeCancel(WOLFTPM2_DEV* dev) /* gen 9 at 512 and above: embedded LMS signature */ #define ST33_MANIFEST_SIZE_LMS 2697 -/* Manifest size the running firmware expects for its next update */ +/* Manifest size the running firmware expects for its next update. Returns 0 + * when the family is unknown, meaning no size can be asserted. */ static uint32_t tpm2_st33_expected_manifest_sz(const WOLFTPM2_CAPS* caps) { - if (caps->fwVerMajor < ST33_FW_GENERATION_LMS_CAPABLE) { - return ST33_MANIFEST_SIZE_NON_LMS_RSA; + switch (tpm2_st33_family(caps->fwVerMajor)) { + case ST33_FW_FAMILY_TPHF2X: + return ST33_MANIFEST_SIZE_NON_LMS_RSA; + case ST33_FW_FAMILY_KTPM: + if (caps->fwVerMinor < ST33_FW_VERSION_LMS_REQUIRED) { + return ST33_MANIFEST_SIZE_NON_LMS; + } + return ST33_MANIFEST_SIZE_LMS; + default: + return 0; + } +} + +/* Target firmware version carried in the manifest (blob0) header. The header + * opens with a zero byte followed by the version the image upgrades to, laid + * out exactly like TPM_PT_FIRMWARE_VERSION_1: UINT16 major then UINT16 minor, + * big endian (for example 00 | 00 02 02 00 for 2.512). */ +#define ST33_MANIFEST_VERSION_OFFSET 1 + +int wolfTPM2_ST33_ManifestVersion(const uint8_t* manifest, + uint32_t manifest_sz, word16* major, word16* minor) +{ + const uint8_t* ver; + + if (manifest == NULL || + manifest_sz < (uint32_t)(ST33_MANIFEST_VERSION_OFFSET + 4)) { + return BAD_FUNC_ARG; + } + ver = &manifest[ST33_MANIFEST_VERSION_OFFSET]; + if (major != NULL) { + *major = (word16)(((word16)ver[0] << 8) | ver[1]); + } + if (minor != NULL) { + *minor = (word16)(((word16)ver[2] << 8) | ver[3]); + } + return TPM_RC_SUCCESS; +} + +/* ST33 implements the field upgrade with one of two command code pairs and + * the wrong one is answered with TPM_RC_COMMAND_CODE. This mirrors the + * selection in ST's reference tool (TPM_FU_STM_V1.py), which uses the + * standard TPM command codes when the running firmware minor version is + * below 256, or when the image targets firmware generation 2, and the + * ST33KTPM vendor codes otherwise. Exposed so the example tool and the unit + * tests exercise the same implementation the library sends from. It is only + * a fallback: wolfTPM2_ST33_GetFwUpgradeCommands asks the TPM first, as the + * heuristic does not hold on every firmware line (an ST33KTPMQ at 11.1 + * implements only the vendor pair despite a minor version below 256). */ +#define ST33_FW_VERSION_STD_CC_MINOR 256 +#define ST33_FW_GENERATION_STD_CC 2 + +int wolfTPM2_ST33_FwUpgradeCommands(word16 fwVerMinor, int haveFwVer, + word16 manifestMajor, TPM_CC* ccStart, TPM_CC* ccData) +{ + int useStd; + + if (ccStart == NULL || ccData == NULL) { + return BAD_FUNC_ARG; } - if (caps->fwVerMinor < ST33_FW_VERSION_LMS_REQUIRED) { - return ST33_MANIFEST_SIZE_NON_LMS; + useStd = (manifestMajor == ST33_FW_GENERATION_STD_CC); + if (haveFwVer && fwVerMinor < ST33_FW_VERSION_STD_CC_MINOR) { + useStd = 1; + } + if (useStd) { + *ccStart = TPM_CC_FieldUpgradeStart; + *ccData = TPM_CC_FieldUpgradeData; + } + else { + *ccStart = TPM_CC_FieldUpgradeStartVendor_ST33; + *ccData = TPM_CC_FieldUpgradeDataVendor_ST33; } - return ST33_MANIFEST_SIZE_LMS; + return TPM_RC_SUCCESS; +} + +/* Report whether the TPM implements a command code. TPM_CAP_COMMANDS returns + * commands with a code at or above the requested property, so a match at + * index 0 of a single-property query means it is implemented. The attributes + * carry the command index with the vendor bit separate, so the full code has + * to be put back together before comparing. */ +int wolfTPM2_ST33_CmdImplemented(TPM_CC cc, int* isImpl) +{ + int rc; + GetCapability_In in; + GetCapability_Out out; + TPML_CCA* cmds; + TPM_CC got; + + if (isImpl == NULL) { + return BAD_FUNC_ARG; + } + *isImpl = 0; + XMEMSET(&in, 0, sizeof(in)); + XMEMSET(&out, 0, sizeof(out)); + in.capability = TPM_CAP_COMMANDS; + in.property = cc; + in.propertyCount = 1; + rc = TPM2_GetCapability(&in, &out); + if (rc != TPM_RC_SUCCESS) { + return rc; + } + /* capabilityData.data is a union - confirm the TPM answered with the + * capability that was asked for before reading the command member */ + if (out.capabilityData.capability != TPM_CAP_COMMANDS) { + return TPM_RC_VALUE; + } + cmds = &out.capabilityData.data.command; + if (cmds->count > 0) { + got = (TPM_CC)(cmds->commandAttributes[0] & TPMA_CC_commandIndex); + if (cmds->commandAttributes[0] & TPMA_CC_V) { + got |= CC_VEND; + } + if (got == cc) { + *isImpl = 1; + } + } + return TPM_RC_SUCCESS; +} + +/* Select the field upgrade command codes. The TPM's own command list is + * authoritative, so ask it rather than infer: ST33 firmware lines do not + * follow the version heuristic consistently (an ST33KTPMQ at 11.1 implements + * only the vendor pair even though its minor version is below 256). Only the + * start codes are queried, the data code always follows its start code. + * wolfTPM2_ST33_FwUpgradeCommands decides when the TPM will not answer - + * which is the case in firmware upgrade mode - or the answer is ambiguous. + * Pass caps as NULL when the running firmware version is not known. */ +int wolfTPM2_ST33_GetFwUpgradeCommands(const WOLFTPM2_CAPS* caps, + word16 manifestMajor, TPM_CC* ccStart, TPM_CC* ccData, int* fromTpm) +{ + int vendorImpl = 0, stdImpl = 0; + int rcVendor, rcStd; + + if (ccStart == NULL || ccData == NULL) { + return BAD_FUNC_ARG; + } + if (fromTpm != NULL) { + *fromTpm = 0; + } + + rcVendor = wolfTPM2_ST33_CmdImplemented(TPM_CC_FieldUpgradeStartVendor_ST33, + &vendorImpl); + rcStd = wolfTPM2_ST33_CmdImplemented(TPM_CC_FieldUpgradeStart, &stdImpl); + + if (rcVendor == TPM_RC_SUCCESS && rcStd == TPM_RC_SUCCESS && + vendorImpl != stdImpl) { + if (vendorImpl) { + *ccStart = TPM_CC_FieldUpgradeStartVendor_ST33; + *ccData = TPM_CC_FieldUpgradeDataVendor_ST33; + } + else { + *ccStart = TPM_CC_FieldUpgradeStart; + *ccData = TPM_CC_FieldUpgradeData; + } + if (fromTpm != NULL) { + *fromTpm = 1; + } + #ifdef DEBUG_WOLFTPM + printf("ST33 Field upgrade command codes from TPM_CAP_COMMANDS\n"); + #endif + return TPM_RC_SUCCESS; + } + +#ifdef DEBUG_WOLFTPM + printf("ST33 Field upgrade command set inconclusive (vendor rc 0x%x " + "impl %d, standard rc 0x%x impl %d), using the firmware version " + "rule\n", (unsigned int)rcVendor, vendorImpl, (unsigned int)rcStd, + stdImpl); +#endif + return wolfTPM2_ST33_FwUpgradeCommands( + (caps != NULL) ? caps->fwVerMinor : 0, (caps != NULL), + manifestMajor, ccStart, ccData); } /* ST33 uses password auth (TPM_RS_PW) for firmware update, not policy */ @@ -11985,7 +12170,7 @@ static uint32_t tpm2_st33_expected_manifest_sz(const WOLFTPM2_CAPS* caps) * 300ms delay: ST reference implementation uses this delay to allow * TPM to switch modes after FieldUpgradeStart command */ static int tpm2_st33_firmware_start_common(WOLFTPM2_DEV* dev, - uint8_t* manifest, uint32_t manifest_sz, int is_lms, + uint8_t* manifest, uint32_t manifest_sz, int is_lms, TPM_CC ccStart, WOLFTPM2_SESSION* startSession) { int rc; @@ -12001,7 +12186,8 @@ static int tpm2_st33_firmware_start_common(WOLFTPM2_DEV* dev, * LMS signature. Send the full manifest directly. */ sessionHandle = (startSession != NULL) ? startSession->handle.hndl : (TPM_HANDLE)TPM_RS_PW; - rc = TPM2_ST33_FieldUpgradeStart(sessionHandle, manifest, manifest_sz); + rc = TPM2_ST33_FieldUpgradeStart_ex(sessionHandle, ccStart, manifest, + manifest_sz); if (rc == TPM_RC_SUCCESS) { /* The TPM consumed the session entering firmware upgrade mode; mark a @@ -12025,8 +12211,9 @@ static int tpm2_st33_firmware_start_common(WOLFTPM2_DEV* dev, } #ifdef DEBUG_WOLFTPM if (rc != TPM_RC_SUCCESS) { - printf("ST33 Firmware upgrade start%s failed 0x%x: %s\n", - is_lms ? " (LMS)" : "", rc, TPM2_GetRCString(rc)); + printf("ST33 Firmware upgrade start%s (cc 0x%08x) failed 0x%x: %s\n", + is_lms ? " (LMS)" : "", (unsigned int)ccStart, rc, + TPM2_GetRCString(rc)); } #else (void)is_lms; /* Suppress unused parameter warning when DEBUG_WOLFTPM not defined */ @@ -12042,7 +12229,7 @@ static int tpm2_st33_firmware_start_common(WOLFTPM2_DEV* dev, * - Bytes 1-2: blob data length (big-endian) * - Bytes 3+: blob data * Each blob is sent complete to the TPM via FieldUpgradeData command */ -static int tpm2_st33_firmware_data(WOLFTPM2_DEV* dev, +static int tpm2_st33_firmware_data(WOLFTPM2_DEV* dev, TPM_CC ccData, wolfTPM2FwDataCb cb, void* cb_ctx) { int rc; @@ -12119,8 +12306,7 @@ static int tpm2_st33_firmware_data(WOLFTPM2_DEV* dev, } /* Send blob to TPM - blob is sent as-is per ST reference */ - rc = TPM2_ST33_FieldUpgradeCommand(TPM_CC_FieldUpgradeDataVendor_ST33, - blob_buf, blob_total); + rc = TPM2_ST33_FieldUpgradeCommand(ccData, blob_buf, blob_total); if (rc != TPM_RC_SUCCESS) { #ifdef DEBUG_WOLFTPM printf("ST33 FieldUpgradeData failed at offset %u: 0x%x\n", @@ -12173,6 +12359,8 @@ static int tpm2_st33_firmware_upgrade_hash(WOLFTPM2_DEV* dev, TPM_ALG_ID hashAlg WOLFTPM2_CAPS caps; int is_lms; uint32_t expected_sz; + word16 manifestMajor = 0, manifestMinor = 0; + TPM_CC ccStart, ccData; /* ST33 sends full manifest directly, not hash */ (void)hashAlg; @@ -12207,16 +12395,29 @@ static int tpm2_st33_firmware_upgrade_hash(WOLFTPM2_DEV* dev, TPM_ALG_ID hashAlg return rc; } + rc = wolfTPM2_ST33_ManifestVersion(manifest, manifest_sz, &manifestMajor, + &manifestMinor); + if (rc != TPM_RC_SUCCESS) { + #ifdef DEBUG_WOLFTPM + printf("ST33 Error: manifest too small to hold a version header\n"); + #endif + return rc; + } + (void)wolfTPM2_ST33_GetFwUpgradeCommands(&caps, manifestMajor, &ccStart, + &ccData, NULL); + #ifdef DEBUG_WOLFTPM printf("ST33 Firmware version: Major=%u, Minor=%u, Vendor=0x%x\n", caps.fwVerMajor, caps.fwVerMinor, caps.fwVerVendor); - printf("ST33 Manifest size: %u bytes, format: %s\n", - manifest_sz, is_lms ? "LMS" : "non-LMS"); + printf("ST33 Manifest size: %u bytes, format: %s, targets %u.%u\n", + manifest_sz, is_lms ? "LMS" : "non-LMS", manifestMajor, manifestMinor); + printf("ST33 Field upgrade command codes: start 0x%08x, data 0x%08x\n", + (unsigned int)ccStart, (unsigned int)ccData); #endif /* Validate the manifest matches what this firmware generation expects */ expected_sz = tpm2_st33_expected_manifest_sz(&caps); - if (manifest_sz != expected_sz) { + if (expected_sz != 0 && manifest_sz != expected_sz) { #ifdef DEBUG_WOLFTPM printf("ST33 Error: manifest size %u does not match the %u bytes " "firmware %u.%u expects\n", manifest_sz, expected_sz, @@ -12225,12 +12426,28 @@ static int tpm2_st33_firmware_upgrade_hash(WOLFTPM2_DEV* dev, TPM_ALG_ID hashAlg return BAD_FUNC_ARG; } + /* The manifest carries the firmware line it upgrades. Within a family a + * version jump is normal and expected - an ST33TPHF2X goes 74.9 to 2.512 - + * so only an image from a different silicon family is refused. The TPM + * would otherwise report that as a signature or command code failure. */ + if (tpm2_st33_family(manifestMajor) != ST33_FW_FAMILY_UNKNOWN && + tpm2_st33_family(caps.fwVerMajor) != ST33_FW_FAMILY_UNKNOWN && + tpm2_st33_family(manifestMajor) != + tpm2_st33_family(caps.fwVerMajor)) { + #ifdef DEBUG_WOLFTPM + printf("ST33 Error: firmware image targets %u.%u, a different part " + "family than this TPM running %u.%u\n", manifestMajor, + manifestMinor, caps.fwVerMajor, caps.fwVerMinor); + #endif + return BAD_FUNC_ARG; + } + /* Send manifest - the common function handles both LMS and non-LMS */ rc = tpm2_st33_firmware_start_common(dev, manifest, manifest_sz, is_lms, - startSession); + ccStart, startSession); if (rc == TPM_RC_SUCCESS) { - rc = tpm2_st33_firmware_data(dev, cb, cb_ctx); + rc = tpm2_st33_firmware_data(dev, ccData, cb, cb_ctx); } /* Note: ST33 doesn't require a finalize command - the firmware update * is complete after all blobs are sent. The TPM will automatically diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 27459425..1f0f8744 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -4215,11 +4215,30 @@ static void test_st33_detect_blob0(void) buf = (byte*)XMALLOC(bufSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); AssertNotNull(buf); - /* Version to expected size: the rule the whole PR turns on */ + /* Version to expected size, grouped by silicon family. ST33TPHF2X spans + * majors 1, 2 and the older 74 line and always signs with RSA-PSS, so a + * 74.9 part upgrading to 2.512 must expect the same 321 bytes at both + * ends. Getting this wrong refuses a legitimate image client side. */ AssertIntEQ((int)st33_expected_blob0(1, 258), ST33_BLOB0_SIZE_NON_LMS_RSA); AssertIntEQ((int)st33_expected_blob0(1, 771), ST33_BLOB0_SIZE_NON_LMS_RSA); + AssertIntEQ((int)st33_expected_blob0(2, 512), ST33_BLOB0_SIZE_NON_LMS_RSA); + AssertIntEQ((int)st33_expected_blob0(74, 8), ST33_BLOB0_SIZE_NON_LMS_RSA); + AssertIntEQ((int)st33_expected_blob0(74, 9), ST33_BLOB0_SIZE_NON_LMS_RSA); AssertIntEQ((int)st33_expected_blob0(9, 257), ST33_BLOB0_SIZE_NON_LMS); AssertIntEQ((int)st33_expected_blob0(9, 512), ST33_BLOB0_SIZE_LMS); + AssertIntEQ((int)st33_expected_blob0(10, 512), ST33_BLOB0_SIZE_LMS); + + /* An unknown family asserts no size rather than guessing one that would + * reject a valid image (an ST33KTPMQ reports major 11) */ + AssertIntEQ((int)st33_expected_blob0(11, 1), 0); + AssertIntEQ(st33_blob0_family(11), ST33_BLOB0_FAMILY_UNKNOWN); + AssertIntEQ(st33_blob0_family(2), ST33_BLOB0_FAMILY_TPHF2X); + AssertIntEQ(st33_blob0_family(74), ST33_BLOB0_FAMILY_TPHF2X); + AssertIntEQ(st33_blob0_family(9), ST33_BLOB0_FAMILY_KTPM); + + /* With no size to prefer, every candidate is still offered exactly once */ + candCnt = st33_blob0_candidates(11, 1, 1, cand); + AssertIntEQ((int)candCnt, ST33_BLOB0_SIZE_CNT); /* Preferred candidate leads, every size still present exactly once */ candCnt = st33_blob0_candidates(1, 771, 1, cand); @@ -4299,6 +4318,100 @@ static void test_st33_detect_blob0(void) printf("Test TPM Wrapper:\tST33 blob0 detection:\t\tPassed\n"); } +/* The manifest header carries the firmware line the image upgrades, and that + * plus the running minor version picks the field upgrade command codes when + * the TPM's command list cannot settle it. The wrong pair is answered with + * TPM_RC_COMMAND_CODE and no upgrade happens, so every combination that + * reaches real parts is pinned here. These are the library's own helpers, so + * this covers the code that actually drives hardware. */ +#if defined(WOLFTPM_FIRMWARE_UPGRADE) && \ + (defined(WOLFTPM_ST33) || defined(WOLFTPM_AUTODETECT)) +static void test_st33_fu_ordinals(void) +{ + byte blob0[16]; + word16 major, minor; + TPM_CC ccStart, ccData; + + /* Header: 00 | major:2 | minor:2, big endian. 2.512 is ST33TPHF2XI2C */ + XMEMSET(blob0, 0, sizeof(blob0)); + blob0[1] = 0x00; blob0[2] = 0x02; blob0[3] = 0x02; blob0[4] = 0x00; + major = minor = 0xFFFF; + AssertIntEQ(wolfTPM2_ST33_ManifestVersion(blob0, sizeof(blob0), &major, + &minor), TPM_RC_SUCCESS); + AssertIntEQ((int)major, 2); + AssertIntEQ((int)minor, 512); + + /* 9.512 is ST33KTPM2X */ + blob0[2] = 0x09; + AssertIntEQ(wolfTPM2_ST33_ManifestVersion(blob0, sizeof(blob0), &major, + &minor), TPM_RC_SUCCESS); + AssertIntEQ((int)major, 9); + AssertIntEQ((int)minor, 512); + + /* Either output may be dropped, and a short buffer is refused rather + * than read past. The version needs offset 1 through 4. */ + AssertIntEQ(wolfTPM2_ST33_ManifestVersion(blob0, sizeof(blob0), NULL, + NULL), TPM_RC_SUCCESS); + AssertIntEQ(wolfTPM2_ST33_ManifestVersion(blob0, 5, &major, &minor), + TPM_RC_SUCCESS); + AssertIntEQ(wolfTPM2_ST33_ManifestVersion(blob0, 4, &major, &minor), + BAD_FUNC_ARG); + AssertIntEQ(wolfTPM2_ST33_ManifestVersion(NULL, sizeof(blob0), &major, + &minor), BAD_FUNC_ARG); + + /* Generation 2 images use the standard codes whatever the TPM reports */ + AssertIntEQ(wolfTPM2_ST33_FwUpgradeCommands(512, 1, 2, &ccStart, &ccData), + TPM_RC_SUCCESS); + AssertIntEQ((int)ccStart, (int)TPM_CC_FieldUpgradeStart); + AssertIntEQ((int)ccData, (int)TPM_CC_FieldUpgradeData); + + /* A running minor version below 256 also uses the standard codes */ + AssertIntEQ(wolfTPM2_ST33_FwUpgradeCommands(8, 1, 74, &ccStart, &ccData), + TPM_RC_SUCCESS); + AssertIntEQ((int)ccStart, (int)TPM_CC_FieldUpgradeStart); + AssertIntEQ((int)ccData, (int)TPM_CC_FieldUpgradeData); + AssertIntEQ(wolfTPM2_ST33_FwUpgradeCommands(255, 1, 9, &ccStart, &ccData), + TPM_RC_SUCCESS); + AssertIntEQ((int)ccStart, (int)TPM_CC_FieldUpgradeStart); + + /* Everything else is the ST33KTPM vendor pair */ + AssertIntEQ(wolfTPM2_ST33_FwUpgradeCommands(258, 1, 1, &ccStart, &ccData), + TPM_RC_SUCCESS); + AssertIntEQ((int)ccStart, (int)TPM_CC_FieldUpgradeStartVendor_ST33); + AssertIntEQ((int)ccData, (int)TPM_CC_FieldUpgradeDataVendor_ST33); + AssertIntEQ(wolfTPM2_ST33_FwUpgradeCommands(256, 1, 9, &ccStart, &ccData), + TPM_RC_SUCCESS); + AssertIntEQ((int)ccStart, (int)TPM_CC_FieldUpgradeStartVendor_ST33); + AssertIntEQ(wolfTPM2_ST33_FwUpgradeCommands(512, 1, 10, &ccStart, &ccData), + TPM_RC_SUCCESS); + AssertIntEQ((int)ccStart, (int)TPM_CC_FieldUpgradeStartVendor_ST33); + + /* An ST33KTPMQ at 11.1 is why the version rule is only a fallback: the + * rule says standard, the part implements only the vendor pair, and the + * TPM_CAP_COMMANDS probe is what gets it right on hardware. */ + AssertIntEQ(wolfTPM2_ST33_FwUpgradeCommands(1, 1, 11, &ccStart, &ccData), + TPM_RC_SUCCESS); + AssertIntEQ((int)ccStart, (int)TPM_CC_FieldUpgradeStart); + + /* In upgrade mode the running version is unknown, so the manifest alone + * decides and the stale minor version must not be consulted */ + AssertIntEQ(wolfTPM2_ST33_FwUpgradeCommands(8, 0, 9, &ccStart, &ccData), + TPM_RC_SUCCESS); + AssertIntEQ((int)ccStart, (int)TPM_CC_FieldUpgradeStartVendor_ST33); + AssertIntEQ(wolfTPM2_ST33_FwUpgradeCommands(8, 0, 2, &ccStart, &ccData), + TPM_RC_SUCCESS); + AssertIntEQ((int)ccStart, (int)TPM_CC_FieldUpgradeStart); + + /* NULL outputs are refused rather than dereferenced */ + AssertIntEQ(wolfTPM2_ST33_FwUpgradeCommands(512, 1, 2, NULL, &ccData), + BAD_FUNC_ARG); + AssertIntEQ(wolfTPM2_ST33_FwUpgradeCommands(512, 1, 2, &ccStart, NULL), + BAD_FUNC_ARG); + + printf("Test TPM Wrapper:\tST33 field upgrade ordinals:\tPassed\n"); +} +#endif /* WOLFTPM_FIRMWARE_UPGRADE && (WOLFTPM_ST33 || WOLFTPM_AUTODETECT) */ + /* A sessioned response whose attacker-controlled parameterSize wraps UINT32 * when added to packet->pos must be rejected up front. Without the bounds * check the wrapped authPos passes the "respSz > authPos" guard and the @@ -8679,6 +8792,10 @@ int unit_tests(int argc, char *argv[]) test_TPM2_DispatchCommand_overflow(); #endif test_st33_detect_blob0(); +#if defined(WOLFTPM_FIRMWARE_UPGRADE) && \ + (defined(WOLFTPM_ST33) || defined(WOLFTPM_AUTODETECT)) + test_st33_fu_ordinals(); +#endif test_TPM2_ResponseProcess_ParamSizeOverflow(); test_TPM2_ResponseProcess_DecParamSizeOverflow(); test_TPM2_ResponseProcess_HmacVerify(); diff --git a/wolftpm/tpm2.h b/wolftpm/tpm2.h index 139abdb5..84a11b88 100644 --- a/wolftpm/tpm2.h +++ b/wolftpm/tpm2.h @@ -320,7 +320,14 @@ typedef enum { /* ST33 Firmware Update Vendor Command Codes * Verified from ST reference implementation (TPM_FU_STM_KTPM_LMS.c): * - Start: 0x2000030C, Data: 0x2000030D - * Note: Abandon still uses placeholder - may need verification */ + * Note: Abandon still uses placeholder - may need verification + * These are only used by the ST33KTPM class of firmware. Other ST33 + * firmware performs the field upgrade with the standard + * TPM_CC_FieldUpgradeStart/Data codes instead; the library queries + * TPM_CAP_COMMANDS to find out which pair a part implements. + * Note the abandon/finalize codes alias TPM_CC_GetRandom2 (0x2000030E) + * and TPM_CC_GPIO_Config (0x2000030F); only the firmware upgrade mode the + * TPM is in tells the two meanings apart. */ TPM_CC_FieldUpgradeStartVendor_ST33 = CC_VEND + 0x030C, TPM_CC_FieldUpgradeAbandonVendor_ST33 = CC_VEND + 0x030E, /* Abandon/cancel */ TPM_CC_FieldUpgradeDataVendor_ST33 = CC_VEND + 0x030D, @@ -3441,6 +3448,11 @@ WOLFTPM_API int TPM2_IFX_FieldUpgradeCommand(TPM_CC cc, uint8_t* data, uint32_t #ifdef WOLFTPM_FIRMWARE_UPGRADE WOLFTPM_API int TPM2_ST33_FieldUpgradeStart(TPM_HANDLE sessionHandle, uint8_t* data, uint32_t size); +/* Same as TPM2_ST33_FieldUpgradeStart, but with the field upgrade start + * command code supplied by the caller. ST33 firmware differs on whether the + * vendor code or the standard TPM_CC_FieldUpgradeStart is implemented. */ +WOLFTPM_API int TPM2_ST33_FieldUpgradeStart_ex(TPM_HANDLE sessionHandle, + TPM_CC cc, uint8_t* data, uint32_t size); WOLFTPM_API int TPM2_ST33_FieldUpgradeCommand(TPM_CC cc, uint8_t* data, uint32_t size); #endif /* WOLFTPM_FIRMWARE_UPGRADE */ #endif /* WOLFTPM_ST33 || WOLFTPM_AUTODETECT */ diff --git a/wolftpm/tpm2_wrap.h b/wolftpm/tpm2_wrap.h index bd2f0ac9..8f12cb4d 100644 --- a/wolftpm/tpm2_wrap.h +++ b/wolftpm/tpm2_wrap.h @@ -5420,6 +5420,100 @@ WOLFTPM_API int wolfTPM2_FirmwareUpgradeRecover_ex(WOLFTPM2_DEV* dev, */ WOLFTPM_API int wolfTPM2_FirmwareUpgradeCancel(WOLFTPM2_DEV* dev); +#if defined(WOLFTPM_ST33) || defined(WOLFTPM_AUTODETECT) +/*! + \ingroup wolfTPM2_Wrappers + \brief Read the firmware version an ST33 firmware image upgrades to + \note The manifest (blob0) header opens with a zero byte followed by the + target version in the TPM_PT_FIRMWARE_VERSION_1 layout: UINT16 major + then UINT16 minor, big endian (00 | 00 02 02 00 is 2.512) + + \return TPM_RC_SUCCESS: successful + \return BAD_FUNC_ARG: manifest is NULL or too small to hold the version + + \param manifest pointer to the manifest (blob0) bytes + \param manifest_sz size of the manifest in bytes + \param major pointer to store the target major version (optional, may be NULL) + \param minor pointer to store the target minor version (optional, may be NULL) + + \sa wolfTPM2_ST33_FwUpgradeCommands + \sa wolfTPM2_FirmwareUpgrade +*/ +WOLFTPM_API int wolfTPM2_ST33_ManifestVersion(const uint8_t* manifest, + uint32_t manifest_sz, word16* major, word16* minor); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Report whether the TPM implements a command code + \note Queries TPM_CAP_COMMANDS, which returns commands at or above the + requested code, so a match at index 0 of a single-property query means + it is implemented. Used to tell which field upgrade command pair an + ST33 part supports + + \return TPM_RC_SUCCESS: query succeeded, isImpl set to 1 or 0 + \return BAD_FUNC_ARG: isImpl is NULL + \return TPM_RC_VALUE: the TPM answered with a different capability + + \param cc command code to look for + \param isImpl pointer to store 1 when implemented, 0 when not + + \sa wolfTPM2_ST33_GetFwUpgradeCommands +*/ +WOLFTPM_API int wolfTPM2_ST33_CmdImplemented(TPM_CC cc, int* isImpl); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Choose the ST33 field upgrade command codes for the attached TPM + \note Asks the TPM which pair it implements, since the firmware version + rule does not hold on every ST33 firmware line, and falls back to + wolfTPM2_ST33_FwUpgradeCommands when the TPM does not settle it. This + is what the library sends, so a caller binding a PolicyCommandCode for + the upgrade should bind the start code this returns + + \return TPM_RC_SUCCESS: successful + \return BAD_FUNC_ARG: ccStart or ccData is NULL + + \param caps capabilities of the attached TPM, or NULL when the running + firmware version is unknown, as it is in firmware upgrade mode + \param manifestMajor target major version from wolfTPM2_ST33_ManifestVersion + \param ccStart pointer to store the field upgrade start command code + \param ccData pointer to store the field upgrade data command code + \param fromTpm pointer to store 1 when the TPM decided and 0 when the + version rule did (optional, may be NULL) + + \sa wolfTPM2_ST33_CmdImplemented + \sa wolfTPM2_ST33_ManifestVersion +*/ +WOLFTPM_API int wolfTPM2_ST33_GetFwUpgradeCommands(const WOLFTPM2_CAPS* caps, + word16 manifestMajor, TPM_CC* ccStart, TPM_CC* ccData, int* fromTpm); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Choose the ST33 field upgrade command codes from the version fields + \note ST33 implements the field upgrade with either the standard + TPM_CC_FieldUpgradeStart/Data or the ST33KTPM vendor codes, and the + wrong pair is answered with TPM_RC_COMMAND_CODE. This applies the rule + ST's reference tool uses. Callers want + wolfTPM2_ST33_GetFwUpgradeCommands instead, which asks the TPM first + and only falls back to this. Pass haveFwVer = 0 when the running + version is unknown, as it is once the TPM has entered upgrade mode + + \return TPM_RC_SUCCESS: successful + \return BAD_FUNC_ARG: ccStart or ccData is NULL + + \param fwVerMinor running firmware minor version from WOLFTPM2_CAPS + \param haveFwVer set to 1 when fwVerMinor is known, 0 when it is not + \param manifestMajor target major version from wolfTPM2_ST33_ManifestVersion + \param ccStart pointer to store the field upgrade start command code + \param ccData pointer to store the field upgrade data command code + + \sa wolfTPM2_ST33_ManifestVersion + \sa wolfTPM2_FirmwareUpgrade +*/ +WOLFTPM_TEST_API int wolfTPM2_ST33_FwUpgradeCommands(word16 fwVerMinor, + int haveFwVer, word16 manifestMajor, TPM_CC* ccStart, TPM_CC* ccData); +#endif /* WOLFTPM_ST33 || WOLFTPM_AUTODETECT */ + #endif /* WOLFTPM_FIRMWARE_UPGRADE */