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() */
// 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.
uint8_t digest[64];
} esp_secure_boot_iv_digest_t;
+
+#ifdef __cplusplus
+}
#endif
--- /dev/null
+// 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);
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)
--- /dev/null
+// 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
// 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>
#include <esp_log.h>
#include <bootloader_flash.h>
#include <bootloader_random.h>
+#include <bootloader_sha.h>
static const char *TAG = "esp_image";
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);
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;
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,
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;
}
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);
// 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)
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
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;
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;
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;
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);
&bootloader_part,
&data);
if (length != NULL) {
- *length = (err == ESP_OK) ? data.image_length : 0;
+ *length = (err == ESP_OK) ? data.image_len : 0;
}
return err;
}
/*
- * 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);
}