From 18cbea59ded672d8f803e523cad23660f4d2b1a2 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 19 Sep 2025 13:01:28 +0200 Subject: [PATCH] Refactoring of IV handling to prevent reusing IVs During fallback operations, use a different encryption IV than the one used to encrypt the backup during the update. This ensures that the same IV is never reused to encrypt different sectors. --- include/encrypt.h | 5 ++ include/wolfboot/wolfboot.h | 1 + src/libwolfboot.c | 57 +++++++++++++--- src/update_flash.c | 128 +++++++++++++++++++++++++++--------- 4 files changed, 149 insertions(+), 42 deletions(-) diff --git a/include/encrypt.h b/include/encrypt.h index aeda235c..231ae9dc 100644 --- a/include/encrypt.h +++ b/include/encrypt.h @@ -69,5 +69,10 @@ void aes_set_iv(uint8_t *nonce, uint32_t address); int ext_flash_encrypt_write(uintptr_t address, const uint8_t *data, int len); int ext_flash_decrypt_read(uintptr_t address, uint8_t *data, int len); +#ifdef EXT_ENCRYPTED +int wolfBoot_enable_fallback_iv(int enable); +void wolfBoot_crypto_set_iv(const uint8_t *nonce, uint32_t iv_counter); +#endif + #endif /* __WOLFBOOT || UNIT_TEST */ #endif /* ENCRYPT_H_INCLUDED */ diff --git a/include/wolfboot/wolfboot.h b/include/wolfboot/wolfboot.h index 2fe7e31e..f51b3806 100644 --- a/include/wolfboot/wolfboot.h +++ b/include/wolfboot/wolfboot.h @@ -381,6 +381,7 @@ int wolfBoot_get_partition_state(uint8_t part, uint8_t *st); #ifdef EXT_ENCRYPTED /* Encryption support */ + #if defined(ENCRYPT_WITH_CHACHA) #define ENCRYPT_BLOCK_SIZE 64 #define ENCRYPT_KEY_SIZE 32 /* Chacha20 - 256bit */ diff --git a/src/libwolfboot.c b/src/libwolfboot.c index 9b9cb824..ef196f1c 100644 --- a/src/libwolfboot.c +++ b/src/libwolfboot.c @@ -62,18 +62,23 @@ #include /* for size_t */ -#if defined(EXT_ENCRYPTED) +#if defined(EXT_ENCRYPTED) && (defined(__WOLFBOOT) || defined(UNIT_TEST)) +#include "encrypt.h" static int encrypt_initialized = 0; static uint8_t encrypt_iv_nonce[ENCRYPT_NONCE_SIZE] XALIGNED(4); - #if defined(__WOLFBOOT) - #include "encrypt.h" - #elif !defined(XMEMSET) +static uint32_t encrypt_iv_offset = 0; + +#define FALLBACK_IV_OFFSET 0x00100000U + #if !defined(XMEMSET) #include #define XMEMSET memset #define XMEMCPY memcpy #define XMEMCMP memcmp #endif +#if defined(ENCRYPT_WITH_AES128) || defined(ENCRYPT_WITH_AES256) +extern void aes_set_iv(uint8_t *nonce, uint32_t address); +#endif #if defined (__WOLFBOOT) || defined (UNIT_TEST) int wolfBoot_initialize_encryption(void) @@ -1027,7 +1032,7 @@ static int decrypt_header(uint8_t *src) uint32_t magic; uint32_t len; for (i = 0; i < IMAGE_HEADER_SIZE; i+=ENCRYPT_BLOCK_SIZE) { - crypto_set_iv(encrypt_iv_nonce, i / ENCRYPT_BLOCK_SIZE); + wolfBoot_crypto_set_iv(encrypt_iv_nonce, i / ENCRYPT_BLOCK_SIZE); crypto_decrypt(dec_hdr + i, src + i, ENCRYPT_BLOCK_SIZE); } magic = *((uint32_t*)(dec_hdr)); @@ -1359,6 +1364,7 @@ int wolfBoot_fallback_is_possible(void) #ifdef EXT_ENCRYPTED #include "encrypt.h" +#include "string.h" #if defined(WOLFBOOT_RENESAS_TSIP) #include "wolfssl/wolfcrypt/port/Renesas/renesas-tsip-crypt.h" @@ -1388,6 +1394,39 @@ int wolfBoot_fallback_is_possible(void) static uint8_t ENCRYPT_KEY[ENCRYPT_KEY_SIZE + ENCRYPT_NONCE_SIZE]; #endif +#if defined(EXT_ENCRYPTED) && defined(__WOLFBOOT) +int RAMFUNCTION wolfBoot_enable_fallback_iv(int enable) +{ + int prev = 0; + if (encrypt_iv_offset != 0) + prev = 1; + + if (enable) + encrypt_iv_offset = FALLBACK_IV_OFFSET; + else + encrypt_iv_offset = 0; + + return prev; +} + +void RAMFUNCTION wolfBoot_crypto_set_iv(const uint8_t *nonce, uint32_t iv_counter) +{ +#if defined(ENCRYPT_WITH_CHACHA) + crypto_set_iv((uint8_t *)nonce, iv_counter + encrypt_iv_offset); +#elif defined(ENCRYPT_WITH_AES128) || defined(ENCRYPT_WITH_AES256) + uint8_t local_nonce[ENCRYPT_NONCE_SIZE]; + XMEMCPY(local_nonce, nonce, ENCRYPT_NONCE_SIZE); + crypto_set_iv(local_nonce, iv_counter + encrypt_iv_offset); +#else + (void)nonce; + (void)iv_counter; +#endif + + /* Fallback IV offset is single-use; clear it once applied. */ + encrypt_iv_offset = 0; +} +#endif /* EXT_ENCRYPTED && __WOLFBOOT */ + static int RAMFUNCTION hal_set_key(const uint8_t *k, const uint8_t *nonce) { #ifdef WOLFBOOT_RENESAS_TSIP @@ -1808,8 +1847,7 @@ int RAMFUNCTION ext_flash_encrypt_write(uintptr_t address, const uint8_t *data, } if (wolfBoot_initialize_encryption() < 0) return -1; - - crypto_set_iv(encrypt_iv_nonce, iv_counter); + wolfBoot_crypto_set_iv(encrypt_iv_nonce, iv_counter); break; case PART_SWAP: /* data is coming from update and is already encrypted */ @@ -1892,7 +1930,7 @@ int RAMFUNCTION ext_flash_decrypt_read(uintptr_t address, uint8_t *data, int len return -1; } } - crypto_set_iv(encrypt_iv_nonce, iv_counter); + wolfBoot_crypto_set_iv(encrypt_iv_nonce, iv_counter); break; case PART_SWAP: break; @@ -1988,7 +2026,7 @@ int wolfBoot_ram_decrypt(uint8_t *src, uint8_t *dst) /* decrypt content */ while (dst_offset < (len + IMAGE_HEADER_SIZE)) { - crypto_set_iv(encrypt_iv_nonce, iv_counter); + wolfBoot_crypto_set_iv(encrypt_iv_nonce, iv_counter); crypto_decrypt(dec_block, row_address, ENCRYPT_BLOCK_SIZE); XMEMCPY(dst + dst_offset, dec_block, ENCRYPT_BLOCK_SIZE); row_address += ENCRYPT_BLOCK_SIZE; @@ -2035,4 +2073,3 @@ int wolfBoot_nsc_write_update(uint32_t address, const uint8_t *buf, uint32_t len } #endif - diff --git a/src/update_flash.c b/src/update_flash.c index 80f2a48b..e254487a 100644 --- a/src/update_flash.c +++ b/src/update_flash.c @@ -39,6 +39,10 @@ int WP11_Library_Init(void); #endif +#ifdef EXT_ENCRYPTED +#include "encrypt.h" +#endif /* EXT_ENCRYPTED */ + #ifdef RAM_CODE #ifndef TARGET_rp2350 extern unsigned int _start_text; @@ -55,9 +59,6 @@ static uint8_t buffer[FLASHBUFFER_SIZE] XALIGNED(4); # endif #endif -#ifdef EXT_ENCRYPTED -#include "encrypt.h" -#endif static void RAMFUNCTION wolfBoot_erase_bootloader(void) { @@ -136,6 +137,7 @@ static int RAMFUNCTION wolfBoot_copy_sector(struct wolfBoot_image *src, uint32_t src_sector_offset = (sector * WOLFBOOT_SECTOR_SIZE); uint32_t dst_sector_offset = src_sector_offset; #ifdef EXT_ENCRYPTED + uint32_t i; uint8_t key[ENCRYPT_KEY_SIZE]; uint8_t nonce[ENCRYPT_NONCE_SIZE]; uint32_t iv_counter; @@ -153,19 +155,21 @@ static int RAMFUNCTION wolfBoot_copy_sector(struct wolfBoot_image *src, dst_sector_offset = 0; #ifdef EXT_ENCRYPTED - if (wolfBoot_initialize_encryption() < 0) { + if (wolfBoot_initialize_encryption() < 0) return -1; - } - wolfBoot_get_encrypt_key(key, nonce); + wolfBoot_get_encrypt_key(key, nonce); if (src->part == PART_SWAP) iv_counter = dst_sector_offset; else + /* + * Always re-derive the IV starting from the source address. + * This guarantees we do not reuse the same IV in the SWAP partition. + */ iv_counter = src_sector_offset; - iv_counter /= ENCRYPT_BLOCK_SIZE; - crypto_set_iv(nonce, iv_counter); -#endif + wolfBoot_crypto_set_iv(nonce, iv_counter); +#endif /* EXT_ENCRYPTED */ #ifdef EXT_FLASH if (PART_IS_EXT(src)) { @@ -222,7 +226,6 @@ static int RAMFUNCTION wolfBoot_backup_last_boot_sector(uint32_t sector) wolfBoot_open_image(src, PART_BOOT); wolfBoot_open_image(dst, PART_SWAP); - wolfBoot_printf("Copy sector %d (part %d->%d)\n", sector, src->part, dst->part); @@ -234,7 +237,12 @@ static int RAMFUNCTION wolfBoot_backup_last_boot_sector(uint32_t sector) iv_counter /= ENCRYPT_BLOCK_SIZE; if (wolfBoot_initialize_encryption() < 0) return -1; - crypto_set_iv(nonce, iv_counter); + /* + * Preserve the IV sequence used by the source sector so that the staging + * copy in SWAP can be decrypted with exactly the same keystream when it is + * restored to BOOT. + */ + wolfBoot_crypto_set_iv(nonce, iv_counter); /* Erase swap space */ wb_flash_erase(dst, dst_sector_offset, WOLFBOOT_SECTOR_SIZE); @@ -523,7 +531,7 @@ static int wolfBoot_delta_update(struct wolfBoot_image *boot, } iv_counter /= ENCRYPT_BLOCK_SIZE; /* Encrypt + send */ - crypto_set_iv(nonce, iv_counter); + wolfBoot_crypto_set_iv(nonce, iv_counter); crypto_encrypt(enc_blk, delta_blk, ret); wr_ret = ext_flash_write( (uint32_t)(WOLFBOOT_PARTITION_SWAP_ADDRESS + len), @@ -659,26 +667,54 @@ static int RAMFUNCTION wolfBoot_update(int fallback_allowed) uint16_t update_type; uint32_t fw_size; uint32_t size; + int inverse = 0; + int fallback_image = 0; #if defined(DISABLE_BACKUP) && defined(EXT_ENCRYPTED) uint8_t key[ENCRYPT_KEY_SIZE]; uint8_t nonce[ENCRYPT_NONCE_SIZE]; #endif #ifdef DELTA_UPDATES uint8_t st; - int inverse = 0; int resume = 0; int stateRet = -1; - uint32_t cur_v; - uint32_t up_v; #endif uint32_t cur_ver, upd_ver; wolfBoot_printf("Staring Update (fallback allowed %d)\n", fallback_allowed); /* No Safety check on open: we might be in the middle of a broken update */ - wolfBoot_open_image(&update, PART_UPDATE); - wolfBoot_open_image(&boot, PART_BOOT); - wolfBoot_open_image(&swap, PART_SWAP); + { + int update_open; +#ifdef EXT_ENCRYPTED + /* Start with the standard IV mapping for every fresh update attempt. */ + wolfBoot_enable_fallback_iv(0); +#endif + update_open = wolfBoot_open_image(&update, PART_UPDATE); +#ifdef EXT_ENCRYPTED + if (update_open < 0) { + int prev = wolfBoot_enable_fallback_iv(1); + (void)prev; + update_open = wolfBoot_open_image(&update, PART_UPDATE); + if (update_open < 0) { + wolfBoot_enable_fallback_iv(0); + return -1; + } + fallback_image = 1; + } + wolfBoot_enable_fallback_iv(fallback_image); +#else + if (update_open < 0) + return -1; +#endif + wolfBoot_open_image(&boot, PART_BOOT); + wolfBoot_open_image(&swap, PART_SWAP); + +#ifdef EXT_ENCRYPTED + wolfBoot_printf("Update partition fallback image: %d\n", fallback_image); + if (fallback_image) + inverse = 1; +#endif + } /* get total size */ total_size = wolfBoot_get_total_size(&boot, &update); @@ -705,12 +741,23 @@ static int RAMFUNCTION wolfBoot_update(int fallback_allowed) wolfBoot_printf("Invalid update size %u\n", update.fw_size); return -1; } - if (!update.hdr_ok - || (wolfBoot_verify_integrity(&update) < 0) - || (wolfBoot_verify_authenticity(&update) < 0)) { - wolfBoot_printf("Update verify failed: Hdr %d, Hash %d, Sig %d\n", - update.hdr_ok, update.sha_ok, update.signature_ok); - return -1; + if (!fallback_image) { + if (!update.hdr_ok + || (wolfBoot_verify_integrity(&update) < 0) + || (wolfBoot_verify_authenticity(&update) < 0)) { + wolfBoot_printf("Update verify failed: Hdr %d, Hash %d, Sig %d\n", + update.hdr_ok, update.sha_ok, update.signature_ok); + return -1; + } + } else { + /* + * When we recover an already-encrypted fallback image, the + * manifest still contains hashes computed with the original IV + * stream. Skip the redundant integrity/authenticity checks here + * and let the bootloader verify the restored image after the swap. + */ + update.sha_ok = 1; + update.signature_ok = 1; } PART_SANITY_CHECK(&update); @@ -731,12 +778,11 @@ static int RAMFUNCTION wolfBoot_update(int fallback_allowed) } #endif } + if (cur_ver > upd_ver) + inverse = 1; #ifdef DELTA_UPDATES if ((update_type & 0x00F0) == HDR_IMG_TYPE_DIFF) { - cur_v = wolfBoot_current_firmware_version(); - up_v = wolfBoot_update_firmware_version(); - inverse = cur_v >= up_v; /* if magic isn't set stateRet will be -1 but that means we're on a * fresh partition and aren't resuming */ stateRet = wolfBoot_get_partition_state(PART_UPDATE, &st); @@ -745,7 +791,7 @@ static int RAMFUNCTION wolfBoot_update(int fallback_allowed) * header we can't determine the direction by version numbers. instead * use the update partition state, updating means regular, new means * reverting */ - if ((stateRet == 0) && ((flag != SECT_FLAG_NEW) || (cur_v == 0))) { + if ((stateRet == 0) && ((flag != SECT_FLAG_NEW) || (cur_ver == 0))) { resume = 1; if (st == IMG_STATE_UPDATING) { inverse = 0; @@ -780,7 +826,6 @@ static int RAMFUNCTION wolfBoot_update(int fallback_allowed) #ifdef EXT_FLASH ext_flash_unlock(); #endif - /* Interruptible swap * The status is saved in the sector flags of the update partition. * If something goes wrong, the operation will be resumed upon reboot. @@ -800,7 +845,22 @@ static int RAMFUNCTION wolfBoot_update(int fallback_allowed) if (size > sector_size) size = sector_size; flag = SECT_FLAG_BACKUP; - wolfBoot_copy_sector(&boot, &update, sector); + { +#ifdef EXT_ENCRYPTED + /* + * When we are performing a fallback, force the alternate + * IV offset only for the segment copied from BOOT into + * UPDATE. All other copies see the offset that was + * active beforehand (0 for the normal path, fallback + * offset for the recovery path). + */ + int prev_iv = wolfBoot_enable_fallback_iv(1); +#endif + wolfBoot_copy_sector(&boot, &update, sector); +#ifdef EXT_ENCRYPTED + wolfBoot_enable_fallback_iv(prev_iv); +#endif + } if (((sector + 1) * sector_size) < WOLFBOOT_PARTITION_SIZE) wolfBoot_set_update_sector_flag(sector, flag); /* FALL THROUGH */ @@ -945,10 +1005,14 @@ static int RAMFUNCTION wolfBoot_update(int fallback_allowed) hal_flash_lock(); /* Save the encryption key after swapping */ - #ifdef EXT_ENCRYPTED +#ifdef EXT_ENCRYPTED wolfBoot_set_encrypt_key(key, nonce); - #endif +#endif #endif /* DISABLE_BACKUP */ +#ifdef EXT_ENCRYPTED + /* Make sure we leave the global IV offset in its normal state. */ + wolfBoot_enable_fallback_iv(0); +#endif return 0; }