mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
bootloader: Calculate SHA-256 of image while loading/verifying
This commit is contained in:
parent
8f6134dd96
commit
43b99edf2b
@ -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() */
|
||||
|
@ -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 <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
|
||||
|
32
components/bootloader_support/include_priv/bootloader_sha.h
Normal file
32
components/bootloader_support/include_priv/bootloader_sha.h
Normal file
@ -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);
|
@ -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)
|
||||
|
156
components/bootloader_support/src/bootloader_sha.c
Normal file
156
components/bootloader_support/src/bootloader_sha.c
Normal file
@ -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
|
@ -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;
|
||||
}
|
||||
|
@ -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>
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user