From 3c7f439d5b3fc7338f1f4eb48c32da5e3207a6bb Mon Sep 17 00:00:00 2001 From: Sachin Parekh Date: Thu, 25 Mar 2021 12:48:27 +0530 Subject: [PATCH] bootloader: Add fault injection resistance to Secure Boot bootloader verification --- .../subproject/main/esp32.bootloader.ld | 3 +- .../include/esp_secure_boot.h | 13 ++ .../include_bootloader/bootloader_utility.h | 1 + .../bootloader_support/src/esp_image_format.c | 206 ++++++++++++++---- .../src/secure_boot_signatures.c | 134 +++++++++--- components/esp32/include/esp_fault.h | 94 ++++++++ components/esptool_py/Makefile.projbuild | 2 +- components/micro-ecc/CMakeLists.txt | 6 +- components/micro-ecc/component.mk | 5 +- components/micro-ecc/uECC_verify_antifault.c | 141 ++++++++++++ components/micro-ecc/uECC_verify_antifault.h | 18 ++ components/partition_table/Makefile.projbuild | 2 +- components/soc/esp32/include/soc/soc.h | 3 + .../soc/include/soc/soc_memory_layout.h | 31 ++- 14 files changed, 580 insertions(+), 79 deletions(-) create mode 100644 components/esp32/include/esp_fault.h create mode 100644 components/micro-ecc/uECC_verify_antifault.c create mode 100644 components/micro-ecc/uECC_verify_antifault.h diff --git a/components/bootloader/subproject/main/esp32.bootloader.ld b/components/bootloader/subproject/main/esp32.bootloader.ld index 7bba4b4693..eb5c94726e 100644 --- a/components/bootloader/subproject/main/esp32.bootloader.ld +++ b/components/bootloader/subproject/main/esp32.bootloader.ld @@ -75,6 +75,7 @@ SECTIONS .dram0.bss (NOLOAD) : { . = ALIGN (8); + _dram_start = ABSOLUTE(.); _bss_start = ABSOLUTE(.); *(.dynsbss) *(.sbss) @@ -151,7 +152,7 @@ SECTIONS *(.gnu.linkonce.lit4.*) _lit4_end = ABSOLUTE(.); . = ALIGN(4); - _heap_start = ABSOLUTE(.); + _dram_end = ABSOLUTE(.); } >dram_seg .iram.text : diff --git a/components/bootloader_support/include/esp_secure_boot.h b/components/bootloader_support/include/esp_secure_boot.h index 17b405e4fa..5914baac86 100644 --- a/components/bootloader_support/include/esp_secure_boot.h +++ b/components/bootloader_support/include/esp_secure_boot.h @@ -123,6 +123,19 @@ typedef struct { esp_err_t esp_secure_boot_verify_signature_block(const esp_secure_boot_sig_block_t *sig_block, const uint8_t *image_digest); +/** @brief Verify the ECDSA secure boot signature block for Secure Boot. + * + * Calculates Deterministic ECDSA w/ SHA256 based on the SHA256 hash of the image. ECDSA signature + * verification must be enabled in project configuration to use this function. + * + * Similar to esp_secure_boot_verify_signature(), but can be used when the digest is precalculated. + * @param sig_block Pointer to ECDSA signature block data + * @param image_digest Pointer to 32 byte buffer holding SHA-256 hash. + * @param verified_digest Pointer to 32 byte buffer that will receive verified digest if verification completes. (Used during bootloader implementation only, result is invalid otherwise.) + * + */ +esp_err_t esp_secure_boot_verify_ecdsa_signature_block(const esp_secure_boot_sig_block_t *sig_block, const uint8_t *image_digest, uint8_t *verified_digest); + #define FLASH_OFFS_SECURE_BOOT_IV_DIGEST 0 /** @brief Secure boot IV+digest header */ diff --git a/components/bootloader_support/include_bootloader/bootloader_utility.h b/components/bootloader_support/include_bootloader/bootloader_utility.h index 227a328a87..6f2c0f7273 100644 --- a/components/bootloader_support/include_bootloader/bootloader_utility.h +++ b/components/bootloader_support/include_bootloader/bootloader_utility.h @@ -14,6 +14,7 @@ #pragma once #include "esp_image_format.h" +#include "bootloader_config.h" /** * @brief Load partition table. diff --git a/components/bootloader_support/src/esp_image_format.c b/components/bootloader_support/src/esp_image_format.c index 7f55952514..8145081805 100644 --- a/components/bootloader_support/src/esp_image_format.c +++ b/components/bootloader_support/src/esp_image_format.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +26,7 @@ #include #include "bootloader_util.h" #include "bootloader_common.h" +#include "soc/soc_memory_layout.h" /* Checking signatures as part of verifying images is necessary: - Always if secure boot is enabled @@ -92,7 +94,7 @@ static esp_err_t verify_segment_header(int index, const esp_image_segment_header 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_signature(bootloader_sha256_handle_t sha_handle, esp_image_metadata_t *data); +static esp_err_t __attribute__((unused)) verify_secure_boot_signature(bootloader_sha256_handle_t sha_handle, esp_image_metadata_t *data,uint8_t *image_digest, uint8_t *verified_digest); static esp_err_t __attribute__((unused)) verify_simple_hash(bootloader_sha256_handle_t sha_handle, esp_image_metadata_t *data); static esp_err_t image_load(esp_image_load_mode_t mode, const esp_partition_pos_t *part, esp_image_metadata_t *data) @@ -108,6 +110,12 @@ static esp_err_t image_load(esp_image_load_mode_t mode, const esp_partition_pos_ uint32_t checksum_word = ESP_ROM_CHECKSUM_INITIAL; bootloader_sha256_handle_t sha_handle = NULL; +#ifdef SECURE_BOOT_CHECK_SIGNATURE + /* used for anti-FI checks */ + uint8_t image_digest[HASH_LEN] = { [ 0 ... 31] = 0xEE }; + uint8_t verified_digest[HASH_LEN] = { [ 0 ... 31 ] = 0x01 }; +#endif + if (data == NULL || part == NULL) { return ESP_ERR_INVALID_ARG; } @@ -196,7 +204,7 @@ static esp_err_t image_load(esp_image_load_mode_t mode, const esp_partition_pos_ if (!is_bootloader) { #ifdef SECURE_BOOT_CHECK_SIGNATURE // secure boot images have a signature appended - err = verify_secure_boot_signature(sha_handle, data); + err = verify_secure_boot_signature(sha_handle, data, image_digest, verified_digest); #else // No secure boot, but SHA-256 can be appended for basic corruption detection if (sha_handle != NULL && !esp_cpu_in_ocd_debug_mode()) { @@ -226,7 +234,22 @@ static esp_err_t image_load(esp_image_load_mode_t mode, const esp_partition_pos_ } #ifdef BOOTLOADER_BUILD - if (do_load) { // Need to deobfuscate RAM + +#ifdef SECURE_BOOT_CHECK_SIGNATURE + /* If signature was checked in bootloader build, verified_digest should equal image_digest + + This is to detect any fault injection that caused signature verification to not complete normally. + + Any attack which bypasses this check should be of limited use as the RAM contents are still obfuscated, therefore we do the check + immediately before we deobfuscate. + + Note: the conditions for making this check are the same as for setting verify_sha above, but on ESP32 SB V1 we move the test for + "only verify signature in bootloader" into the macro so it's tested multiple times. + */ + ESP_FAULT_ASSERT(data->start_addr == ESP_BOOTLOADER_OFFSET || memcmp(image_digest, verified_digest, HASH_LEN) == 0); +#endif // SECURE_BOOT_CHECK_SIGNATURE + + if (do_load && ram_obfs_value[0] != 0 && ram_obfs_value[1] != 0) { // Need to deobfuscate RAM for (int i = 0; i < data->image.segment_count; i++) { uint32_t load_addr = data->segments[i].load_addr; if (should_load(load_addr)) { @@ -298,6 +321,127 @@ static esp_err_t verify_image_header(uint32_t src_addr, const esp_image_header_t return err; } +#ifdef BOOTLOADER_BUILD +/* Check the region load_addr - load_end doesn't overlap any memory used by the bootloader, registers, or other invalid memory + */ +static bool verify_load_addresses(int segment_index, intptr_t load_addr, intptr_t load_end, bool print_error, bool no_recurse) +{ + /* Addresses of static data and the "loader" section of bootloader IRAM, all defined in ld script */ + const char *reason = NULL; + extern int _dram_start, _dram_end, _loader_text_start, _loader_text_end; + void *load_addr_p = (void *)load_addr; + void *load_end_p = (void *)load_end; + + if (load_end == load_addr) { + return true; // zero-length segments are fine + } + assert(load_end > load_addr); // data_len<16MB is checked in verify_segment_header() which is called before this, so this should always be true + + if (esp_ptr_in_dram(load_addr_p) && esp_ptr_in_dram(load_end_p)) { /* Writing to DRAM */ + /* Check if we're clobbering the stack */ + intptr_t sp = (intptr_t)get_sp(); + if (bootloader_util_regions_overlap(sp - STACK_LOAD_HEADROOM, SOC_ROM_STACK_START, + load_addr, load_end)) { + reason = "overlaps bootloader stack"; + goto invalid; + } + + /* Check if we're clobbering static data + + (_dram_start.._dram_end includes bss, data, rodata sections in DRAM) + */ + if (bootloader_util_regions_overlap((intptr_t)&_dram_start, (intptr_t)&_dram_end, load_addr, load_end)) { + reason = "overlaps bootloader data"; + goto invalid; + } + + /* LAST DRAM CHECK (recursive): for D/IRAM, check the equivalent IRAM addresses if needed + + Allow for the possibility that even though both pointers are IRAM, only part of the region is in a D/IRAM + section. In which case we recurse to check the part which falls in D/IRAM. + + Note: We start with SOC_DIRAM_DRAM_LOW/HIGH and convert that address to IRAM to account for any reversing of word order + (chip-specific). + */ + if (!no_recurse && bootloader_util_regions_overlap(SOC_DIRAM_DRAM_LOW, SOC_DIRAM_DRAM_HIGH, load_addr, load_end)) { + intptr_t iram_load_addr, iram_load_end; + + if (esp_ptr_in_diram_dram(load_addr_p)) { + iram_load_addr = (intptr_t)esp_ptr_diram_dram_to_iram(load_addr_p); + } else { + iram_load_addr = (intptr_t)esp_ptr_diram_dram_to_iram((void *)SOC_DIRAM_DRAM_LOW); + } + + if (esp_ptr_in_diram_dram(load_end_p)) { + iram_load_end = (intptr_t)esp_ptr_diram_dram_to_iram(load_end_p); + } else { + iram_load_end = (intptr_t)esp_ptr_diram_dram_to_iram((void *)SOC_DIRAM_DRAM_HIGH); + } + + if (iram_load_end < iram_load_addr) { + return verify_load_addresses(segment_index, iram_load_end, iram_load_addr, print_error, true); + } else { + return verify_load_addresses(segment_index, iram_load_addr, iram_load_end, print_error, true); + } + } + } + else if (esp_ptr_in_iram(load_addr_p) && esp_ptr_in_iram(load_end_p)) { /* Writing to IRAM */ + /* Check for overlap of 'loader' section of IRAM */ + if (bootloader_util_regions_overlap((intptr_t)&_loader_text_start, (intptr_t)&_loader_text_end, + load_addr, load_end)) { + reason = "overlaps loader IRAM"; + goto invalid; + } + + /* LAST IRAM CHECK (recursive): for D/IRAM, check the equivalent DRAM address if needed + + Allow for the possibility that even though both pointers are IRAM, only part of the region is in a D/IRAM + section. In which case we recurse to check the part which falls in D/IRAM. + Note: We start with SOC_DIRAM_IRAM_LOW/HIGH and convert that address to DRAM to account for any reversing of word order + (chip-specific). + */ + if (!no_recurse && bootloader_util_regions_overlap(SOC_DIRAM_IRAM_LOW, SOC_DIRAM_IRAM_HIGH, load_addr, load_end)) { + intptr_t dram_load_addr, dram_load_end; + + if (esp_ptr_in_diram_iram(load_addr_p)) { + dram_load_addr = (intptr_t)esp_ptr_diram_iram_to_dram(load_addr_p); + } else { + dram_load_addr = (intptr_t)esp_ptr_diram_iram_to_dram((void *)SOC_DIRAM_IRAM_LOW); + } + + if (esp_ptr_in_diram_iram(load_end_p)) { + dram_load_end = (intptr_t)esp_ptr_diram_iram_to_dram(load_end_p); + } else { + dram_load_end = (intptr_t)esp_ptr_diram_iram_to_dram((void *)SOC_DIRAM_IRAM_HIGH); + } + + if (dram_load_end < dram_load_addr) { + return verify_load_addresses(segment_index, dram_load_end, dram_load_addr, print_error, true); + } else { + return verify_load_addresses(segment_index, dram_load_addr, dram_load_end, print_error, true); + } + } + /* Sections entirely in RTC memory won't overlap with a vanilla bootloader but are valid load addresses, thus skipping them from the check */ + } else if (esp_ptr_in_rtc_iram_fast(load_addr_p) && esp_ptr_in_rtc_iram_fast(load_end_p)){ + return true; + } else if (esp_ptr_in_rtc_dram_fast(load_addr_p) && esp_ptr_in_rtc_dram_fast(load_end_p)){ + return true; + } else if (esp_ptr_in_rtc_slow(load_addr_p) && esp_ptr_in_rtc_slow(load_end_p)) { + return true; + } else { /* Not a DRAM or an IRAM or RTC Fast IRAM, RTC Fast DRAM or RTC Slow address */ + reason = "bad load address range"; + goto invalid; + } + return true; + + invalid: + if (print_error) { + ESP_LOGE(TAG, "Segment %d 0x%08x-0x%08x invalid: %s", segment_index, load_addr, load_end, reason); + } + return false; +} +#endif // BOOTLOADER_BUILD + static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segment_header_t *header, bool silent, bool do_load, bootloader_sha256_handle_t sha_handle, uint32_t *checksum) { esp_err_t err; @@ -337,37 +481,11 @@ static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segme (do_load)?"load":(is_mapping)?"map":""); } - #ifdef BOOTLOADER_BUILD /* Before loading segment, check it doesn't clobber bootloader RAM. */ - if (do_load) { - const intptr_t load_end = load_addr + data_len; - if (load_end <= (intptr_t) SOC_DIRAM_DRAM_HIGH) { - /* Writing to DRAM */ - intptr_t sp = (intptr_t)get_sp(); - if (load_end > sp - STACK_LOAD_HEADROOM) { - /* Bootloader .data/.rodata/.bss is above the stack, so this - * also checks that we aren't overwriting these segments. - * - * TODO: This assumes specific arrangement of sections we have - * in the ESP32. Rewrite this in a generic way to support other - * layouts. - */ - ESP_LOGE(TAG, "Segment %d end address 0x%08x too high (bootloader stack 0x%08x limit 0x%08x)", - index, load_end, sp, sp - STACK_LOAD_HEADROOM); - return ESP_ERR_IMAGE_INVALID; - } - } else { - /* Writing to IRAM */ - const intptr_t loader_iram_start = (intptr_t) &_loader_text_start; - const intptr_t loader_iram_end = (intptr_t) &_loader_text_end; - - if (bootloader_util_regions_overlap(loader_iram_start, loader_iram_end, - load_addr, load_end)) { - ESP_LOGE(TAG, "Segment %d (0x%08x-0x%08x) overlaps bootloader IRAM (0x%08x-0x%08x)", - index, load_addr, load_end, loader_iram_start, loader_iram_end); - return ESP_ERR_IMAGE_INVALID; - } + if (do_load && data_len > 0) { + if (!verify_load_addresses(index, load_addr, load_addr + data_len, true, false)) { + return ESP_ERR_IMAGE_INVALID; } } #endif // BOOTLOADER_BUILD @@ -377,6 +495,10 @@ static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segme int32_t data_len_remain = data_len; while (data_len_remain > 0) { +#if defined(SECURE_BOOT_CHECK_SIGNATURE) && defined(BOOTLOADER_BUILD) + /* Double check the address verification done above */ + ESP_FAULT_ASSERT(!do_load || verify_load_addresses(0, load_addr, load_addr + data_len_remain, false, false)); +#endif uint32_t offset_page = ((data_addr & MMAP_ALIGNED_MASK) != 0) ? 1 : 0; /* Data we could map in case we are not aligned to PAGE boundary is one page size lesser. */ data_len = MIN(data_len_remain, ((free_page_count - offset_page) * SPI_FLASH_MMU_PAGE_SIZE)); @@ -572,28 +694,33 @@ static esp_err_t verify_checksum(bootloader_sha256_handle_t sha_handle, uint32_t static void debug_log_hash(const uint8_t *image_hash, const char *caption); -static esp_err_t verify_secure_boot_signature(bootloader_sha256_handle_t sha_handle, esp_image_metadata_t *data) +static esp_err_t verify_secure_boot_signature(bootloader_sha256_handle_t sha_handle, esp_image_metadata_t *data, uint8_t *image_digest, uint8_t *verified_digest) { - uint8_t image_hash[HASH_LEN] = { 0 }; +#ifdef SECURE_BOOT_CHECK_SIGNATURE + uint32_t end = data->start_addr + data->image_len; ESP_LOGI(TAG, "Verifying image signature..."); // 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); + const void *simple_hash = bootloader_mmap(end - HASH_LEN, HASH_LEN); bootloader_sha256_data(sha_handle, simple_hash, HASH_LEN); bootloader_munmap(simple_hash); } - bootloader_sha256_finish(sha_handle, image_hash); + bootloader_sha256_finish(sha_handle, image_digest); // Log the hash for debugging - debug_log_hash(image_hash, "Calculated secure boot hash"); + debug_log_hash(image_digest, "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); + esp_err_t err = ESP_ERR_IMAGE_INVALID; + const void *sig_block; + ESP_FAULT_ASSERT(memcmp(image_digest, verified_digest, HASH_LEN) != 0); /* sanity check that these values start differently */ + sig_block = bootloader_mmap(data->start_addr + data->image_len, sizeof(esp_secure_boot_sig_block_t)); + err = esp_secure_boot_verify_ecdsa_signature_block(sig_block, image_digest, verified_digest); + bootloader_munmap(sig_block); if (err != ESP_OK) { ESP_LOGE(TAG, "Secure boot signature verification failed"); @@ -614,6 +741,7 @@ static esp_err_t verify_secure_boot_signature(bootloader_sha256_handle_t sha_han return ESP_ERR_IMAGE_INVALID; } +#endif // SECURE_BOOT_CHECK_SIGNATURE return ESP_OK; } diff --git a/components/bootloader_support/src/secure_boot_signatures.c b/components/bootloader_support/src/secure_boot_signatures.c index 6981872f27..ceeaeec5ed 100644 --- a/components/bootloader_support/src/secure_boot_signatures.c +++ b/components/bootloader_support/src/secure_boot_signatures.c @@ -15,11 +15,22 @@ #include "bootloader_flash.h" #include "bootloader_sha.h" +#include "bootloader_utility.h" #include "esp_log.h" #include "esp_image_format.h" #include "esp_secure_boot.h" -#include "uECC.h" +#ifdef BOOTLOADER_BUILD +#include "uECC_verify_antifault.h" +#else +#include "mbedtls/sha256.h" +#include "mbedtls/x509.h" +#include "mbedtls/md.h" +#include "mbedtls/platform.h" +#include "mbedtls/entropy.h" +#include "mbedtls/ctr_drbg.h" +#include +#endif #include @@ -38,38 +49,17 @@ extern const uint8_t signature_verification_key_end[] asm("_binary_signature_ver esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length) { uint8_t digest[DIGEST_LEN]; - const uint8_t *data; + uint8_t verified_digest[DIGEST_LEN] = { 0 }; /* ignored in this function */ const esp_secure_boot_sig_block_t *sigblock; ESP_LOGD(TAG, "verifying signature src_addr 0x%x length 0x%x", src_addr, length); - bootloader_sha256_handle_t handle = bootloader_sha256_start(); + esp_err_t err = bootloader_sha256_flash_contents(src_addr, length, digest); - uint32_t free_page_count = bootloader_mmap_get_free_pages(); - ESP_LOGD(TAG, "free data page_count 0x%08x", free_page_count); - - int32_t data_len_remain = length; - uint32_t data_addr = src_addr; - while (data_len_remain > 0) { - uint32_t offset_page = ((data_addr & MMAP_ALIGNED_MASK) != 0) ? 1 : 0; - /* Data we could map in case we are not aligned to PAGE boundary is one page size lesser. */ - uint32_t data_len = MIN(data_len_remain, ((free_page_count - offset_page) * SPI_FLASH_MMU_PAGE_SIZE)); - data = (const uint8_t *) bootloader_mmap(data_addr, data_len); - if(!data) { - ESP_LOGE(TAG, "bootloader_mmap(0x%x, 0x%x) failed", data_addr, data_len); - bootloader_sha256_finish(handle, NULL); - return ESP_FAIL; - } - bootloader_sha256_data(handle, data, data_len); - bootloader_munmap(data); - - data_addr += data_len; - data_len_remain -= data_len; + if (err != ESP_OK) { + return err; } - /* Done! Get the digest */ - bootloader_sha256_finish(handle, digest); - // Map the signature block sigblock = (const esp_secure_boot_sig_block_t *) bootloader_mmap(src_addr + length, sizeof(esp_secure_boot_sig_block_t)); if(!sigblock) { @@ -77,20 +67,27 @@ esp_err_t esp_secure_boot_verify_signature(uint32_t src_addr, uint32_t length) return ESP_FAIL; } // Verify the signature - esp_err_t err = esp_secure_boot_verify_signature_block(sigblock, digest); + err = esp_secure_boot_verify_ecdsa_signature_block(sigblock, digest, verified_digest); // Unmap bootloader_munmap(sigblock); return err; } +#ifdef BOOTLOADER_BUILD esp_err_t esp_secure_boot_verify_signature_block(const esp_secure_boot_sig_block_t *sig_block, const uint8_t *image_digest) +{ + uint8_t verified_digest[DIGEST_LEN] = { 0 }; + return esp_secure_boot_verify_ecdsa_signature_block(sig_block, image_digest, verified_digest); +} + +esp_err_t esp_secure_boot_verify_ecdsa_signature_block(const esp_secure_boot_sig_block_t *sig_block, const uint8_t *image_digest, uint8_t *verified_digest) { ptrdiff_t keylen; bool is_valid; 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); return ESP_FAIL; } @@ -102,11 +99,88 @@ esp_err_t esp_secure_boot_verify_signature_block(const esp_secure_boot_sig_block ESP_LOGD(TAG, "Verifying secure boot signature"); - is_valid = uECC_verify(signature_verification_key_start, + is_valid = uECC_verify_antifault(signature_verification_key_start, image_digest, DIGEST_LEN, sig_block->signature, - uECC_secp256r1()); + uECC_secp256r1(), + verified_digest); ESP_LOGD(TAG, "Verification result %d", is_valid); return is_valid ? ESP_OK : ESP_ERR_IMAGE_INVALID; } + +#else // BOOTLOADER_BUILD + +esp_err_t esp_secure_boot_verify_ecdsa_signature_block(const esp_secure_boot_sig_block_t *sig_block, const uint8_t *image_digest, uint8_t *verified_digest) +{ +#if !(defined(CONFIG_MBEDTLS_ECDSA_C) && defined(CONFIG_MBEDTLS_ECP_DP_SECP256R1_ENABLED)) + ESP_LOGE(TAG, "Signature verification requires ECDSA & SECP256R1 curve enabled"); + return ESP_ERR_NOT_SUPPORTED; +#else + ptrdiff_t keylen; + + /* Note: in IDF app image verification we don't add any fault injection resistance, boot-time checks only */ + memset(verified_digest, 0, DIGEST_LEN); + + keylen = signature_verification_key_end - signature_verification_key_start; + if (keylen != SIGNATURE_VERIFICATION_KEYLEN) { + ESP_LOGE(TAG, "Embedded public verification key has wrong length %d", keylen); + 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; + } + + ESP_LOGD(TAG, "Verifying secure boot signature"); + + int ret; + mbedtls_mpi r, s; + + mbedtls_mpi_init(&r); + mbedtls_mpi_init(&s); + + /* Extract r and s components from RAW ECDSA signature of 64 bytes */ +#define ECDSA_INTEGER_LEN 32 + ret = mbedtls_mpi_read_binary(&r, &sig_block->signature[0], ECDSA_INTEGER_LEN); + if (ret != 0) { + ESP_LOGE(TAG, "Failed mbedtls_mpi_read_binary(1), err:%d", ret); + return ESP_FAIL; + } + + ret = mbedtls_mpi_read_binary(&s, &sig_block->signature[ECDSA_INTEGER_LEN], ECDSA_INTEGER_LEN); + if (ret != 0) { + ESP_LOGE(TAG, "Failed mbedtls_mpi_read_binary(2), err:%d", ret); + mbedtls_mpi_free(&r); + return ESP_FAIL; + } + + /* Initialise ECDSA context */ + mbedtls_ecdsa_context ecdsa_context; + mbedtls_ecdsa_init(&ecdsa_context); + + mbedtls_ecp_group_load(&ecdsa_context.grp, MBEDTLS_ECP_DP_SECP256R1); + size_t plen = mbedtls_mpi_size(&ecdsa_context.grp.P); + if (keylen != 2 * plen) { + ESP_LOGE(TAG, "Incorrect ECDSA key length %d", keylen); + ret = ESP_FAIL; + goto cleanup; + } + + /* Extract X and Y components from ECDSA public key */ + MBEDTLS_MPI_CHK(mbedtls_mpi_read_binary(&ecdsa_context.Q.X, signature_verification_key_start, plen)); + MBEDTLS_MPI_CHK(mbedtls_mpi_read_binary(&ecdsa_context.Q.Y, signature_verification_key_start + plen, plen)); + MBEDTLS_MPI_CHK(mbedtls_mpi_lset(&ecdsa_context.Q.Z, 1)); + + ret = mbedtls_ecdsa_verify(&ecdsa_context.grp, image_digest, DIGEST_LEN, &ecdsa_context.Q, &r, &s); + ESP_LOGD(TAG, "Verification result %d", ret); + +cleanup: + mbedtls_mpi_free(&r); + mbedtls_mpi_free(&s); + mbedtls_ecdsa_free(&ecdsa_context); + return ret == 0 ? ESP_OK : ESP_ERR_IMAGE_INVALID; +#endif // CONFIG_MBEDTLS_ECDSA_C && CONFIG_MBEDTLS_ECP_DP_SECP256R1_ENABLED +} +#endif // BOOTLOADER_BUILD diff --git a/components/esp32/include/esp_fault.h b/components/esp32/include/esp_fault.h new file mode 100644 index 0000000000..84700b8abb --- /dev/null +++ b/components/esp32/include/esp_fault.h @@ -0,0 +1,94 @@ +// Copyright 2020 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "sdkconfig.h" +#include "soc/rtc_cntl_reg.h" + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Assert a condition is true, in a way that should be resistant to fault injection for + * single fault attacks. + * + * - Expands CONDITION multiple times (condition must have no side effects) + * - Compiler is told all registers are invalid before evaluating CONDITION each time, to avoid a fault + * causing a misread of a register used in all three evaluations of CONDITION. + * - If CONDITION is ever false, a system reset is triggered. + * + * @note Place this macro after a "normal" check of CONDITION that will fail with a normal error + * message. This is the fallback in case a fault injection attack skips or corrupts the result of + * that check. (Although ensure that an attacker can't use fault injection to skip past the "normal" + * error message, to avoid this check entirely.) + * + * @note This macro increases binary size and is slow and should be used sparingly. +* + * @note This macro does not guarantee fault injection resistance. In particular CONDITION must be + * chosen carefully - a fault injection attack which sets CONDITION to true will not be detected by + * this macro. Care must also be taken that an attacker can't use a fault to completely bypass calling + * whatever function tests ESP_FAULT_ASSERT. + * + * @note This is difficult to debug as a failure triggers an instant software reset, and UART output + * is often truncated (as FIFO is not flushed). Define the ESP_FAULT_ASSERT_DEBUG macro to debug any + * failures of this macro due to software bugs. + * + * @param CONDITION A condition which will evaluate true unless an attacker used fault injection to skip or corrupt some other critical system + calculation. + * + */ +#define ESP_FAULT_ASSERT(CONDITION) do { \ + asm volatile ("" ::: "memory"); \ + if(!(CONDITION)) _ESP_FAULT_RESET(); \ + asm volatile ("" ::: "memory"); \ + if(!(CONDITION)) _ESP_FAULT_RESET(); \ + asm volatile ("" ::: "memory"); \ + if(!(CONDITION)) _ESP_FAULT_RESET(); \ +} while(0) + + +// Uncomment this macro to get debug output if ESP_FAULT_ASSERT() fails +// +// Note that uncommenting this macro reduces the anti-FI effectiveness +// +//#define ESP_FAULT_ASSERT_DEBUG + +/* Internal macro, purpose is to trigger a system reset if an inconsistency due to fault injection + is detected. + + Illegal instruction opcodes are there as a fallback to crash the CPU in case it doesn't + reset as expected. +*/ +#ifndef ESP_FAULT_ASSERT_DEBUG + +#define _ESP_FAULT_RESET() do { \ + REG_WRITE(RTC_CNTL_OPTIONS0_REG, RTC_CNTL_SW_SYS_RST); \ + asm volatile("ill; ill; ill;"); \ + } while(0) + +#else // ESP_FAULT_ASSERT_DEBUG + +#warning "Enabling ESP_FAULT_ASSERT_DEBUG makes ESP_FAULT_ASSERT() less effective" + +#define _ESP_FAULT_RESET() do { \ + ets_printf("ESP_FAULT_ASSERT %s:%d\n", __FILE__, __LINE__); \ + asm volatile("ill;"); \ + } while(0) + +#endif // ESP_FAULT_ASSERT_DEBUG + +#ifdef __cplusplus +} +#endif diff --git a/components/esptool_py/Makefile.projbuild b/components/esptool_py/Makefile.projbuild index 707b1a3cf8..d51b643963 100644 --- a/components/esptool_py/Makefile.projbuild +++ b/components/esptool_py/Makefile.projbuild @@ -57,7 +57,7 @@ ifndef IS_BOOTLOADER_BUILD APP_BIN_UNSIGNED := $(APP_BIN:.bin=-unsigned.bin) $(APP_BIN): $(APP_BIN_UNSIGNED) $(SECURE_BOOT_SIGNING_KEY) $(SDKCONFIG_MAKEFILE) - $(ESPSECUREPY) sign_data --keyfile $(SECURE_BOOT_SIGNING_KEY) -o $@ $< + $(ESPSECUREPY) sign_data --version 1 --keyfile $(SECURE_BOOT_SIGNING_KEY) -o $@ $< endif endif # non-secure boot (or bootloader), both these files are the same diff --git a/components/micro-ecc/CMakeLists.txt b/components/micro-ecc/CMakeLists.txt index a0e1693b11..9a7cefc604 100644 --- a/components/micro-ecc/CMakeLists.txt +++ b/components/micro-ecc/CMakeLists.txt @@ -1,7 +1,7 @@ -# only compile the "micro-ecc/uECC.c" source file -set(COMPONENT_SRCS "micro-ecc/uECC.c") +# only compile the "uECC_verify_antifault.c" file which includes the "micro-ecc/uECC.c" source file +set(COMPONENT_SRCS "uECC_verify_antifault.c") -set(COMPONENT_ADD_INCLUDEDIRS micro-ecc) +set(COMPONENT_ADD_INCLUDEDIRS . micro-ecc) set(COMPONENT_REQUIRES) diff --git a/components/micro-ecc/component.mk b/components/micro-ecc/component.mk index 8c569df597..3ad1dbaf79 100644 --- a/components/micro-ecc/component.mk +++ b/components/micro-ecc/component.mk @@ -1,8 +1,7 @@ # only compile the micro-ecc/uECC.c source file # (SRCDIRS is needed so build system can find the source file) -COMPONENT_SRCDIRS := micro-ecc -COMPONENT_OBJS := micro-ecc/uECC.o +COMPONENT_SRCDIRS := . -COMPONENT_ADD_INCLUDEDIRS := micro-ecc +COMPONENT_ADD_INCLUDEDIRS := . micro-ecc COMPONENT_SUBMODULES := micro-ecc diff --git a/components/micro-ecc/uECC_verify_antifault.c b/components/micro-ecc/uECC_verify_antifault.c new file mode 100644 index 0000000000..55ad2cfbb1 --- /dev/null +++ b/components/micro-ecc/uECC_verify_antifault.c @@ -0,0 +1,141 @@ +/* Copyright 2014, Kenneth MacKay. Licensed under the BSD 2-clause license. + + Modifications Copyright 2020, Espressif Systems (Shanghai) PTE LTD. Licensed under the BSD + 2-clause license. +*/ + +/* uECC_verify() calls a number of static functions form here and + uses other definitions, so we just build that whole source file here and then append + our modified version uECC_verify_antifault(). */ +#include "micro-ecc/uECC.c" + +/* Version of uECC_verify() which also copies message_hash into verified_hash, + but only if the signature is valid. Does this in an FI resistant way. +*/ +int uECC_verify_antifault(const uint8_t *public_key, + const uint8_t *message_hash, + unsigned hash_size, + const uint8_t *signature, + uECC_Curve curve, + uint8_t *verified_hash) { + uECC_word_t u1[uECC_MAX_WORDS], u2[uECC_MAX_WORDS]; + uECC_word_t z[uECC_MAX_WORDS]; + uECC_word_t sum[uECC_MAX_WORDS * 2]; + uECC_word_t rx[uECC_MAX_WORDS]; + uECC_word_t ry[uECC_MAX_WORDS]; + uECC_word_t tx[uECC_MAX_WORDS]; + uECC_word_t ty[uECC_MAX_WORDS]; + uECC_word_t tz[uECC_MAX_WORDS]; + const uECC_word_t *points[4]; + const uECC_word_t *point; + bitcount_t num_bits; + bitcount_t i; +#if uECC_VLI_NATIVE_LITTLE_ENDIAN + uECC_word_t *_public = (uECC_word_t *)public_key; +#else + uECC_word_t _public[uECC_MAX_WORDS * 2]; +#endif + uECC_word_t r[uECC_MAX_WORDS], s[uECC_MAX_WORDS]; + wordcount_t num_words = curve->num_words; + wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits); + + rx[num_n_words - 1] = 0; + r[num_n_words - 1] = 0; + s[num_n_words - 1] = 0; + +#if uECC_VLI_NATIVE_LITTLE_ENDIAN + bcopy((uint8_t *) r, signature, curve->num_bytes); + bcopy((uint8_t *) s, signature + curve->num_bytes, curve->num_bytes); +#else + uECC_vli_bytesToNative(_public, public_key, curve->num_bytes); + uECC_vli_bytesToNative( + _public + num_words, public_key + curve->num_bytes, curve->num_bytes); + uECC_vli_bytesToNative(r, signature, curve->num_bytes); + uECC_vli_bytesToNative(s, signature + curve->num_bytes, curve->num_bytes); +#endif + + /* r, s must not be 0. */ + if (uECC_vli_isZero(r, num_words) || uECC_vli_isZero(s, num_words)) { + return 0; + } + + /* r, s must be < n. */ + if (uECC_vli_cmp(curve->n, r, num_n_words) != 1 || + uECC_vli_cmp(curve->n, s, num_n_words) != 1) { + return 0; + } + + /* Calculate u1 and u2. */ + uECC_vli_modInv(z, s, curve->n, num_n_words); /* z = 1/s */ + u1[num_n_words - 1] = 0; + bits2int(u1, message_hash, hash_size, curve); + uECC_vli_modMult(u1, u1, z, curve->n, num_n_words); /* u1 = e/s */ + uECC_vli_modMult(u2, r, z, curve->n, num_n_words); /* u2 = r/s */ + + /* Calculate sum = G + Q. */ + uECC_vli_set(sum, _public, num_words); + uECC_vli_set(sum + num_words, _public + num_words, num_words); + uECC_vli_set(tx, curve->G, num_words); + uECC_vli_set(ty, curve->G + num_words, num_words); + uECC_vli_modSub(z, sum, tx, curve->p, num_words); /* z = x2 - x1 */ + XYcZ_add(tx, ty, sum, sum + num_words, curve); + uECC_vli_modInv(z, z, curve->p, num_words); /* z = 1/z */ + apply_z(sum, sum + num_words, z, curve); + + /* Use Shamir's trick to calculate u1*G + u2*Q */ + points[0] = 0; + points[1] = curve->G; + points[2] = _public; + points[3] = sum; + num_bits = smax(uECC_vli_numBits(u1, num_n_words), + uECC_vli_numBits(u2, num_n_words)); + + point = points[(!!uECC_vli_testBit(u1, num_bits - 1)) | + ((!!uECC_vli_testBit(u2, num_bits - 1)) << 1)]; + uECC_vli_set(rx, point, num_words); + uECC_vli_set(ry, point + num_words, num_words); + uECC_vli_clear(z, num_words); + z[0] = 1; + + for (i = num_bits - 2; i >= 0; --i) { + uECC_word_t index; + curve->double_jacobian(rx, ry, z, curve); + + index = (!!uECC_vli_testBit(u1, i)) | ((!!uECC_vli_testBit(u2, i)) << 1); + point = points[index]; + if (point) { + uECC_vli_set(tx, point, num_words); + uECC_vli_set(ty, point + num_words, num_words); + apply_z(tx, ty, z, curve); + uECC_vli_modSub(tz, rx, tx, curve->p, num_words); /* Z = x2 - x1 */ + XYcZ_add(tx, ty, rx, ry, curve); + uECC_vli_modMult_fast(z, z, tz, curve); + } + } + + uECC_vli_modInv(z, z, curve->p, num_words); /* Z = 1/Z */ + apply_z(rx, ry, z, curve); + + /* v = x1 (mod n) */ + if (uECC_vli_cmp(curve->n, rx, num_n_words) != 1) { + uECC_vli_sub(rx, rx, curve->n, num_n_words); + } + + /* Anti-FI addition. Copy message_hash into verified_hash, but do it in a + way that it will only happen if v == r (ie, rx == r) + */ + const uECC_word_t *mhash_words = (const uECC_word_t *)message_hash; + uECC_word_t *vhash_words = (uECC_word_t *)verified_hash; + unsigned hash_words = hash_size / sizeof(uECC_word_t); + for (unsigned int w = 0; w < hash_words; w++) { + /* note: using curve->num_words here to encourage compiler to re-read this variable */ + vhash_words[w] = mhash_words[w] ^ rx[w % curve->num_words] ^ r[w % curve->num_words]; + } + /* Curve may be longer than hash, in which case keep reading the rest of the bytes */ + for (int w = hash_words; w < curve->num_words; w++) { + vhash_words[w % hash_words] |= rx[w] ^ r[w]; + } + + /* Accept only if v == r. */ + return (int)(uECC_vli_equal(rx, r, num_words)); +} diff --git a/components/micro-ecc/uECC_verify_antifault.h b/components/micro-ecc/uECC_verify_antifault.h new file mode 100644 index 0000000000..fa14b24f66 --- /dev/null +++ b/components/micro-ecc/uECC_verify_antifault.h @@ -0,0 +1,18 @@ +/* Copyright 2014, Kenneth MacKay. Licensed under the BSD 2-clause license. + + Modifications Copyright 2020, Espressif Systems (Shanghai) PTE LTD. Licensed under the BSD + 2-clause license. +*/ +#pragma once +#include "uECC.h" + +/* Version uECC_verify() that also copies message_hash to verified_hash + if the signature is valid, and does it in a way that is harder to attack + with fault injection. +*/ +int uECC_verify_antifault(const uint8_t *public_key, + const uint8_t *message_hash, + unsigned hash_size, + const uint8_t *signature, + uECC_Curve curve, + uint8_t *verified_hash); diff --git a/components/partition_table/Makefile.projbuild b/components/partition_table/Makefile.projbuild index 57aeca2b93..11ee24a547 100644 --- a/components/partition_table/Makefile.projbuild +++ b/components/partition_table/Makefile.projbuild @@ -50,7 +50,7 @@ ifdef CONFIG_SECURE_BOOT_BUILD_SIGNED_BINARIES PARTITION_TABLE_BIN_UNSIGNED := $(PARTITION_TABLE_BIN:.bin=-unsigned.bin) # add an extra signing step for secure partition table $(PARTITION_TABLE_BIN): $(PARTITION_TABLE_BIN_UNSIGNED) $(SDKCONFIG_MAKEFILE) $(SECURE_BOOT_SIGNING_KEY) - $(ESPSECUREPY) sign_data --keyfile $(SECURE_BOOT_SIGNING_KEY) -o $@ $< + $(ESPSECUREPY) sign_data --version 1 --keyfile $(SECURE_BOOT_SIGNING_KEY) -o $@ $< else # secure bootloader disabled, both files are the same PARTITION_TABLE_BIN_UNSIGNED := $(PARTITION_TABLE_BIN) diff --git a/components/soc/esp32/include/soc/soc.h b/components/soc/esp32/include/soc/soc.h index d1d468303c..2aa3cc5020 100644 --- a/components/soc/esp32/include/soc/soc.h +++ b/components/soc/esp32/include/soc/soc.h @@ -322,6 +322,9 @@ #define SOC_MEM_INTERNAL_LOW 0x3FF90000 #define SOC_MEM_INTERNAL_HIGH 0x400C2000 +// Start (highest address) of ROM boot stack, only relevant during early boot +#define SOC_ROM_STACK_START 0x3ffe3f20 + //Interrupt hardware source table //This table is decided by hardware, don't touch this. #define ETS_WIFI_MAC_INTR_SOURCE 0/**< interrupt of WiFi MAC, level*/ diff --git a/components/soc/include/soc/soc_memory_layout.h b/components/soc/include/soc/soc_memory_layout.h index 9dbe4fbd86..caae8e384b 100644 --- a/components/soc/include/soc/soc_memory_layout.h +++ b/components/soc/include/soc/soc_memory_layout.h @@ -207,9 +207,38 @@ inline static bool IRAM_ATTR esp_ptr_in_diram_iram(const void *p) { return ((intptr_t)p >= SOC_DIRAM_IRAM_LOW && (intptr_t)p < SOC_DIRAM_IRAM_HIGH); } - inline static bool IRAM_ATTR esp_stack_ptr_is_sane(uint32_t sp) { //Check if stack ptr is in between SOC_DRAM_LOW and SOC_DRAM_HIGH, and 16 byte aligned. return !(sp < SOC_DRAM_LOW + 0x10 || sp > SOC_DRAM_HIGH - 0x10 || ((sp & 0xF) != 0)); } + +inline static bool IRAM_ATTR esp_ptr_in_rtc_iram_fast(const void *p) { + return ((intptr_t)p >= SOC_RTC_IRAM_LOW && (intptr_t)p < SOC_RTC_IRAM_HIGH); +} + +inline static bool IRAM_ATTR esp_ptr_in_rtc_dram_fast(const void *p) { + return ((intptr_t)p >= SOC_RTC_DRAM_LOW && (intptr_t)p < SOC_RTC_DRAM_HIGH); +} + +inline static bool IRAM_ATTR esp_ptr_in_rtc_slow(const void *p) { + return ((intptr_t)p >= SOC_RTC_DATA_LOW && (intptr_t)p < SOC_RTC_DATA_HIGH); +} + +/* Convert a D/IRAM DRAM pointer to equivalent word address in IRAM + + - Address must be word aligned + - Address must pass esp_ptr_in_diram_dram() test, or result will be invalid pointer +*/ +inline static void * IRAM_ATTR esp_ptr_diram_dram_to_iram(const void *p) { + return (void *) ( SOC_DIRAM_IRAM_LOW + ((intptr_t)p - SOC_DIRAM_DRAM_LOW) ); +} + +/* Convert a D/IRAM IRAM pointer to equivalent word address in DRAM + + - Address must be word aligned + - Address must pass esp_ptr_in_diram_iram() test, or result will be invalid pointer +*/ +inline static void * IRAM_ATTR esp_ptr_diram_iram_to_dram(const void *p) { + return (void *) ( SOC_DIRAM_DRAM_LOW + ((intptr_t)p - SOC_DIRAM_IRAM_LOW) ); +}