]> granicus.if.org Git - esp-idf/commitdiff
bootloader: Calculate SHA-256 of image while loading/verifying
authorAngus Gratton <angus@espressif.com>
Tue, 27 Jun 2017 07:25:30 +0000 (17:25 +1000)
committerAngus Gratton <gus@projectgus.com>
Wed, 19 Jul 2017 08:25:17 +0000 (18:25 +1000)
components/bootloader_support/include/esp_image_format.h
components/bootloader_support/include/esp_secure_boot.h
components/bootloader_support/include_priv/bootloader_sha.h [new file with mode: 0644]
components/bootloader_support/src/bootloader_flash.c
components/bootloader_support/src/bootloader_sha.c [new file with mode: 0644]
components/bootloader_support/src/esp_image_format.c
components/bootloader_support/test/test_verify_image.c

index 97afafc3f0781104e1c876916d3e4b4262f53d00..2e49252aa5e62502af33013a5b813c0020a7ed7d 100644 (file)
@@ -81,8 +81,7 @@ typedef struct {
   esp_image_header_t image; /* Header for entire image */
   esp_image_segment_header_t segments[ESP_IMAGE_MAX_SEGMENTS]; /* Per-segment header data */
   uint32_t segment_data[ESP_IMAGE_MAX_SEGMENTS]; /* Data offsets for each segment */
-  uint32_t image_length;
-
+  uint32_t image_len; /* Length of image on flash, in bytes */
 } esp_image_metadata_t;
 
 /* Mode selection for esp_image_load() */
index 8e33a8b46095802de4dfff826f6d9389a4bec9df..003328557d3b8a9e3751812cbd45f800b7c4d22a 100644 (file)
 // 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.
-#ifndef __ESP32_SECUREBOOT_H
-#define __ESP32_SECUREBOOT_H
+#pragma once
 
 #include <stdbool.h>
 #include <esp_err.h>
 #include "soc/efuse_reg.h"
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 /* Support functions for secure boot features.
 
    Can be compiled as part of app or bootloader code.
@@ -88,4 +91,7 @@ typedef struct {
     uint8_t digest[64];
 } esp_secure_boot_iv_digest_t;
 
+
+#ifdef __cplusplus
+}
 #endif
diff --git a/components/bootloader_support/include_priv/bootloader_sha.h b/components/bootloader_support/include_priv/bootloader_sha.h
new file mode 100644 (file)
index 0000000..0434000
--- /dev/null
@@ -0,0 +1,32 @@
+// Copyright 2015-2016 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.
+#pragma once
+
+/* Provide a SHA256 API for bootloader_support code,
+   that can be used from bootloader or app code.
+
+   This header is available to source code in the bootloader & bootloader_support components only.
+   Use mbedTLS APIs or include hwcrypto/sha.h to calculate SHA256 in IDF apps.
+*/
+
+#include <stdint.h>
+#include <stdlib.h>
+
+typedef void *bootloader_sha256_handle_t;
+
+bootloader_sha256_handle_t bootloader_sha256_start();
+
+void bootloader_sha256_data(bootloader_sha256_handle_t handle, const void *data, size_t data_len);
+
+void bootloader_sha256_finish(bootloader_sha256_handle_t handle, uint8_t *digest);
index 0432d755c2cd10b935472ffa220d3f4b2cad30c8..9a82590393e6850786ec70b8e45773dc6ef71a00 100644 (file)
@@ -32,11 +32,13 @@ const void *bootloader_mmap(uint32_t src_addr, uint32_t size)
         return NULL; /* existing mapping in use... */
     }
     const void *result = NULL;
-    esp_err_t err = spi_flash_mmap(src_addr, size, SPI_FLASH_MMAP_DATA, &result, &map);
+    uint32_t src_page = src_addr & ~(SPI_FLASH_MMU_PAGE_SIZE-1);
+    size += (src_addr - src_page);
+    esp_err_t err = spi_flash_mmap(src_page, size, SPI_FLASH_MMAP_DATA, &result, &map);
     if (err != ESP_OK) {
         result = NULL;
     }
-    return result;
+    return (void *)((intptr_t)result + (src_addr - src_page));
 }
 
 void bootloader_munmap(const void *mapping)
