diff --git a/.github/workflows/test-wolfhsm-simulator.yml b/.github/workflows/test-wolfhsm-simulator.yml index a5646d1ac1..2228a9bd9f 100644 --- a/.github/workflows/test-wolfhsm-simulator.yml +++ b/.github/workflows/test-wolfhsm-simulator.yml @@ -44,6 +44,39 @@ jobs: needs_posix_server: false posix_server_nvminit: false needs_nvm_image: true + # The "secondary root fallback" entries below provision NVM with a + # mismatched root CA at the primary slot (id 1) and the real root at + # the secondary slot (id 2). This forces wolfBoot to take the + # WOLFHSM_SECONDARY_ROOT_CA_NVM_ID fallback path during cert chain + # verification and ensures the sunny-day update still succeeds. + - name: "wolfHSM client cert chain verify ECC, secondary root fallback" + file: "config/examples/sim-wolfHSM-client-certchain-ecc.config" + needs_posix_server: true + posix_server_nvminit: true + needs_nvm_image: false + secondary_root_fallback: true + cert_algo: ecc256 + - name: "wolfHSM client cert chain verify RSA4096, secondary root fallback" + file: "config/examples/sim-wolfHSM-client-certchain-rsa4096.config" + needs_posix_server: true + posix_server_nvminit: true + needs_nvm_image: false + secondary_root_fallback: true + cert_algo: rsa4096 + - name: "wolfHSM server cert chain verify ECC, secondary root fallback" + file: "config/examples/sim-wolfHSM-server-certchain-ecc.config" + needs_posix_server: false + posix_server_nvminit: false + needs_nvm_image: true + secondary_root_fallback: true + cert_algo: ecc256 + - name: "wolfHSM server cert chain verify RSA4096, secondary root fallback" + file: "config/examples/sim-wolfHSM-server-certchain-rsa4096.config" + needs_posix_server: false + posix_server_nvminit: false + needs_nvm_image: true + secondary_root_fallback: true + cert_algo: rsa4096 fail-fast: false @@ -74,7 +107,25 @@ jobs: - name: Build wolfboot.elf run: | - make clean && make test-sim-internal-flash-with-update + make clean + if [ "${{ matrix.config.secondary_root_fallback }}" = "true" ]; then + make test-sim-internal-flash-with-update WOLFHSM_SECONDARY_ROOT_CA_NVM_ID=2 + else + make test-sim-internal-flash-with-update + fi + + # Generate an unrelated "wrong" root CA to provision at the primary NVM + # slot. This forces verification against the primary root to fail so the + # WOLFHSM_SECONDARY_ROOT_CA_NVM_ID fallback path is exercised. + - name: Generate wrong root CA for secondary root fallback test + if: matrix.config.secondary_root_fallback + run: | + rm -rf test-dummy-ca-wrong + tools/scripts/sim-gen-dummy-chain.sh --algo ${{ matrix.config.cert_algo }} --outdir test-dummy-ca-wrong + if cmp -s test-dummy-ca/root-cert.der test-dummy-ca-wrong/root-cert.der; then + echo "Wrong root cert is identical to the real root; secondary fallback would not be exercised" >&2 + exit 1 + fi - name: Build example POSIX TCP server if: matrix.config.needs_posix_server @@ -87,7 +138,13 @@ jobs: cd lib/wolfHSM/examples/posix/wh_posix_server if [ "${{ matrix.config.posix_server_nvminit }}" = "true" ]; then tmpfile=$(mktemp) - echo "obj 1 0xFFFF 0x0000 \"cert CA\" ../../../../../test-dummy-ca/root-cert.der" >> $tmpfile + if [ "${{ matrix.config.secondary_root_fallback }}" = "true" ]; then + # Mismatched root at primary slot (id 1) and real root at secondary slot (id 2) + echo "obj 1 0xFFFF 0x0000 \"cert CA wrong\" ../../../../../test-dummy-ca-wrong/root-cert.der" >> $tmpfile + echo "obj 2 0xFFFF 0x0000 \"cert CA right\" ../../../../../test-dummy-ca/root-cert.der" >> $tmpfile + else + echo "obj 1 0xFFFF 0x0000 \"cert CA\" ../../../../../test-dummy-ca/root-cert.der" >> $tmpfile + fi ./Build/wh_posix_server.elf --type tcp --nvminit $tmpfile & else # --flags=0x100 sets the WH_NVM_FLAGS_USAGE_VERIFY flag @@ -103,7 +160,13 @@ jobs: run: | make -C lib/wolfHSM/tools/whnvmtool tmpfile=$(mktemp) - echo "obj 1 0xFFFF 0x0000 \"cert CA\" test-dummy-ca/root-cert.der" >> $tmpfile + if [ "${{ matrix.config.secondary_root_fallback }}" = "true" ]; then + # Mismatched root at primary slot (id 1) and real root at secondary slot (id 2) + echo "obj 1 0xFFFF 0x0000 \"cert CA wrong\" test-dummy-ca-wrong/root-cert.der" >> $tmpfile + echo "obj 2 0xFFFF 0x0000 \"cert CA right\" test-dummy-ca/root-cert.der" >> $tmpfile + else + echo "obj 1 0xFFFF 0x0000 \"cert CA\" test-dummy-ca/root-cert.der" >> $tmpfile + fi ./lib/wolfHSM/tools/whnvmtool/whnvmtool --image=wolfBoot_wolfHSM_NVM.bin --size=16348 --invert-erased-byte $tmpfile # Run the sunny day update test against the server diff --git a/docs/Signing.md b/docs/Signing.md index af21fa6855..f34fa5cdc7 100644 --- a/docs/Signing.md +++ b/docs/Signing.md @@ -134,6 +134,7 @@ When building wolfBoot and the test app with the Makefile, the `USER_*` variable - `USER_PRIVATE_KEY`: Path to your leaf signing key (DER format) - `USER_PUBLIC_KEY`: Path to your leaf public key (DER format) - `USER_CERT_CHAIN`: Path to your certificate chain (DER format) +- `WOLFHSM_SECONDARY_ROOT_CA_NVM_ID`: Optional wolfHSM NVM object ID for a secondary root CA fallback Example: @@ -150,7 +151,7 @@ If `USER_CERT_CHAIN` is not provided when `CERT_CHAIN_VERIFY=1`, a dummy certifi **Note:** If your private key is managed by a third party and you only have access to the public key, use `keygen -i` to import it instead. See the [Keygen tool](#keygen-tool) section above. -Certificate chain verification of images is currently limited to use in conjunction with wolfHSM. See [wolfHSM.md](wolfHSM.md) for more details. +Certificate chain verification of images is currently limited to use in conjunction with wolfHSM. For wolfHSM builds, `WOLFHSM_SECONDARY_ROOT_CA_NVM_ID=` enables fallback to a second root CA only when primary root verification fails. The secondary root certificate must be provisioned in wolfHSM NVM at that ID. See [wolfHSM.md](wolfHSM.md) for more details. #### Target partition id (Multiple partition images, "self-update" feature) diff --git a/docs/compile.md b/docs/compile.md index 4081d837d9..ac936185af 100644 --- a/docs/compile.md +++ b/docs/compile.md @@ -441,6 +441,10 @@ make CERT_CHAIN_VERIFY=1 \ When `CERT_CHAIN_VERIFY=1` is set without `USER_CERT_CHAIN`, the build auto-generates a dummy 3-tier certificate hierarchy in `test-dummy-ca/` for testing. This also applies to wolfHSM NVM image generation when applicable. +For wolfHSM certificate-chain builds, `WOLFHSM_SECONDARY_ROOT_CA_NVM_ID=` can be set to compile in a secondary root CA fallback. wolfBoot verifies against the primary root CA NVM ID provided by the HAL first, then retries with the configured secondary NVM ID only if the primary certificate-chain verification fails. Leave it unset for the existing single-root behavior. + +The secondary root certificate must be provisioned in wolfHSM NVM at the configured object ID. The option applies only to wolfHSM certificate-chain trust validation; signature verification continues to use the cached leaf public key from whichever root verified the chain. + ### Manual Key Management For advanced scenarios (multiple keys, mixed algorithms, partition-restricted keys, or third-party managed private keys), use the `keygen` tool directly instead of the `USER_*` variables. diff --git a/docs/wolfHSM.md b/docs/wolfHSM.md index b6fafbcb94..c8afe64823 100644 --- a/docs/wolfHSM.md +++ b/docs/wolfHSM.md @@ -51,7 +51,7 @@ The certificate verification process with wolfHSM works as follows: 4. A certificate chain is created consisting of the signing identity certificate and an optional number of intermediate certificates, where trust is chained back to the root CA. 5. During the signing process, the image is signed with the signer private key and the certificate chain is embedded in the firmware image header. 6. During boot, wolfBoot extracts the certificate chain from the firmware header -7. wolfBoot uses the wolfHSM server (remotely or directly, depending on configuration) to verify the certificate chain against a pre-provisioned root CA certificate stored on the HSM and caches the public key of the leaf certificate if the chain verifies as trusted +7. wolfBoot uses the wolfHSM server (remotely or directly, depending on configuration) to verify the certificate chain against a pre-provisioned root CA certificate stored on the HSM and caches the public key of the leaf certificate if the chain verifies as trusted. If `WOLFHSM_SECONDARY_ROOT_CA_NVM_ID` is configured and the primary root check fails, wolfBoot retries the certificate chain against that secondary root CA NVM object. 8. If the chain is trusted, wolfBoot uses the cached public key from the leaf certificate to verify the firmware signature on the wolfHSM server To use certificate verification with wolfHSM: @@ -61,6 +61,8 @@ To use certificate verification with wolfHSM: 3. Pre-provision the root CA certificate on the wolfHSM server at the NVM ID specified by the HAL `hsmNvmIdCertRootCA` 4. Sign firmware images with the `--cert-chain` option, providing a DER-encoded certificate chain +Optionally set `WOLFHSM_SECONDARY_ROOT_CA_NVM_ID=` to allow certificate-chain fallback to a second wolfHSM root CA object. The secondary root certificate must be provisioned in wolfHSM NVM at the configured object ID. This fallback only affects wolfHSM certificate-chain trust validation; the image signature is still verified with the cached leaf public key from the chain that verified successfully. + To build the simulator using wolfHSM for certificate verification: - **Client Mode**: Use [config/examples/sim-wolfHSM-client-certchain.config](config/examples/sim-wolfHSM-client-certchain.config) @@ -84,6 +86,12 @@ This option enables use of the reserved wolfHSM public key ID for firmware authe If this option is not defined, cryptographic operations are still performed on the wolfHSM server, but wolfBoot assumes the key material is present in the keystore and NOT stored on the HSM. This means that wolfBoot will first load keys from the keystore, send the key material to the wolfHSM server at the time of use (cached as ephemeral keys), and finally evict the key from the HSM after usage. This behavior is typically only useful for debugging or testing scenarios, where the keys may not be pre-loaded onto the HSM. The keystore for use in this mode should not be generated with the `--nolocalkeys` option. +### `WOLFHSM_SECONDARY_ROOT_CA_NVM_ID` + +This Make/config option defines `WOLFBOOT_WOLFHSM_SECONDARY_ROOT_CA_NVM_ID` and enables a secondary root CA fallback for wolfHSM certificate-chain verification. wolfBoot first verifies the image certificate chain against `hsmNvmIdCertRootCA`. If that attempt fails due to a wolfHSM API error or a certificate validation failure, wolfBoot retries with the configured secondary NVM ID. + +Leave this option unset to preserve the normal single-root behavior. When set, the secondary root CA certificate must already be provisioned in wolfHSM NVM at that object ID, for example `WOLFHSM_SECONDARY_ROOT_CA_NVM_ID=2`. + ## HAL Implementations In addition to the standard wolfBoot HAL functions, wolfHSM-enabled platforms must also implement or instantiate the following wolfHSM-specific items in the platform HAL: @@ -96,6 +104,7 @@ In addition to the standard wolfBoot HAL functions, wolfHSM-enabled platforms mu - `hsmDevIdHash`: The HSM device ID for hash operations. This is used to identify the HSM device to wolfBoot. - `hsmDevIdPubKey`: The HSM device ID for public key operations. This is used to identify the HSM device to wolfBoot. - `hsmKeyIdPubKey`: The HSM key ID for public key operations. This is used to identify the key to use for public key operations. +- `hsmNvmIdCertRootCA`: The wolfHSM NVM ID for the primary trusted root CA certificate when certificate-chain verification is enabled. ### Client HAL Functions diff --git a/options.mk b/options.mk index b70cb4e6b8..1cda79123c 100644 --- a/options.mk +++ b/options.mk @@ -1384,6 +1384,11 @@ ifeq ($(WOLFHSM_SERVER),1) endif +# Optional secondary wolfHSM root CA NVM ID for certificate-chain fallback. +ifneq ($(WOLFHSM_SECONDARY_ROOT_CA_NVM_ID),) + CFLAGS += -DWOLFBOOT_WOLFHSM_SECONDARY_ROOT_CA_NVM_ID=$(WOLFHSM_SECONDARY_ROOT_CA_NVM_ID) +endif + # wolfBoot hooks framework # WOLFBOOT_HOOKS_FILE: path to a single .c file containing hook definitions WOLFBOOT_HOOKS_ENABLED := diff --git a/src/image.c b/src/image.c index aee76a2874..577927dbad 100644 --- a/src/image.c +++ b/src/image.c @@ -76,6 +76,59 @@ int NOINLINEFUNCTION image_CT_compare( defined(WOLFBOOT_ENABLE_WOLFHSM_SERVER)) static whKeyId g_certLeafKeyId = WH_KEYID_ERASED; static int g_leafKeyIdValid = 0; + +static void wolfBoot_cert_chain_evict_leaf_key(void) +{ + if (g_certLeafKeyId != WH_KEYID_ERASED) { +#if defined(WOLFBOOT_ENABLE_WOLFHSM_CLIENT) + (void)wh_Client_KeyEvict(&hsmClientCtx, g_certLeafKeyId); +#elif defined(WOLFBOOT_ENABLE_WOLFHSM_SERVER) + (void)wh_Server_KeystoreEvictKey(&hsmServerCtx, g_certLeafKeyId); +#endif + } + + g_certLeafKeyId = WH_KEYID_ERASED; + g_leafKeyIdValid = 0; +} + +static int wolfBoot_cert_chain_verify_with_root(uint8_t *cert_chain, + uint16_t cert_chain_size, whNvmId root_id, int32_t *verify_result) +{ + int hsm_ret = WH_ERROR_OK; + + *verify_result = -1; + + /* Verify certificate chain using wolfHSM's verification API. Use DMA if + * available in the wolfHSM configuration. */ +#if defined(WOLFBOOT_ENABLE_WOLFHSM_CLIENT) +#if defined(WOLFHSM_CFG_DMA) + wolfBoot_printf("verifying cert chain and caching leaf pubkey " + "(root=%08x, using DMA)\n", (unsigned int)root_id); + hsm_ret = wh_Client_CertVerifyDmaAndCacheLeafPubKey( + &hsmClientCtx, cert_chain, cert_chain_size, root_id, + WH_NVM_FLAGS_USAGE_VERIFY, &g_certLeafKeyId, verify_result); +#else + wolfBoot_printf("verifying cert chain and caching leaf pubkey " + "(root=%08x)\n", (unsigned int)root_id); + hsm_ret = wh_Client_CertVerifyAndCacheLeafPubKey( + &hsmClientCtx, cert_chain, cert_chain_size, root_id, + WH_NVM_FLAGS_USAGE_VERIFY, &g_certLeafKeyId, verify_result); +#endif +#elif defined(WOLFBOOT_ENABLE_WOLFHSM_SERVER) + wolfBoot_printf("verifying cert chain and caching leaf pubkey " + "(root=%08x)\n", (unsigned int)root_id); + hsm_ret = wh_Server_CertVerify( + &hsmServerCtx, cert_chain, cert_chain_size, root_id, + WH_CERT_FLAGS_CACHE_LEAF_PUBKEY, WH_NVM_FLAGS_USAGE_VERIFY, + &g_certLeafKeyId); + if (hsm_ret == WH_ERROR_OK) { + *verify_result = 0; + } + wolfBoot_printf("wh_Server_CertVerify returned %d\n", hsm_ret); +#endif + + return hsm_ret; +} #endif /* TPM based verify */ @@ -328,12 +381,7 @@ static void wolfBoot_verify_signature_ecc(uint8_t key_slot, } #if defined(WOLFBOOT_CERT_CHAIN_VERIFY) if (g_leafKeyIdValid) { - #if defined(WOLFBOOT_ENABLE_WOLFHSM_CLIENT) - (void)wh_Client_KeyEvict(&hsmClientCtx, g_certLeafKeyId); - #elif defined(WOLFBOOT_ENABLE_WOLFHSM_SERVER) - (void)wh_Server_KeystoreEvictKey(&hsmServerCtx, g_certLeafKeyId); - #endif - g_leafKeyIdValid = 0; + wolfBoot_cert_chain_evict_leaf_key(); } #endif #else @@ -546,12 +594,7 @@ static void wolfBoot_verify_signature_rsa_common(uint8_t key_slot, } #elif defined(WOLFBOOT_CERT_CHAIN_VERIFY) if (g_leafKeyIdValid) { -#if defined(WOLFBOOT_ENABLE_WOLFHSM_CLIENT) - (void)wh_Client_KeyEvict(&hsmClientCtx, g_certLeafKeyId); -#elif defined(WOLFBOOT_ENABLE_WOLFHSM_SERVER) - (void)wh_Server_KeystoreEvictKey(&hsmServerCtx, g_certLeafKeyId); -#endif - g_leafKeyIdValid = 0; + wolfBoot_cert_chain_evict_leaf_key(); } #endif /* !WOLFBOOT_USE_WOLFHSM_PUBKEY_ID */ #else @@ -2260,11 +2303,17 @@ int wolfBoot_verify_authenticity(struct wolfBoot_image *img) defined(WOLFBOOT_ENABLE_WOLFHSM_SERVER)) uint8_t* cert_chain; uint16_t cert_chain_size; - int32_t cert_verify_result; + int32_t cert_verify_result; + int32_t primary_verify_result; int hsm_ret; + int primary_hsm_ret; +#ifdef WOLFBOOT_WOLFHSM_SECONDARY_ROOT_CA_NVM_ID + int32_t secondary_verify_result; + int secondary_hsm_ret; +#endif /* Reset certificate chain usage for this verification */ - g_leafKeyIdValid = 0; + wolfBoot_cert_chain_evict_leaf_key(); #endif stored_signature_size = get_header(img, HDR_SIGNATURE, &stored_signature); @@ -2336,41 +2385,49 @@ int wolfBoot_verify_authenticity(struct wolfBoot_image *img) wolfBoot_printf("Found certificate chain (%d bytes)\n", cert_chain_size); - /* Verify certificate chain using wolfHSM's verification API. Use DMA if - * available in the wolfHSM configuration */ -#if defined(WOLFBOOT_ENABLE_WOLFHSM_CLIENT) -#if defined(WOLFHSM_CFG_DMA) - wolfBoot_printf( - "verifying cert chain and caching leaf pubkey (using DMA)\n"); - hsm_ret = wh_Client_CertVerifyDmaAndCacheLeafPubKey( - &hsmClientCtx, cert_chain, cert_chain_size, hsmNvmIdCertRootCA, - WH_NVM_FLAGS_USAGE_VERIFY, &g_certLeafKeyId, &cert_verify_result); -#else - wolfBoot_printf("verifying cert chain and caching leaf pubkey\n"); - hsm_ret = wh_Client_CertVerifyAndCacheLeafPubKey( - &hsmClientCtx, cert_chain, cert_chain_size, hsmNvmIdCertRootCA, - WH_NVM_FLAGS_USAGE_VERIFY, &g_certLeafKeyId, &cert_verify_result); -#endif -#elif defined(WOLFBOOT_ENABLE_WOLFHSM_SERVER) - wolfBoot_printf("verifying cert chain and caching leaf pubkey\n"); - hsm_ret = wh_Server_CertVerify( - &hsmServerCtx, cert_chain, cert_chain_size, hsmNvmIdCertRootCA, - WH_CERT_FLAGS_CACHE_LEAF_PUBKEY, WH_NVM_FLAGS_USAGE_VERIFY, - &g_certLeafKeyId); - if (hsm_ret == WH_ERROR_OK) { - cert_verify_result = 0; + primary_hsm_ret = wolfBoot_cert_chain_verify_with_root(cert_chain, + cert_chain_size, hsmNvmIdCertRootCA, &primary_verify_result); + hsm_ret = primary_hsm_ret; + cert_verify_result = primary_verify_result; + +#ifdef WOLFBOOT_WOLFHSM_SECONDARY_ROOT_CA_NVM_ID + if (hsm_ret != WH_ERROR_OK || cert_verify_result != 0) { + wolfBoot_printf("Primary certificate chain verification failed: " + "hsm_ret=%d, verify_result=%d; retrying with " + "secondary root %08x\n", + hsm_ret, cert_verify_result, + (unsigned int) + WOLFBOOT_WOLFHSM_SECONDARY_ROOT_CA_NVM_ID); + wolfBoot_cert_chain_evict_leaf_key(); + + secondary_hsm_ret = wolfBoot_cert_chain_verify_with_root(cert_chain, + cert_chain_size, + (whNvmId)WOLFBOOT_WOLFHSM_SECONDARY_ROOT_CA_NVM_ID, + &secondary_verify_result); + hsm_ret = secondary_hsm_ret; + cert_verify_result = secondary_verify_result; + + if (hsm_ret != WH_ERROR_OK || cert_verify_result != 0) { + wolfBoot_printf("Certificate chain verification failed: " + "primary_hsm_ret=%d, " + "primary_verify_result=%d, " + "secondary_hsm_ret=%d, " + "secondary_verify_result=%d\n", + primary_hsm_ret, primary_verify_result, + secondary_hsm_ret, secondary_verify_result); + wolfBoot_cert_chain_evict_leaf_key(); + return -1; + } } - wolfBoot_printf("wh_Server_CertVerify returned %d\n", hsm_ret); -#endif - - /* Error or verification failure results in standard auth check failure - * path */ - if (hsm_ret != 0 || cert_verify_result != 0) { +#else + if (hsm_ret != WH_ERROR_OK || cert_verify_result != 0) { wolfBoot_printf("Certificate chain verification failed: " "hsm_ret=%d, verify_result=%d\n", hsm_ret, cert_verify_result); + wolfBoot_cert_chain_evict_leaf_key(); return -1; } +#endif wolfBoot_printf("Certificate chain verified, using leaf key ID: %08x\n", (unsigned int)g_certLeafKeyId); diff --git a/tools/config.mk b/tools/config.mk index 4ef65ca06b..4b2b9da3b3 100644 --- a/tools/config.mk +++ b/tools/config.mk @@ -91,6 +91,7 @@ ifeq ($(ARCH),) FLASH_MULTI_SECTOR_ERASE=0 WOLFHSM_CLIENT=0 WOLFHSM_CLIENT_LOCAL_KEYS=0 + WOLFHSM_SECONDARY_ROOT_CA_NVM_ID?= endif CONFIG_VARS:= ARCH TARGET SIGN HASH MCUXSDK MCUXPRESSO MCUXPRESSO_CPU MCUXPRESSO_DRIVERS \ @@ -125,4 +126,5 @@ CONFIG_VARS:= ARCH TARGET SIGN HASH MCUXSDK MCUXPRESSO MCUXPRESSO_CPU MCUXPRESSO SIGN_SECONDARY \ WOLFHSM_CLIENT \ WOLFHSM_CLIENT_LOCAL_KEYS \ + WOLFHSM_SECONDARY_ROOT_CA_NVM_ID \ ENCRYPT_CACHE