bootloader: Calculate SHA256 hash of image on every boot

Makes app image booting more reliable (256-bit rather than 8-bit verification.)

Some measurements, time to boot a 655KB app.bin file and run to app_main() execution.

(All for rev 1 silicon, ie no 340ms spurious WDT delay.)

80MHz QIO mode:
before = 300ms
after = 140ms

40MHz DIO mode:
before = 712ms
after = 577ms

40MHz DIO mode, secure boot enabled
before = 1380ms
after = 934ms

(Secure boot involves two ECC signature verifications (partition table, app) that take approx 300ms each with 80MHz CPU.)
This commit is contained in:
Angus Gratton 2017-06-28 16:46:34 +10:00 committed by Angus Gratton
parent caaa29c676
commit 17adb40ca8
12 changed files with 290 additions and 137 deletions

View File

@ -241,7 +241,7 @@ esp_err_t esp_ota_end(esp_ota_handle_t handle)
} }
#ifdef CONFIG_SECURE_BOOT_ENABLED #ifdef CONFIG_SECURE_BOOT_ENABLED
ret = esp_secure_boot_verify_signature(it->part->address, data.image_length); ret = esp_secure_boot_verify_signature(it->part->address, data.image_len);
if (ret != ESP_OK) { if (ret != ESP_OK) {
ret = ESP_ERR_OTA_VALIDATE_FAILED; ret = ESP_ERR_OTA_VALIDATE_FAILED;
goto cleanup; goto cleanup;
@ -385,7 +385,7 @@ esp_err_t esp_ota_set_boot_partition(const esp_partition_t *partition)
} }
#ifdef CONFIG_SECURE_BOOT_ENABLED #ifdef CONFIG_SECURE_BOOT_ENABLED
esp_err_t ret = esp_secure_boot_verify_signature(partition->address, data.image_length); esp_err_t ret = esp_secure_boot_verify_signature(partition->address, data.image_len);
if (ret != ESP_OK) { if (ret != ESP_OK) {
return ESP_ERR_OTA_VALIDATE_FAILED; return ESP_ERR_OTA_VALIDATE_FAILED;
} }

View File