diff --git a/components/bootloader_support/src/bootloader_sha.c b/components/bootloader_support/src/bootloader_sha.c
new file mode 100644 (file)
index 0000000..dc029d9
--- /dev/null
@@ -0,0 +1,156 @@
+// Copyright 2015-2016 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 "bootloader_sha.h"
+#include <stdbool.h>
+#include <string.h>
+#include <assert.h>
+#include <sys/param.h>
+
+#ifndef BOOTLOADER_BUILD
+// App version is a wrapper around mbedTLS SHA API
+#include <mbedtls/sha256.h>
+
+bootloader_sha256_handle_t bootloader_sha256_start()
+{
+    mbedtls_sha256_context *ctx = (mbedtls_sha256_context *)malloc(sizeof(mbedtls_sha256_context));
+    if (!ctx) {
+        return NULL;
+    }
+    mbedtls_sha256_init(ctx);
+    mbedtls_sha256_starts(ctx, false);
+    return ctx;
+}
+
+void bootloader_sha256_data(bootloader_sha256_handle_t handle, const void *data, size_t data_len)
+{
+    assert(handle != NULL);
+    mbedtls_sha256_context *ctx = (mbedtls_sha256_context *)handle;
+    mbedtls_sha256_update(ctx, data, data_len);
+}
+
+void bootloader_sha256_finish(bootloader_sha256_handle_t handle, uint8_t *digest)
+{
+    assert(handle != NULL);
+    mbedtls_sha256_context *ctx = (mbedtls_sha256_context *)handle;
+    mbedtls_sha256_finish(ctx, digest);
+}
+
+#else // Bootloader version
+
+#include "rom/sha.h"
+#include "soc/dport_reg.h"
+#include "soc/hwcrypto_reg.h"
+
+#include "rom/ets_sys.h" // TO REMOVE
+
+static uint32_t words_hashed;
+
+// Words per SHA256 block
+static const size_t BLOCK_WORDS = (64/sizeof(uint32_t));
+
+bootloader_sha256_handle_t bootloader_sha256_start()
+{
+    // Enable SHA hardware
+    ets_sha_enable();
+    words_hashed = 0;
+    return (bootloader_sha256_handle_t)&words_hashed; // Meaningless non-NULL value
+}
+
+void bootloader_sha256_data(bootloader_sha256_handle_t handle, const void *data, size_t data_len)
+{
+    assert(handle != NULL);
+    assert(data_len % 4 == 0);
+
+    const uint32_t *w = (const uint32_t *)data;
+    size_t word_len = data_len / 4;
+    uint32_t *sha_text_reg = (uint32_t *)(SHA_TEXT_BASE);
+
+    //ets_printf("word_len %d so far %d\n", word_len, words_hashed);
+    while (word_len > 0) {
+        size_t block_count = words_hashed % BLOCK_WORDS;
+        size_t copy_words = (BLOCK_WORDS - block_count);
+
+        copy_words = MIN(word_len, copy_words);
+
+        // Wait for SHA engine idle
+        while(REG_READ(SHA_256_BUSY_REG) != 0) { }
+
+        // Copy to memory block
+        //ets_printf("block_count %d copy_words %d\n", block_count, copy_words);
+        for (int i = 0; i < copy_words; i++) {
+            sha_text_reg[block_count + i] = __builtin_bswap32(w[i]);
+        }
+        asm volatile ("memw");
+
+        // Update counters
+        words_hashed += copy_words;
+        block_count += copy_words;
+        word_len -= copy_words;
+        w += copy_words;
+
+        // If we loaded a full block, run the SHA engine
+        if (block_count == BLOCK_WORDS) {
+            //ets_printf("running engine @ count %d\n", words_hashed);
+            if (words_hashed == BLOCK_WORDS) {
+                REG_WRITE(SHA_256_START_REG, 1);
+            } else {
+                REG_WRITE(SHA_256_CONTINUE_REG, 1);
+            }
+            block_count = 0;
+        }
+    }
+}
+
+void bootloader_sha256_finish(bootloader_sha256_handle_t handle, uint8_t *digest)
+{
+    assert(handle != NULL);
+
+    uint32_t data_words = words_hashed;
+    ets_printf("Padding from %d bytes\n", data_words * 4);
+
+    // Pad to a 60 byte long block loaded in the engine
+    // (normally end of block is a 64-bit length, but we know
+    // the upper 32 bits will be zeroes.)
+    int block_bytes = (words_hashed % BLOCK_WORDS) * 4;
+    int pad_bytes = 60 - block_bytes;
+    if (pad_bytes < 0) {
+        pad_bytes += 64;
+    }
+    static const uint8_t padding[64] = { 0x80, 0, };
+
+    bootloader_sha256_data(handle, padding, pad_bytes);
+
+    assert(words_hashed % BLOCK_WORDS == 56/4);
+
+    // Calculate 32-bit length for final 32 bits of data
+    uint32_t bit_count = __builtin_bswap32( data_words * 32 );
+    bootloader_sha256_data(handle, &bit_count, sizeof(bit_count));
+
+    assert(words_hashed % BLOCK_WORDS == 0);
+
+    ets_printf("Padded to %d bytes\n", words_hashed * 4);
+
+    while(REG_READ(SHA_256_BUSY_REG) == 1) { }
+    REG_WRITE(SHA_256_LOAD_REG, 1);
+    while(REG_READ(SHA_256_BUSY_REG) == 1) { }
+
+    uint32_t *digest_words = (uint32_t *)digest;
+    uint32_t *sha_text_reg = (uint32_t *)(SHA_TEXT_BASE);
+    for (int i = 0; i < BLOCK_WORDS; i++) {
+        digest_words[i] = __builtin_bswap32(sha_text_reg[i]);
+    }
+    asm volatile ("memw");
+}
+
+#endif
index ef2aca8f5b51a6652812254d003ffd7079e45b07..aac9275a6823f73b7a36e5fa057b3b069d97d977 100644 (file)
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 #include <string.h>
+#include <sys/param.h>
 
 #include <rom/rtc.h>
 #include <soc/cpu.h>
