bootloader: Calculate SHA-256 of image while loading/verifying

This commit is contained in:
Angus Gratton 2017-06-27 17:25:30 +10:00 committed by Angus Gratton
parent 8f6134dd96
commit 43b99edf2b
7 changed files with 278 additions and 25 deletions

View 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() */

View file

@ -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

View 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);

View 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)

View 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

View 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;
}

View 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>
@ -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);
}