@ -87,11 +87,15 @@ void call_start_cpu0()
cpu_configure_region_protection(); cpu_configure_region_protection();
/* Sanity check that static RAM is after the stack */ /* Sanity check that static RAM is after the stack */
#ifndef NDEBUG
{
int *sp = get_sp(); int *sp = get_sp();
assert(&_bss_start <= &_bss_end); assert(&_bss_start <= &_bss_end);
assert(&_data_start <= &_data_end); assert(&_data_start <= &_data_end);
assert(sp < &_bss_start); assert(sp < &_bss_start);
assert(sp < &_data_start); assert(sp < &_data_start);
}
#endif
//Clear bss //Clear bss
memset(&_bss_start, 0, (&_bss_end - &_bss_start) * sizeof(_bss_start)); memset(&_bss_start, 0, (&_bss_end - &_bss_start) * sizeof(_bss_start));
@ -425,24 +429,13 @@ static void unpack_load_app(const esp_partition_pos_t* partition)
esp_image_metadata_t data; esp_image_metadata_t data;
/* TODO: load the app image as part of OTA boot decision, so can fallback if loading fails */ /* TODO: load the app image as part of OTA boot decision, so can fallback if loading fails */
/* Loading the image here also includes secure boot verification */
err = esp_image_load(ESP_IMAGE_LOAD, partition, &data); err = esp_image_load(ESP_IMAGE_LOAD, partition, &data);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to verify app image @ 0x%x (%d)", partition->offset, err); ESP_LOGE(TAG, "Failed to verify app image @ 0x%x (%d)", partition->offset, err);
return; return;
} }
#ifdef CONFIG_SECURE_BOOT_ENABLED
if (esp_secure_boot_enabled()) {
ESP_LOGI(TAG, "Verifying app signature @ 0x%x (length 0x%x)", partition->offset, data.image_length);
err = esp_secure_boot_verify_signature(partition->offset, data.image_length);
if (err != ESP_OK) {
ESP_LOGE(TAG, "App image @ 0x%x failed signature verification (%d)", partition->offset, err);
return;
}
ESP_LOGD(TAG, "App signature is valid");
}
#endif
uint32_t drom_addr = 0; uint32_t drom_addr = 0;
uint32_t drom_load_addr = 0; uint32_t drom_load_addr = 0;
uint32_t drom_size = 0; uint32_t drom_size = 0;
@ -497,6 +490,14 @@ static void set_cache_and_start_app(
ESP_LOGD(TAG, "configure drom and irom and start"); ESP_LOGD(TAG, "configure drom and irom and start");
Cache_Read_Disable( 0 ); Cache_Read_Disable( 0 );
Cache_Flush( 0 ); Cache_Flush( 0 );
/* Clear the MMU entries that are already set up,
so the new app only has the mappings it creates.
*/
for (int i = 0; i < DPORT_FLASH_MMU_TABLE_SIZE; i++) {
DPORT_PRO_FLASH_MMU_TABLE[i] = DPORT_FLASH_MMU_TABLE_INVALID_VAL;
}
uint32_t drom_page_count = (drom_size + 64*1024 - 1) / (64*1024); // round up to 64k uint32_t drom_page_count = (drom_size + 64*1024 - 1) / (64*1024); // round up to 64k
ESP_LOGV(TAG, "d mmu set paddr=%08x vaddr=%08x size=%d n=%d", drom_addr & 0xffff0000, drom_load_addr & 0xffff0000, drom_size, drom_page_count ); ESP_LOGV(TAG, "d mmu set paddr=%08x vaddr=%08x size=%d n=%d", drom_addr & 0xffff0000, drom_load_addr & 0xffff0000, drom_size, drom_page_count );
int rc = cache_flash_mmu_set( 0, 0, drom_load_addr & 0xffff0000, drom_addr & 0xffff0000, 64, drom_page_count ); int rc = cache_flash_mmu_set( 0, 0, drom_load_addr & 0xffff0000, drom_addr & 0xffff0000, 64, drom_page_count );

View File

@ -59,12 +59,27 @@ typedef enum {
typedef struct { typedef struct {
uint8_t magic; uint8_t magic;
uint8_t segment_count; uint8_t segment_count;
uint8_t spi_mode; /* flash read mode (esp_image_spi_mode_t as uint8_t) */ /* flash read mode (esp_image_spi_mode_t as uint8_t) */
uint8_t spi_speed: 4; /* flash frequency (esp_image_spi_freq_t as uint8_t) */ uint8_t spi_mode;
uint8_t spi_size: 4; /* flash chip size (esp_image_flash_size_t as uint8_t) */ /* flash frequency (esp_image_spi_freq_t as uint8_t) */
uint8_t spi_speed: 4;
/* flash chip size (esp_image_flash_size_t as uint8_t) */
uint8_t spi_size: 4;
uint32_t entry_addr; uint32_t entry_addr;
uint8_t extra_header[16]; /* ESP32 additional header, unused by second bootloader */ /* WP pin when SPI pins set via efuse (read by ROM bootloader, the IDF bootloader uses software to configure the WP
} esp_image_header_t; * pin and sets this field to 0xEE=disabled) */
uint8_t wp_pin;
/* Drive settings for the SPI flash pins (read by ROM bootloader) */
uint8_t spi_pin_drv[3];
/* Reserved bytes in ESP32 additional header space, currently unused */
uint8_t reserved[11];
/* If 1, a SHA256 digest "simple hash" (of the entire image) is appended after the checksum. Included in image length. This digest
* is separate to secure boot and only used for detecting corruption. For secure boot signed images, the signature
* is appended after this (and the simple hash is included in the signed data). */
uint8_t hash_appended;
} __attribute__((packed)) esp_image_header_t;
_Static_assert(sizeof(esp_image_header_t) == 24, "binary image header should be 24 bytes");
/* Header of binary image segment */ /* Header of binary image segment */
typedef struct { typedef struct {
@ -97,18 +112,23 @@ typedef enum {
* *
* If encryption is enabled, data will be transparently decrypted. * If encryption is enabled, data will be transparently decrypted.
* *
* @param mode Mode of operation (verify, silent verify, or load).
* @param part Partition to load the app from. * @param part Partition to load the app from.
* @param[out] data Pointer to the metadata structure to be filled in by this function. 'start_addr' member should be set (to the start address of the image.) Other fields will all be initialised by this function. * @param[inout] data Pointer to the image metadata structure which is be filled in by this function. 'start_addr' member should be set (to the start address of the image.) Other fields will all be initialised by this function.
* @param log_errors Log errors reading the image.
* *
* Image validation checks: * Image validation checks:
* - Magic byte * - Magic byte.
* - Partition smaller than 16MB * - Partition smaller than 16MB.
* - All segments & image fit in partition * - All segments & image fit in partition.
* - 8 bit image checksum is valid * - 8 bit image checksum is valid.
* - (Signature) if signature verification is enabled * - SHA-256 of image is valid (if image has this appended).
* - (Signature) if signature verification is enabled.
* *
* @return ESP_OK if image metadata was loaded successfully, ESP_ERR_IMAGE_FLASH_FAIL if a SPI flash error occurs, ESP_ERR_IMAGE_INVALID if the image appears invalid, ESP_ERR_INVALID_ARG if the data pointer is invalid. * @return
* - ESP_OK if verify or load was successful
* - ESP_ERR_IMAGE_FLASH_FAIL if a SPI flash error occurs
* - ESP_ERR_IMAGE_INVALID if the image appears invalid.
* - ESP_ERR_INVALID_ARG if the partition or data pointers are invalid.
*/ */
esp_err_t esp_image_load(esp_image_load_mode_t mode, const esp_partition_pos_t *part, esp_image_metadata_t *data); esp_err_t esp_image_load(esp_image_load_mode_t mode, const esp_partition_pos_t *part, esp_image_metadata_t *data);

View File

@ -77,12 +77,22 @@ esp_err_t esp_secure_boot_permanently_enable(void);
*/ */
esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length); esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length);
/** @brief Verify the secure boot signature block (deterministic ECDSA w/ SHA256) based on the SHA256 hash of some data.
*
* Similar to esp_secure_boot_verify_signature(), but can be used when the digest is precalculated.
* @param sig_block Pointer to signature block data
* @param image_digest Pointer to 32 byte buffer holding SHA-256 hash.
*
*/
/** @brief Secure boot verification block, on-flash data format. */ /** @brief Secure boot verification block, on-flash data format. */
typedef struct { typedef struct {
uint32_t version; uint32_t version;
uint8_t signature[64]; uint8_t signature[64];
} esp_secure_boot_sig_block_t; } esp_secure_boot_sig_block_t;
esp_err_t esp_secure_boot_verify_signature_block(const esp_secure_boot_sig_block_t *sig_block, const uint8_t *image_digest);
#define FLASH_OFFS_SECURE_BOOT_IV_DIGEST 0 #define FLASH_OFFS_SECURE_BOOT_IV_DIGEST 0
/** @brief Secure boot IV+digest header */ /** @brief Secure boot IV+digest header */

View File

@ -1,4 +1,4 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD // Copyright 2017 Espressif Systems (Shanghai) PTE LTD
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.

View File

@ -1,4 +1,4 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD // Copyright 2017 Espressif Systems (Shanghai) PTE LTD
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -43,8 +43,12 @@ void bootloader_sha256_finish(bootloader_sha256_handle_t handle, uint8_t *digest
{ {
assert(handle != NULL); assert(handle != NULL);
mbedtls_sha256_context *ctx = (mbedtls_sha256_context *)handle; mbedtls_sha256_context *ctx = (mbedtls_sha256_context *)handle;
if (digest != NULL) {
mbedtls_sha256_finish(ctx, digest); mbedtls_sha256_finish(ctx, digest);
} }
mbedtls_sha256_free(ctx);
free(handle);
}
#else // Bootloader version #else // Bootloader version
@ -58,6 +62,8 @@ static uint32_t words_hashed;
// Words per SHA256 block // Words per SHA256 block
static const size_t BLOCK_WORDS = (64/sizeof(uint32_t)); static const size_t BLOCK_WORDS = (64/sizeof(uint32_t));
// Words in final SHA256 digest
static const size_t DIGEST_WORDS = (32/sizeof(uint32_t));
bootloader_sha256_handle_t bootloader_sha256_start() bootloader_sha256_handle_t bootloader_sha256_start()
{ {
@ -116,22 +122,28 @@ void bootloader_sha256_finish(bootloader_sha256_handle_t handle, uint8_t *digest
{ {
assert(handle != NULL); assert(handle != NULL);
uint32_t data_words = words_hashed; if (digest == NULL) {
ets_printf("Padding from %d bytes\n", data_words * 4); return; // We'd free resources here, but there are none to free
}
// Pad to a 60 byte long block loaded in the engine uint32_t data_words = words_hashed;
// (normally end of block is a 64-bit length, but we know
// the upper 32 bits will be zeroes.) // Pad to a 55 byte long block loaded in the engine
// (leaving 1 byte 0x80 plus variable padding plus 8 bytes of length,
// to fill a 64 byte block.)
int block_bytes = (words_hashed % BLOCK_WORDS) * 4; int block_bytes = (words_hashed % BLOCK_WORDS) * 4;
int pad_bytes = 60 - block_bytes; int pad_bytes = 55 - block_bytes;
if (pad_bytes < 0) { if (pad_bytes < 0) {
pad_bytes += 64; pad_bytes += 64;
} }
static const uint8_t padding[64] = { 0x80, 0, }; static const uint8_t padding[64] = { 0x80, 0, };
pad_bytes += 5; // 1 byte for 0x80 plus first 4 bytes of the 64-bit length
assert(pad_bytes % 4 == 0); // should be, as (block_bytes % 4 == 0)
bootloader_sha256_data(handle, padding, pad_bytes); bootloader_sha256_data(handle, padding, pad_bytes);
assert(words_hashed % BLOCK_WORDS == 56/4); assert(words_hashed % BLOCK_WORDS == 60/4); // 32-bits left in block
// Calculate 32-bit length for final 32 bits of data // Calculate 32-bit length for final 32 bits of data
uint32_t bit_count = __builtin_bswap32( data_words * 32 ); uint32_t bit_count = __builtin_bswap32( data_words * 32 );
@ -139,15 +151,13 @@ void bootloader_sha256_finish(bootloader_sha256_handle_t handle, uint8_t *digest
assert(words_hashed % BLOCK_WORDS == 0); assert(words_hashed % BLOCK_WORDS == 0);
ets_printf("Padded to %d bytes\n", words_hashed * 4);
while(REG_READ(SHA_256_BUSY_REG) == 1) { } while(REG_READ(SHA_256_BUSY_REG) == 1) { }
REG_WRITE(SHA_256_LOAD_REG, 1); REG_WRITE(SHA_256_LOAD_REG, 1);
while(REG_READ(SHA_256_BUSY_REG) == 1) { } while(REG_READ(SHA_256_BUSY_REG) == 1) { }
uint32_t *digest_words = (uint32_t *)digest; uint32_t *digest_words = (uint32_t *)digest;
uint32_t *sha_text_reg = (uint32_t *)(SHA_TEXT_BASE); uint32_t *sha_text_reg = (uint32_t *)(SHA_TEXT_BASE);
for (int i = 0; i < BLOCK_WORDS; i++) { for (int i = 0; i < DIGEST_WORDS; i++) {
digest_words[i] = __builtin_bswap32(sha_text_reg[i]); digest_words[i] = __builtin_bswap32(sha_text_reg[i]);
} }
asm volatile ("memw"); asm volatile ("memw");

View File

@ -17,6 +17,7 @@
#include <rom/rtc.h> #include <rom/rtc.h>
#include <soc/cpu.h> #include <soc/cpu.h>
#include <esp_image_format.h> #include <esp_image_format.h>
#include <esp_secure_boot.h>
#include <esp_log.h> #include <esp_log.h>
#include <bootloader_flash.h> #include <bootloader_flash.h>
#include <bootloader_random.h> #include <bootloader_random.h>
@ -24,11 +25,13 @@
static const char *TAG = "esp_image"; static const char *TAG = "esp_image";
#define HASH_LEN 32 /* SHA-256 digest length */
#define SIXTEEN_MB 0x1000000 #define SIXTEEN_MB 0x1000000
#define ESP_ROM_CHECKSUM_INITIAL 0xEF #define ESP_ROM_CHECKSUM_INITIAL 0xEF
/* Headroom to ensure between stack SP (at time of checking) and data loaded from flash */ /* Headroom to ensure between stack SP (at time of checking) and data loaded from flash */
#define STACK_LOAD_HEADROOM 4096 #define STACK_LOAD_HEADROOM 32768
#ifdef BOOTLOADER_BUILD #ifdef BOOTLOADER_BUILD
/* 64 bits of random data to obfuscate loaded RAM with, until verification is complete /* 64 bits of random data to obfuscate loaded RAM with, until verification is complete
@ -60,6 +63,11 @@ static esp_err_t verify_segment_header(int index, const esp_image_segment_header
} \ } \
while(0) while(0)
static esp_err_t verify_checksum(bootloader_sha256_handle_t sha_handle, uint32_t checksum_word, esp_image_metadata_t *data);
static esp_err_t __attribute__((unused)) verify_secure_boot(bootloader_sha256_handle_t sha_handle, esp_image_metadata_t *data);
static esp_err_t __attribute__((unused)) verify_simple_hash(bootloader_sha256_handle_t sha_handle, esp_image_metadata_t *data);
esp_err_t esp_image_load(esp_image_load_mode_t mode, const esp_partition_pos_t *part, esp_image_metadata_t *data) esp_err_t esp_image_load(esp_image_load_mode_t mode, const esp_partition_pos_t *part, esp_image_metadata_t *data)
{ {
#ifdef BOOTLOADER_BUILD #ifdef BOOTLOADER_BUILD
@ -72,7 +80,6 @@ esp_err_t esp_image_load(esp_image_load_mode_t mode, const esp_partition_pos_t *
// checksum the image a word at a time. This shaves 30-40ms per MB of image size // checksum the image a word at a time. This shaves 30-40ms per MB of image size
uint32_t checksum_word = ESP_ROM_CHECKSUM_INITIAL; uint32_t checksum_word = ESP_ROM_CHECKSUM_INITIAL;
bootloader_sha256_handle_t sha_handle = NULL; bootloader_sha256_handle_t sha_handle = NULL;
uint8_t image_digest[32] = { 0 };
if (data == NULL || part == NULL) { if (data == NULL || part == NULL) {
return ESP_ERR_INVALID_ARG; return ESP_ERR_INVALID_ARG;
@ -86,17 +93,24 @@ esp_err_t esp_image_load(esp_image_load_mode_t mode, const esp_partition_pos_t *
bzero(data, sizeof(esp_image_metadata_t)); bzero(data, sizeof(esp_image_metadata_t));
data->start_addr = part->offset; data->start_addr = part->offset;
sha_handle = bootloader_sha256_start();
if (sha_handle == NULL) {
return ESP_ERR_NO_MEM;
}
ESP_LOGD(TAG, "reading image header @ 0x%x", data->start_addr); ESP_LOGD(TAG, "reading image header @ 0x%x", data->start_addr);
err = bootloader_flash_read(data->start_addr, &data->image, sizeof(esp_image_header_t), true); err = bootloader_flash_read(data->start_addr, &data->image, sizeof(esp_image_header_t), true);
if (err != ESP_OK) { if (err != ESP_OK) {
goto err; goto err;
} }
// Calculate SHA-256 of image if secure boot is on, or if image has a hash appended
#ifdef CONFIG_SECURE_BOOT_ENABLED
if (1) {
#else
if (data->image.hash_appended) {
#endif
sha_handle = bootloader_sha256_start();
if (sha_handle == NULL) {
return ESP_ERR_NO_MEM;
}
bootloader_sha256_data(sha_handle, &data->image, sizeof(esp_image_header_t)); bootloader_sha256_data(sha_handle, &data->image, sizeof(esp_image_header_t));
}
ESP_LOGD(TAG, "image header: 0x%02x 0x%02x 0x%02x 0x%02x %08x", ESP_LOGD(TAG, "image header: 0x%02x 0x%02x 0x%02x 0x%02x %08x",
data->image.magic, data->image.magic,
@ -134,48 +148,32 @@ goto err;
FAIL_LOAD("image offset has wrapped"); FAIL_LOAD("image offset has wrapped");
} }
uint32_t unpadded_length = end_addr - data->start_addr; data->image_len = end_addr - data->start_addr;
uint32_t length = unpadded_length + 1; // Add a byte for the checksum ESP_LOGV(TAG, "image start 0x%08x end of last section 0x%08x", data->start_addr, end_addr);
length = (length + 15) & ~15; // Pad to next full 16 byte block err = verify_checksum(sha_handle, checksum_word, data);
if (length > part->size) { if (err != ESP_OK) {
FAIL_LOAD("Image length %d doesn't fit in partition length %d", length, part->size); goto err;
} }
// Verify checksum if (data->image_len > part->size) {
uint32_t buf[16/sizeof(uint32_t)]; FAIL_LOAD("Image length %d doesn't fit in partition length %d", data->image_len, part->size);
err = bootloader_flash_read(end_addr, buf, length - unpadded_length, true);
uint8_t calc = ((uint8_t *)buf)[length - unpadded_length - 1];
uint8_t checksum = (checksum_word >> 24)
^ (checksum_word >> 16)
^ (checksum_word >> 8)
^ (checksum_word >> 0);
if (err != ESP_OK || checksum != calc) {
FAIL_LOAD("Checksum failed. Calculated 0x%x read 0x%x",
checksum, calc);
} }
bootloader_sha256_data(sha_handle, buf, length - unpadded_length); #ifdef CONFIG_SECURE_BOOT_ENABLED
bootloader_sha256_finish(sha_handle, image_digest); err = verify_secure_boot(sha_handle, data);
sha_handle = NULL;
#if BOOT_LOG_LEVEL >= LOG_LEVEL_DEBUG if (err != ESP_OK) {
char digest_print[sizeof(image_digest)*2 + 1]; goto err;
digest_print[sizeof(image_digest)*2] = 0; }
for (int i = 0; i < sizeof(image_digest); i++) { #else // No secure boot, but SHA-256 can be appended for basic corruption detection
for (int shift = 0; shift < 2; shift++) { if (sha_handle != NULL) {
uint8_t nibble = (image_digest[i] >> (shift ? 0 : 4)) & 0x0F; err = verify_simple_hash(sha_handle, data);
if (nibble < 10) { sha_handle = NULL;
digest_print[i*2+shift] = '0' + nibble; if (err != ESP_OK) {
} else { goto err;
digest_print[i*2+shift] = 'a' + nibble - 10;
} }
} }
}
ESP_LOGD(TAG, "Total image length %d bytes (unpagged %d)", length, unpadded_length);
ESP_LOGD(TAG, "Image SHA256 digest: %s", digest_print);
#endif #endif
// Verify digest here
data->image_length = length;
#ifdef BOOTLOADER_BUILD #ifdef BOOTLOADER_BUILD
if (do_load) { // Need to deobfuscate RAM if (do_load) { // Need to deobfuscate RAM
@ -199,8 +197,8 @@ goto err;
err = ESP_ERR_IMAGE_INVALID; err = ESP_ERR_IMAGE_INVALID;
} }
if (sha_handle != NULL) { if (sha_handle != NULL) {
// Need to finish the digest process to free the handle // Need to finish the hash process to free the handle
bootloader_sha256_finish(sha_handle, image_digest); bootloader_sha256_finish(sha_handle, NULL);
} }
// Prevent invalid/incomplete data leaking out // Prevent invalid/incomplete data leaking out
bzero(data, sizeof(esp_image_metadata_t)); bzero(data, sizeof(esp_image_metadata_t));
@ -238,9 +236,12 @@ static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segme
/* read segment header */ /* read segment header */
err = bootloader_flash_read(flash_addr, header, sizeof(esp_image_segment_header_t), true); err = bootloader_flash_read(flash_addr, header, sizeof(esp_image_segment_header_t), true);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(TAG, "bootloader_flash_read failed at 0x%08x", flash_addr);
return err; return err;
} }
if (sha_handle != NULL) {
bootloader_sha256_data(sha_handle, header, sizeof(esp_image_segment_header_t)); bootloader_sha256_data(sha_handle, header, sizeof(esp_image_segment_header_t));
}
intptr_t load_addr = header->load_addr; intptr_t load_addr = header->load_addr;
uint32_t data_len = header->data_len; uint32_t data_len = header->data_len;
@ -311,7 +312,7 @@ static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segme
// counter-intuitive, but it's ~3ms better than using the // counter-intuitive, but it's ~3ms better than using the
// SHA256 block size. // SHA256 block size.
const size_t SHA_CHUNK = 1024; const size_t SHA_CHUNK = 1024;
if (i % SHA_CHUNK == 0) { if (sha_handle != NULL && i % SHA_CHUNK == 0) {
bootloader_sha256_data(sha_handle, &src[w_i], bootloader_sha256_data(sha_handle, &src[w_i],
MIN(SHA_CHUNK, data_len - i)); MIN(SHA_CHUNK, data_len - i));
} }
@ -349,7 +350,7 @@ static esp_err_t verify_segment_header(int index, const esp_image_segment_header
if (map_segment if (map_segment
&& ((segment_data_offs % SPI_FLASH_MMU_PAGE_SIZE) != (load_addr % SPI_FLASH_MMU_PAGE_SIZE))) { && ((segment_data_offs % SPI_FLASH_MMU_PAGE_SIZE) != (load_addr % SPI_FLASH_MMU_PAGE_SIZE))) {
if (!silent) { if (!silent) {
ESP_LOGE(TAG, "Segment %d has load address 0x%08x, doesn't match segment data at 0x%08x", ESP_LOGE(TAG, "Segment %d load address 0x%08x, doesn't match data 0x%08x",
index, load_addr, segment_data_offs); index, load_addr, segment_data_offs);
} }
return ESP_ERR_IMAGE_INVALID; return ESP_ERR_IMAGE_INVALID;
@ -411,3 +412,120 @@ esp_err_t esp_image_verify_bootloader(uint32_t *length)
} }
return err; return err;
} }
static esp_err_t verify_checksum(bootloader_sha256_handle_t sha_handle, uint32_t checksum_word, esp_image_metadata_t *data)
{
uint32_t unpadded_length = data->image_len;
uint32_t length = unpadded_length + 1; // Add a byte for the checksum
length = (length + 15) & ~15; // Pad to next full 16 byte block
// Verify checksum
uint8_t buf[16];
esp_err_t err = bootloader_flash_read(data->start_addr + unpadded_length, buf, length - unpadded_length, true);
uint8_t calc = buf[length - unpadded_length - 1];
uint8_t checksum = (checksum_word >> 24)
^ (checksum_word >> 16)
^ (checksum_word >> 8)
^ (checksum_word >> 0);
if (err != ESP_OK || checksum != calc) {
ESP_LOGE(TAG, "Checksum failed. Calculated 0x%x read 0x%x", checksum, calc);
return ESP_ERR_IMAGE_INVALID;
}
if (sha_handle != NULL) {
bootloader_sha256_data(sha_handle, buf, length - unpadded_length);
}
if (data->image.hash_appended) {
// Account for the hash in the total image length
length += HASH_LEN;
}
data->image_len = length;
return ESP_OK;
}
static void debug_log_hash(const uint8_t *image_hash, const char *caption);
static esp_err_t verify_secure_boot(bootloader_sha256_handle_t sha_handle, esp_image_metadata_t *data)
{
uint8_t image_hash[HASH_LEN] = { 0 };
// For secure boot, we calculate the signature hash over the whole file, which includes any "simple" hash
// appended to the image for corruption detection
if (data->image.hash_appended) {
const void *simple_hash = bootloader_mmap(data->start_addr + data->image_len - HASH_LEN, HASH_LEN);
bootloader_sha256_data(sha_handle, simple_hash, HASH_LEN);
bootloader_munmap(simple_hash);
}
bootloader_sha256_finish(sha_handle, image_hash);
// Log the hash for debugging
debug_log_hash(image_hash, "Calculated secure boot hash");
// Use hash to verify signature block
const esp_secure_boot_sig_block_t *sig_block = bootloader_mmap(data->start_addr + data->image_len, sizeof(esp_secure_boot_sig_block_t));
esp_err_t err = esp_secure_boot_verify_signature_block(sig_block, image_hash);
bootloader_munmap(sig_block);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Secure boot signature verification failed");
// Go back and check if the simple hash matches or not (we're off the fast path so we can re-hash the whole image now)
ESP_LOGI(TAG, "Calculating simple hash to check for corruption...");
const void *whole_image = bootloader_mmap(data->start_addr, data->image_len - HASH_LEN);
if (whole_image != NULL) {
sha_handle = bootloader_sha256_start();
bootloader_sha256_data(sha_handle, whole_image, data->image_len - HASH_LEN);
bootloader_munmap(whole_image);
if (verify_simple_hash(sha_handle, data) != ESP_OK) {
ESP_LOGW(TAG, "image corrupted on flash");
} else {
ESP_LOGW(TAG, "image valid, signature bad");
}
}
return ESP_ERR_IMAGE_INVALID;
}
return ESP_OK;
}
static esp_err_t verify_simple_hash(bootloader_sha256_handle_t sha_handle, esp_image_metadata_t *data)
{
uint8_t image_hash[HASH_LEN] = { 0 };
bootloader_sha256_finish(sha_handle, image_hash);
// Log the hash for debugging
debug_log_hash(image_hash, "Calculated hash");
// Simple hash for verification only
const void *hash = bootloader_mmap(data->start_addr + data->image_len - HASH_LEN, HASH_LEN);
if (memcmp(hash, image_hash, HASH_LEN) != 0) {
ESP_LOGE(TAG, "Image hash failed - image is corrupt");
debug_log_hash(hash, "Expected hash");
bootloader_munmap(hash);
return ESP_ERR_IMAGE_INVALID;
}
bootloader_munmap(hash);
return ESP_OK;
}
// Log a hash as a hex string
static void debug_log_hash(const uint8_t *image_hash, const char *label)
{
#if BOOT_LOG_LEVEL >= LOG_LEVEL_DEBUG
char hash_print[sizeof(image_hash)*2 + 1];
hash_print[sizeof(image_hash)*2] = 0;
for (int i = 0; i < sizeof(image_hash); i++) {
for (int shift = 0; shift < 2; shift++) {
uint8_t nibble = (image_hash[i] >> (shift ? 0 : 4)) & 0x0F;
if (nibble < 10) {
hash_print[i*2+shift] = '0' + nibble;
} else {
hash_print[i*2+shift] = 'a' + nibble - 10;
}
}
}
ESP_LOGD(TAG, "%s: %s", label, hash_print);
#endif
}

View File

@ -14,6 +14,7 @@
#include "sdkconfig.h" #include "sdkconfig.h"
#include "bootloader_flash.h" #include "bootloader_flash.h"
#include "bootloader_sha.h"
#include "esp_log.h" #include "esp_log.h"
#include "esp_image_format.h" #include "esp_image_format.h"
#include "esp_secure_boot.h" #include "esp_secure_boot.h"
@ -34,20 +35,13 @@ extern const uint8_t signature_verification_key_end[] asm("_binary_signature_ver
#define SIGNATURE_VERIFICATION_KEYLEN 64 #define SIGNATURE_VERIFICATION_KEYLEN 64
#define DIGEST_LEN 32
esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length) esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length)
{ {
#ifdef BOOTLOADER_BUILD uint8_t digest[DIGEST_LEN];
SHA_CTX sha;
#endif
uint8_t digest[32];
ptrdiff_t keylen;
const uint8_t *data; const uint8_t *data;
const esp_secure_boot_sig_block_t *sigblock; const esp_secure_boot_sig_block_t *sigblock;
bool is_valid;
#ifdef BOOTLOADER_BUILD
const uint8_t *digest_data;
uint32_t digest_len;
#endif
ESP_LOGD(TAG, "verifying signature src_addr 0x%x length 0x%x", src_addr, length); ESP_LOGD(TAG, "verifying signature src_addr 0x%x length 0x%x", src_addr, length);
@ -57,46 +51,43 @@ esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length)
return ESP_FAIL; return ESP_FAIL;
} }
sigblock = (const esp_secure_boot_sig_block_t *)(data + length); // Calculate digest of main image
if (sigblock->version != 0) {
ESP_LOGE(TAG, "src 0x%x has invalid signature version field 0x%08x", src_addr, sigblock->version);
goto unmap_and_fail;
}
#ifdef BOOTLOADER_BUILD #ifdef BOOTLOADER_BUILD
/* Use ROM SHA functions directly */ bootloader_sha256_handle_t handle = bootloader_sha256_start();
ets_sha_enable(); bootloader_sha256_data(handle, data, length);
ets_sha_init(&sha); bootloader_sha256_finish(handle, digest);
digest_len = length * 8;
digest_data = data;
while (digest_len > 0) {
uint32_t chunk_len = (digest_len > 64) ? 64 : digest_len;
ets_sha_update(&sha, SHA2_256, digest_data, chunk_len);
digest_len -= chunk_len;
digest_data += chunk_len / 8;
}
ets_sha_finish(&sha, SHA2_256, digest);
ets_sha_disable();
#else #else
/* Use thread-safe esp-idf SHA function */ /* Use thread-safe esp-idf SHA function */
esp_sha(SHA2_256, data, length, digest); esp_sha(SHA2_256, data, length, digest);
#endif #endif
// Map the signature block and verify the signature
sigblock = (const esp_secure_boot_sig_block_t *)(data + length);
esp_err_t err = esp_secure_boot_verify_signature_block(sigblock, digest);
bootloader_munmap(data);
return err;
}
esp_err_t esp_secure_boot_verify_signature_block(const esp_secure_boot_sig_block_t *sig_block, const uint8_t *image_digest)
{
ptrdiff_t keylen;
bool is_valid;
keylen = signature_verification_key_end - signature_verification_key_start; keylen = signature_verification_key_end - signature_verification_key_start;
if(keylen != SIGNATURE_VERIFICATION_KEYLEN) { if(keylen != SIGNATURE_VERIFICATION_KEYLEN) {
ESP_LOGE(TAG, "Embedded public verification key has wrong length %d", keylen); ESP_LOGE(TAG, "Embedded public verification key has wrong length %d", keylen);
goto unmap_and_fail; return ESP_FAIL;
}
if (sig_block->version != 0) {
ESP_LOGE(TAG, "image has invalid signature version field 0x%08x", sig_block->version);
return ESP_FAIL;
} }
is_valid = uECC_verify(signature_verification_key_start, is_valid = uECC_verify(signature_verification_key_start,
digest, sizeof(digest), sigblock->signature, image_digest,
DIGEST_LEN,
sig_block->signature,
uECC_secp256r1()); uECC_secp256r1());
bootloader_munmap(data);
return is_valid ? ESP_OK : ESP_ERR_IMAGE_INVALID; return is_valid ? ESP_OK : ESP_ERR_IMAGE_INVALID;
unmap_and_fail:
bootloader_munmap(data);
return ESP_FAIL;
} }

@ -1 +1 @@
Subproject commit 325f01637b667af02cc6390965b09d50e6a31dac Subproject commit a4207741eca1fb8e5e3670e498ed058320bbcb5a

View File

@ -4261,7 +4261,9 @@
/* Flash MMU table for APP CPU */ /* Flash MMU table for APP CPU */
#define DPORT_APP_FLASH_MMU_TABLE ((volatile uint32_t*) 0x3FF12000) #define DPORT_APP_FLASH_MMU_TABLE ((volatile uint32_t*) 0x3FF12000)
#define DPORT_FLASH_MMU_TABLE_SIZE 0x100
#define DPORT_FLASH_MMU_TABLE_INVALID_VAL 0x100
#endif /*_SOC_DPORT_REG_H_ */ #endif /*_SOC_DPORT_REG_H_ */

View File

@ -83,14 +83,14 @@ static void IRAM_ATTR spi_flash_mmap_init()
uint32_t entry_app = DPORT_APP_FLASH_MMU_TABLE[i]; uint32_t entry_app = DPORT_APP_FLASH_MMU_TABLE[i];
if (entry_pro != entry_app) { if (entry_pro != entry_app) {
// clean up entries used by boot loader // clean up entries used by boot loader
entry_pro = INVALID_ENTRY_VAL; entry_pro = DPORT_FLASH_MMU_TABLE_INVALID_VAL;
DPORT_PRO_FLASH_MMU_TABLE[i] = INVALID_ENTRY_VAL; DPORT_PRO_FLASH_MMU_TABLE[i] = DPORT_FLASH_MMU_TABLE_INVALID_VAL;
} }
if ((entry_pro & INVALID_ENTRY_VAL) == 0 && (i == 0 || i == PRO_IRAM0_FIRST_USABLE_PAGE || entry_pro != 0)) { if ((entry_pro & INVALID_ENTRY_VAL) == 0 && (i == 0 || i == PRO_IRAM0_FIRST_USABLE_PAGE || entry_pro != 0)) {
s_mmap_page_refcnt[i] = 1; s_mmap_page_refcnt[i] = 1;
} else { } else {
DPORT_PRO_FLASH_MMU_TABLE[i] = INVALID_ENTRY_VAL; DPORT_PRO_FLASH_MMU_TABLE[i] = DPORT_FLASH_MMU_TABLE_INVALID_VAL;
DPORT_APP_FLASH_MMU_TABLE[i] = INVALID_ENTRY_VAL; DPORT_APP_FLASH_MMU_TABLE[i] = DPORT_FLASH_MMU_TABLE_INVALID_VAL;
} }
} }
} }

View File

@ -19,6 +19,7 @@ CONFIG_LOG_BOOTLOADER_LEVEL_WARN=y
# CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG is not set # CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG is not set
# CONFIG_LOG_BOOTLOADER_LEVEL_VERBOSE is not set # CONFIG_LOG_BOOTLOADER_LEVEL_VERBOSE is not set
CONFIG_LOG_BOOTLOADER_LEVEL=2 CONFIG_LOG_BOOTLOADER_LEVEL=2
# CONFIG_BOOTLOADER_LTO is not set
# #
# Security features # Security features