@@ -19,6 +20,7 @@
 #include <esp_log.h>
 #include <bootloader_flash.h>
 #include <bootloader_random.h>
+#include <bootloader_sha.h>
 
 static const char *TAG = "esp_image";
 
@@ -41,7 +43,7 @@ static bool should_load(uint32_t load_addr);
 static bool should_map(uint32_t load_addr);
 
 /* Load or verify a segment */
-static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segment_header_t *header, bool silent, bool do_load, uint32_t *checksum);
+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);
 
 /* Verify the main image header */
 static esp_err_t verify_image_header(uint32_t src_addr, const esp_image_header_t *image, bool silent);
@@ -69,6 +71,8 @@ esp_err_t esp_image_load(esp_image_load_mode_t mode, const esp_partition_pos_t *
     esp_err_t err = ESP_OK;
     // 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;
+    bootloader_sha256_handle_t sha_handle = NULL;
+    uint8_t image_digest[32] = { 0 };
 
     if (data == NULL || part == NULL) {
         return ESP_ERR_INVALID_ARG;
@@ -82,11 +86,17 @@ esp_err_t esp_image_load(esp_image_load_mode_t mode, const esp_partition_pos_t *
     bzero(data, sizeof(esp_image_metadata_t));
     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);
     err = bootloader_flash_read(data->start_addr, &data->image, sizeof(esp_image_header_t), true);
     if (err != ESP_OK) {
         goto err;
     }
+    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",
              data->image.magic,
@@ -109,7 +119,7 @@ goto err;
     for(int i = 0; i < data->image.segment_count; i++) {
         esp_image_segment_header_t *header = &data->segments[i];
         ESP_LOGV(TAG, "loading segment header %d at offset 0x%x", i, next_addr);
-        err = process_segment(i, next_addr, header, silent, do_load, &checksum_word);
+        err = process_segment(i, next_addr, header, silent, do_load, sha_handle, &checksum_word);
         if (err != ESP_OK) {
             goto err;
         }
@@ -124,8 +134,8 @@ goto err;
         FAIL_LOAD("image offset has wrapped");
     }
 
-    uint32_t length = end_addr - data->start_addr;
-    length = length + 1; // Add a byte for the checksum
+    uint32_t unpadded_length = end_addr - data->start_addr;
+    uint32_t length = unpadded_length + 1; // Add a byte for the checksum
     length = (length + 15) & ~15; // Pad to next full 16 byte block
     if (length > part->size) {
         FAIL_LOAD("Image length %d doesn't fit in partition length %d", length, part->size);
@@ -133,8 +143,8 @@ goto err;
 
     // Verify checksum
     uint32_t buf[16/sizeof(uint32_t)];
-    err = bootloader_flash_read(data->start_addr + length - 16, buf, 16, true);
-    uint8_t calc = ((uint8_t *)buf)[15];
+    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)
@@ -144,6 +154,27 @@ goto err;
                   checksum, calc);
     }
 
