diff --git a/.github/workflows/test-configs.yml b/.github/workflows/test-configs.yml index 91d517fa29..13503fd2e4 100644 --- a/.github/workflows/test-configs.yml +++ b/.github/workflows/test-configs.yml @@ -518,6 +518,18 @@ jobs: arch: arm config-file: ./config/examples/stm32h5-tz-tpm.config + stm32h5_tz_tpm_mfgid_test: + uses: ./.github/workflows/test-build.yml + with: + arch: arm + config-file: ./config/examples/stm32h5-tz-tpm-mfgid.config + + stm32h5_tz_tpm_mfgid_precomputed_test: + uses: ./.github/workflows/test-build.yml + with: + arch: arm + config-file: ./config/examples/stm32h5-tz-tpm-mfgid-precomputed.config + stm32h5_tz_dualbank_test: uses: ./.github/workflows/test-build.yml with: diff --git a/config/examples/stm32h5-tz-tpm-mfgid-precomputed.config b/config/examples/stm32h5-tz-tpm-mfgid-precomputed.config new file mode 100644 index 0000000000..2a661e4c2e --- /dev/null +++ b/config/examples/stm32h5-tz-tpm-mfgid-precomputed.config @@ -0,0 +1,44 @@ +ARCH?=ARM +TZEN?=1 +TARGET?=stm32h5 +SIGN?=ECC256 +HASH?=SHA256 +DEBUG?=0 +VTOR?=1 +CORTEX_M0?=0 +CORTEX_M33?=1 +NO_ASM?=0 +NO_MPU=1 +EXT_FLASH?=0 +SPI_FLASH?=0 +ALLOW_DOWNGRADE?=0 +NVM_FLASH_WRITEONCE?=1 +WOLFBOOT_VERSION?=1 +V?=0 +SPMATH?=1 +RAM_CODE?=1 +DUALBANK_SWAP?=0 +WOLFBOOT_PARTITION_SIZE?=0xA0000 +WOLFBOOT_SECTOR_SIZE?=0x2000 +WOLFBOOT_KEYVAULT_ADDRESS?=0x0C040000 +WOLFBOOT_KEYVAULT_SIZE?=0x1C000 +WOLFBOOT_NSC_ADDRESS?=0x0C05C000 +WOLFBOOT_NSC_SIZE?=0x4000 +WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x08060000 +WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x0C100000 +WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x0C1A0000 +FLAGS_HOME=0 +DISABLE_BACKUP=0 +WOLFCRYPT_TZ=1 +WOLFCRYPT_TZ_PKCS11=1 +IMAGE_HEADER_SIZE?=1024 +ARMORED=1 +WOLFTPM=1 +# Exercise the pre-provisioned ST33KTPM identity keys (IAK/IDevID). +# ST33 vendor support is required (wolfTPM2_SetIdentityAuth -> TPM2_GetProductInfo). +CFLAGS_EXTRA+=-DWOLFTPM_ST33 +CFLAGS_EXTRA+=-DWOLFTPM_MFG_IDENTITY +# Default precomputed mode (WOLFBOOT_TPM_MFG_AUTH_DERIVE unset): the per-device +# authValue is set directly from the WOLFBOOT_TPM_MFG_AIK_AUTH/EH_AUTH macros. +# Ships a 0xFF placeholder that fails TPM auth until provisioned per-device; this +# config exists to build-test the precomputed branch in CI. diff --git a/config/examples/stm32h5-tz-tpm-mfgid.config b/config/examples/stm32h5-tz-tpm-mfgid.config new file mode 100644 index 0000000000..aec830f0ad --- /dev/null +++ b/config/examples/stm32h5-tz-tpm-mfgid.config @@ -0,0 +1,44 @@ +ARCH?=ARM +TZEN?=1 +TARGET?=stm32h5 +SIGN?=ECC256 +HASH?=SHA256 +DEBUG?=0 +VTOR?=1 +CORTEX_M0?=0 +CORTEX_M33?=1 +NO_ASM?=0 +NO_MPU=1 +EXT_FLASH?=0 +SPI_FLASH?=0 +ALLOW_DOWNGRADE?=0 +NVM_FLASH_WRITEONCE?=1 +WOLFBOOT_VERSION?=1 +V?=0 +SPMATH?=1 +RAM_CODE?=1 +DUALBANK_SWAP?=0 +WOLFBOOT_PARTITION_SIZE?=0xA0000 +WOLFBOOT_SECTOR_SIZE?=0x2000 +WOLFBOOT_KEYVAULT_ADDRESS?=0x0C040000 +WOLFBOOT_KEYVAULT_SIZE?=0x1C000 +WOLFBOOT_NSC_ADDRESS?=0x0C05C000 +WOLFBOOT_NSC_SIZE?=0x4000 +WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x08060000 +WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x0C100000 +WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x0C1A0000 +FLAGS_HOME=0 +DISABLE_BACKUP=0 +WOLFCRYPT_TZ=1 +WOLFCRYPT_TZ_PKCS11=1 +IMAGE_HEADER_SIZE?=1024 +ARMORED=1 +WOLFTPM=1 +# Exercise the pre-provisioned ST33KTPM identity keys (IAK/IDevID). +# ST33 vendor support is required (wolfTPM2_SetIdentityAuth -> TPM2_GetProductInfo). +CFLAGS_EXTRA+=-DWOLFTPM_ST33 +CFLAGS_EXTRA+=-DWOLFTPM_MFG_IDENTITY +# Derive the authValue on-device so the test-app works on a sample TPM. The +# default precomputed mode ships a 0xFF placeholder that must be provisioned +# per-device first. +WOLFBOOT_TPM_MFG_AUTH_DERIVE=1 diff --git a/docs/TPM.md b/docs/TPM.md index 94fd77f2af..c25b279536 100644 --- a/docs/TPM.md +++ b/docs/TPM.md @@ -17,6 +17,42 @@ In wolfBoot we support TPM based root of trust, sealing/unsealing, cryptographic | `WOLFBOOT_TPM_SEAL=1` | `WOLFBOOT_TPM_SEAL` | Enables support for sealing/unsealing based on PCR policy signed externally. | | `WOLFBOOT_TPM_SEAL_NV_BASE=0x01400300` | `WOLFBOOT_TPM_SEAL_NV_BASE` | To override the default sealed blob storage location in the platform hierarchy. | | `WOLFBOOT_TPM_SEAL_AUTH=secret` | `WOLFBOOT_TPM_SEAL_AUTH` | Password for sealing/unsealing secrets, if omitted the PCR policy will be used | +| `WOLFBOOT_TPM_MFG_AUTH_DERIVE=1` | `WOLFBOOT_TPM_MFG_AUTH_DERIVE` | MFG identity: opt into on-device derive-from-master. The default is a precomputed per-device authValue (no master secret on device). Requires `WOLFTPM_MFG_IDENTITY`. | +| (header macro) | `WOLFBOOT_TPM_MFG_AIK_AUTH` / `WOLFBOOT_TPM_MFG_EH_AUTH` | Default (precomputed) mode: the 16-byte per-device AIK / EH authValues (placeholder `0xFF` default). | +| (header macro) | `WOLFBOOT_TPM_MFG_EH_MASTER` | Derive mode: override the endorsement-hierarchy master value (16-byte initializer list, sample default). | + +## TPM manufacturing identity (IAK / IDevID authValue) + +When `WOLFTPM_MFG_IDENTITY` is enabled, `wolfBoot_tpm2_get_aik()` and +`wolfBoot_tpm2_get_timestamp()` authorize the pre-provisioned ST33KTPM identity +keys. There are two ways to supply the required `authValue`: + +- **Precomputed mode (default, recommended).** The final **per-device** + `authValue` is set directly into the key handle; no master secret is present on + the device. Defaults to a `0xFF` placeholder (fails TPM auth until + provisioned). Per-device values are computed off-device at provisioning + (`SHA-256(CPSN || master)`, low 16 bytes) and baked in via + `WOLFBOOT_TPM_MFG_AIK_AUTH` / `WOLFBOOT_TPM_MFG_EH_AUTH`. When `WOLFBOOT_TPM_MFG_AUTH_DERIVE` + is not enabled, the `authOverride` argument to `wolfBoot_tpm2_get_aik()` is + treated as an optional override for the final AIK `authValue` (not a master secret). + +- **Derive mode (`WOLFBOOT_TPM_MFG_AUTH_DERIVE`).** The `authValue` is computed + on-device as the low 16 bytes of `SHA-256(TPM serial || master)`. + The endorsement master defaults to a sample and is overridable with + `WOLFBOOT_TPM_MFG_EH_MASTER`; the AIK master is passed to + `wolfBoot_tpm2_get_aik()` (NULL = sample). Convenient, but the master is + **shared across the whole reel/batch** — extracting it + from one device's firmware lets an attacker derive the `authValue` for every + sibling device. + +The byte-array macros are header defaults (overridable via `-D` / `CFLAGS_EXTRA`); +they are not plain `options.mk` variables because brace initializers contain +commas. `WOLFTPM_MFG_IDENTITY` itself is supplied via `CFLAGS_EXTRA`. + +> Note: because precomputed mode is the default and ships a `0xFF` placeholder, a +> build that enables `WOLFTPM_MFG_IDENTITY` without provisioning the authValue (or +> selecting `WOLFBOOT_TPM_MFG_AUTH_DERIVE`) fails TPM auth by design. The test-app +> builds with `WOLFBOOT_TPM_MFG_AUTH_DERIVE` so it works on a sample TPM. ## Root of Trust (ROT) diff --git a/include/tpm.h b/include/tpm.h index 08cd104468..7802f734dd 100644 --- a/include/tpm.h +++ b/include/tpm.h @@ -60,8 +60,42 @@ int CSME_NSE_API wolfBoot_tpm2_read_pcr(uint8_t pcrIndex, uint8_t* digest, int* int CSME_NSE_API wolfBoot_tpm2_read_cert(uint32_t handle, uint8_t* cert, uint32_t* certSz); #ifdef WOLFTPM_MFG_IDENTITY + +/* MFG identity auth provisioning. + * Precomputed mode (default): the final per-device authValue is set directly, + * no master secret on the device. In this mode, wolfBoot_tpm2_get_aik() treats + * the authOverride argument as an optional *authValue* override. + * Derive mode (WOLFBOOT_TPM_MFG_AUTH_DERIVE): authValue = low 16 bytes of + * SHA-256(TPM serial || master); the master is shared across the reel. + * For wolfBoot_tpm2_get_aik() the master secret is provided via the + * authOverride argument (NULL = sample). */ +#ifdef WOLFBOOT_TPM_MFG_AUTH_DERIVE +/* EH master for derive mode (sample - override in production) */ +#ifndef WOLFBOOT_TPM_MFG_EH_MASTER +#define WOLFBOOT_TPM_MFG_EH_MASTER { \ + 0xDE, 0xEF, 0x8C, 0xDF, 0x1B, 0x77, 0xBD, 0x00, \ + 0x30, 0x58, 0x5E, 0x47, 0xB8, 0x21, 0x46, 0x0B } +#endif +#else +/* 16-byte per-device authValues. Placeholder defaults (all 0xFF) fail TPM auth + * until overwritten per-device by the provisioning tool. */ +#ifndef WOLFBOOT_TPM_MFG_AIK_AUTH +#define WOLFBOOT_TPM_MFG_AIK_AUTH { \ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, \ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } +#endif +#ifndef WOLFBOOT_TPM_MFG_EH_AUTH +#define WOLFBOOT_TPM_MFG_EH_AUTH { \ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, \ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } +#endif +#endif + +/* authOverride meaning depends on WOLFBOOT_TPM_MFG_AUTH_DERIVE: + * derive mode -> master secret hashed into the authValue (NULL = sample) + * precomputed mode -> optional literal authValue override (NULL = built-in) */ int CSME_NSE_API wolfBoot_tpm2_get_aik(WOLFTPM2_KEY* aik, - uint8_t* masterPassword, uint16_t masterPasswordSz); + uint8_t* authOverride, uint16_t authOverrideSz); int CSME_NSE_API wolfBoot_tpm2_get_timestamp(WOLFTPM2_KEY* aik, GetTime_Out* getTime); int CSME_NSE_API wolfBoot_tpm2_quote(WOLFTPM2_KEY* aik, byte* pcrArray, word32 pcrArraySz, Quote_Out* quoteResult); diff --git a/options.mk b/options.mk index 37f676d797..b6b18a5af0 100644 --- a/options.mk +++ b/options.mk @@ -58,6 +58,12 @@ ifeq ($(WOLFBOOT_TPM_KEYSTORE),1) endif endif +## TPM manufacturing identity: precomputed per-device authValue is the default +## (no master secret on device). Opt into on-device derive-from-master mode: +ifeq ($(WOLFBOOT_TPM_MFG_AUTH_DERIVE),1) + CFLAGS+=-D"WOLFBOOT_TPM_MFG_AUTH_DERIVE" +endif + ifeq ($(WOLFBOOT_ATTESTATION_IAK),1) CFLAGS+=-D"WOLFBOOT_ATTESTATION_IAK" endif diff --git a/src/tpm.c b/src/tpm.c index 6a11aad253..efb783f85b 100644 --- a/src/tpm.c +++ b/src/tpm.c @@ -1345,8 +1345,22 @@ int CSME_NSE_API wolfBoot_tpm2_read_cert(uint32_t handle, uint8_t* cert, uint32_ } #ifdef WOLFTPM_MFG_IDENTITY +#ifndef WOLFBOOT_TPM_MFG_AUTH_DERIVE +/* Copy a precomputed authValue directly into a TPM handle. */ +static int wolfBoot_tpm2_set_handle_auth(WOLFTPM2_HANDLE* handle, + const uint8_t* auth, uint16_t authSz) +{ + if (authSz > (uint16_t)sizeof(handle->auth.buffer)) { + return BAD_FUNC_ARG; + } + handle->auth.size = authSz; + XMEMCPY(handle->auth.buffer, auth, authSz); + return 0; +} +#endif + int CSME_NSE_API wolfBoot_tpm2_get_aik(WOLFTPM2_KEY* aik, - uint8_t* masterPassword, uint16_t masterPasswordSz) + uint8_t* authOverride, uint16_t authOverrideSz) { int rc; if (aik == NULL) { @@ -1355,19 +1369,30 @@ int CSME_NSE_API wolfBoot_tpm2_get_aik(WOLFTPM2_KEY* aik, if (WOLFBOOT_TPM_NS_RW(aik, sizeof(*aik)) == NULL) { return BAD_FUNC_ARG; } - if (masterPassword != NULL && - WOLFBOOT_TPM_NS_R(masterPassword, masterPasswordSz) == NULL) { + if (authOverride != NULL && + WOLFBOOT_TPM_NS_R(authOverride, authOverrideSz) == NULL) { return BAD_FUNC_ARG; } /* Load existing AIK and set auth */ rc = wolfTPM2_ReadPublicKey(&wolftpm_dev, aik, TPM2_IAK_KEY_HANDLE); if (rc == 0) { - /* Custom should supply their own custom master password used during - * device provisioning. If using a sample TPM supply NULL to use the - * default password. */ +#ifdef WOLFBOOT_TPM_MFG_AUTH_DERIVE + /* Derives the authValue on-device from a master secret shared across the + * reel; the precomputed default is preferred. Supply NULL for + * authOverride to use the sample default. */ rc = wolfTPM2_SetIdentityAuth(&wolftpm_dev, &aik->handle, - masterPassword, masterPasswordSz); + authOverride, authOverrideSz); +#else + /* Precomputed (default): set the final per-device authValue directly (no + * master secret on device). Caller may override the default via + * authOverride. */ + static const uint8_t aikAuth[] = WOLFBOOT_TPM_MFG_AIK_AUTH; + const uint8_t* auth = (authOverride != NULL) ? authOverride : aikAuth; + uint16_t authSz = (authOverride != NULL) ? + authOverrideSz : (uint16_t)sizeof(aikAuth); + rc = wolfBoot_tpm2_set_handle_auth(&aik->handle, auth, authSz); +#endif } return rc; } @@ -1376,11 +1401,13 @@ int CSME_NSE_API wolfBoot_tpm2_get_timestamp(WOLFTPM2_KEY* aik, GetTime_Out* get { int rc; WOLFTPM2_HANDLE eh_handle; - /* sample master password for EH */ - uint8_t Master_EH_AuthValue[] = { - 0xDE, 0xEF, 0x8C, 0xDF, 0x1B, 0x77, 0xBD, 0x00, - 0x30, 0x58, 0x5E, 0x47, 0xB8, 0x21, 0x46, 0x0B - }; +#ifdef WOLFBOOT_TPM_MFG_AUTH_DERIVE + /* EH master secret (shared across the reel) */ + uint8_t Master_EH_AuthValue[] = WOLFBOOT_TPM_MFG_EH_MASTER; +#else + /* final per-device EH authValue */ + static const uint8_t eh_auth[] = WOLFBOOT_TPM_MFG_EH_AUTH; +#endif if (aik == NULL || getTime == NULL) { return BAD_FUNC_ARG; @@ -1395,9 +1422,17 @@ int CSME_NSE_API wolfBoot_tpm2_get_timestamp(WOLFTPM2_KEY* aik, GetTime_Out* get eh_handle.hndl = TPM_RH_ENDORSEMENT; +#ifdef WOLFBOOT_TPM_MFG_AUTH_DERIVE /* Calculate EH auth value */ rc = wolfTPM2_SetIdentityAuth(&wolftpm_dev, &eh_handle, Master_EH_AuthValue, (uint16_t)sizeof(Master_EH_AuthValue)); + /* master secret consumed; clear it from the stack */ + TPM2_ForceZero(Master_EH_AuthValue, sizeof(Master_EH_AuthValue)); +#else + /* Set EH authValue directly */ + rc = wolfBoot_tpm2_set_handle_auth(&eh_handle, eh_auth, + (uint16_t)sizeof(eh_auth)); +#endif if (rc == 0) { /* Set EH auth */ wolfTPM2_SetAuthHandle(&wolftpm_dev, 0, &eh_handle); diff --git a/tools/config.mk b/tools/config.mk index 98aa0dcae2..59d830f641 100644 --- a/tools/config.mk +++ b/tools/config.mk @@ -58,6 +58,7 @@ ifeq ($(ARCH),) MEASURED_BOOT?=0 WOLFBOOT_TPM_SEAL?=0 WOLFBOOT_TPM_KEYSTORE?=0 + WOLFBOOT_TPM_MFG_AUTH_DERIVE?=0 WOLFBOOT_ATTESTATION_IAK?=0 WOLFBOOT_ATTESTATION_TEST?=0 WOLFBOOT_UNIVERSAL_KEYSTORE?=0 @@ -106,6 +107,7 @@ CONFIG_VARS:= ARCH TARGET SIGN HASH MCUXSDK MCUXPRESSO MCUXPRESSO_CPU MCUXPRESSO DISABLE_BACKUP WOLFBOOT_VERSION V NO_MPU ENCRYPT FLAGS_HOME FLAGS_INVERT \ SPMATH SPMATHALL RAM_CODE DUALBANK_SWAP IMAGE_HEADER_SIZE PKA TZEN PSOC6_CRYPTO \ WOLFTPM WOLFBOOT_TPM_VERIFY MEASURED_BOOT WOLFBOOT_TPM_SEAL WOLFBOOT_TPM_KEYSTORE \ + WOLFBOOT_TPM_MFG_AUTH_DERIVE \ WOLFBOOT_ATTESTATION_IAK \ WOLFBOOT_ATTESTATION_TEST \ WOLFBOOT_UDS_UID_FALLBACK_FORTEST \