Pārlūkot izejas kodu

bootloader: Add fault injection resistance to Secure Boot bootloader verification

Sachin Parekh 5 gadi atpakaļ
vecāks
revīzija
3c7f439d5b

+ 2 - 1
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 :

+ 13 - 0
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 */

+ 1 - 0
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.

+ 167 - 39
components/bootloader_support/src/esp_image_format.c

@@ -18,6 +18,7 @@
 #include <soc/cpu.h>
 #include <esp_image_format.h>
 #include <esp_secure_boot.h>
+#include <esp_fault.h>
 #include <esp_log.h>
 #include <esp_spi_flash.h>
 #include <bootloader_flash.h>
@@ -25,6 +26,7 @@
 #include <bootloader_sha.h>
 #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;
 }
 

+ 105 - 31
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 <string.h>
+#endif
 
 #include <sys/param.h>
 
@@ -38,37 +49,16 @@ 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();
-
-    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;
-    }
+    esp_err_t err = bootloader_sha256_flash_contents(src_addr, length, digest);
 
-    /* Done! Get the digest */
-    bootloader_sha256_finish(handle, digest);
+    if (err != ESP_OK) {
+        return err;
+    }
 
     // Map the signature block
     sigblock = (const esp_secure_boot_sig_block_t *) bootloader_mmap(src_addr + length, sizeof(esp_secure_boot_sig_block_t));
@@ -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

+ 94 - 0
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

+ 1 - 1
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

+ 3 - 3
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)
 

+ 2 - 3
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

+ 141 - 0
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));
+}

+ 18 - 0
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);

+ 1 - 1
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)

+ 3 - 0
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*/

+ 30 - 1
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) );
+}