diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b0d521469e4..2f80070ebe6 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 1f4d7051dd2..d1be3bdab97 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 00000000000..516ad82a83c --- /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 af25229b4d5..c49dfd66f06 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)) @@ -446,6 +449,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; @@ -471,6 +549,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 e58d56dc283..7896e0a2225 100644 --- a/components/esp_partition/partition.c +++ b/components/esp_partition/partition.c @@ -602,34 +602,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 2ecdf1071f5..e56a87b667b 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 2824e9e78a5..8fde4d06cf0 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 27fcc9a7a88..75b162ada9b 100644 --- a/examples/system/ota/partitions_ota/pytest_partitions_ota.py +++ b/examples/system/ota/partitions_ota/pytest_partitions_ota.py @@ -97,7 +97,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 @@ -109,19 +109,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 00000000000..a469358b504 --- /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 00000000000..413efc1cbdd --- /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 00000000000..8d5c702ad5b --- /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 00000000000..8ddc5d53466 Binary files /dev/null and b/examples/system/ota/partitions_ota/test/signed_storage.bin differ