From: Angus Gratton Date: Tue, 27 Jun 2017 07:25:30 +0000 (+1000) Subject: bootloader: Calculate SHA-256 of image while loading/verifying X-Git-Tag: v3.1-dev~454^2~4 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=43b99edf2b6f23dc7d0011c257fb11e8d04edb71;p=esp-idf bootloader: Calculate SHA-256 of image while loading/verifying --- diff --git a/components/bootloader_support/include/esp_image_format.h b/components/bootloader_support/include/esp_image_format.h index 97afafc3f0..2e49252aa5 100644 --- a/components/bootloader_support/include/esp_image_format.h +++ b/components/bootloader_support/include/esp_image_format.h @@ -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() */ diff --git a/components/bootloader_support/include/esp_secure_boot.h b/components/bootloader_support/include/esp_secure_boot.h index 8e33a8b460..003328557d 100644 --- a/components/bootloader_support/include/esp_secure_boot.h +++ b/components/bootloader_support/include/esp_secure_boot.h @@ -11,13 +11,16 @@ // 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 #include #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 index 0000000000..0434000d08 --- /dev/null +++ b/components/bootloader_support/include_priv/bootloader_sha.h @@ -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 +#include + +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); diff --git a/components/bootloader_support/src/bootloader_flash.c b/components/bootloader_support/src/bootloader_flash.c index 0432d755c2..9a82590393 100644 --- a/components/bootloader_support/src/bootloader_flash.c +++ b/components/bootloader_support/src/bootloader_flash.c @@ -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 index 0000000000..dc029d9f26 --- /dev/null +++ b/components/bootloader_support/src/bootloader_sha.c @@ -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 +#include +#include +#include + +#ifndef BOOTLOADER_BUILD +// App version is a wrapper around mbedTLS SHA API +#include + +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 diff --git a/components/bootloader_support/src/esp_image_format.c b/components/bootloader_support/src/esp_image_format.c index ef2aca8f5b..aac9275a68 100644 --- a/components/bootloader_support/src/esp_image_format.c +++ b/components/bootloader_support/src/esp_image_format.c @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. #include +#include #include #include @@ -19,6 +20,7 @@ #include #include #include +#include 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; } diff --git a/components/bootloader_support/test/test_verify_image.c b/components/bootloader_support/test/test_verify_image.c index 0c6d1ac5bf..a7c3794922 100644 --- a/components/bootloader_support/test/test_verify_image.c +++ b/components/bootloader_support/test/test_verify_image.c @@ -1,5 +1,5 @@ /* - * Tests for bootloader_support esp_image_basic_verify() + * Tests for bootloader_support esp_load(ESP_IMAGE_VERIFY, ...) */ #include @@ -19,19 +19,31 @@ 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); }