+    bootloader_sha256_data(sha_handle, buf, length - unpadded_length);
+    bootloader_sha256_finish(sha_handle, image_digest);
+
+#if BOOT_LOG_LEVEL >= LOG_LEVEL_DEBUG
+    char digest_print[sizeof(image_digest)*2 + 1];
+    digest_print[sizeof(image_digest)*2] = 0;
+    for (int i = 0; i < sizeof(image_digest); i++) {
+        for (int shift = 0; shift < 2; shift++) {
+            uint8_t nibble = (image_digest[i] >> (shift ? 0 : 4)) & 0x0F;
+            if (nibble < 10) {
+                digest_print[i*2+shift] = '0' + nibble;
+            } else {
+                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
+    // Verify digest here
+
     data->image_length = length;
 
 #ifdef BOOTLOADER_BUILD
@@ -167,6 +198,10 @@ goto err;
     if (err == ESP_OK) {
       err = ESP_ERR_IMAGE_INVALID;
     }
+    if (sha_handle != NULL) {
+        // Need to finish the digest process to free the handle
+        bootloader_sha256_finish(sha_handle, image_digest);
+    }
     // Prevent invalid/incomplete data leaking out
     bzero(data, sizeof(esp_image_metadata_t));
     return err;
@@ -196,7 +231,7 @@ static esp_err_t verify_image_header(uint32_t src_addr, const esp_image_header_t
     return err;
 }
 
-static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segment_header_t *header, bool silent, bool do_load, uint32_t *checksum)
+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;
 
@@ -205,6 +240,7 @@ static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segme
     if (err != ESP_OK) {
         return err;
     }
+    bootloader_sha256_data(sha_handle, header, sizeof(esp_image_segment_header_t));
 
     intptr_t load_addr = header->load_addr;
     uint32_t data_len = header->data_len;
@@ -261,14 +297,24 @@ static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segme
 
     const uint32_t *src = data;
 
-    for (int i = 0; i < data_len/sizeof(uint32_t); i++) {
-        uint32_t w = src[i];
+    for (int i = 0; i < data_len; i += 4) {
+        int w_i = i/4; // Word index
+        uint32_t w = src[w_i];
         *checksum ^= w;
 #ifdef BOOTLOADER_BUILD
         if (do_load) {
-            dest[i] = w ^ ((i & 1) ? ram_obfs_value[0] : ram_obfs_value[1]);
+            dest[w_i] = w ^ ((w_i & 1) ? ram_obfs_value[0] : ram_obfs_value[1]);
         }
 #endif
+        // SHA_CHUNK determined experimentally as the optimum size
+        // to call bootloader_sha256_data() with. This is a bit
+        // counter-intuitive, but it's ~3ms better than using the
+        // SHA256 block size.
+        const size_t SHA_CHUNK = 1024;
+        if (i % SHA_CHUNK == 0) {
+            bootloader_sha256_data(sha_handle, &src[w_i],
+                                   MIN(SHA_CHUNK, data_len - i));
+        }
     }
 
     bootloader_munmap(data);
@@ -361,7 +407,7 @@ esp_err_t esp_image_verify_bootloader(uint32_t *length)
                                    &bootloader_part,
                                    &data);
     if (length != NULL) {
-        *length = (err == ESP_OK) ? data.image_length : 0;
+        *length = (err == ESP_OK) ? data.image_len : 0;
     }
     return err;
 }
index 0c6d1ac5bfad56b7522978437b8236ec2e897f99..a7c3794922295bf9592549da9fc9c4d640d701ee 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Tests for bootloader_support esp_image_basic_verify()
+ * Tests for bootloader_support esp_load(ESP_IMAGE_VERIFY, ...)
  */
 
 #include <esp_types.h>
 
 TEST_CASE("Verify bootloader image in flash", "[bootloader_support]")
 {
-    uint32_t image_len = 0;
-    TEST_ASSERT_EQUAL_HEX(ESP_OK, esp_image_basic_verify(0x1000, true, &image_len));
-    TEST_ASSERT_NOT_EQUAL(0, image_len);
+    const esp_partition_pos_t fake_bootloader_partition = {
+        .offset = ESP_BOOTLOADER_OFFSET,
+        .size = ESP_PARTITION_TABLE_OFFSET - ESP_BOOTLOADER_OFFSET,
+    };
+    esp_image_metadata_t data = { 0 };
+    TEST_ASSERT_EQUAL_HEX(ESP_OK, esp_image_load(ESP_IMAGE_VERIFY, &fake_bootloader_partition, &data));
+    TEST_ASSERT_NOT_EQUAL(0, data.image_len);
+
+    uint32_t bootloader_length = 0;
+    TEST_ASSERT_EQUAL_HEX(ESP_OK, esp_image_verify_bootloader(&bootloader_length));
+    TEST_ASSERT_EQUAL(data.image_len, bootloader_length);
 }
 
 TEST_CASE("Verify unit test app image", "[bootloader_support]")
 {
-    uint32_t image_len = 0;
+    esp_image_metadata_t data = { 0 };
     const esp_partition_t *running = esp_ota_get_running_partition();
     TEST_ASSERT_NOT_EQUAL(NULL, running);
+    const esp_partition_pos_t running_pos  = {
+        .offset = running->address,
+        .size = running->size,
+    };
 
-    TEST_ASSERT_EQUAL_HEX(ESP_OK, esp_image_basic_verify(running->address, true, &image_len));
-    TEST_ASSERT_NOT_EQUAL(0, image_len);
-    TEST_ASSERT_TRUE(image_len <= running->size);
+    TEST_ASSERT_EQUAL_HEX(ESP_OK, esp_image_load(ESP_IMAGE_VERIFY, &running_pos, &data));
+    TEST_ASSERT_NOT_EQUAL(0, data.image_len);
+    TEST_ASSERT_TRUE(data.image_len <= running->size);
 }