From ab80578d59a4b507cce8f3e8787008da91449df1 Mon Sep 17 00:00:00 2001 From: Marco Oliverio Date: Thu, 25 Jun 2026 14:44:58 +0200 Subject: [PATCH] image: check integrity in wolfBoot_verify_authenticity() --- src/image.c | 5 +- tools/unit-tests/Makefile | 8 +- tools/unit-tests/unit-image.c | 162 +++++++++++++++++++++++++++++++++- 3 files changed, 169 insertions(+), 6 deletions(-) diff --git a/src/image.c b/src/image.c index 2be35718e0..2ed39b3467 100644 --- a/src/image.c +++ b/src/image.c @@ -2305,10 +2305,9 @@ int wolfBoot_verify_authenticity(struct wolfBoot_image *img) image_type = (uint16_t)(image_type_buf[0] + (image_type_buf[1] << 8)); if ((image_type & HDR_IMG_TYPE_AUTH_MASK) != HDR_IMG_TYPE_AUTH) return -1; - if (img->sha_hash == NULL) { - if (image_hash(img, digest) != 0) + if ((img->sha_hash == NULL) || (img->sha_ok != 1U)) { + if (wolfBoot_verify_integrity(img) != 0) return -1; - img->sha_hash = digest; } image_part = image_type & HDR_IMG_TYPE_PART_MASK; #ifdef WOLFBOOT_NO_KEYSTORE diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index 6d92660010..bd83eb9fca 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -52,7 +52,7 @@ TESTS:=unit-parser unit-fdt unit-extflash unit-string unit-spi-flash unit-aes128 unit-uart-flash \ unit-aes256 unit-chacha20 unit-pci unit-mock-state unit-sectorflags \ unit-max-space \ - unit-image unit-image-rsa unit-nvm unit-nvm-flagshome unit-enc-nvm \ + unit-image unit-image-hybrid unit-image-rsa unit-nvm unit-nvm-flagshome unit-enc-nvm \ unit-enc-nvm-flagshome unit-delta unit-gzip unit-update-flash unit-update-flash-delta \ unit-update-flash-hook \ unit-update-flash-self-update \ @@ -417,6 +417,12 @@ unit-sectorflags: ../../include/target.h unit-sectorflags.c unit-image: unit-image.c unit-common.c $(WOLFCRYPT_SRC) gcc -o $@ $^ $(CFLAGS) $(WOLFCRYPT_CFLAGS) $(LDFLAGS) +unit-image-hybrid: ../../include/target.h unit-image.c unit-common.c $(WOLFCRYPT_SRC) + gcc -o $@ unit-image.c unit-common.c $(WOLFCRYPT_SRC) \ + $(CFLAGS) $(WOLFCRYPT_CFLAGS) -DUNIT_IMAGE_HYBRID_ONLY \ + -DSIGN_HYBRID -DWOLFBOOT_SIGN_SECONDARY_ECC256 \ + -DIMAGE_HEADER_SIZE=512 $(LDFLAGS) + unit-image-nopart: ../../include/target.h unit-image.c unit-common.c $(WOLFCRYPT_SRC) gcc -o $@ unit-image.c unit-common.c $(WOLFCRYPT_SRC) \ $(CFLAGS) $(WOLFCRYPT_CFLAGS) -DWOLFBOOT_NO_PARTITIONS -DMOCK_PARTITIONS \ diff --git a/tools/unit-tests/unit-image.c b/tools/unit-tests/unit-image.c index 5407eed590..84e3276048 100644 --- a/tools/unit-tests/unit-image.c +++ b/tools/unit-tests/unit-image.c @@ -79,6 +79,10 @@ static int ecc_init_fail = 1; static int ecc_import_fail = 1; static int verify_called = 0; +static int verify_hash_count = 0; +static int verify_capture_hashes = 0; +static int verify_reject_hash_mismatch = 0; +static uint8_t verify_hashes[2][WOLFBOOT_SHA_DIGEST_SIZE]; static int find_header_fail = 0; static int find_header_called = 0; @@ -263,6 +267,48 @@ static void patch_image_type_part(uint8_t *img, uint32_t img_len, uint16_t part) ptr[0] = (uint8_t)(type & 0xFF); ptr[1] = (uint8_t)(type >> 8); } + +static void patch_stored_hash(uint8_t *img, uint32_t img_len) +{ + struct wolfBoot_image tmp_img; + uint8_t hash[WOLFBOOT_SHA_DIGEST_SIZE]; + uint8_t *stored_sha = NULL; + uint16_t stored_sha_len; + int saved_find_header_mocked = find_header_mocked; + + (void)img_len; + find_header_mocked = 0; + memset(&tmp_img, 0, sizeof(tmp_img)); + tmp_img.part = PART_BOOT; + tmp_img.hdr = img; + tmp_img.fw_base = img + IMAGE_HEADER_SIZE; + tmp_img.fw_size = wolfBoot_image_size(img); + + ck_assert_int_eq(image_hash(&tmp_img, hash), 0); + stored_sha_len = _find_header(img + IMAGE_HEADER_OFFSET, WOLFBOOT_SHA_HDR, + &stored_sha); + ck_assert_uint_eq(stored_sha_len, WOLFBOOT_SHA_DIGEST_SIZE); + memcpy(stored_sha, hash, WOLFBOOT_SHA_DIGEST_SIZE); + find_header_mocked = saved_find_header_mocked; +} + +static void append_header_tag(uint8_t *img, uint32_t img_len, uint32_t *idx, + uint16_t type, uint16_t len, const uint8_t *data) +{ + ck_assert_ptr_nonnull(img); + ck_assert_ptr_nonnull(idx); + ck_assert_ptr_nonnull(data); + ck_assert_uint_le(*idx + 4U + len, img_len); + ck_assert_uint_le(*idx + 4U + len, IMAGE_HEADER_SIZE); + + img[*idx] = (uint8_t)(type & 0xFF); + img[*idx + 1U] = (uint8_t)(type >> 8); + img[*idx + 2U] = (uint8_t)(len & 0xFF); + img[*idx + 3U] = (uint8_t)(len >> 8); + memcpy(img + *idx + 4U, data, len); + *idx += 4U + len; +} + static const unsigned int test_img_len = 275; @@ -293,6 +339,37 @@ unsigned char test_img_v123_signed_bin[] = { }; unsigned int test_img_v123_signed_bin_len = 275; +#if defined(UNIT_IMAGE_HYBRID_ONLY) +static void build_hybrid_test_image(uint8_t *img, uint32_t img_len) +{ + uint8_t secondary_pubkey_hint[WOLFBOOT_SHA_DIGEST_SIZE]; + uint8_t secondary_signature[ECC_IMAGE_SIGNATURE_SIZE]; + uint32_t idx = 256U; + uint32_t fixture_payload_len; + + ck_assert_uint_ge(IMAGE_HEADER_SIZE, 512U); + ck_assert_uint_ge(img_len, IMAGE_HEADER_SIZE + + wolfBoot_image_size(test_img_v123_signed_bin)); + + memset(img, 0xFF, img_len); + memcpy(img, test_img_v123_signed_bin, 256U); + + fixture_payload_len = test_img_v123_signed_bin_len - 256U; + memcpy(img + IMAGE_HEADER_SIZE, test_img_v123_signed_bin + 256U, + fixture_payload_len); + + patch_image_type_auth(img, img_len); + patch_pubkey_hint_slot(img, img_len, 0); + + key_hash(2, secondary_pubkey_hint); + memset(secondary_signature, 0, sizeof(secondary_signature)); + append_header_tag(img, img_len, &idx, HDR_SECONDARY_PUBKEY, + sizeof(secondary_pubkey_hint), secondary_pubkey_hint); + append_header_tag(img, img_len, &idx, HDR_SECONDARY_SIGNATURE, + sizeof(secondary_signature), secondary_signature); +} +#endif + static uint16_t _find_header(uint8_t *haystack, uint16_t type, uint8_t **ptr) { @@ -388,8 +465,21 @@ int wc_ecc_import_unsigned(ecc_key* key, const byte* qx, const byte* qy, int wc_ecc_verify_hash_ex(mp_int *r, mp_int *s, const byte* hash, word32 hashlen, int* res, ecc_key* key) { + int call_idx = verify_hash_count; + int valid_hash = (hash != NULL && hashlen == WOLFBOOT_SHA_DIGEST_SIZE); + + if (verify_capture_hashes && call_idx < 2 && valid_hash) { + memcpy(verify_hashes[call_idx], hash, WOLFBOOT_SHA_DIGEST_SIZE); + } + verify_hash_count++; verify_called++; - *res = 1; + if (verify_capture_hashes && verify_reject_hash_mismatch && + call_idx > 0 && valid_hash && + memcmp(hash, verify_hashes[0], WOLFBOOT_SHA_DIGEST_SIZE) != 0) { + *res = 0; + } else { + *res = 1; + } return 0; } #endif @@ -648,6 +738,7 @@ START_TEST(test_headers) } #if defined(WOLFBOOT_SIGN_ECC256) +#ifdef WOLFBOOT_FIXED_PARTITIONS START_TEST(test_verify_authenticity) { struct wolfBoot_image test_img; @@ -685,12 +776,16 @@ START_TEST(test_verify_authenticity) ext_flash_erase(0, 2 * WOLFBOOT_SECTOR_SIZE); ext_flash_write(0, test_img_v123_signed_bin, test_img_v123_signed_bin_len); + memset(&test_img, 0, sizeof(struct wolfBoot_image)); + ret = wolfBoot_open_image(&test_img, PART_UPDATE); + ck_assert_int_eq(ret, 0); test_img.signature_ok = 1; /* mock for VERIFY_FN */ ret = wolfBoot_verify_authenticity(&test_img); ck_assert_int_eq(ret, 0); } END_TEST +#endif /* WOLFBOOT_FIXED_PARTITIONS */ START_TEST(test_verify_authenticity_bad_siglen) { @@ -762,6 +857,7 @@ START_TEST(test_verify_authenticity_rejects_disallowed_key_mask) } END_TEST +#ifdef WOLFBOOT_FIXED_PARTITIONS START_TEST(test_verify_authenticity_allows_permitted_key_mask) { struct wolfBoot_image test_img; @@ -772,6 +868,7 @@ START_TEST(test_verify_authenticity_allows_permitted_key_mask) patch_image_type_auth(buf, sizeof(buf)); patch_pubkey_hint_slot(buf, sizeof(buf), 1); patch_image_type_part(buf, sizeof(buf), HDR_IMG_TYPE_APP); + patch_stored_hash(buf, sizeof(buf)); find_header_mocked = 0; find_header_fail = 0; @@ -782,12 +879,60 @@ START_TEST(test_verify_authenticity_allows_permitted_key_mask) ext_flash_write(0, buf, sizeof(buf)); memset(&test_img, 0, sizeof(struct wolfBoot_image)); - test_img.part = PART_UPDATE; + ret = wolfBoot_open_image(&test_img, PART_UPDATE); + ck_assert_int_eq(ret, 0); test_img.signature_ok = 1; ret = wolfBoot_verify_authenticity(&test_img); ck_assert_int_eq(ret, 0); } END_TEST +#endif /* WOLFBOOT_FIXED_PARTITIONS */ + +#if defined(UNIT_IMAGE_HYBRID_ONLY) && defined(SIGN_HYBRID) && \ + defined(WOLFBOOT_SIGN_SECONDARY_ECC256) +START_TEST(test_verify_authenticity_hybrid_direct_call_uses_verified_sha) +{ + struct wolfBoot_image test_img; + uint8_t *stored_sha = NULL; + uint16_t stored_sha_len; + uint32_t image_len; + uint8_t image[IMAGE_HEADER_SIZE + 123U]; + int ret; + + find_header_mocked = 0; + find_header_fail = 0; + hdr_cpy_done = 0; + ecc_import_fail = 0; + ecc_init_fail = 0; + verify_hash_count = 0; + verify_capture_hashes = 1; + verify_reject_hash_mismatch = 1; + verify_called = 0; + + build_hybrid_test_image(image, sizeof(image)); + image_len = IMAGE_HEADER_SIZE + wolfBoot_image_size(image); + + ext_flash_erase(WOLFBOOT_PARTITION_UPDATE_ADDRESS, WOLFBOOT_SECTOR_SIZE); + ext_flash_write(WOLFBOOT_PARTITION_UPDATE_ADDRESS, image, image_len); + + ret = wolfBoot_open_image(&test_img, PART_UPDATE); + ck_assert_int_eq(ret, 0); + ck_assert_ptr_null(test_img.sha_hash); + ck_assert_uint_eq(test_img.sha_ok, 0); + + ret = wolfBoot_verify_authenticity(&test_img); + ck_assert_int_eq(ret, 0); + + stored_sha_len = get_header(&test_img, WOLFBOOT_SHA_HDR, &stored_sha); + ck_assert_uint_eq(stored_sha_len, WOLFBOOT_SHA_DIGEST_SIZE); + ck_assert_uint_eq(test_img.sha_ok, 1); + ck_assert_ptr_eq(test_img.sha_hash, stored_sha); + ck_assert_int_eq(verify_hash_count, 2); + ck_assert_mem_eq(verify_hashes[0], verify_hashes[1], + WOLFBOOT_SHA_DIGEST_SIZE); +} +END_TEST +#endif #endif #ifdef WOLFBOOT_FIXED_PARTITIONS @@ -938,6 +1083,15 @@ Suite *wolfboot_suite(void) return s; #endif +#ifdef UNIT_IMAGE_HYBRID_ONLY + TCase* tcase_hybrid_auth = tcase_create("hybrid_auth"); + tcase_set_timeout(tcase_hybrid_auth, 20); + tcase_add_test(tcase_hybrid_auth, + test_verify_authenticity_hybrid_direct_call_uses_verified_sha); + suite_add_tcase(s, tcase_hybrid_auth); + return s; +#endif + #if defined(WOLFBOOT_SIGN_ECC256) TCase* tcase_verify_signature = tcase_create("verify_signature"); tcase_set_timeout(tcase_verify_signature, 20); @@ -960,14 +1114,18 @@ Suite *wolfboot_suite(void) #if defined(WOLFBOOT_SIGN_ECC256) TCase* tcase_verify_authenticity = tcase_create("verify_authenticity"); tcase_set_timeout(tcase_verify_authenticity, 20); +#ifdef WOLFBOOT_FIXED_PARTITIONS tcase_add_test(tcase_verify_authenticity, test_verify_authenticity); +#endif /* WOLFBOOT_FIXED_PARTITIONS */ tcase_add_test(tcase_verify_authenticity, test_verify_authenticity_bad_siglen); tcase_add_test(tcase_verify_authenticity, test_verify_authenticity_rejects_mismatched_auth_type); tcase_add_test(tcase_verify_authenticity, test_verify_authenticity_rejects_disallowed_key_mask); +#ifdef WOLFBOOT_FIXED_PARTITIONS tcase_add_test(tcase_verify_authenticity, test_verify_authenticity_allows_permitted_key_mask); +#endif /* WOLFBOOT_FIXED_PARTITIONS */ suite_add_tcase(s, tcase_verify_authenticity); #endif