From f93575a622bca813f372ea41052410ce81d51478 Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Wed, 3 Sep 2025 18:29:32 +0800 Subject: [PATCH 1/2] feat(secure_boot): adds api to verify data partition integrity Closes https://github.com/espressif/esp-idf/issues/17482 --- .pre-commit-config.yaml | 1 + components/app_update/CMakeLists.txt | 4 + components/app_update/Kconfig.projbuild | 9 ++ components/app_update/esp_ota_ops.c | 89 ++++++++++++++++++ components/esp_partition/partition.c | 73 +++++++++----- .../system/ota/partitions_ota/CMakeLists.txt | 13 ++- .../system/ota/partitions_ota/main/app_main.c | 9 +- .../partitions_ota/pytest_partitions_ota.py | 21 ++++- ...ash_enc_wifi_2.data_partition_verification | 32 +++++++ ...c_wifi_2.data_partition_verification.esp32 | 3 + .../test/partitions_efuse_emul_4.csv | 10 ++ .../partitions_ota/test/signed_storage.bin | Bin 0 -> 8192 bytes 12 files changed, 232 insertions(+), 32 deletions(-) create mode 100644 components/app_update/Kconfig.projbuild create mode 100644 examples/system/ota/partitions_ota/sdkconfig.ci.flash_enc_wifi_2.data_partition_verification create mode 100644 examples/system/ota/partitions_ota/sdkconfig.ci.flash_enc_wifi_2.data_partition_verification.esp32 create mode 100644 examples/system/ota/partitions_ota/test/partitions_efuse_emul_4.csv create mode 100644 examples/system/ota/partitions_ota/test/signed_storage.bin diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 820ace021a..f9842dbb9d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,6 +40,7 @@ repos: - id: check-executables-have-shebangs - id: mixed-line-ending args: ['-f=lf'] + exclude: *whitespace_excludes - id: double-quote-string-fixer - id: no-commit-to-branch name: Do not use more than one slash in the branch name diff --git a/components/app_update/CMakeLists.txt b/components/app_update/CMakeLists.txt index 1f4d7051dd..d1be3bdab9 100644 --- a/components/app_update/CMakeLists.txt +++ b/components/app_update/CMakeLists.txt @@ -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") diff --git a/components/app_update/Kconfig.projbuild b/components/app_update/Kconfig.projbuild new file mode 100644 index 0000000000..516ad82a83 --- /dev/null +++ b/components/app_update/Kconfig.projbuild @@ -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 diff --git a/components/app_update/esp_ota_ops.c b/components/app_update/esp_ota_ops.c index 22fff3ab58..f5f81e3599 100644 --- a/components/app_update/esp_ota_ops.c +++ b/components/app_update/esp_ota_ops.c @@ -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; } diff --git a/components/esp_partition/partition.c b/components/esp_partition/partition.c index 86df614979..d7ce987050 100644 --- a/components/esp_partition/partition.c +++ b/components/esp_partition/partition.c @@ -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; } diff --git a/examples/system/ota/partitions_ota/CMakeLists.txt b/examples/system/ota/partitions_ota/CMakeLists.txt index 2ecdf1071f..e56a87b667 100644 --- a/examples/system/ota/partitions_ota/CMakeLists.txt +++ b/examples/system/ota/partitions_ota/CMakeLists.txt @@ -6,10 +6,15 @@ include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(partitions_ota) # Copy storage.bin from test folder to build directory +if(CONFIG_SECURE_SIGNED_DATA_PARTITION) + set(storage_file signed_storage.bin) +else() + set(storage_file storage.bin) +endif() add_custom_target(copy_storage_bin ALL COMMAND ${CMAKE_COMMAND} -E copy - ${CMAKE_SOURCE_DIR}/test/storage.bin - ${CMAKE_BINARY_DIR}/storage.bin - COMMENT "Copying test/storage.bin to build directory" - DEPENDS ${CMAKE_SOURCE_DIR}/test/storage.bin + ${CMAKE_SOURCE_DIR}/test/${storage_file} + ${CMAKE_BINARY_DIR}/${storage_file} + COMMENT "Copying test/${storage_file} to build directory" + DEPENDS ${CMAKE_SOURCE_DIR}/test/${storage_file} ) diff --git a/examples/system/ota/partitions_ota/main/app_main.c b/examples/system/ota/partitions_ota/main/app_main.c index 2824e9e78a..8fde4d06cf 100644 --- a/examples/system/ota/partitions_ota/main/app_main.c +++ b/examples/system/ota/partitions_ota/main/app_main.c @@ -209,14 +209,19 @@ static esp_err_t ota_update_partitions(esp_https_ota_config_t *ota_config) } } else if (strstr(ota_config->http_config->url, "storage.bin") != NULL) { +#if CONFIG_SECURE_SIGNED_DATA_PARTITION + ota_config->partition.staging = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "staging"); + assert(ota_config->partition.staging != NULL); +#else ota_config->partition.staging = NULL; // free app ota partition will be selected and used for downloading a new image +#endif // SECURE_SIGNED_DATA_PARTITION ota_config->partition.final = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage"); assert(ota_config->partition.final != NULL); ota_config->partition.finalize_with_copy = true; // After the download is complete, copy the received image to the final partition automatically ret = esp_https_ota(ota_config); - char text[16]; + char text[16] = {0}; ESP_ERROR_CHECK(esp_partition_read(ota_config->partition.final, 0, text, sizeof(text))); - ESP_LOG_BUFFER_CHAR(TAG, text, sizeof(text)); + ESP_LOG_BUFFER_HEXDUMP(TAG, text, sizeof(text), ESP_LOG_INFO); assert(memcmp("7296406769363431", text, sizeof(text)) == 0); } else { diff --git a/examples/system/ota/partitions_ota/pytest_partitions_ota.py b/examples/system/ota/partitions_ota/pytest_partitions_ota.py index 297ff3f37f..6c85e38cd6 100644 --- a/examples/system/ota/partitions_ota/pytest_partitions_ota.py +++ b/examples/system/ota/partitions_ota/pytest_partitions_ota.py @@ -93,7 +93,7 @@ def test_examples_partitions_ota(dut: Dut) -> None: dut.serial.bootloader_flash() print(' - Start app (flash partition_table and app)') dut.serial.write_flash_no_enc() - update_partitions(dut, 'wifi_high_traffic') + update_partitions(dut, 'wifi_high_traffic', False) @pytest.mark.flash_encryption_wifi_high_traffic @@ -105,19 +105,32 @@ def test_examples_partitions_ota(dut: Dut) -> None: def test_examples_partitions_ota_with_flash_encryption_wifi(dut: Dut) -> None: dut.serial.erase_flash() dut.serial.flash() - update_partitions(dut, 'flash_encryption_wifi_high_traffic') + update_partitions(dut, 'flash_encryption_wifi_high_traffic', False) -def update_partitions(dut: Dut, env_name: str | None) -> None: +@pytest.mark.flash_encryption_wifi_high_traffic +@pytest.mark.parametrize('config', ['flash_enc_wifi_2.data_partition_verification'], indirect=True) +@pytest.mark.parametrize('skip_autoflash', ['y'], indirect=True) +@idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target']) +def test_examples_partitions_ota_with_flash_enc_wifi_2_data_partition_verification(dut: Dut) -> None: + dut.serial.erase_flash() + dut.serial.flash() + update_partitions(dut, 'flash_encryption_wifi_high_traffic', True) + + +def update_partitions(dut: Dut, env_name: str | None, signed_storage: bool | None) -> None: port = 8000 thread1 = multiprocessing.Process(target=start_https_server, args=(dut.app.binary_path, '0.0.0.0', port)) thread1.daemon = True thread1.start() try: + if signed_storage: + update(dut, port, 'signed_storage.bin', env_name) + else: + update(dut, port, 'storage.bin', env_name) update(dut, port, 'partitions_ota.bin', env_name) update(dut, port, 'bootloader/bootloader.bin', env_name) update(dut, port, 'partition_table/partition-table.bin', env_name) - update(dut, port, 'storage.bin', env_name) finally: thread1.terminate() diff --git a/examples/system/ota/partitions_ota/sdkconfig.ci.flash_enc_wifi_2.data_partition_verification b/examples/system/ota/partitions_ota/sdkconfig.ci.flash_enc_wifi_2.data_partition_verification new file mode 100644 index 0000000000..a469358b50 --- /dev/null +++ b/examples/system/ota/partitions_ota/sdkconfig.ci.flash_enc_wifi_2.data_partition_verification @@ -0,0 +1,32 @@ +# Common configs +CONFIG_EXAMPLE_WIFI_SSID_PWD_FROM_STDIN=y +CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL="FROM_STDIN" +CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK=y +CONFIG_EXAMPLE_FIRMWARE_UPGRADE_BIND_IF=y + +CONFIG_MBEDTLS_TLS_CLIENT_ONLY=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_EXAMPLE_CONNECT_IPV6=n + +CONFIG_SECURE_FLASH_ENC_ENABLED=y +CONFIG_SECURE_FLASH_ENCRYPTION_MODE_DEVELOPMENT=y +CONFIG_SECURE_BOOT_ALLOW_ROM_BASIC=y +CONFIG_SECURE_BOOT_ALLOW_JTAG=y +CONFIG_SECURE_FLASH_UART_BOOTLOADER_ALLOW_ENC=y +CONFIG_SECURE_FLASH_UART_BOOTLOADER_ALLOW_DEC=y +CONFIG_SECURE_FLASH_UART_BOOTLOADER_ALLOW_CACHE=y +CONFIG_SECURE_FLASH_REQUIRE_ALREADY_ENABLED=y +CONFIG_NVS_SEC_KEY_PROTECT_USING_FLASH_ENC=y + +# This is required for nvs encryption (which is enabled by default with flash encryption) +CONFIG_PARTITION_TABLE_OFFSET=0x9000 + +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="test/partitions_efuse_emul_4.csv" + +CONFIG_SECURE_SIGNED_APPS_NO_SECURE_BOOT=y +CONFIG_SECURE_SIGNED_ON_UPDATE_NO_SECURE_BOOT=y +CONFIG_SECURE_SIGNED_APPS_RSA_SCHEME=y +CONFIG_SECURE_BOOT_SIGNING_KEY="test/secure_boot_signing_key.pem" +CONFIG_SECURE_BOOT_ALLOW_SHORT_APP_PARTITION=y +CONFIG_SECURE_SIGNED_DATA_PARTITION=y diff --git a/examples/system/ota/partitions_ota/sdkconfig.ci.flash_enc_wifi_2.data_partition_verification.esp32 b/examples/system/ota/partitions_ota/sdkconfig.ci.flash_enc_wifi_2.data_partition_verification.esp32 new file mode 100644 index 0000000000..413efc1cbd --- /dev/null +++ b/examples/system/ota/partitions_ota/sdkconfig.ci.flash_enc_wifi_2.data_partition_verification.esp32 @@ -0,0 +1,3 @@ +# ESP32 supports SECURE_BOOT_V2 only in ECO3 +CONFIG_IDF_TARGET="esp32" +CONFIG_ESP32_REV_MIN_3=y diff --git a/examples/system/ota/partitions_ota/test/partitions_efuse_emul_4.csv b/examples/system/ota/partitions_ota/test/partitions_efuse_emul_4.csv new file mode 100644 index 0000000000..8d5c702ad5 --- /dev/null +++ b/examples/system/ota/partitions_ota/test/partitions_efuse_emul_4.csv @@ -0,0 +1,10 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, , 0x6000, +nvs_key, data, nvs_keys, , 4K, +staging, data, , , 0x4000, +storage, data, , , 0x4000, encrypted +otadata, data, ota, , 0x2000, +phy_init, data, phy, , 0x1000, +emul_efuse, data, efuse, , 0x2000, +ota_0, app, ota_0, , 0x1B0000, +ota_1, app, ota_1, , 0x1B0000, diff --git a/examples/system/ota/partitions_ota/test/signed_storage.bin b/examples/system/ota/partitions_ota/test/signed_storage.bin new file mode 100644 index 0000000000000000000000000000000000000000..8ddc5d534667a5fd57343ce538c137ed813cce8d GIT binary patch literal 8192 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 B!PNi& literal 0 HcmV?d00001 From abe0d37d94c6abaa8d2705fde1bdeefc41ecac80 Mon Sep 17 00:00:00 2001 From: Ashish Sharma Date: Mon, 23 Feb 2026 11:17:47 +0800 Subject: [PATCH 2/2] docs(security): adds data partition verification docs --- docs/en/api-reference/system/ota.rst | 17 +++++++++++++++++ docs/en/security/secure-boot-v2.rst | 10 ++++++++++ docs/zh_CN/api-reference/system/ota.rst | 17 +++++++++++++++++ docs/zh_CN/security/secure-boot-v2.rst | 10 ++++++++++ 4 files changed, 54 insertions(+) diff --git a/docs/en/api-reference/system/ota.rst b/docs/en/api-reference/system/ota.rst index 2c3944a3b8..db61889879 100644 --- a/docs/en/api-reference/system/ota.rst +++ b/docs/en/api-reference/system/ota.rst @@ -229,6 +229,23 @@ The verification of signed OTA updates can be performed even without enabling ha For more information, please refer to :ref:`signed-app-verify`. +.. _secure-signed-data-partition: + +Signed Data Partition Updates +------------------------------ + +Data partition images can be verified using the same Secure Boot v2 signature mechanism as application images. Enable :ref:`CONFIG_SECURE_SIGNED_DATA_PARTITION` to verify data partitions with subtype ``ESP_PARTITION_SUBTYPE_DATA_UNDEFINED`` during OTA updates. + +Sign data partition images using: + +.. code-block:: bash + + idf.py secure-sign-data --keyfile PRIVATE_SIGNING_KEY --output signed_data.bin data.bin + +The signing key must match the one used for application signing, with its public key digest programmed into eFuse. The signed image format is: data content (padded to 4 KB) + 4 KB signature block (see :ref:`signature-block-format` in :doc:`/security/secure-boot-v2`). + +For a complete example, see :example:`system/ota/partitions_ota`. + Tuning OTA Performance ---------------------- diff --git a/docs/en/security/secure-boot-v2.rst b/docs/en/security/secure-boot-v2.rst index 06b10c0594..829844c299 100644 --- a/docs/en/security/secure-boot-v2.rst +++ b/docs/en/security/secure-boot-v2.rst @@ -408,6 +408,16 @@ An image is verified if the public key stored in any signature block is valid fo 3. Use the public key to verify the signature of the bootloader image, using either RSA-PSS (section 8.1.2 of RFC8017) or ECDSA signature verification (section 5.3.3 of RFC6090) with the image digest calculated in step (2) for comparison. +Verifying Data Partitions +-------------------------- + +The Secure Boot v2 signature verification can also verify data partition images during OTA updates. Enable :ref:`CONFIG_SECURE_SIGNED_DATA_PARTITION` to verify data partitions with subtype ``ESP_PARTITION_SUBTYPE_DATA_UNDEFINED``. + +Data partition images must be signed using ``idf.py secure-sign-data`` with the same signing key and follow the same format as application images. The verification uses the public key digest(s) stored in eFuse and follows the process described in :ref:`verify_image`. + +For detailed information including OTA procedures and partition configuration, see :ref:`secure-signed-data-partition`. + + Bootloader Size --------------- diff --git a/docs/zh_CN/api-reference/system/ota.rst b/docs/zh_CN/api-reference/system/ota.rst index 85f89589cb..a1bdf9ace9 100644 --- a/docs/zh_CN/api-reference/system/ota.rst +++ b/docs/zh_CN/api-reference/system/ota.rst @@ -229,6 +229,23 @@ Kconfig 中的 :ref:`CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE` 可以帮助用户 具体可参考 :ref:`signed-app-verify`。 +.. _secure-signed-data-partition: + +签名数据分区的更新 +------------------ + +数据分区镜像可以使用与应用镜像相同的 Secure Boot v2 签名机制进行验证。启用 :ref:`CONFIG_SECURE_SIGNED_DATA_PARTITION`,以便在 OTA 更新期间验证子类型为 ``ESP_PARTITION_SUBTYPE_DATA_UNDEFINED`` 的数据分区。 + +使用以下命令对数据分区镜像进行签名: + +.. code-block:: bash + + idf.py secure-sign-data --keyfile PRIVATE_SIGNING_KEY --output signed_data.bin data.bin + +签名密钥必须与用于应用签名的密钥一致,并将其公钥摘要写入 eFuse。签名镜像格式为:数据内容(填充到 4 KB)+ 4 KB 签名块(参见 :doc:`/security/secure-boot-v2` 中的 :ref:`signature-block-format`)。 + +如需完整示例,请参见 :example:`system/ota/partitions_ota`。 + OTA 性能调优 ------------ diff --git a/docs/zh_CN/security/secure-boot-v2.rst b/docs/zh_CN/security/secure-boot-v2.rst index f1bef576e6..1777d3e21e 100644 --- a/docs/zh_CN/security/secure-boot-v2.rst +++ b/docs/zh_CN/security/secure-boot-v2.rst @@ -408,6 +408,16 @@ 3. 使用公钥,采用 RSA-PSS(RFC8017 的第 8.1.2 节)算法或 ECDSA(RFC6090 的第 5.3.3 节)算法,验证引导加载程序镜像的签名,并与步骤 (2) 中计算的镜像摘要比较。 +验证数据分区 +------------ + +Secure Boot v2 签名验证也可以在 OTA 更新期间验证数据分区镜像。启用 :ref:`CONFIG_SECURE_SIGNED_DATA_PARTITION` 以验证子类型为 ``ESP_PARTITION_SUBTYPE_DATA_UNDEFINED`` 的数据分区。 + +数据分区镜像必须使用相同的签名密钥,通过 ``idf.py secure-sign-data`` 进行签名,并采用与应用镜像相同的格式。验证使用存储在 eFuse 中的一个或多个公钥摘要,并遵循 :ref:`verify_image` 中所述的流程。 + +关于包括 OTA 流程和分区配置在内的详细信息,请参见 :ref:`secure-signed-data-partition`。 + + 引导加载程序的大小 ------------------