feat(secure_boot): adds api to verify data partition integrity

Closes https://github.com/espressif/esp-idf/issues/17482
This commit is contained in:
Ashish Sharma
2026-03-04 10:22:33 +08:00
parent 12f36a021f
commit f93575a622
12 changed files with 232 additions and 32 deletions
+4
View File
@@ -9,6 +9,10 @@ idf_component_register(SRCS "esp_ota_ops.c"
REQUIRES partition_table bootloader_support esp_app_format esp_bootloader_format esp_partition
PRIV_REQUIRES esptool_py efuse spi_flash)
if(CONFIG_SECURE_SIGNED_DATA_PARTITION)
idf_component_optional_requires(PRIVATE mbedtls)
endif()
if(NOT BOOTLOADER_BUILD)
partition_table_get_partition_info(otadata_offset "--partition-type data --partition-subtype ota" "offset")
partition_table_get_partition_info(otadata_size "--partition-type data --partition-subtype ota" "size")
+9
View File
@@ -0,0 +1,9 @@
menu "App Update config"
config SECURE_SIGNED_DATA_PARTITION
default n
bool "Require signed Data partition images"
depends on SECURE_SIGNED_ON_UPDATE_NO_SECURE_BOOT || SECURE_SIGNED_APPS
help
If set, the Data partition images will be verified during OTA updates.
Only partitions with subtype ESP_PARTITION_SUBTYPE_DATA_UNDEFINED will be verified.
endmenu # App Update config
+89
View File
@@ -31,6 +31,9 @@
#include "esp_bootloader_desc.h"
#include "esp_flash.h"
#include "esp_private/esp_flash_internal.h" //For dangerous write protection
#if CONFIG_SECURE_SIGNED_DATA_PARTITION
#include "psa/crypto.h"
#endif // CONFIG_SECURE_SIGNED_DATA_PARTITION
#define OTA_SLOT(i) (i & 0x0F)
#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
@@ -470,6 +473,81 @@ esp_err_t esp_ota_abort(esp_ota_handle_t handle)
return ESP_OK;
}
#if CONFIG_SECURE_SIGNED_DATA_PARTITION
#define SHA_CHUNK 256
static esp_err_t ota_calc_partition_bin_sha(const esp_partition_t *partition, uint32_t length, uint8_t out_digest[ESP_SECURE_BOOT_DIGEST_LEN], psa_algorithm_t alg)
{
esp_err_t err = ESP_OK;
psa_hash_operation_t hash_operation = PSA_HASH_OPERATION_INIT;
uint8_t sha_buf[SHA_CHUNK];
size_t sha_length = 0;
uint32_t i = 0;
psa_status_t status = psa_hash_setup(&hash_operation, alg);
if (status != PSA_SUCCESS) {
ESP_LOGE(TAG, "Failed to setup psa, status: %d", status);
return ESP_FAIL;
}
while (i < length) {
uint32_t n = (length - i > SHA_CHUNK) ? SHA_CHUNK : (length - i);//take a chunk, or the last of it
err = esp_partition_read(partition, i, sha_buf, n);
if (err != ESP_OK) {
psa_hash_abort(&hash_operation);
return err;
}
status = psa_hash_update(&hash_operation, sha_buf, n);
if (status != PSA_SUCCESS) {
ESP_LOGE(TAG, "Failed to update psa hash, status: %d", status);
psa_hash_abort(&hash_operation);
return ESP_FAIL;
}
i += n;
}
status = psa_hash_finish(&hash_operation, out_digest, ESP_SECURE_BOOT_DIGEST_LEN, &sha_length);
if (status != PSA_SUCCESS) {
ESP_LOGE(TAG, "Failed to finish psa hash, status: %d", status);
psa_hash_abort(&hash_operation);
return ESP_FAIL;
}
return err;
}
static esp_err_t ota_verify_data_partition_signature(const esp_partition_t *partition, uint32_t total_written_size)
{
esp_err_t err = ESP_FAIL;
uint8_t digest[ESP_SECURE_BOOT_DIGEST_LEN] = {0};
/* Calculate data length by excluding the signature sector from total written size */
uint32_t data_length = ((total_written_size) & ~((SPI_FLASH_SEC_SIZE) - 1)) - SPI_FLASH_SEC_SIZE;
/* Rounding off data length to the upper 4k boundary for hash calculation */
uint32_t padded_length = ALIGN_UP(data_length, SPI_FLASH_SEC_SIZE);
#if CONFIG_SECURE_BOOT_ECDSA_KEY_LEN_384_BITS
err = ota_calc_partition_bin_sha(partition, padded_length, digest, PSA_ALG_SHA_384);
#else
err = ota_calc_partition_bin_sha(partition, padded_length, digest, PSA_ALG_SHA_256);
#endif
if (err != ESP_OK) {
ESP_LOGE(TAG, "Digest calculation failed partition: %s", partition->label);
return err;
}
const ets_secure_boot_signature_t sig_block = {0};
err = esp_partition_read(partition, data_length, (void*)&sig_block, sizeof(ets_secure_boot_signature_t));
if (err != ESP_OK) {
ESP_LOGE(TAG, "Reading signature block failed for partition: %s", partition->label);
return err;
}
err = esp_secure_boot_verify_sbv2_signature_block(&sig_block, digest, NULL);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Secure Boot V2 verification failed.");
}
return err;
}
#endif // CONFIG_SECURE_SIGNED_DATA_PARTITION
static esp_err_t ota_verify_partition(ota_ops_entry_t *ota_ops)
{
esp_err_t ret = ESP_OK;
@@ -495,6 +573,17 @@ static esp_err_t ota_verify_partition(ota_ops_entry_t *ota_ops)
esp_partition_munmap(partition_table_map);
}
}
#if CONFIG_SECURE_SIGNED_DATA_PARTITION
else if (ota_ops->partition.final->type == ESP_PARTITION_TYPE_DATA &&
ota_ops->partition.final->subtype == ESP_PARTITION_SUBTYPE_DATA_UNDEFINED) {
esp_err_t err = ota_verify_data_partition_signature(ota_ops->partition.staging, ota_ops->wrote_size);
if (err != ESP_OK) {
ESP_LOGE(TAG,"esp_secure_boot_verify_signature failed for partition %s, return %d", ota_ops->partition.final->label, err);
return ESP_ERR_OTA_VALIDATE_FAILED;
}
return ESP_OK;
}
#endif // CONFIG_SECURE_SIGNED_DATA_PARTITION
return ret;
}
+51 -22
View File
@@ -601,34 +601,63 @@ esp_err_t esp_partition_copy(const esp_partition_t* dest_part, uint32_t dest_off
uint32_t src_current_offset = src_offset;
uint32_t dest_current_offset = dest_offset;
size_t remaining_size = size;
/* Read the portion that fits in the free MMU pages */
uint32_t mmu_free_pages_count = spi_flash_mmap_get_free_pages(SPI_FLASH_MMAP_DATA);
int attempts_for_mmap = 0;
while (remaining_size > 0) {
uint32_t chunk_size = MIN(remaining_size, mmu_free_pages_count * SPI_FLASH_MMU_PAGE_SIZE);
esp_partition_mmap_handle_t src_part_map;
const void *src_data = NULL;
error = esp_partition_mmap(src_part, src_current_offset, chunk_size, ESP_PARTITION_MMAP_DATA, &src_data, &src_part_map);
if (error == ESP_OK) {
attempts_for_mmap = 0;
if (src_part->encrypted) {
/* Read the portion that fits in the free MMU pages */
uint32_t mmu_free_pages_count = spi_flash_mmap_get_free_pages(SPI_FLASH_MMAP_DATA);
int attempts_for_mmap = 0;
while (remaining_size > 0) {
uint32_t chunk_size = MIN(remaining_size, mmu_free_pages_count * SPI_FLASH_MMU_PAGE_SIZE);
esp_partition_mmap_handle_t src_part_map;
const void *src_data = NULL;
error = esp_partition_mmap(src_part, src_current_offset, chunk_size, ESP_PARTITION_MMAP_DATA, &src_data, &src_part_map);
if (error == ESP_OK) {
attempts_for_mmap = 0;
error = esp_partition_write(dest_part, dest_current_offset, src_data, chunk_size);
if (error != ESP_OK) {
ESP_LOGE(TAG, "Writing to destination partition failed (err=0x%x)", error);
esp_partition_munmap(src_part_map);
break;
}
esp_partition_munmap(src_part_map);
} else {
mmu_free_pages_count = spi_flash_mmap_get_free_pages(SPI_FLASH_MMAP_DATA);
chunk_size = 0;
if (++attempts_for_mmap >= 3) {
ESP_LOGE(TAG, "Failed to mmap source partition after a few attempts, mmu_free_pages = %" PRIu32 " (err=0x%x)", mmu_free_pages_count, error);
break;
}
}
src_current_offset += chunk_size;
dest_current_offset += chunk_size;
remaining_size -= chunk_size;
}
} else {
// In case of unencrypted partition, we can read and write directly
while (remaining_size > 0) {
uint32_t chunk_size = MIN(remaining_size, SPI_FLASH_SEC_SIZE);
void *src_data = malloc(chunk_size);
if (src_data == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for chunk (size: %" PRIu32 ")", chunk_size);
error = ESP_ERR_NO_MEM;
break;
}
error = esp_partition_read(src_part, src_current_offset, src_data, chunk_size);
if (error != ESP_OK) {
ESP_LOGE(TAG, "Reading from source partition failed (err=0x%x)", error);
free(src_data);
break;
}
error = esp_partition_write(dest_part, dest_current_offset, src_data, chunk_size);
if (error != ESP_OK) {
ESP_LOGE(TAG, "Writing to destination partition failed (err=0x%x)", error);
esp_partition_munmap(src_part_map);
break;
}
esp_partition_munmap(src_part_map);
} else {
mmu_free_pages_count = spi_flash_mmap_get_free_pages(SPI_FLASH_MMAP_DATA);
chunk_size = 0;
if (++attempts_for_mmap >= 3) {
ESP_LOGE(TAG, "Failed to mmap source partition after a few attempts, mmu_free_pages = %" PRIu32 " (err=0x%x)", mmu_free_pages_count, error);
free(src_data);
break;
}
free(src_data);
src_current_offset += chunk_size;
dest_current_offset += chunk_size;
remaining_size -= chunk_size;
}
src_current_offset += chunk_size;
dest_current_offset += chunk_size;
remaining_size -= chunk_size;
}
return error;
}