diff --git a/components/bootloader/subproject/main/ld/esp32c2/bootloader.ld b/components/bootloader/subproject/main/ld/esp32c2/bootloader.ld index f80441cebb..394cc3690b 100644 --- a/components/bootloader/subproject/main/ld/esp32c2/bootloader.ld +++ b/components/bootloader/subproject/main/ld/esp32c2/bootloader.ld @@ -27,7 +27,7 @@ bootloader_usable_dram_end = 0x3fcdcb70; bootloader_stack_overhead = 0x2000; /* For safety margin between bootloader data section and startup stacks */ bootloader_dram_seg_len = 0x5000; bootloader_iram_loader_seg_len = 0x7000; -bootloader_iram_seg_len = 0x2000; +bootloader_iram_seg_len = 0x2800; /* Start of the lower region is determined by region size and the end of the higher region */ bootloader_dram_seg_end = bootloader_usable_dram_end - bootloader_stack_overhead; diff --git a/components/bootloader/subproject/main/ld/esp32c3/bootloader.ld b/components/bootloader/subproject/main/ld/esp32c3/bootloader.ld index 21f19c14fe..e455c2e128 100644 --- a/components/bootloader/subproject/main/ld/esp32c3/bootloader.ld +++ b/components/bootloader/subproject/main/ld/esp32c3/bootloader.ld @@ -27,7 +27,7 @@ bootloader_usable_dram_end = 0x3fcdc710; bootloader_stack_overhead = 0x2000; /* For safety margin between bootloader data section and startup stacks */ bootloader_dram_seg_len = 0x5000; bootloader_iram_loader_seg_len = 0x7000; -bootloader_iram_seg_len = 0x2000; +bootloader_iram_seg_len = 0x2800; /* Start of the lower region is determined by region size and the end of the higher region */ bootloader_dram_seg_end = bootloader_usable_dram_end - bootloader_stack_overhead; diff --git a/components/bootloader/subproject/main/ld/esp32c5/bootloader.ld b/components/bootloader/subproject/main/ld/esp32c5/bootloader.ld index e389464825..0e7a30baa0 100644 --- a/components/bootloader/subproject/main/ld/esp32c5/bootloader.ld +++ b/components/bootloader/subproject/main/ld/esp32c5/bootloader.ld @@ -24,7 +24,7 @@ bootloader_usable_dram_end = 0x4085c9a0; bootloader_stack_overhead = 0x2000; /* For safety margin between bootloader data section and startup stacks */ bootloader_dram_seg_len = 0x5000; bootloader_iram_loader_seg_len = 0x7000; -bootloader_iram_seg_len = 0x2200; +bootloader_iram_seg_len = 0x2A00; /* Start of the lower region is determined by region size and the end of the higher region */ bootloader_dram_seg_end = bootloader_usable_dram_end - bootloader_stack_overhead; diff --git a/components/bootloader/subproject/main/ld/esp32c6/bootloader.ld b/components/bootloader/subproject/main/ld/esp32c6/bootloader.ld index 9afa821408..8c47badd70 100644 --- a/components/bootloader/subproject/main/ld/esp32c6/bootloader.ld +++ b/components/bootloader/subproject/main/ld/esp32c6/bootloader.ld @@ -24,7 +24,7 @@ bootloader_usable_dram_end = 0x4087c610; bootloader_stack_overhead = 0x2000; /* For safety margin between bootloader data section and startup stacks */ bootloader_dram_seg_len = 0x5000; bootloader_iram_loader_seg_len = 0x7000; -bootloader_iram_seg_len = 0x2500; +bootloader_iram_seg_len = 0x2D00; /* Start of the lower region is determined by region size and the end of the higher region */ bootloader_dram_seg_end = bootloader_usable_dram_end - bootloader_stack_overhead; diff --git a/components/bootloader/subproject/main/ld/esp32c61/bootloader.ld b/components/bootloader/subproject/main/ld/esp32c61/bootloader.ld index 160619c0e1..a5134ccd9f 100644 --- a/components/bootloader/subproject/main/ld/esp32c61/bootloader.ld +++ b/components/bootloader/subproject/main/ld/esp32c61/bootloader.ld @@ -24,7 +24,7 @@ bootloader_usable_dram_end = 0x4084ca70; bootloader_stack_overhead = 0x2000; /* For safety margin between bootloader data section and startup stacks */ bootloader_dram_seg_len = 0x5000; bootloader_iram_loader_seg_len = 0x7000; -bootloader_iram_seg_len = 0x2500; +bootloader_iram_seg_len = 0x2D00; /* Start of the lower region is determined by region size and the end of the higher region */ bootloader_dram_seg_end = bootloader_usable_dram_end - bootloader_stack_overhead; diff --git a/components/bootloader/subproject/main/ld/esp32h2/bootloader.ld b/components/bootloader/subproject/main/ld/esp32h2/bootloader.ld index 9ae2a74d8c..1880bf6792 100644 --- a/components/bootloader/subproject/main/ld/esp32h2/bootloader.ld +++ b/components/bootloader/subproject/main/ld/esp32h2/bootloader.ld @@ -25,7 +25,7 @@ bootloader_usable_dram_end = 0x4084cfd0; bootloader_stack_overhead = 0x2000; /* For safety margin between bootloader data section and startup stacks */ bootloader_dram_seg_len = 0x5000; bootloader_iram_loader_seg_len = 0x7000; -bootloader_iram_seg_len = 0x2500; +bootloader_iram_seg_len = 0x2D00; /* Start of the lower region is determined by region size and the end of the higher region */ bootloader_dram_seg_end = bootloader_usable_dram_end - bootloader_stack_overhead; diff --git a/components/bootloader/subproject/main/ld/esp32h21/bootloader.ld b/components/bootloader/subproject/main/ld/esp32h21/bootloader.ld index c2e755175e..ea03761c95 100644 --- a/components/bootloader/subproject/main/ld/esp32h21/bootloader.ld +++ b/components/bootloader/subproject/main/ld/esp32h21/bootloader.ld @@ -25,7 +25,7 @@ bootloader_usable_dram_end = 0x40849a78; bootloader_stack_overhead = 0x2000; /* For safety margin between bootloader data section and startup stacks */ bootloader_dram_seg_len = 0x5000; bootloader_iram_loader_seg_len = 0x7000; -bootloader_iram_seg_len = 0x2500; +bootloader_iram_seg_len = 0x2D00; /* Start of the lower region is determined by region size and the end of the higher region */ bootloader_dram_seg_end = bootloader_usable_dram_end - bootloader_stack_overhead; diff --git a/components/bootloader/subproject/main/ld/esp32p4/bootloader.ld b/components/bootloader/subproject/main/ld/esp32p4/bootloader.ld index b73ae430f4..7f45fa8a63 100644 --- a/components/bootloader/subproject/main/ld/esp32p4/bootloader.ld +++ b/components/bootloader/subproject/main/ld/esp32p4/bootloader.ld @@ -26,7 +26,7 @@ bootloader_usable_dram_end = 0x4ff3abd0; bootloader_stack_overhead = 0x2000; /* For safety margin between bootloader data section and startup stacks */ bootloader_dram_seg_len = 0x5000; bootloader_iram_loader_seg_len = 0x7000; -bootloader_iram_seg_len = 0x2000; +bootloader_iram_seg_len = 0x2D00; /* Start of the lower region is determined by region size and the end of the higher region */ bootloader_dram_seg_end = bootloader_usable_dram_end - bootloader_stack_overhead; diff --git a/components/esp_partition/partition_bootloader.c b/components/esp_partition/partition_bootloader.c index 8cfc5ff48b..a408037fb3 100644 --- a/components/esp_partition/partition_bootloader.c +++ b/components/esp_partition/partition_bootloader.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 * @@ -46,9 +46,9 @@ const esp_partition_t* esp_partition_find_first(esp_partition_type_t type, esp_p // if everything matches, populate the internal_partition if (partition->type == type - && partition->subtype == subtype - && strncmp((char*) partition->label, label, sizeof(partition->label) - 1) == 0) { - + && partition->subtype == subtype + && (!label || (label && (strncmp((char*) partition->label, label, sizeof(partition->label) - 1) == 0))) + ) { ESP_LOGV(TAG, "Matched", partition->type, partition->subtype, partition->label); internal_partition.flash_chip = NULL; //esp_flash_default_chip; internal_partition.type = partition->type; @@ -74,6 +74,18 @@ const esp_partition_t* esp_partition_find_first(esp_partition_type_t type, esp_p esp_err_t esp_partition_read(const esp_partition_t *partition, size_t src_offset, void *dst, size_t size) { + assert(partition != NULL); + if (src_offset > partition->size) { + return ESP_ERR_INVALID_ARG; + } + if (size > partition->size - src_offset) { + return ESP_ERR_INVALID_SIZE; + } + + if (!partition->encrypted) { + return bootloader_flash_read(partition->address + src_offset, dst, size, false); + } + const void *buf; // log call to mmap diff --git a/components/esp_rom/CMakeLists.txt b/components/esp_rom/CMakeLists.txt index 580a5bfc48..0570c4de84 100644 --- a/components/esp_rom/CMakeLists.txt +++ b/components/esp_rom/CMakeLists.txt @@ -156,6 +156,13 @@ if(BOOTLOADER_BUILD) endif() endif() + if(CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL_BOOTLOADER) + rom_linker_script("mbedtls") + # For ESP32C2(ECO4), mbedTLS in ROM has been updated to v3.6.0-LTS + if(CONFIG_ESP32C2_REV_MIN_FULL GREATER_EQUAL 200) + rom_linker_script("mbedtls.eco4") + endif() + endif() else() # Regular app build if(target STREQUAL "esp32") diff --git a/components/esptool_py/project_include.cmake b/components/esptool_py/project_include.cmake index 89547014ed..20cd576727 100644 --- a/components/esptool_py/project_include.cmake +++ b/components/esptool_py/project_include.cmake @@ -289,13 +289,13 @@ function(esptool_py_partition_needs_encryption retencrypted partition_name) # - DATA 0x01 # Subtypes: # - ota 0x00 - # - nvs 0x02 - # If the partition is an app, an OTA or an NVS partition, then it should + # - nvs_keys 0x04 + # If the partition is an app, an OTA or an NVS keys partition, then it should # be encrypted if( (${type} EQUAL 0) OR (${type} EQUAL 1 AND ${subtype} EQUAL 0) OR - (${type} EQUAL 1 AND ${subtype} EQUAL 2) + (${type} EQUAL 1 AND ${subtype} EQUAL 4) ) set(encrypted TRUE) endif() diff --git a/components/mbedtls/CMakeLists.txt b/components/mbedtls/CMakeLists.txt index 8bc72f6179..8662869c89 100644 --- a/components/mbedtls/CMakeLists.txt +++ b/components/mbedtls/CMakeLists.txt @@ -5,6 +5,18 @@ idf_build_get_property(esp_tee_build ESP_TEE_BUILD) if(esp_tee_build) include(${COMPONENT_DIR}/esp_tee/esp_tee_mbedtls.cmake) return() + +elseif(BOOTLOADER_BUILD) # TODO: IDF-11673 + if(CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL_BOOTLOADER) + set(include_dirs "${COMPONENT_DIR}/mbedtls/include" + "port/mbedtls_rom") + set(srcs "port/mbedtls_rom/mbedtls_rom_osi_bootloader.c") + endif() + + idf_component_register(SRCS "${srcs}" + INCLUDE_DIRS "${include_dirs}" + PRIV_REQUIRES hal) + return() endif() if(NOT ${IDF_TARGET} STREQUAL "linux") @@ -88,7 +100,6 @@ if(CONFIG_MBEDTLS_CERTIFICATE_BUNDLE) "${crt_bundle}") endif() - # Only build mbedtls libraries set(ENABLE_TESTING CACHE BOOL OFF) set(ENABLE_PROGRAMS CACHE BOOL OFF) diff --git a/components/mbedtls/Kconfig b/components/mbedtls/Kconfig index 7fd3d87ab6..b1edb31fba 100644 --- a/components/mbedtls/Kconfig +++ b/components/mbedtls/Kconfig @@ -1212,6 +1212,17 @@ menu "mbedTLS" Disabling this config can save some code/rodata size as the error string conversion implementation is replaced with an empty stub. + config MBEDTLS_USE_CRYPTO_ROM_IMPL_BOOTLOADER + bool "Use ROM implementation of the crypto algorithm in the bootloader" + depends on ESP_ROM_HAS_MBEDTLS_CRYPTO_LIB + default "n" + select MBEDTLS_AES_C + help + Enable this flag to use mbedtls crypto algorithm from ROM instead of ESP-IDF + in case of a bootloader build. + Similar to the MBEDTLS_USE_CRYPTO_ROM_IMPL config but enables usage of the + mbedtls crypto algorithm from ROM for the bootloader build. + config MBEDTLS_USE_CRYPTO_ROM_IMPL bool "Use ROM implementation of the crypto algorithm" depends on ESP_ROM_HAS_MBEDTLS_CRYPTO_LIB diff --git a/components/mbedtls/port/mbedtls_rom/mbedtls_rom_osi.h b/components/mbedtls/port/mbedtls_rom/mbedtls_rom_osi.h index b612adfa55..52fe58de3e 100644 --- a/components/mbedtls/port/mbedtls_rom/mbedtls_rom_osi.h +++ b/components/mbedtls/port/mbedtls_rom/mbedtls_rom_osi.h @@ -1,11 +1,12 @@ /* - * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once +#include #include "mbedtls/aes.h" #include "mbedtls/asn1.h" #include "mbedtls/asn1write.h" @@ -43,6 +44,7 @@ extern "C" { #endif +#ifndef BOOTLOADER_BUILD #if (!defined(CONFIG_MBEDTLS_THREADING_C)) #error CONFIG_MBEDTLS_THREADING_C #endif @@ -51,6 +53,9 @@ typedef void (*_rom_mbedtls_threading_set_alt_t)(void (*mutex_init)(mbedtls_thre void (*mutex_free)(mbedtls_threading_mutex_t *), int (*mutex_lock)(mbedtls_threading_mutex_t *), int (*mutex_unlock)(mbedtls_threading_mutex_t *)); +#else /* BOOTLOADER_BUILD */ +typedef void mbedtls_threading_mutex_t; +#endif /* BOOTLOADER_BUILD */ typedef struct mbedtls_rom_funcs { void (*_rom_mbedtls_aes_init)( mbedtls_aes_context *ctx ); @@ -659,8 +664,8 @@ typedef struct mbedtls_rom_eco4_funcs { #define STRUCT_OFFSET_CHECK(x, y, z) _Static_assert((offsetof(x,y)==(z)), "The variables type of "#x" before "#y" should be "#z) #define STRUCT_SIZE_CHECK(x, y) _Static_assert((sizeof(x)==(y)), "The sizeof "#x" should be "#y) -#if (!defined(CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL)) -#error "CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL" +#if (!defined(CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL) && !defined(CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL_BOOTLOADER)) +#error "Please enable CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL or CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL_BOOTLOADER" #endif /* platform_util.c */ @@ -668,6 +673,7 @@ typedef struct mbedtls_rom_eco4_funcs { #error "MBEDTLS_PLATFORM_ZEROIZE_ALT" #endif +#ifndef BOOTLOADER_BUILD /* sha1.c */ STRUCT_OFFSET_CHECK(mbedtls_sha1_context, total, 0); STRUCT_OFFSET_CHECK(mbedtls_sha1_context, state, 8); @@ -788,6 +794,11 @@ STRUCT_OFFSET_CHECK(mbedtls_md5_context, MBEDTLS_PRIVATE(state), 8); STRUCT_OFFSET_CHECK(mbedtls_md5_context, MBEDTLS_PRIVATE(buffer), 24); STRUCT_SIZE_CHECK(mbedtls_md5_context, 88); #endif +#endif /* BOOTLOADER_BUILD */ + +#if BOOTLOADER_BUILD +void mbedtls_rom_osi_functions_init_bootloader(void); +#endif /* BOOTLOADER_BUILD */ #ifdef __cplusplus } diff --git a/components/mbedtls/port/mbedtls_rom/mbedtls_rom_osi_bootloader.c b/components/mbedtls/port/mbedtls_rom/mbedtls_rom_osi_bootloader.c new file mode 100644 index 0000000000..2c730a9ff7 --- /dev/null +++ b/components/mbedtls/port/mbedtls_rom/mbedtls_rom_osi_bootloader.c @@ -0,0 +1,58 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/chip_revision.h" +#include "hal/efuse_hal.h" +#include "mbedtls_rom_osi.h" + +/* This structure can be automatically generated by the script with rom.mbedtls.ld. */ +static const mbedtls_rom_funcs_t mbedtls_rom_funcs_table = { + /* Fill the ROM functions into mbedtls rom function table. */ + /* aes module */ + ._rom_mbedtls_aes_init = mbedtls_aes_init, + ._rom_mbedtls_aes_free = mbedtls_aes_free, + ._rom_mbedtls_aes_setkey_enc = mbedtls_aes_setkey_enc, + ._rom_mbedtls_aes_setkey_dec = mbedtls_aes_setkey_dec, + ._rom_mbedtls_aes_crypt_ecb = mbedtls_aes_crypt_ecb, + ._rom_mbedtls_aes_crypt_cbc = mbedtls_aes_crypt_cbc, + ._rom_mbedtls_internal_aes_encrypt = mbedtls_internal_aes_encrypt, + ._rom_mbedtls_internal_aes_decrypt = mbedtls_internal_aes_decrypt, +}; + +/* This structure can be automatically generated by the script with rom.mbedtls.ld. */ +static const mbedtls_rom_eco4_funcs_t mbedtls_rom_eco4_funcs_table = { + /* Fill the ROM functions into mbedtls rom function table. */ + /* aes module */ + ._rom_mbedtls_aes_init = mbedtls_aes_init, + ._rom_mbedtls_aes_free = mbedtls_aes_free, + ._rom_mbedtls_aes_setkey_enc = mbedtls_aes_setkey_enc, + ._rom_mbedtls_aes_setkey_dec = mbedtls_aes_setkey_dec, + ._rom_mbedtls_aes_crypt_ecb = mbedtls_aes_crypt_ecb, + ._rom_mbedtls_aes_crypt_cbc = mbedtls_aes_crypt_cbc, + ._rom_mbedtls_internal_aes_encrypt = mbedtls_internal_aes_encrypt, + ._rom_mbedtls_internal_aes_decrypt = mbedtls_internal_aes_decrypt, + + ._rom_mbedtls_aes_xts_init = mbedtls_aes_xts_init, + ._rom_mbedtls_aes_xts_free = mbedtls_aes_xts_free, + ._rom_mbedtls_aes_xts_setkey_enc = mbedtls_aes_xts_setkey_enc, + ._rom_mbedtls_aes_xts_setkey_dec = mbedtls_aes_xts_setkey_dec, + ._rom_mbedtls_aes_crypt_xts = mbedtls_aes_crypt_xts, +}; + +void mbedtls_rom_osi_functions_init_bootloader(void) +{ + // /* Export the rom mbedtls functions table pointer */ + extern void *mbedtls_rom_osi_funcs_ptr; + + unsigned chip_version = efuse_hal_chip_revision(); + if ( ESP_CHIP_REV_ABOVE(chip_version, 200) ) { + /* Initialize the pointer of rom eco4 mbedtls functions table. */ + mbedtls_rom_osi_funcs_ptr = (mbedtls_rom_eco4_funcs_t *)&mbedtls_rom_eco4_funcs_table; + } else { + /* Initialize the pointer of rom mbedtls functions table. */ + mbedtls_rom_osi_funcs_ptr = (mbedtls_rom_funcs_t *)&mbedtls_rom_funcs_table; + } +} diff --git a/components/nvs_flash/.build-test-rules.yml b/components/nvs_flash/.build-test-rules.yml index cdfd738050..e939aa9bbd 100644 --- a/components/nvs_flash/.build-test-rules.yml +++ b/components/nvs_flash/.build-test-rules.yml @@ -21,5 +21,10 @@ components/nvs_flash/test_apps_bootloader: - spi_flash - nvs_flash - esp_partition + disable: + - if: CONFIG_NAME == "nvs_enc_flash_enc" and (SOC_AES_SUPPORTED != 1 and ESP_ROM_HAS_MBEDTLS_CRYPTO_LIB != 1) + - if: (CONFIG_NAME == "nvs_enc_hmac" or CONFIG_NAME == "nvs_enc_hmac_no_cfg") and (SOC_HMAC_SUPPORTED != 1 or (SOC_HMAC_SUPPORTED == 1 and (SOC_AES_SUPPORTED != 1 and ESP_ROM_HAS_MBEDTLS_CRYPTO_LIB != 1))) + + reason: As of now in such cases, we do not have any way to perform AES operations in the bootloader build disable_test: - if: IDF_TARGET not in ["esp32", "esp32c3"] diff --git a/components/nvs_flash/CMakeLists.txt b/components/nvs_flash/CMakeLists.txt index 27f66cc9be..eb8057afe2 100644 --- a/components/nvs_flash/CMakeLists.txt +++ b/components/nvs_flash/CMakeLists.txt @@ -1,10 +1,14 @@ if(BOOTLOADER_BUILD) # bootloader build simplified version - set(srcs "src/nvs_bootloader.c") + set(srcs "src/nvs_bootloader.c" + "src/nvs_bootloader_aes.c" + "src/nvs_bootloader_xts_aes.c") + set(requires "esp_partition") idf_component_register(SRCS "${srcs}" REQUIRES "${requires}" + PRIV_REQUIRES "mbedtls" INCLUDE_DIRS "include" PRIV_INCLUDE_DIRS "private_include" ) @@ -52,7 +56,9 @@ else() target_compile_options(${COMPONENT_LIB} PUBLIC --coverage) target_link_libraries(${COMPONENT_LIB} PUBLIC --coverage) else() - target_sources(${COMPONENT_LIB} PRIVATE "src/nvs_encrypted_partition.cpp") + target_sources(${COMPONENT_LIB} PRIVATE "src/nvs_encrypted_partition.cpp" + "src/nvs_bootloader_aes.c" + "src/nvs_bootloader_xts_aes.c") target_link_libraries(${COMPONENT_LIB} PRIVATE idf::mbedtls) endif() diff --git a/components/nvs_flash/include/nvs_bootloader.h b/components/nvs_flash/include/nvs_bootloader.h index cdb11fb3f2..162319cfd6 100644 --- a/components/nvs_flash/include/nvs_bootloader.h +++ b/components/nvs_flash/include/nvs_bootloader.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -8,6 +8,7 @@ #include #include "esp_err.h" // esp_err_t #include "nvs.h" // nvs entry data types +#include "nvs_flash.h" // nvs_sec_cfg_t #ifdef __cplusplus extern "C" { @@ -104,6 +105,37 @@ esp_err_t nvs_bootloader_read(const char* partition_name, const size_t read_list_count, nvs_bootloader_read_list_t read_list[]); +/** + * @brief Initialize internal NVS security context, thus, enabling the NVS bootloader read API to decrypt encrypted NVS partitions + * + * @note Once `nvs_bootloader_secure_init()` is performed, `nvs_bootloader_read()` can correctly read only those NVS partitions + * that are encrypted using the given `nvs_sec_cfg_t` security config, until `nvs_bootloader_secure_deinit()` clears the internal + * NVS security context. + * + * @param sec_cfg NVS security key that would be used for decrypting the NVS partition + * @return ESP_OK if security initialization is successful + */ +esp_err_t nvs_bootloader_secure_init(const nvs_sec_cfg_t *sec_cfg); + +/** + * @brief Clear the internal NVS security context + */ +void nvs_bootloader_secure_deinit(void); + +/** + * @brief Reads NVS bootloader security configuration set by the specified security scheme + * + * @param[in] scheme_cfg Security scheme specific configuration + * + * @param[out] cfg Security configuration (encryption keys) + * + * @return + * - ESP_OK, if cfg was read successfully; + * - ESP_ERR_INVALID_ARG, if scheme_cfg or cfg is NULL; + * - ESP_FAIL, if the key reading process fails + */ +esp_err_t nvs_bootloader_read_security_cfg(nvs_sec_scheme_t *scheme_cfg, nvs_sec_cfg_t* cfg); + #ifdef __cplusplus } #endif diff --git a/components/nvs_flash/private_include/nvs_bootloader_aes.h b/components/nvs_flash/private_include/nvs_bootloader_aes.h new file mode 100644 index 0000000000..97fc6230be --- /dev/null +++ b/components/nvs_flash/private_include/nvs_bootloader_aes.h @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "soc/soc_caps.h" +#include "sdkconfig.h" + +#if defined(CONFIG_IDF_TARGET_ESP32) || !defined(SOC_AES_SUPPORTED) +enum AES_TYPE { + AES_ENC, + AES_DEC, +}; + +#if !SOC_AES_SUPPORTED +enum AES_BITS { + AES128, + AES192, + AES256 +}; +#endif /* !SOC_AES_SUPPORTED */ +#endif /* CONFIG_IDF_TARGET_ESP32 || !SOC_AES_SUPPORTED */ + +#if SOC_AES_SUPPORTED +#include "rom/aes.h" + +int nvs_bootloader_aes_crypt_ecb(enum AES_TYPE mode, + const unsigned char *key, + enum AES_BITS key_bits, + const unsigned char input[16], + unsigned char output[16]); + +#endif /* SOC_AES_SUPPORTED */ diff --git a/components/nvs_flash/private_include/nvs_bootloader_private.h b/components/nvs_flash/private_include/nvs_bootloader_private.h index 5b4929ffe0..5be089e622 100644 --- a/components/nvs_flash/private_include/nvs_bootloader_private.h +++ b/components/nvs_flash/private_include/nvs_bootloader_private.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -9,6 +9,8 @@ #include "nvs_constants.h" // NVS_CONST_ENTRY_SIZE and all size related constants shared with cpp implementation of NVS #include "nvs_bootloader.h" // nvs_bootloader_read_list_t and function prototypes #include "esp_partition.h" // esp_partition_t +#include "nvs_flash.h" // nvs_sec_cfg_t +#include "nvs_bootloader_xts_aes.h" // nvs_bootloader_xts_aes_context #ifdef __cplusplus extern "C" { diff --git a/components/nvs_flash/private_include/nvs_bootloader_xts_aes.h b/components/nvs_flash/private_include/nvs_bootloader_xts_aes.h new file mode 100644 index 0000000000..d1862c370b --- /dev/null +++ b/components/nvs_flash/private_include/nvs_bootloader_xts_aes.h @@ -0,0 +1,94 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "esp_err.h" +#include "nvs_bootloader_aes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \brief The XTS-AES context-type definition. + */ +typedef struct +{ + uint8_t crypt_key[32]; /*!< The AES context to use for AES block + encryption or decryption. */ + uint8_t tweak_key[32]; /*!< The AES context used for tweak + computation. */ +} nvs_bootloader_xts_aes_context; + +/** Invalid key length. */ +#define NVS_BOOTLOADER_ERR_AES_INVALID_KEY_LENGTH -0x0020 +/** Invalid data input length. */ +#define NVS_BOOTLOADER_ERR_AES_INVALID_INPUT_LENGTH -0x0022 + +/** + * @brief Initializes the specified XTS-AES context. + * + * @param ctx The XTS-AES context to be initialized. This must not be \c NULL. + */ +void nvs_bootloader_xts_aes_init(nvs_bootloader_xts_aes_context *ctx); + +/** + * @brief Clears the specified XTS-AES context. + * + * @param ctx The XTS-AES context to clear. + * If this is \c NULL, this function does nothing. + * Otherwise, the context must have been at least initialized. + */ +void nvs_bootloader_xts_aes_free(nvs_bootloader_xts_aes_context *ctx); + +/** + * @brief Sets the XTS-AES encryption-decryption key + * + * @param ctx The XTS-AES context to which the key should be bound. + * It must be initialized. + * @param key The encryption key. + * This must be a readable buffer of size \p key_bytes bytes. + * @param key_bytes The size of data passed in bits. Valid options are: + *
  • 128 bits
  • + *
  • 192 bits
  • + *
  • 256 bits
+ * @return \c 0 indicating success. + */ +int nvs_bootloader_xts_aes_setkey(nvs_bootloader_xts_aes_context *ctx, + const unsigned char *key, + unsigned int key_bytes); + +/** + * @brief Performs an XTS-AES encryption or decryption operation for an entire XTS data unit. + * + * @param ctx The XTS-AES context to use for XTS-AES operation + * It must be initialized and bound to a key. + * @param mode The AES operation: AES_ENC or AES_DEC. + * @param length The length of a data unit in bytes. + * @param data_unit The address of the data unit encoded as an array of 16 + * bytes in little-endian format. For disk encryption, this + * is typically the index of the block device sector that + * contains the data. + * @param input The buffer holding the input data (which is an entire + * data unit). This function reads \p length Bytes from \p + * input. + * @param output The buffer holding the output data (which is an entire + * data unit). This function writes \p length Bytes to \p + * output. + * @return \c 0 on success. + * 1 on failure. + */ +int nvs_bootloader_aes_crypt_xts(nvs_bootloader_xts_aes_context *ctx, + enum AES_TYPE mode, + size_t length, + const unsigned char data_unit[16], + const unsigned char *input, + unsigned char *output); + +#ifdef __cplusplus +} +#endif diff --git a/components/nvs_flash/project_include.cmake b/components/nvs_flash/project_include.cmake index a427c1ed01..b465024a2b 100644 --- a/components/nvs_flash/project_include.cmake +++ b/components/nvs_flash/project_include.cmake @@ -53,6 +53,7 @@ function(nvs_create_partition_image partition csv) if(arg_FLASH_IN_PROJECT) esptool_py_flash_to_partition(flash "${partition}" "${image_file}") add_dependencies(flash nvs_${partition}_bin) + add_dependencies(encrypted-flash nvs_${partition}_bin) endif() else() set(message diff --git a/components/nvs_flash/src/nvs_bootloader.c b/components/nvs_flash/src/nvs_bootloader.c index 07126c966a..9347cfd3ba 100644 --- a/components/nvs_flash/src/nvs_bootloader.c +++ b/components/nvs_flash/src/nvs_bootloader.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -9,95 +9,115 @@ #include "nvs_bootloader.h" #include "nvs_bootloader_private.h" #include "esp_assert.h" +#include "sdkconfig.h" #include "esp_partition.h" #include "nvs_constants.h" #include +#if CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL_BOOTLOADER && BOOTLOADER_BUILD +#include "mbedtls_rom_osi.h" +#endif + static const char* TAG = "nvs_bootloader"; -const bool const_is_encrypted = false; // Static asserts ensuring that the size of the c structures match NVS physical footprint on the flash ESP_STATIC_ASSERT(sizeof(nvs_bootloader_page_header_t) == NVS_CONST_ENTRY_SIZE, "nvs_bootloader_page_header_t size is not 32 bytes"); ESP_STATIC_ASSERT(sizeof(nvs_bootloader_page_entry_states_t) == NVS_CONST_ENTRY_SIZE, "nvs_bootloader_page_entry_states_t size is not 32 bytes"); ESP_STATIC_ASSERT(sizeof(nvs_bootloader_single_entry_t) == NVS_CONST_ENTRY_SIZE, "nvs_bootloader_single_entry_t size is not 32 bytes"); -esp_err_t nvs_bootloader_read(const char* partition_name, - const size_t read_list_count, - nvs_bootloader_read_list_t read_list[]) +#define NVS_KEY_SIZE 32 // AES-256 + +/* Currently we support only single-threaded use-cases of reading encrypted NVS partitions */ +static nvs_bootloader_xts_aes_context dec_ctx; +static bool is_nvs_partition_encrypted = false; + +static esp_err_t decrpyt_data(uint32_t src_offset, void* dst, size_t size) { + // decrypt data + // sector num, could have been just uint64/32. + uint8_t data_unit[16]; + uint8_t *destination = (uint8_t *)dst; + uint32_t relAddr = src_offset; + + memset(data_unit, 0, sizeof(data_unit)); + memcpy(data_unit, &relAddr, sizeof(relAddr)); + + return nvs_bootloader_aes_crypt_xts(&dec_ctx, AES_DEC, size, data_unit, destination, destination); +} + +static esp_err_t nvs_bootloader_partition_read_string_value(const esp_partition_t *partition, size_t src_offset, void *block, size_t block_len) +{ + esp_err_t ret = ESP_FAIL; + + /* For the bootloader build, the esp_partition_read() API internally is calls bootloader_flash_read() that + * requires the src_address, length and the destination address to be word aligned. + * src_address: NVS keys and values are always stored at a word aligned offset + * length: Reading bytes of length divisible by 4 at a time (BOOTLOADER_FLASH_READ_LEN) + * destination address: Using a word aligned buffer to read the flash contents (bootloader_flash_read_buffer) + */ + #define BOOTLOADER_FLASH_READ_LEN 32 // because it matches the below discussed XTS-AES requirements as well /* - The flow: + * When reading an encrypted partition, we implement the splitting method, that is, we read and decrypt data size in the multiples of 16. + * This is necessary because of a bug present in the mbedtls_aes_crypt_xts() function wherein "inplace" encryption/decryption + * calculation fails if the length of buffers is not a multiple of 16. + * Reference: https://github.com/Mbed-TLS/mbedtls/issues/4302 + * + * Thus, in this case we first operate over the aligned down to 16 length of data and then over the remaining data, by copying + * it to a buffer of size 16. + * + * Also, until https://github.com/Mbed-TLS/mbedtls/issues/9827 is resolved, we need to operate over chunks of length 32. + */ + #define XTS_AES_PROCESS_BLOCK_LEN 32 - 1. validate parameters and if there are any, report errors. - 2. check if the partition exists - 3. read the partition and browse all NVS pages to figure out whether there is any page in the state of "FREEING" - 3.1. if there is a page in the state of "FREEING", then reading from the page marked as "ACTIVE" will be skipped - 4. read entries from pages marked as "FULL" or ("ACTIVE" xor "FREEING") to identify namespace indexes of the requested namespaces - 5. read the requested entries, same skipping of pages in the state of "ACTIVE" as in step 4 + if (src_offset & 3 || block_len & 3 || (intptr_t) block & 3 + || is_nvs_partition_encrypted + ) { + WORD_ALIGNED_ATTR uint8_t bootloader_flash_read_buffer[BOOTLOADER_FLASH_READ_LEN] = { 0 }; - */ - // load input parameters - ESP_LOGD(TAG, "nvs_bootloader_read called with partition_name: %s, read_list_count: %u", partition_name, (unsigned)read_list_count); + size_t block_data_len = block_len / BOOTLOADER_FLASH_READ_LEN * BOOTLOADER_FLASH_READ_LEN; + size_t remaining_data_len = block_len % BOOTLOADER_FLASH_READ_LEN; - // Placeholder return value, replace with actual error handling - esp_err_t ret = ESP_OK; + /* Process block data */ + if (block_data_len > 0) { + for (size_t data_processed = 0; data_processed < block_data_len; data_processed += BOOTLOADER_FLASH_READ_LEN) { + ret = esp_partition_read(partition, src_offset + data_processed, bootloader_flash_read_buffer, BOOTLOADER_FLASH_READ_LEN); + if (ret != ESP_OK) { + return ret; + } - // Check if the parameters are valid - ret = nvs_bootloader_check_parameters(partition_name, read_list_count, read_list); - if (ret != ESP_OK) { - ESP_LOGD(TAG, "nvs_bootloader_check_parameters failed"); - return ret; + if (is_nvs_partition_encrypted) { + ret = decrpyt_data(src_offset + data_processed, bootloader_flash_read_buffer, BOOTLOADER_FLASH_READ_LEN); + if (ret != ESP_OK) { + return ret; + } + } + + memcpy(block + data_processed, bootloader_flash_read_buffer, BOOTLOADER_FLASH_READ_LEN); + } + } + + /* Process remaining data */ + if (remaining_data_len) { + ret = esp_partition_read(partition, src_offset + block_data_len, bootloader_flash_read_buffer, BOOTLOADER_FLASH_READ_LEN); + if (ret != ESP_OK) { + return ret; + } + + if (is_nvs_partition_encrypted) { + ret = decrpyt_data(src_offset + block_data_len, bootloader_flash_read_buffer, BOOTLOADER_FLASH_READ_LEN); + if (ret != ESP_OK) { + return ret; + } + } + + memcpy(block + block_data_len, bootloader_flash_read_buffer, remaining_data_len); + } + } else { + ret = esp_partition_read(partition, src_offset, block, block_len); } - const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, partition_name); - if (partition == NULL) { - ESP_LOGV(TAG, "esp_partition_find_first failed"); - return ESP_ERR_NVS_PART_NOT_FOUND; - } - - // log the partition details - ESP_LOGV(TAG, "Partition %s found, size is: %" PRIu32 "", partition->label, partition->size); - - // visit pages to get the number of pages in the state of "ACTIVE" and "FREEING" - nvs_bootloader_page_visitor_param_get_page_states_t page_states_count = {0}; - - ret = nvs_bootloader_visit_pages(partition, nvs_bootloader_page_visitor_get_page_states, (nvs_bootloader_page_visitor_param_t*)&page_states_count); - - if (ret != ESP_OK) { - ESP_LOGV(TAG, "Failed reading page states"); - return ret; - } - if (page_states_count.no_freeing_pages > 1) { - ESP_LOGV(TAG, "Multiple pages in the state of FREEING"); - return ESP_ERR_INVALID_STATE; - } - if (page_states_count.no_active_pages > 1) { - ESP_LOGV(TAG, "Multiple pages in the state of ACTIVE"); - return ESP_ERR_INVALID_STATE; - } - - // Here we have at most 1 active page and at most 1 freeing page. If there exists a freeing page, we will skip reading from the active page - // Visit pages to get the namespace indexes of the requested namespaces - nvs_bootloader_page_visitor_param_read_entries_t read_entries = { .skip_active_page = page_states_count.no_freeing_pages > 0, .read_list_count = read_list_count, .read_list = read_list }; - - // log the visitor parameters - ESP_LOGV(TAG, "nvs_bootloader_read - visiting pages to get namespace indexes"); - - ret = nvs_bootloader_visit_pages(partition, nvs_bootloader_page_visitor_get_namespaces, (nvs_bootloader_page_visitor_param_t*) &read_entries); - - if (ret != ESP_OK) { - ESP_LOGV(TAG, "nvs_bootloader_page_visitor_get_namespaces failed"); - return ret; - } - - // log the visitor parameters - ESP_LOGV(TAG, "nvs_bootloader_read - visiting pages to get key - value pairs"); - - // Visit pages to read the requested key - value pairs - ret = nvs_bootloader_visit_pages(partition, nvs_bootloader_page_visitor_get_key_value_pairs, (nvs_bootloader_page_visitor_param_t*) &read_entries); - return ret; } @@ -215,7 +235,7 @@ esp_err_t nvs_bootloader_page_visitor_get_namespaces(nvs_bootloader_page_visitor // iterate over all entries with state written // if the entry is namespace entry, then iterate the read_list and populate the namespace index by matching the namespace name uint8_t start_index = 0; - nvs_bootloader_single_entry_t item = {0}; + WORD_ALIGNED_ATTR nvs_bootloader_single_entry_t item = {0}; // repeat finding single entry items on the page until all entries are processed or error occurs while (ret == ESP_OK) { @@ -275,7 +295,7 @@ esp_err_t nvs_bootloader_page_visitor_get_key_value_pairs(nvs_bootloader_page_vi // if the entry is not a namespace entry, then iterate the read_list and populate the value by matching the namespace index, key name and value type uint8_t next_index = 0; // index of the next entry to read, updated to the next entry by the read_next_single_entry_item uint8_t current_index = 0; // index of the actual entry being processed - nvs_bootloader_single_entry_t item = {0}; + WORD_ALIGNED_ATTR nvs_bootloader_single_entry_t item = {0}; // repeat finding single entry items on the page until all entries are processed or error occurs while (ret == ESP_OK) { @@ -446,7 +466,6 @@ esp_err_t nvs_bootloader_read_next_single_entry_item(const esp_partition_t *part uint8_t *entry_index, nvs_bootloader_single_entry_t *item) { - // log parameters ESP_LOGV(TAG, "nvs_bootloader_read_next_single_entry_item called with page_index: %u, entry_index: %d", (unsigned)page_index, *entry_index); @@ -463,11 +482,17 @@ esp_err_t nvs_bootloader_read_next_single_entry_item(const esp_partition_t *part if (NVS_BOOTLOADER_GET_ENTRY_STATE(page_entry_states, *entry_index) == NVS_CONST_ENTRY_STATE_WRITTEN) { ret = esp_partition_read(partition, page_index * NVS_CONST_PAGE_SIZE + NVS_CONST_PAGE_ENTRY_DATA_OFFSET + (*entry_index) * NVS_CONST_ENTRY_SIZE, (void*)item, sizeof(nvs_bootloader_single_entry_t)); - if (ret != ESP_OK) { return ret; } + if (is_nvs_partition_encrypted) { + ret = decrpyt_data(page_index * NVS_CONST_PAGE_SIZE + NVS_CONST_PAGE_ENTRY_DATA_OFFSET + (*entry_index) * NVS_CONST_ENTRY_SIZE, (void*)item, sizeof(nvs_bootloader_single_entry_t)); + if (ret != ESP_OK) { + return ret; + } + } + // only look at item with consistent header if (nvs_bootloader_check_item_header_consistency(item, *entry_index)) { // advance the start index @@ -544,9 +569,9 @@ esp_err_t nvs_bootloader_read_entries_block(const esp_partition_t *partition, return ret; } - size_t data_offset = page_index * NVS_CONST_PAGE_SIZE + NVS_CONST_PAGE_ENTRY_DATA_OFFSET + entry_index * NVS_CONST_ENTRY_SIZE ; + size_t data_offset = page_index * NVS_CONST_PAGE_SIZE + NVS_CONST_PAGE_ENTRY_DATA_OFFSET + entry_index * NVS_CONST_ENTRY_SIZE; - return esp_partition_read(partition, data_offset, block, block_len); + return nvs_bootloader_partition_read_string_value(partition, data_offset, block, block_len); } // validates item's header @@ -602,3 +627,116 @@ bool nvs_bootloader_check_item_header_consistency(const nvs_bootloader_single_en return ret; } + +esp_err_t nvs_bootloader_read(const char* partition_name, + const size_t read_list_count, + nvs_bootloader_read_list_t read_list[]) +{ + /* + The flow: + + 1. validate parameters and if there are any, report errors. + 2. check if the partition exists + 3. read the partition and browse all NVS pages to figure out whether there is any page in the state of "FREEING" + 3.1. if there is a page in the state of "FREEING", then reading from the page marked as "ACTIVE" will be skipped + 4. read entries from pages marked as "FULL" or ("ACTIVE" xor "FREEING") to identify namespace indexes of the requested namespaces + 5. read the requested entries, same skipping of pages in the state of "ACTIVE" as in step 4 + + */ + // load input parameters + ESP_LOGD(TAG, "nvs_bootloader_read called with partition_name: %s, read_list_count: %u", partition_name, (unsigned)read_list_count); + + // Placeholder return value, replace with actual error handling + esp_err_t ret = ESP_OK; + + // Check if the parameters are valid + ret = nvs_bootloader_check_parameters(partition_name, read_list_count, read_list); + if (ret != ESP_OK) { + ESP_LOGD(TAG, "nvs_bootloader_check_parameters failed"); + return ret; + } + + const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, partition_name); + if (partition == NULL) { + ESP_LOGV(TAG, "esp_partition_find_first failed"); + return ESP_ERR_NVS_PART_NOT_FOUND; + } + + // log the partition details + ESP_LOGV(TAG, "Partition %s found, size is: %" PRIu32 "", partition->label, partition->size); + + // visit pages to get the number of pages in the state of "ACTIVE" and "FREEING" + nvs_bootloader_page_visitor_param_get_page_states_t page_states_count = {0}; + + ret = nvs_bootloader_visit_pages(partition, nvs_bootloader_page_visitor_get_page_states, (nvs_bootloader_page_visitor_param_t*)&page_states_count); + + if (ret != ESP_OK) { + ESP_LOGV(TAG, "Failed reading page states"); + return ret; + } + if (page_states_count.no_freeing_pages > 1) { + ESP_LOGV(TAG, "Multiple pages in the state of FREEING"); + return ESP_ERR_INVALID_STATE; + } + if (page_states_count.no_active_pages > 1) { + ESP_LOGV(TAG, "Multiple pages in the state of ACTIVE"); + return ESP_ERR_INVALID_STATE; + } + + // Here we have at most 1 active page and at most 1 freeing page. If there exists a freeing page, we will skip reading from the active page + // Visit pages to get the namespace indexes of the requested namespaces + nvs_bootloader_page_visitor_param_read_entries_t read_entries = { .skip_active_page = page_states_count.no_freeing_pages > 0, .read_list_count = read_list_count, .read_list = read_list }; + + // log the visitor parameters + ESP_LOGV(TAG, "nvs_bootloader_read - visiting pages to get namespace indexes"); + + ret = nvs_bootloader_visit_pages(partition, nvs_bootloader_page_visitor_get_namespaces, (nvs_bootloader_page_visitor_param_t*) &read_entries); + + if (ret != ESP_OK) { + ESP_LOGV(TAG, "nvs_bootloader_page_visitor_get_namespaces failed"); + return ret; + } + + // log the visitor parameters + ESP_LOGV(TAG, "nvs_bootloader_read - visiting pages to get key - value pairs"); + + // Visit pages to read the requested key - value pairs + ret = nvs_bootloader_visit_pages(partition, nvs_bootloader_page_visitor_get_key_value_pairs, (nvs_bootloader_page_visitor_param_t*) &read_entries); + + return ret; +} + +esp_err_t nvs_bootloader_secure_init(const nvs_sec_cfg_t *sec_cfg) +{ +#if CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL_BOOTLOADER && BOOTLOADER_BUILD + // To enable usage of mbedtls APIs form the ROM, we need to initialize the rom mbedtls functions table pointer. + // In case of application, a constructor present in the mbedtls component initializes the rom mbedtls functions table pointer during boot up. + mbedtls_rom_osi_functions_init_bootloader(); +#endif /* CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL_BOOTLOADER && BOOTLOADER_BUILD */ + + nvs_bootloader_xts_aes_init(&dec_ctx); + + esp_err_t ret = nvs_bootloader_xts_aes_setkey(&dec_ctx, (const uint8_t*) sec_cfg, 2 * NVS_KEY_SIZE); + if (ret != ESP_OK) { + return ret; + } + + is_nvs_partition_encrypted = true; + return ret; +} + +void nvs_bootloader_secure_deinit(void) +{ + if (is_nvs_partition_encrypted) { + nvs_bootloader_xts_aes_free(&dec_ctx); + is_nvs_partition_encrypted = false; + } +} + +esp_err_t nvs_bootloader_read_security_cfg(nvs_sec_scheme_t *scheme_cfg, nvs_sec_cfg_t* cfg) +{ + if (scheme_cfg == NULL || cfg == NULL || scheme_cfg->nvs_flash_read_cfg == NULL) { + return ESP_ERR_INVALID_ARG; + } + return (scheme_cfg->nvs_flash_read_cfg)(scheme_cfg->scheme_data, cfg); +} diff --git a/components/nvs_flash/src/nvs_bootloader_aes.c b/components/nvs_flash/src/nvs_bootloader_aes.c new file mode 100644 index 0000000000..5cbbab5ad0 --- /dev/null +++ b/components/nvs_flash/src/nvs_bootloader_aes.c @@ -0,0 +1,111 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include "esp_log.h" +#include "soc/soc_caps.h" +#include "nvs_bootloader_aes.h" +#include "sdkconfig.h" + +#if SOC_AES_SUPPORTED +int nvs_bootloader_aes_crypt_ecb(enum AES_TYPE mode, + const unsigned char *key, + enum AES_BITS key_bits, + const unsigned char input[16], + unsigned char output[16]) +{ + int ret = -1; + ets_aes_enable(); + +#if CONFIG_IDF_TARGET_ESP32 + if (mode == AES_ENC) { + ret = ets_aes_setkey_enc(key, key_bits); + } else { + ret = ets_aes_setkey_dec(key, key_bits); + } + + if (ret) { + ets_aes_crypt(input, output); + // In case of esp32, ets_aes_setkey_dec returns 1 on success, + // whereas for other targets ets_aes_setkey return 0 on success + ret = 0; + } +#else /* CONFIG_IDF_TARGET_ESP32 */ + ret = ets_aes_setkey(mode, key, key_bits); + + if (ret == 0) { + ets_aes_block(input, output); + } +#endif /* !CONFIG_IDF_TARGET_ESP32 */ + + ets_aes_disable(); + return ret; +} + +#else /* SOC_AES_SUPPORTED */ +#if BOOTLOADER_BUILD && !CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL_BOOTLOADER +#if CONFIG_ESP_ROM_HAS_MBEDTLS_CRYPTO_LIB +#error "Enable `CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL_BOOTLOADER` for non SOC_AES_SUPPORTED targets for supporting NVS encryption in bootloader build" +#else /* !CONFIG_ESP_ROM_HAS_MBEDTLS_CRYPTO_LIB */ +// TODO: IDF-11673 +// Due to unavailability of an software AES layer for bootloader build, +// we cannot support the below NVS bootloader's AES operations +// Thus we are adding stub APIs to indicate that the following operation fail. + +static const char *TAG = "nvs_bootloader_aes"; +static const char *op_unsupported_error = "AES operation in bootloader unsupported for this target"; + +int nvs_bootloader_aes_crypt_ecb(enum AES_TYPE mode, + const unsigned char *key, + enum AES_BITS key_bits, + const unsigned char input[16], + unsigned char output[16]) +{ + ESP_EARLY_LOGE(TAG, "%s", op_unsupported_error); + abort(); + return -1; +} +#endif /* CONFIG_ESP_ROM_HAS_MBEDTLS_CRYPTO_LIB */ +#else /* BOOTLOADER_BUILD && !CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL_BOOTLOADER */ +#include "mbedtls/aes.h" + +int nvs_bootloader_aes_crypt_ecb(enum AES_TYPE mode, + const unsigned char *key, + enum AES_BITS key_bits, + const unsigned char input[16], + unsigned char output[16]) +{ + int ret = -1; + + uint16_t keybits = key_bits == AES256 ? 256 : key_bits == AES192 ? 192 : 128; + int mbedtls_aes_mode = mode == AES_ENC ? MBEDTLS_AES_ENCRYPT : MBEDTLS_AES_DECRYPT; + + mbedtls_aes_context ctx; + mbedtls_aes_init(&ctx); + + if (mode == AES_ENC) { + ret = mbedtls_aes_setkey_enc(&ctx, key, keybits); + } else { + ret = mbedtls_aes_setkey_dec(&ctx, key, keybits); + } + + if (ret != 0) { + mbedtls_aes_free(&ctx); + return ret; + } + + ret = mbedtls_aes_crypt_ecb(&ctx, mbedtls_aes_mode, input, output); + + if (ret != 0) { + mbedtls_aes_free(&ctx); + return ret; + } + + mbedtls_aes_free(&ctx); + return ret; +} +#endif /* !(BOOTLOADER_BUILD && !CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL_BOOTLOADER) */ +#endif /* !SOC_AES_SUPPORTED */ diff --git a/components/nvs_flash/src/nvs_bootloader_xts_aes.c b/components/nvs_flash/src/nvs_bootloader_xts_aes.c new file mode 100644 index 0000000000..ce204f6a9d --- /dev/null +++ b/components/nvs_flash/src/nvs_bootloader_xts_aes.c @@ -0,0 +1,299 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include "esp_err.h" +#include "esp_log.h" + +#include "nvs_bootloader_aes.h" +#include "nvs_bootloader_xts_aes.h" + +#include "sdkconfig.h" +#include "soc/soc_caps.h" + +#if SOC_AES_SUPPORTED +/* + * NOTE: The implementation of the below APIs have been copied + * from the mbedtls (v3.6.2) implementation of the XTS-AES APIs. + */ +void nvs_bootloader_xts_aes_init(nvs_bootloader_xts_aes_context *ctx) +{ + bzero(&ctx->crypt_key, sizeof(ctx->crypt_key)); + bzero(&ctx->tweak_key, sizeof(ctx->tweak_key)); +} + +void nvs_bootloader_xts_aes_free(nvs_bootloader_xts_aes_context *ctx) +{ + if (ctx) { + bzero(&ctx->crypt_key, sizeof(ctx->crypt_key)); + bzero(&ctx->tweak_key, sizeof(ctx->tweak_key)); + } +} + +int nvs_bootloader_xts_aes_setkey(nvs_bootloader_xts_aes_context *ctx, + const unsigned char *key, + unsigned int key_bytes) +{ + size_t xts_key_bytes = key_bytes / 2; + + memcpy(&ctx->crypt_key, key, xts_key_bytes); + memcpy(&ctx->tweak_key, &key[xts_key_bytes], xts_key_bytes); + return 0; +} + +/* Endianness with 64 bits values */ +#ifndef GET_UINT64_LE +#define GET_UINT64_LE(n,b,i) \ +{ \ + (n) = ((uint64_t) (b)[(i) + 7] << 56) \ + | ((uint64_t) (b)[(i) + 6] << 48) \ + | ((uint64_t) (b)[(i) + 5] << 40) \ + | ((uint64_t) (b)[(i) + 4] << 32) \ + | ((uint64_t) (b)[(i) + 3] << 24) \ + | ((uint64_t) (b)[(i) + 2] << 16) \ + | ((uint64_t) (b)[(i) + 1] << 8) \ + | ((uint64_t) (b)[(i) ] ); \ +} +#endif + +#ifndef PUT_UINT64_LE +#define PUT_UINT64_LE(n,b,i) \ +{ \ + (b)[(i) + 7] = (unsigned char) ((n) >> 56); \ + (b)[(i) + 6] = (unsigned char) ((n) >> 48); \ + (b)[(i) + 5] = (unsigned char) ((n) >> 40); \ + (b)[(i) + 4] = (unsigned char) ((n) >> 32); \ + (b)[(i) + 3] = (unsigned char) ((n) >> 24); \ + (b)[(i) + 2] = (unsigned char) ((n) >> 16); \ + (b)[(i) + 1] = (unsigned char) ((n) >> 8); \ + (b)[(i) ] = (unsigned char) ((n) ); \ +} +#endif + +/* + * GF(2^128) multiplication function + * + * This function multiplies a field element by x in the polynomial field + * representation. It uses 64-bit word operations to gain speed but compensates + * for machine endianness and hence works correctly on both big and little + * endian machines. + */ +static void bootloader_gf128mul_x_ble(unsigned char r[16], + const unsigned char x[16]) +{ + uint64_t a, b, ra, rb; + + GET_UINT64_LE(a, x, 0); + GET_UINT64_LE(b, x, 8); + + ra = (a << 1) ^ 0x0087 >> (8 - ((b >> 63) << 3)); + rb = (a >> 63) | (b << 1); + + PUT_UINT64_LE(ra, r, 0); + PUT_UINT64_LE(rb, r, 8); +} + +/* + * XTS-AES buffer encryption/decryption + */ +int nvs_bootloader_aes_crypt_xts(nvs_bootloader_xts_aes_context *ctx, + enum AES_TYPE mode, + size_t length, + const unsigned char data_unit[16], + const unsigned char *input, + unsigned char *output) +{ + int ret; + size_t blocks = length / 16; + size_t leftover = length % 16; + unsigned char tweak[16] = {}; + unsigned char prev_tweak[16] = {}; + unsigned char tmp[16] = {}; + + /* Sectors must be at least 16 bytes. */ + if (length < 16) { + return NVS_BOOTLOADER_ERR_AES_INVALID_INPUT_LENGTH; + } + + /* NIST SP 80-38E disallows data units larger than 2**20 blocks. */ + if (length > ( 1 << 20 ) * 16) { + return NVS_BOOTLOADER_ERR_AES_INVALID_INPUT_LENGTH; + } + + /* Compute the tweak. */ + ret = nvs_bootloader_aes_crypt_ecb(AES_ENC, (const unsigned char *) &ctx->tweak_key, + AES256, data_unit, tweak); + if (ret != 0) { + return (ret); + } + + while (blocks--) { + size_t i; + + if (leftover && (mode == AES_DEC) && blocks == 0) { + /* We are on the last block in a decrypt operation that has + * leftover bytes, so we need to use the next tweak for this block, + * and this tweak for the lefover bytes. Save the current tweak for + * the leftovers and then update the current tweak for use on this, + * the last full block. */ + memcpy(prev_tweak, tweak, sizeof(tweak)); + bootloader_gf128mul_x_ble(tweak, tweak); + } + + for (i = 0; i < 16; i++) { + tmp[i] = input[i] ^ tweak[i]; + } + + ret = nvs_bootloader_aes_crypt_ecb(mode, (const unsigned char *) &ctx->crypt_key, AES256, tmp, tmp); + if (ret != 0) { + return (ret); + } + + for (i = 0; i < 16; i++) { + output[i] = tmp[i] ^ tweak[i]; + } + + /* Update the tweak for the next block. */ + bootloader_gf128mul_x_ble(tweak, tweak); + + output += 16; + input += 16; + } + + if (leftover) { + /* If we are on the leftover bytes in a decrypt operation, we need to + * use the previous tweak for these bytes (as saved in prev_tweak). */ + unsigned char *t = mode == AES_DEC ? prev_tweak : tweak; + + /* We are now on the final part of the data unit, which doesn't divide + * evenly by 16. It's time for ciphertext stealing. */ + size_t i; + unsigned char *prev_output = output - 16; + + /* Copy ciphertext bytes from the previous block to our output for each + * byte of ciphertext we won't steal. At the same time, copy the + * remainder of the input for this final round (since the loop bounds + * are the same). */ + for (i = 0; i < leftover; i++) { + output[i] = prev_output[i]; + tmp[i] = input[i] ^ t[i]; + } + + /* Copy ciphertext bytes from the previous block for input in this + * round. */ + for (; i < 16; i++) { + tmp[i] = prev_output[i] ^ t[i]; + } + + ret = nvs_bootloader_aes_crypt_ecb(mode, (const unsigned char *) &ctx->crypt_key, AES256, tmp, tmp); + if (ret != 0) { + return ret; + } + + /* Write the result back to the previous block, overriding the previous + * output we copied. */ + for (i = 0; i < 16; i++) { + prev_output[i] = tmp[i] ^ t[i]; + } + } + + return 0; +} +#else /* SOC_AES_SUPPORTED */ +#if BOOTLOADER_BUILD && !CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL_BOOTLOADER +#if CONFIG_ESP_ROM_HAS_MBEDTLS_CRYPTO_LIB +#error "Enable `CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL_BOOTLOADER` for non SOC_AES_SUPPORTED targets for supporting NVS encryption in bootloader build" +#else /* !CONFIG_ESP_ROM_HAS_MBEDTLS_CRYPTO_LIB */ +// TODO: IDF-11673 +// Due to unavailability of an software AES layer for bootloader build, +// we cannot support the below NVS bootloader's AES operations +// Thus we are adding stub APIs to indicate that the following operation fail. + +static const char *TAG = "nvs_bootloader_xts_aes"; +static const char *op_unsupported_error = "XTS-AES operation in bootloader unsupported for this target"; + +void nvs_bootloader_xts_aes_init(nvs_bootloader_xts_aes_context *ctx) +{ + (void) ctx; + ESP_EARLY_LOGE(TAG, "%s", op_unsupported_error); + abort(); +} + +void nvs_bootloader_xts_aes_free(nvs_bootloader_xts_aes_context *ctx) +{ + (void) ctx; + ESP_EARLY_LOGE(TAG, "%s", op_unsupported_error); + abort(); +} + +int nvs_bootloader_xts_aes_setkey(nvs_bootloader_xts_aes_context *ctx, + const unsigned char *key, + unsigned int key_bytes) +{ + (void) ctx; + ESP_EARLY_LOGE(TAG, "%s", op_unsupported_error); + abort(); + return -1; +} +/* + * XTS-AES buffer encryption/decryption + */ +int nvs_bootloader_aes_crypt_xts(nvs_bootloader_xts_aes_context *ctx, + enum AES_TYPE mode, + size_t length, + const unsigned char data_unit[16], + const unsigned char *input, + unsigned char *output) +{ + (void) ctx; + ESP_EARLY_LOGE(TAG, "XTS-AES operation in bootloader unsupported"); + abort(); + return -1; +} + +#endif /* CONFIG_ESP_ROM_HAS_MBEDTLS_CRYPTO_LIB */ +#else /* BOOTLOADER_BUILD && !CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL_BOOTLOADER */ +#include "mbedtls/aes.h" + +static mbedtls_aes_xts_context ctx_xts; + +void nvs_bootloader_xts_aes_init(nvs_bootloader_xts_aes_context *ctx) +{ + (void) ctx; + mbedtls_aes_xts_init(&ctx_xts); +} + +void nvs_bootloader_xts_aes_free(nvs_bootloader_xts_aes_context *ctx) +{ + (void) ctx; + mbedtls_aes_xts_free(&ctx_xts); +} + +int nvs_bootloader_xts_aes_setkey(nvs_bootloader_xts_aes_context *ctx, + const unsigned char *key, + unsigned int key_bytes) +{ + (void) ctx; + return mbedtls_aes_xts_setkey_dec(&ctx_xts, key, key_bytes * 8); +} +/* + * XTS-AES buffer encryption/decryption + */ +int nvs_bootloader_aes_crypt_xts(nvs_bootloader_xts_aes_context *ctx, + enum AES_TYPE mode, + size_t length, + const unsigned char data_unit[16], + const unsigned char *input, + unsigned char *output) +{ + (void) ctx; + + int mbedtls_aes_mode = mode == AES_ENC ? MBEDTLS_AES_ENCRYPT : MBEDTLS_AES_DECRYPT; + return mbedtls_aes_crypt_xts(&ctx_xts, mbedtls_aes_mode, length, data_unit, input, output); +} +#endif /* !(BOOTLOADER_BUILD && !CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL_BOOTLOADER) */ +#endif /* !SOC_AES_SUPPORTED */ diff --git a/components/nvs_flash/test_apps_bootloader/main/CMakeLists.txt b/components/nvs_flash/test_apps_bootloader/main/CMakeLists.txt index 978ea7e30f..18179a483d 100644 --- a/components/nvs_flash/test_apps_bootloader/main/CMakeLists.txt +++ b/components/nvs_flash/test_apps_bootloader/main/CMakeLists.txt @@ -1,6 +1,19 @@ -idf_component_register(SRCS "test_app_main.c" "test_nvs_bootloader.c" +set(srcs "test_app_main.c" "test_nvs_bootloader.c") +set(embed_txtfiles "") + +if(CONFIG_NVS_ENCRYPTION OR SOC_HMAC_SUPPORTED) + list(APPEND srcs "test_encrypted_nvs_bootloader.c") + list(APPEND embed_txtfiles "nvs_partition.bin" "partition_encrypted.bin" "partition_encrypted_hmac.bin") +endif() + +if(CONFIG_NVS_SEC_KEY_PROTECT_USING_FLASH_ENC) + list(APPEND embed_txtfiles "encryption_keys.bin") +endif() + +idf_component_register(SRCS "${srcs}" INCLUDE_DIRS "." - REQUIRES unity nvs_flash + REQUIRES unity nvs_flash nvs_sec_provider bootloader_support + EMBED_TXTFILES "${embed_txtfiles}" WHOLE_ARCHIVE ) diff --git a/components/nvs_flash/test_apps_bootloader/main/encryption_keys.bin b/components/nvs_flash/test_apps_bootloader/main/encryption_keys.bin new file mode 100644 index 0000000000..9ef4439d8c --- /dev/null +++ b/components/nvs_flash/test_apps_bootloader/main/encryption_keys.bin @@ -0,0 +1 @@ +"""""""""""""""""""""""""""""""",< \ No newline at end of file diff --git a/components/nvs_flash/test_apps_bootloader/main/nvs_enc_hmac_key.bin b/components/nvs_flash/test_apps_bootloader/main/nvs_enc_hmac_key.bin new file mode 100644 index 0000000000..2ea3dec3e1 --- /dev/null +++ b/components/nvs_flash/test_apps_bootloader/main/nvs_enc_hmac_key.bin @@ -0,0 +1,2 @@ + +  \ No newline at end of file diff --git a/components/nvs_flash/test_apps_bootloader/main/nvs_partition.bin b/components/nvs_flash/test_apps_bootloader/main/nvs_partition.bin new file mode 100644 index 0000000000..b00ed0c962 Binary files /dev/null and b/components/nvs_flash/test_apps_bootloader/main/nvs_partition.bin differ diff --git a/components/nvs_flash/test_apps_bootloader/main/partition_encrypted.bin b/components/nvs_flash/test_apps_bootloader/main/partition_encrypted.bin new file mode 100644 index 0000000000..8d34e7031b Binary files /dev/null and b/components/nvs_flash/test_apps_bootloader/main/partition_encrypted.bin differ diff --git a/components/nvs_flash/test_apps_bootloader/main/partition_encrypted_hmac.bin b/components/nvs_flash/test_apps_bootloader/main/partition_encrypted_hmac.bin new file mode 100644 index 0000000000..b3bd917cf7 Binary files /dev/null and b/components/nvs_flash/test_apps_bootloader/main/partition_encrypted_hmac.bin differ diff --git a/components/nvs_flash/test_apps_bootloader/main/test_encrypted_nvs_bootloader.c b/components/nvs_flash/test_apps_bootloader/main/test_encrypted_nvs_bootloader.c new file mode 100644 index 0000000000..10c3acaa47 --- /dev/null +++ b/components/nvs_flash/test_apps_bootloader/main/test_encrypted_nvs_bootloader.c @@ -0,0 +1,198 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include + +#include "esp_err.h" +#include "esp_flash_encrypt.h" +#include "esp_partition.h" +#include "nvs_sec_provider.h" +#include "unity.h" + +#include "nvs_bootloader.h" + +static esp_err_t configure_nvs_sec_cfg(nvs_sec_cfg_t *cfg, nvs_sec_scheme_t **sec_scheme_handle) +{ + const esp_partition_t* nvs_part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, NULL); + TEST_ASSERT(nvs_part && "partition table must have an NVS partition"); + printf("\n nvs_part size:%" PRId32 "\n", nvs_part->size); + ESP_ERROR_CHECK(esp_partition_erase_range(nvs_part, 0, nvs_part->size)); + +#if CONFIG_NVS_SEC_KEY_PROTECT_USING_FLASH_ENC + if (!esp_flash_encryption_enabled()) { + TEST_IGNORE_MESSAGE("flash encryption disabled, skipping nvs_api tests with encryption enabled"); + } + + extern const char nvs_key_start[] asm("_binary_encryption_keys_bin_start"); + extern const char nvs_key_end[] asm("_binary_encryption_keys_bin_end"); + extern const char nvs_data_sch0_start[] asm("_binary_partition_encrypted_bin_start"); + extern const char nvs_data_sch0_end[] asm("_binary_partition_encrypted_bin_end"); + + const esp_partition_t* key_part = esp_partition_find_first( + ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS_KEYS, NULL); + + assert(key_part && "partition table must have a KEY partition"); + TEST_ASSERT_TRUE((nvs_key_end - nvs_key_start - 1) == key_part->erase_size); + + ESP_ERROR_CHECK(esp_partition_erase_range(key_part, 0, key_part->size)); + + for (int i = 0; i < key_part->size; i+= key_part->erase_size) { + ESP_ERROR_CHECK( esp_partition_write(key_part, i, nvs_key_start + i, key_part->erase_size) ); + } + + const int content_size = nvs_data_sch0_end - nvs_data_sch0_start - 1; + TEST_ASSERT_TRUE((content_size % key_part->erase_size) == 0); + + const int size_to_write = MIN(content_size, nvs_part->size); + for (int i = 0; i < size_to_write; i+= nvs_part->erase_size) { + ESP_ERROR_CHECK( esp_partition_write(nvs_part, i, nvs_data_sch0_start + i, nvs_part->erase_size) ); + } + + nvs_sec_config_flash_enc_t sec_scheme_cfg = { + .nvs_keys_part = key_part + }; + + TEST_ESP_OK(nvs_sec_provider_register_flash_enc(&sec_scheme_cfg, sec_scheme_handle)); + return nvs_flash_read_security_cfg_v2(*sec_scheme_handle, cfg); + +#elif SOC_HMAC_SUPPORTED + extern const char nvs_data_sch1_start[] asm("_binary_partition_encrypted_hmac_bin_start"); + extern const char nvs_data_sch1_end[] asm("_binary_partition_encrypted_hmac_bin_end"); + + const int content_size = nvs_data_sch1_end - nvs_data_sch1_start - 1; + TEST_ASSERT_TRUE((content_size % nvs_part->erase_size) == 0); + + const int size_to_write = MIN(content_size, nvs_part->size); + for (int i = 0; i < size_to_write; i+= nvs_part->erase_size) { + ESP_ERROR_CHECK( esp_partition_write(nvs_part, i, nvs_data_sch1_start + i, nvs_part->erase_size) ); + } + +#ifndef CONFIG_NVS_ENCRYPTION + nvs_sec_config_hmac_t sec_scheme_cfg = { + .hmac_key_id = HMAC_KEY0, + }; +#else + nvs_sec_config_hmac_t sec_scheme_cfg = NVS_SEC_PROVIDER_CFG_HMAC_DEFAULT(); +#endif /* CONFIG_NVS_ENCRYPTION */ + + TEST_ESP_OK(nvs_sec_provider_register_hmac(&sec_scheme_cfg, sec_scheme_handle)); + return nvs_flash_read_security_cfg_v2(*sec_scheme_handle, cfg); +#endif + + return ESP_FAIL; +} + +static void restore_nvs_partition(void) +{ + const esp_partition_t* nvs_part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, NULL); + TEST_ASSERT(nvs_part && "partition table must have an NVS partition"); + printf("\n nvs_part size:%" PRId32 "\n", nvs_part->size); + ESP_ERROR_CHECK(esp_partition_erase_range(nvs_part, 0, nvs_part->size)); + + extern const char nvs_data_start[] asm("_binary_nvs_partition_bin_start"); + extern const char nvs_data_end[] asm("_binary_nvs_partition_bin_end"); + + const int content_size = nvs_data_end - nvs_data_start - 1; + TEST_ASSERT_TRUE((content_size % nvs_part->erase_size) == 0); + + const int size_to_write = MIN(content_size, nvs_part->size); + for (int i = 0; i < size_to_write; i+= nvs_part->erase_size) { + ESP_ERROR_CHECK(esp_partition_write(nvs_part, i, nvs_data_start + i, nvs_part->erase_size)); + } +} + +TEST_CASE("Verify encrypted nvs bootloader read_list result_code and value if bootloader read is successful", "[nvs_encrypted_bootloader]") +{ + nvs_sec_cfg_t xts_cfg; + nvs_sec_scheme_t *sec_scheme_handle = NULL; + TEST_ESP_OK(configure_nvs_sec_cfg(&xts_cfg, &sec_scheme_handle)); + + nvs_bootloader_read_list_t read_list[] = { +// {namespace_name, key_name, value_type, result_code, value, namespace_index}} + { .namespace_name = "storage", .key_name = "u8_key", .value_type = NVS_TYPE_U8 }, //0 OK + { .namespace_name = "storage", .key_name = "u16_key", .value_type = NVS_TYPE_U16 }, //1 OK + { .namespace_name = "storage", .key_name = "u32_key", .value_type = NVS_TYPE_U32 }, //2 OK + { .namespace_name = "storage", .key_name = "i32_key", .value_type = NVS_TYPE_I32 }, //3 OK + { .namespace_name = "storage", .key_name = "i8_key", .value_type = NVS_TYPE_U8 }, //4 Type mismatch + { .namespace_name = "storage", .key_name = "i16_key", .value_type = NVS_TYPE_I16 }, //5 Not found + }; + uint8_t size = sizeof(read_list) / sizeof(read_list[0]); + + TEST_ESP_OK(nvs_bootloader_secure_init(&xts_cfg)); + TEST_ESP_OK(nvs_bootloader_read("nvs", size, read_list)); + nvs_bootloader_secure_deinit(); + + TEST_ASSERT_EQUAL(ESP_OK, read_list[0].result_code); + TEST_ASSERT_EQUAL(ESP_OK, read_list[1].result_code); + TEST_ASSERT_EQUAL(ESP_OK, read_list[2].result_code); + TEST_ASSERT_EQUAL(ESP_OK, read_list[3].result_code); + TEST_ASSERT_EQUAL(ESP_ERR_NVS_TYPE_MISMATCH, read_list[4].result_code); + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, read_list[5].result_code); + + TEST_ASSERT_EQUAL(255, read_list[0].value.u8_val); + TEST_ASSERT_EQUAL(65535, read_list[1].value.u16_val); + TEST_ASSERT_EQUAL(4294967295, read_list[2].value.u32_val); + TEST_ASSERT_EQUAL(-2147483648, read_list[3].value.i32_val); + + TEST_ESP_OK(nvs_sec_provider_deregister(sec_scheme_handle)); + restore_nvs_partition(); +} + +TEST_CASE("Verify encrypted nvs bootloader read_list result_code if bootloader read fails", "[nvs_encrypted_bootloader]") +{ + nvs_sec_cfg_t xts_cfg; + nvs_sec_scheme_t *sec_scheme_handle = NULL; + TEST_ESP_OK(configure_nvs_sec_cfg(&xts_cfg, &sec_scheme_handle)); + + nvs_bootloader_read_list_t read_list[] = { +// {namespace_name, key_name, value_type, result_code, value, namespace_index}} + { .namespace_name = "too_long_namespace", .key_name = "i32_key", .value_type = NVS_TYPE_I32 }, //0 Invalid name + { .namespace_name = "nvs", .key_name = "too_long_key_name", .value_type = NVS_TYPE_I32 }, //1 Key too long + { .namespace_name = "nvs", .key_name = "str_key", .value_type = NVS_TYPE_BLOB }, //2 Invalid arg + { .namespace_name = "nvs", .key_name = "i32_key", .value_type = NVS_TYPE_I32 }, //3 Not found + }; + uint8_t size = sizeof(read_list) / sizeof(read_list[0]); + + TEST_ESP_OK(nvs_bootloader_secure_init(&xts_cfg)); + esp_err_t ret = nvs_bootloader_read("nvs", size, read_list); + + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, ret); + TEST_ASSERT_EQUAL(ESP_ERR_NVS_INVALID_NAME, read_list[0].result_code); + TEST_ASSERT_EQUAL(ESP_ERR_NVS_KEY_TOO_LONG, read_list[1].result_code); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, read_list[2].result_code); + TEST_ASSERT_EQUAL(ESP_ERR_NVS_NOT_FOUND, read_list[3].result_code); + + nvs_bootloader_secure_deinit(); + + TEST_ESP_OK(nvs_sec_provider_deregister(sec_scheme_handle)); + restore_nvs_partition(); +} + +TEST_CASE("Verify nvs_bootloader_read_encrypted failure cases", "[nvs_encrypted_bootloader]") +{ + nvs_sec_cfg_t xts_cfg; + nvs_sec_scheme_t *sec_scheme_handle = NULL; + TEST_ESP_OK(configure_nvs_sec_cfg(&xts_cfg, &sec_scheme_handle)); + + nvs_bootloader_read_list_t read_list[] = { +// {namespace_name, key_name, value_type, result_code, value, namespace_index}} + { "nvs", "i32_key", NVS_TYPE_I32, ESP_OK, {0}, 0} + }; + uint8_t size = sizeof(read_list) / sizeof(read_list[0]); + + TEST_ESP_OK(nvs_bootloader_secure_init(&xts_cfg)); + esp_err_t ret = nvs_bootloader_read("nvs_partition_name_too_long", size, read_list); + TEST_ASSERT_EQUAL(ESP_ERR_NVS_INVALID_NAME, ret); + + ret = nvs_bootloader_read("nvs_part", size, read_list); + TEST_ASSERT_EQUAL(ESP_ERR_NVS_PART_NOT_FOUND, ret); + + nvs_bootloader_secure_deinit(); + TEST_ESP_OK(nvs_sec_provider_deregister(sec_scheme_handle)); + restore_nvs_partition(); +} diff --git a/components/nvs_flash/test_apps_bootloader/pytest_nvs_bootloader_support.py b/components/nvs_flash/test_apps_bootloader/pytest_nvs_bootloader_support.py index b7562bf5a0..b44656e19d 100644 --- a/components/nvs_flash/test_apps_bootloader/pytest_nvs_bootloader_support.py +++ b/components/nvs_flash/test_apps_bootloader/pytest_nvs_bootloader_support.py @@ -1,12 +1,36 @@ -# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD -# SPDX-License-Identifier: CC0-1.0 +# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 import pytest -from pytest_embedded import Dut +from pytest_embedded_idf.dut import IdfDut @pytest.mark.esp32 @pytest.mark.esp32c3 @pytest.mark.generic @pytest.mark.parametrize('config', ['default'], indirect=True) -def test_nvs_bootloader_support(dut: Dut) -> None: +def test_nvs_bootloader_support(dut: IdfDut) -> None: + dut.run_all_single_board_cases(group='!nvs_encrypted_bootloader', timeout=120) + + +@pytest.mark.esp32c3 +@pytest.mark.nvs_encr_hmac +@pytest.mark.parametrize('config', ['nvs_enc_hmac'], indirect=True) +def test_nvs_bootloader_support_encr_hmac(dut: IdfDut) -> None: + dut.run_all_single_board_cases() + + +@pytest.mark.esp32 +@pytest.mark.esp32c3 +@pytest.mark.flash_encryption +@pytest.mark.parametrize('config', ['nvs_enc_flash_enc'], indirect=True) +def test_nvs_bootloader_support_encr_flash_enc(dut: IdfDut) -> None: + # Erase the nvs_key partition + dut.serial.erase_partition('nvs_key') + dut.run_all_single_board_cases() + + +@pytest.mark.esp32c3 +@pytest.mark.nvs_encr_hmac +@pytest.mark.parametrize('config', ['nvs_enc_hmac_no_cfg'], indirect=True) +def test_nvs_bootloader_support_encr_hmac_no_cfg(dut: IdfDut) -> None: dut.run_all_single_board_cases() diff --git a/components/nvs_flash/test_apps_bootloader/sdkconfig.ci.nvs_enc_flash_enc b/components/nvs_flash/test_apps_bootloader/sdkconfig.ci.nvs_enc_flash_enc new file mode 100644 index 0000000000..13c6b00aaf --- /dev/null +++ b/components/nvs_flash/test_apps_bootloader/sdkconfig.ci.nvs_enc_flash_enc @@ -0,0 +1,15 @@ +# Enabling Flash Encryption +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 + +# Enabling NVS Encryption (Flash Encryption-based scheme) +CONFIG_NVS_ENCRYPTION=y +CONFIG_NVS_SEC_KEY_PROTECT_USING_FLASH_ENC=y + +CONFIG_PARTITION_TABLE_SINGLE_APP_ENCRYPTED_NVS=y diff --git a/components/nvs_flash/test_apps_bootloader/sdkconfig.ci.nvs_enc_hmac b/components/nvs_flash/test_apps_bootloader/sdkconfig.ci.nvs_enc_hmac new file mode 100644 index 0000000000..3f0d0fc27c --- /dev/null +++ b/components/nvs_flash/test_apps_bootloader/sdkconfig.ci.nvs_enc_hmac @@ -0,0 +1,15 @@ +# NOTE: The runner for this test-app has flash-encryption enabled +# Enabling Flash Encryption +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 + +# Enabling NVS Encryption (HMAC-based scheme) +CONFIG_NVS_ENCRYPTION=y +CONFIG_NVS_SEC_KEY_PROTECT_USING_HMAC=y +CONFIG_NVS_SEC_HMAC_EFUSE_KEY_ID=0 diff --git a/components/nvs_flash/test_apps_bootloader/sdkconfig.ci.nvs_enc_hmac_no_cfg b/components/nvs_flash/test_apps_bootloader/sdkconfig.ci.nvs_enc_hmac_no_cfg new file mode 100644 index 0000000000..7541167a38 --- /dev/null +++ b/components/nvs_flash/test_apps_bootloader/sdkconfig.ci.nvs_enc_hmac_no_cfg @@ -0,0 +1,12 @@ +# NOTE: The runner for this test-app has flash-encryption enabled +# Enabling Flash Encryption +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_ENCRYPTION=n diff --git a/components/nvs_flash/test_apps_bootloader/sdkconfig.defaults.esp32c2 b/components/nvs_flash/test_apps_bootloader/sdkconfig.defaults.esp32c2 new file mode 100644 index 0000000000..5e3a3c88f4 --- /dev/null +++ b/components/nvs_flash/test_apps_bootloader/sdkconfig.defaults.esp32c2 @@ -0,0 +1,2 @@ +CONFIG_IDF_TARGET="esp32c2" +CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL_BOOTLOADER=y diff --git a/components/nvs_sec_provider/CMakeLists.txt b/components/nvs_sec_provider/CMakeLists.txt index 7fdcc9e81f..9de023e3d3 100644 --- a/components/nvs_sec_provider/CMakeLists.txt +++ b/components/nvs_sec_provider/CMakeLists.txt @@ -4,7 +4,13 @@ if(${target} STREQUAL "linux") return() # This component is not supported by the POSIX/Linux simulator endif() -idf_component_register(SRCS "nvs_sec_provider.c" +if(BOOTLOADER_BUILD) + set(srcs "nvs_bootloader_sec_provider.c") +else() + set(srcs "nvs_sec_provider.c") +endif() + +idf_component_register(SRCS ${srcs} INCLUDE_DIRS include PRIV_REQUIRES bootloader_support efuse esp_partition nvs_flash) diff --git a/components/nvs_sec_provider/Kconfig b/components/nvs_sec_provider/Kconfig index e50839d3a8..4ed79c4e87 100644 --- a/components/nvs_sec_provider/Kconfig +++ b/components/nvs_sec_provider/Kconfig @@ -32,8 +32,8 @@ menu "NVS Security Provider" config NVS_SEC_HMAC_EFUSE_KEY_ID int "eFuse key ID storing the HMAC key" depends on NVS_SEC_KEY_PROTECT_USING_HMAC - range 0 6 - default 6 + range -1 5 + default -1 help eFuse block key ID storing the HMAC key for deriving the NVS encryption keys diff --git a/components/nvs_sec_provider/include/private/nvs_sec_provider_private.h b/components/nvs_sec_provider/include/private/nvs_sec_provider_private.h new file mode 100644 index 0000000000..20d2cfcf08 --- /dev/null +++ b/components/nvs_sec_provider/include/private/nvs_sec_provider_private.h @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define EKEY_SEED 0xAEBE5A5A +#define TKEY_SEED 0xCEDEA5A5 diff --git a/components/nvs_sec_provider/nvs_bootloader_sec_provider.c b/components/nvs_sec_provider/nvs_bootloader_sec_provider.c new file mode 100644 index 0000000000..d8ffbde1b8 --- /dev/null +++ b/components/nvs_sec_provider/nvs_bootloader_sec_provider.c @@ -0,0 +1,212 @@ +/* + * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_err.h" +#include "esp_log.h" +#include "soc/soc_caps.h" +#include "sdkconfig.h" +#include "esp_partition.h" +#include "nvs_flash.h" +#include "nvs_sec_provider.h" +#include "esp_rom_crc.h" +#include "private/nvs_sec_provider_private.h" + +#if SOC_HMAC_SUPPORTED +#include "rom/efuse.h" +#include "rom/hmac.h" +#endif // SOC_HMAC_SUPPORTED + +static __attribute__((unused)) const char *TAG = "nvs_bootloader_sec_provider"; + +static __attribute__((unused)) nvs_sec_scheme_t sec_scheme; + +#if CONFIG_NVS_SEC_KEY_PROTECT_USING_FLASH_ENC + +static nvs_sec_config_flash_enc_t nvs_sec_config_flash_enc_scheme_data; + +static esp_err_t nvs_bootloader_read_security_cfg(const esp_partition_t* partition, nvs_sec_cfg_t* cfg) +{ + if (cfg == NULL || partition == NULL) { + return ESP_ERR_INVALID_ARG; + } + + uint32_t crc_read, crc_calc; + + esp_err_t err = esp_partition_read(partition, 0, cfg->eky, NVS_KEY_SIZE); + if (err != ESP_OK) { + return err; + } + + err = esp_partition_read(partition, NVS_KEY_SIZE, cfg->tky, NVS_KEY_SIZE); + if (err != ESP_OK) { + return err; + } + + err = esp_partition_read(partition, 2 * NVS_KEY_SIZE, &crc_read, 4); + if (err != ESP_OK) { + return err; + } + + crc_calc = esp_rom_crc32_le(0xffffffff, cfg->eky, NVS_KEY_SIZE); + crc_calc = esp_rom_crc32_le(crc_calc, cfg->tky, NVS_KEY_SIZE); + + if (crc_calc != crc_read) { + return ESP_ERR_NVS_CORRUPT_KEY_PART; + } + + return ESP_OK; +} + +static esp_err_t read_security_cfg_flash_enc(const void* sec_scheme_cfg, nvs_sec_cfg_t* cfg) +{ + if (sec_scheme_cfg == NULL || cfg == NULL) { + return ESP_ERR_INVALID_ARG; + } + + nvs_sec_config_flash_enc_t *scheme_cfg_flash_enc = (nvs_sec_config_flash_enc_t *)sec_scheme_cfg; + return nvs_bootloader_read_security_cfg(scheme_cfg_flash_enc->nvs_keys_part, cfg); +} + +esp_err_t nvs_sec_provider_register_flash_enc(const nvs_sec_config_flash_enc_t *sec_scheme_cfg, nvs_sec_scheme_t **sec_scheme_handle_out) +{ + if (sec_scheme_cfg == NULL || sec_scheme_handle_out == NULL) { + return ESP_ERR_INVALID_ARG; + } + + bzero(&sec_scheme, sizeof(nvs_sec_scheme_t)); + + sec_scheme.scheme_id = NVS_SEC_SCHEME_FLASH_ENC; + sec_scheme.nvs_flash_read_cfg = &read_security_cfg_flash_enc; + + bzero(&nvs_sec_config_flash_enc_scheme_data, sizeof(nvs_sec_config_flash_enc_t)); + sec_scheme.scheme_data = &nvs_sec_config_flash_enc_scheme_data; + + memcpy(sec_scheme.scheme_data, (void *)sec_scheme_cfg, sizeof(nvs_sec_config_flash_enc_t)); + + *sec_scheme_handle_out = &sec_scheme; + return ESP_OK; +} +#endif + +#if SOC_HMAC_SUPPORTED + +static nvs_sec_config_hmac_t nvs_sec_config_hmac_scheme_data; + +#if CONFIG_NVS_SEC_HMAC_EFUSE_KEY_ID < 0 +#error "NVS Encryption (HMAC): Configured eFuse block (CONFIG_NVS_SEC_HMAC_EFUSE_KEY_ID) out of range!" +#endif + +static esp_err_t nvs_bootloader_hmac_calculate(ets_efuse_block_t hmac_key, + const void *message, + size_t message_len, + uint8_t *hmac) +{ + ets_hmac_enable(); + + int hmac_ret = ets_hmac_calculate_message(hmac_key, message, message_len, hmac); + + ets_hmac_disable(); + + if (hmac_ret != 0) { + return ESP_FAIL; + } else { + return ESP_OK; + } +} + +static esp_err_t compute_nvs_keys_with_hmac(ets_efuse_block_t hmac_key, nvs_sec_cfg_t* cfg) +{ + uint32_t ekey_seed[8] = {[0 ... 7] = EKEY_SEED}; + uint32_t tkey_seed[8] = {[0 ... 7] = TKEY_SEED}; + + esp_err_t err = nvs_bootloader_hmac_calculate(hmac_key, ekey_seed, sizeof(ekey_seed), (uint8_t *)cfg->eky); + if (err != ESP_OK) { + ESP_LOGD(TAG, "Failed to calculate seed HMAC: [0x%02X]", err); + return err; + } + + err = nvs_bootloader_hmac_calculate(hmac_key, tkey_seed, sizeof(tkey_seed), (uint8_t *)cfg->tky); + if (err != ESP_OK) { + ESP_LOGD(TAG, "Failed to calculate seed HMAC: [0x%02X]", err); + return err; + } + + /* NOTE: If the XTS E-key and T-key are the same, we have a hash collision */ + if (memcmp(cfg->eky, cfg->tky, NVS_KEY_SIZE) == 0) { + ESP_LOGD(TAG, "The XTS-AES E-key and T-key are the same, we have a hash collision"); + return ESP_FAIL; + } + + return ESP_OK; +} + +static bool is_hmac_key_burnt_in_efuse(ets_efuse_block_t hmac_key_blk) +{ + + ets_efuse_purpose_t hmac_efuse_blk_purpose = ets_efuse_get_key_purpose(hmac_key_blk); + + if (hmac_efuse_blk_purpose == ETS_EFUSE_KEY_PURPOSE_HMAC_UP) { + return true; + } + + return false; +} + + +static ets_efuse_block_t convert_key_type(hmac_key_id_t key_id) { + return (ets_efuse_block_t)(ETS_EFUSE_BLOCK_KEY0 + (ets_efuse_block_t) key_id); +} + +static esp_err_t read_security_cfg_hmac(const void* scheme_cfg, nvs_sec_cfg_t* cfg) +{ + if (cfg == NULL) { + return ESP_ERR_INVALID_ARG; + } + + nvs_sec_config_hmac_t *scheme_cfg_hmac = (nvs_sec_config_hmac_t *)scheme_cfg; + + if (scheme_cfg_hmac->hmac_key_id >= HMAC_KEY_MAX) { + ESP_LOGD(TAG, "Invalid HMAC key ID received!"); + return ESP_ERR_INVALID_ARG; + } + + ets_efuse_block_t hmac_key = convert_key_type(scheme_cfg_hmac->hmac_key_id); + + if (!is_hmac_key_burnt_in_efuse(hmac_key)) { + ESP_LOGD(TAG, "Could not find HMAC key in configured eFuse block!"); + return ESP_ERR_NVS_SEC_HMAC_KEY_NOT_FOUND; + } + + if (compute_nvs_keys_with_hmac(hmac_key, cfg) != ESP_OK) { + ESP_LOGD(TAG, "Failed to derive the encryption keys using HMAC!"); + return ESP_ERR_NVS_SEC_HMAC_XTS_KEYS_DERIV_FAILED; + } + + return ESP_OK; +} + +esp_err_t nvs_sec_provider_register_hmac(const nvs_sec_config_hmac_t *sec_scheme_cfg, nvs_sec_scheme_t **sec_scheme_handle_out) +{ + if (sec_scheme_cfg == NULL || sec_scheme_handle_out == NULL) { + return ESP_ERR_INVALID_ARG; + } + + bzero(&sec_scheme, sizeof(nvs_sec_scheme_t)); + + sec_scheme.scheme_id = NVS_SEC_SCHEME_HMAC; + sec_scheme.nvs_flash_read_cfg = &read_security_cfg_hmac; + + bzero(&nvs_sec_config_hmac_scheme_data, sizeof(nvs_sec_config_hmac_t)); + sec_scheme.scheme_data = &nvs_sec_config_hmac_scheme_data; + + memcpy(sec_scheme.scheme_data, (void *)sec_scheme_cfg, sizeof(nvs_sec_config_hmac_t)); + + *sec_scheme_handle_out = &sec_scheme; + return ESP_OK; +} + +#endif // SOC_HMAC_SUPPORTED diff --git a/components/nvs_sec_provider/nvs_sec_provider.c b/components/nvs_sec_provider/nvs_sec_provider.c index e45c84f4db..ab9a3e1533 100644 --- a/components/nvs_sec_provider/nvs_sec_provider.c +++ b/components/nvs_sec_provider/nvs_sec_provider.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -12,6 +12,7 @@ #include "sdkconfig.h" #include "nvs_flash.h" #include "nvs_sec_provider.h" +#include "private/nvs_sec_provider_private.h" #include "esp_private/startup_internal.h" #if SOC_HMAC_SUPPORTED @@ -106,14 +107,14 @@ ESP_SYSTEM_INIT_FN(nvs_sec_provider_register_flash_enc_scheme, SECONDARY, BIT(0) #if SOC_HMAC_SUPPORTED -#if CONFIG_NVS_SEC_HMAC_EFUSE_KEY_ID > 5 +#if CONFIG_NVS_SEC_HMAC_EFUSE_KEY_ID < 0 #error "NVS Encryption (HMAC): Configured eFuse block (CONFIG_NVS_SEC_HMAC_EFUSE_KEY_ID) out of range!" #endif static esp_err_t compute_nvs_keys_with_hmac(hmac_key_id_t hmac_key_id, nvs_sec_cfg_t* cfg) { - uint32_t ekey_seed[8] = {[0 ... 7] = 0xAEBE5A5A}; - uint32_t tkey_seed[8] = {[0 ... 7] = 0xCEDEA5A5}; + uint32_t ekey_seed[8] = {[0 ... 7] = EKEY_SEED}; + uint32_t tkey_seed[8] = {[0 ... 7] = TKEY_SEED}; esp_err_t err = esp_hmac_calculate(hmac_key_id, ekey_seed, sizeof(ekey_seed), (uint8_t *)cfg->eky); if (err != ESP_OK) { diff --git a/docs/en/api-reference/storage/nvs_bootloader.rst b/docs/en/api-reference/storage/nvs_bootloader.rst index fa0049b795..d643350691 100644 --- a/docs/en/api-reference/storage/nvs_bootloader.rst +++ b/docs/en/api-reference/storage/nvs_bootloader.rst @@ -14,6 +14,25 @@ The API supports reading all NVS datatypes except for blobs. One call to the API To read string entries, the API requires the caller to provide a buffer and its size, due to the heap memory allocation restriction in the bootloader. +Reading encrypted NVS partitions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The API also supports decrypting NVS data, if the NVS partition is encrypted using one of the schemes as mentioned in the :doc:`nvs_encryption` guide. + +Applications are expected to follow the steps below in order to enable decryption of a NVS partition using the NVS bootloader read API: + + 1. Populate the NVS security configuration structure :cpp:type:`nvs_sec_cfg_t` according to the selected NVS encryption scheme (Please refer to :doc:`nvs_encryption` for more details). + 2. Read NVS security configuration set by the specified security scheme using the :cpp:func:`nvs_bootloader_read_security_cfg` API. + 3. Initialise the NVS flash partition with the above read security configuration using the :cpp:func:`nvs_bootloader_secure_init` API. + 4. Perform NVS read operations using the :cpp:func:`nvs_bootloader_read` API. + 5. Deinitialise and clear the security configuration of the NVS flash partition using the :cpp:func:`nvs_bootloader_secure_deinit` API. + +.. only:: SOC_HMAC_SUPPORTED + + .. note:: + While using the HMAC-based scheme, the above workflow can be used without enabling any of the config options for NVS encryption - :ref:`CONFIG_NVS_ENCRYPTION`, :ref:`CONFIG_NVS_SEC_KEY_PROTECTION_SCHEME` -> ``CONFIG_NVS_SEC_KEY_PROTECT_USING_HMAC`` and :ref:`CONFIG_NVS_SEC_HMAC_EFUSE_KEY_ID` to encrypt the default as well as custom NVS partitions with :cpp:func:`nvs_flash_secure_init` API. + + Application Example ------------------- diff --git a/docs/en/api-reference/storage/nvs_encryption.rst b/docs/en/api-reference/storage/nvs_encryption.rst index 826bd5e8cf..cbae3878dd 100644 --- a/docs/en/api-reference/storage/nvs_encryption.rst +++ b/docs/en/api-reference/storage/nvs_encryption.rst @@ -121,7 +121,7 @@ It is possible for an application to use different keys for different NVS partit .. note:: - The valid range for the config :ref:`CONFIG_NVS_SEC_HMAC_EFUSE_KEY_ID` is from ``0`` (:cpp:enumerator:`hmac_key_id_t::HMAC_KEY0`) to ``5`` (:cpp:enumerator:`hmac_key_id_t::HMAC_KEY5`). By default, the config is set to ``6`` (:cpp:enumerator:`hmac_key_id_t::HMAC_KEY_MAX`), which have to be configured before building the user application. + The valid range for the config :ref:`CONFIG_NVS_SEC_HMAC_EFUSE_KEY_ID` is from ``0`` (:cpp:enumerator:`hmac_key_id_t::HMAC_KEY0`) to ``5`` (:cpp:enumerator:`hmac_key_id_t::HMAC_KEY5`). By default, the config is set to ``-1``, which have to be configured before building the user application. - If no key is found, a key is generated internally and stored at the eFuse block specified at :ref:`CONFIG_NVS_SEC_HMAC_EFUSE_KEY_ID`. - If a key is found with the purpose :cpp:enumerator:`esp_efuse_purpose_t::ESP_EFUSE_KEY_PURPOSE_HMAC_UP`, the same is used for the derivation of the XTS encryption keys. diff --git a/docs/zh_CN/api-reference/storage/nvs_encryption.rst b/docs/zh_CN/api-reference/storage/nvs_encryption.rst index 13c3bb59d3..49dd20d283 100644 --- a/docs/zh_CN/api-reference/storage/nvs_encryption.rst +++ b/docs/zh_CN/api-reference/storage/nvs_encryption.rst @@ -121,7 +121,7 @@ NVS 密钥分区 .. note:: - :ref:`CONFIG_NVS_SEC_HMAC_EFUSE_KEY_ID` 配置的有效范围为 ``0`` (:cpp:enumerator:`hmac_key_id_t::HMAC_KEY0`) 到 ``5`` (:cpp:enumerator:`hmac_key_id_t::HMAC_KEY5`)。默认情况下该配置为 ``6`` (:cpp:enumerator:`hmac_key_id_t::HMAC_KEY_MAX`),须在构建用户应用程序之前进行修改。 + :ref:`CONFIG_NVS_SEC_HMAC_EFUSE_KEY_ID` 配置的有效范围为 ``0`` (:cpp:enumerator:`hmac_key_id_t::HMAC_KEY0`) 到 ``5`` (:cpp:enumerator:`hmac_key_id_t::HMAC_KEY5`)。默认情况下该配置为 ``-1``,须在构建用户应用程序之前进行修改。 - 如果找不到密钥,会内部生成一个密钥,并储存在 :ref:`CONFIG_NVS_SEC_HMAC_EFUSE_KEY_ID` 指定的 eFuse 块中。 - 如果找到用于 :cpp:enumerator:`esp_efuse_purpose_t::ESP_EFUSE_KEY_PURPOSE_HMAC_UP` 的密钥,该密钥也会用于 XTS 加密密钥的生成。 diff --git a/examples/storage/.build-test-rules.yml b/examples/storage/.build-test-rules.yml index dfbff5c073..9fe58ad263 100644 --- a/examples/storage/.build-test-rules.yml +++ b/examples/storage/.build-test-rules.yml @@ -16,6 +16,15 @@ examples/storage/emmc: - if: IDF_TARGET == "esp32s3" reason: only support on esp32s3 +examples/storage/nvs_bootloader: + depends_components: + - nvs_flash + - nvs_sec_provider + disable: + - if: CONFIG_NAME == "nvs_enc_flash_enc" and (SOC_AES_SUPPORTED != 1 and ESP_ROM_HAS_MBEDTLS_CRYPTO_LIB != 1) + - if: CONFIG_NAME == "nvs_enc_hmac" and (SOC_HMAC_SUPPORTED != 1 or (SOC_HMAC_SUPPORTED == 1 and (SOC_AES_SUPPORTED != 1 and ESP_ROM_HAS_MBEDTLS_CRYPTO_LIB != 1))) + reason: As of now in such cases, we do not have any way to perform AES operations in the bootloader build + examples/storage/nvs_rw_blob: depends_components: - nvs_flash diff --git a/examples/storage/nvs_bootloader/README.md b/examples/storage/nvs_bootloader/README.md index a5247f64a3..e0b22376e5 100644 --- a/examples/storage/nvs_bootloader/README.md +++ b/examples/storage/nvs_bootloader/README.md @@ -3,7 +3,9 @@ # NVS Bootloader -The purpose of this example is to show how to use the simplified, read-only API of NVS flash that can be used as a part of bootloader +The purpose of this example is to show how to use the simplified, read-only API of NVS flash that can be used as a part of bootloader. + +A very practical application of being able to access the NVS in the bootloader build would be faster device restoration, where-in the application stores the device's current state/configurations and post a reset it would read the NVS to restore the device's last state, without waiting for application to boot-up. ## Usage of this example: @@ -123,3 +125,116 @@ Below is a short explanation of files in the project folder. The example creates request/response array `read_list[]`, populates it with identifiers of the data to be read. Function `nvs_bootloader_read()` tries to find respective data in the partition (here `"nvs"`) and if the data is found, it populates the request/response array with data. For nvs entries either not found or not matching are indicated in response array as well. Function `log_nvs_bootloader_read_list()`is used before and after reading from nvs to show request/response data to the console. + +# Encrypted NVS Bootloader + +This example is extended to support reading encrypted NVS partition when NVS encryption is enabled. + +## Usage of this example: + +Enable NVS encryption using your preferred scheme. Please find more details regarding the `flash encryption based NVS encryption scheme` and the `HMAC based NVS encryption scheme` in the [NVS encryption documentation](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/storage/nvs_encryption.html). + +(Note: In case you select the `HMAC based NVS encryption scheme`, make sure that you burn the below mentioned [HMAC key](./main/nvs_enc_hmac_key.bin) in the efuses.) + +For generating the encrypted NVS partitions, we shall use [NVS partition generator](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/storage/nvs_partition_gen.html#nvs-partition-generator-utility). +We shall use the [nvs_partition_gen.py](../../../components/nvs_flash/nvs_partition_generator/nvs_partition_gen.py) script for the operations. + +Along with the above mentioned file structure, the project folder also contains pre-generated encrypted partitions and the partition corresponding to the selected NVS encryption scheme is flashed along with the build artefacts using the `main/CMakeLists.txt`. + +In case the data in `nvs_data.csv` is modified, these encrypted NVS partitions can be re-generated using the following commands: + +1. NVS Encryption using the flash encryption scheme + +``` +python nvs_partition_gen.py encrypt $IDF_PATH/examples/storage/nvs_bootloader/nvs_data.csv $IDF_PATH/examples/storage/nvs_bootloader/main/nvs_encrypted.bin 0x6000 --inputkey $IDF_PATH/examples/storage/nvs_bootloader/main/encryption_keys.bin +``` + +2. NVS Encryption using the HMAC scheme + +``` +python nvs_partition_gen.py encrypt $IDF_PATH/examples/storage/nvs_bootloader/nvs_data.csv $IDF_PATH/examples/storage/nvs_bootloader/main/nvs_encrypted_hmac.bin 0x6000 --keygen --key_protect_hmac --kp_hmac_inputkey $IDF_PATH/examples/storage/nvs_bootloader/main/nvs_enc_hmac_key.bin +``` + +Build the application using configurations corresponding to the NVS encryption scheme that you have selected: + +``` +idf.py set-target + +# For NVS encryption using flash encryption scheme +cat sdkconfig.ci.nvs_enc_flash_enc >> sdkconfig + +OR + +# For NVS encryption using the HMAC scheme +cat sdkconfig.ci.nvs_enc_hmac >> sdkconfig + +idf.py build +``` + +Then flash it and open the monitor with the following command: +``` +idf.py flash monitor +``` + +If everything went well, the console output should contain the same three blocks of log messages that are mentioned above. + +### Running the example using QEMU + +You could quickly try out this example using QEMU. Refer this [link](https://github.com/espressif/esp-toolchain-docs/blob/main/qemu/README.md#choose-your-target) to know which targets are currently supported in QEMU. + +#### Using the NVS encryption's flash encryption scheme + +1. Configure the application with the corresponding configurations + +``` +idf.py set-target + +cat sdkconfig.ci.nvs_enc_flash_enc >> sdkconfig + +# Disable the below config as it was enabled as a CI related configuration, thus enabling flash encryption during boot-up in QEMU +echo "CONFIG_SECURE_FLASH_REQUIRE_ALREADY_ENABLED=n" >> sdkconfig + +``` + +2. Building the app + +``` +idf.py build +``` + +3. Running the app + +``` +idf.py qemu monitor +``` + +#### Using the NVS encryption's HMAC scheme + +1. Build the application using the corresponding configurations + +``` +idf.py set-target + +cat sdkconfig.ci.nvs_enc_hmac >> sdkconfig + +# Disable the below config as it was enabled as a CI related configuration, thus enabling flash encryption during boot-up in QEMU +echo "CONFIG_SECURE_FLASH_REQUIRE_ALREADY_ENABLED=n" >> sdkconfig +``` + +2. Building the app + +``` +idf.py build +``` + +3. Burn the related HMAC key in the efuses + +``` +idf.py qemu efuse-burn-key BLOCK_KEY0 main/nvs_enc_hmac_key.bin HMAC_UP +``` + +4. Running the app + +``` +idf.py qemu monitor +``` diff --git a/examples/storage/nvs_bootloader/bootloader_components/nvs_bootloader_example/CMakeLists.txt b/examples/storage/nvs_bootloader/bootloader_components/nvs_bootloader_example/CMakeLists.txt index 91038a9e13..cbe40eafec 100644 --- a/examples/storage/nvs_bootloader/bootloader_components/nvs_bootloader_example/CMakeLists.txt +++ b/examples/storage/nvs_bootloader/bootloader_components/nvs_bootloader_example/CMakeLists.txt @@ -1,6 +1,6 @@ -idf_component_register( SRCS "src/nvs_bootloader_example.c" "src/nvs_bootloader_example_utils.c" +idf_component_register(SRCS "src/nvs_bootloader_example.c" "src/nvs_bootloader_example_utils.c" INCLUDE_DIRS "include" - REQUIRES "nvs_flash" ) + REQUIRES "nvs_flash" "nvs_sec_provider") # We need to force GCC to integrate this static library into the # bootloader link. Indeed, by default, as the hooks in the bootloader are weak, diff --git a/examples/storage/nvs_bootloader/bootloader_components/nvs_bootloader_example/src/nvs_bootloader_example.c b/examples/storage/nvs_bootloader/bootloader_components/nvs_bootloader_example/src/nvs_bootloader_example.c index cf83507aaf..4b1348390c 100644 --- a/examples/storage/nvs_bootloader/bootloader_components/nvs_bootloader_example/src/nvs_bootloader_example.c +++ b/examples/storage/nvs_bootloader/bootloader_components/nvs_bootloader_example/src/nvs_bootloader_example.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -8,6 +8,8 @@ #include "nvs_bootloader.h" #include "nvs_bootloader_example_utils.h" +#include "nvs_sec_provider.h" + static const char* TAG = "nvs_bootloader_example"; // Function used to tell the linker to include this file @@ -21,14 +23,11 @@ void bootloader_before_init(void) { } -void log_request_call_read_evaluate_output(const char* nvs_partition_label, nvs_bootloader_read_list_t read_list[], const size_t read_list_count) { - // log the request structure before the read to see the requested keys and namespaces - // with the ESP_ERR_NOT_FINISHED return code we are just telling the log function to show the request data and omit printing the result data - // it is useful for debugging the request structure - log_nvs_bootloader_read_list(ESP_ERR_NOT_FINISHED, read_list, read_list_count); - +void log_request_call_read_evaluate_output(const char* nvs_partition_label, nvs_bootloader_read_list_t read_list[], const size_t read_list_count, const nvs_sec_cfg_t* sec_cfg) +{ + esp_err_t ret = ESP_FAIL; // call the read function - esp_err_t ret = nvs_bootloader_read(nvs_partition_label, read_list_count, read_list); + ret = nvs_bootloader_read(nvs_partition_label, read_list_count, read_list); // Error code ESP_OK means that the read function was successful and individual, per record results are stored in the read_list if (ret == ESP_OK) { @@ -62,10 +61,48 @@ void bootloader_after_init(void) { // we are going to read from the default nvs partition labelled 'nvs' const char* nvs_partition_label = "nvs"; - #define STR_BUFF_LEN 10+1 // 10 characters + null terminator - char str_buff[STR_BUFF_LEN]; + #define STR_BUFF_LEN_10 10+1 // 10 characters + null terminator + #define STR_BUFF_LEN_66 66+1 // 66 characters + null terminator + char str_buff_10[STR_BUFF_LEN_10]; + char str_buff_66[STR_BUFF_LEN_66]; + + nvs_sec_cfg_t* sec_cfg = NULL; + +#if CONFIG_NVS_ENCRYPTION + nvs_sec_cfg_t cfg = {}; + nvs_sec_scheme_t *sec_scheme_handle = NULL; +#if CONFIG_NVS_SEC_KEY_PROTECT_USING_HMAC + nvs_sec_config_hmac_t sec_scheme_cfg = NVS_SEC_PROVIDER_CFG_HMAC_DEFAULT(); + if (nvs_sec_provider_register_hmac(&sec_scheme_cfg, &sec_scheme_handle) != ESP_OK) { + ESP_EARLY_LOGE(TAG, "Registering the HMAC scheme failed"); + return; + } +#elif CONFIG_NVS_SEC_KEY_PROTECT_USING_FLASH_ENC + nvs_sec_config_flash_enc_t sec_scheme_cfg = NVS_SEC_PROVIDER_CFG_FLASH_ENC_DEFAULT(); + if (sec_scheme_cfg.nvs_keys_part == NULL) { + ESP_EARLY_LOGE(TAG, "partition with subtype \"nvs_keys\" not found"); + } + + if (nvs_sec_provider_register_flash_enc(&sec_scheme_cfg, &sec_scheme_handle) != ESP_OK) { + ESP_EARLY_LOGE(TAG, "Registering the Flash Encryption scheme failed"); + return; + } +#endif /* CONFIG_NVS_SEC_KEY_PROTECT_USING_HMAC */ + + if (nvs_bootloader_read_security_cfg(sec_scheme_handle, &cfg) != ESP_OK) { + ESP_EARLY_LOGE(TAG, "Reading the NVS security configuration failed"); + return; + } + + if (nvs_bootloader_secure_init(&cfg) != ESP_OK) { + ESP_LOGE(TAG, "Secure initialization of NVS failed"); + return; + } +#endif /* CONFIG_NVS_ENCRYPTION*/ // --- This is the request structure for the read function showing validation errors - function will return ESP_ERR_INVALID_ARG --- + ESP_EARLY_LOGI(TAG, "Trying to read the NVS partition by passing invalid arguments"); + nvs_bootloader_read_list_t bad_read_list_indicate_problems[] = { { .namespace_name = "sunny_day", .key_name = "u8", .value_type = NVS_TYPE_U8 }, // ESP_ERR_NVS_NOT_FOUND // this is correct request, not found is expected default result code @@ -75,19 +112,21 @@ void bootloader_after_init(void) { // too long key name { .namespace_name = "clowny_day", .key_name = "blobeee", .value_type = NVS_TYPE_BLOB }, // ESP_ERR_INVALID_ARG // not supported data type - { .namespace_name = "sunny_day", .key_name = "string_10_chars", .value_type = NVS_TYPE_STR, .value.str_val = { .buff_ptr = str_buff, .buff_len = 0 } }, + { .namespace_name = "sunny_day", .key_name = "string_10_chars", .value_type = NVS_TYPE_STR, .value.str_val = { .buff_ptr = str_buff_10, .buff_len = 0 } }, // ESP_ERR_INVALID_SIZE // buffer size is 0 - { .namespace_name = "sunny_day", .key_name = "string_10_chars", .value_type = NVS_TYPE_STR, .value.str_val = { .buff_ptr = NULL, .buff_len = 10 } } + { .namespace_name = "sunny_day", .key_name = "string_66_chars", .value_type = NVS_TYPE_STR, .value.str_val = { .buff_ptr = NULL, .buff_len = 66 } } // ESP_ERR_INVALID_SIZE // buffer pointer is invalid }; size_t bad_read_list_indicate_problems_count = sizeof(bad_read_list_indicate_problems) / sizeof(bad_read_list_indicate_problems[0]); - log_request_call_read_evaluate_output(nvs_partition_label, bad_read_list_indicate_problems, bad_read_list_indicate_problems_count); + log_request_call_read_evaluate_output(nvs_partition_label, bad_read_list_indicate_problems, bad_read_list_indicate_problems_count, sec_cfg); // --- This is the request structure for the read function showing runtime errors - function will return ESP_OK --- // but some records will have result_code set to ESP_ERR_NVS_NOT_FOUND, ESP_ERR_NVS_TYPE_MISMATCH, ESP_ERR_INVALID_SIZE + ESP_EARLY_LOGI(TAG, "Trying to read the NVS partition by expecting incorrect data"); + nvs_bootloader_read_list_t good_read_list_bad_results[] = { { .namespace_name = "sunny_day", .key_name = "u8", .value_type = NVS_TYPE_I8 }, // ESP_ERR_NVS_TYPE_MISMATCH // data in the partition is of different type (NVS_TYPE_U8) @@ -95,17 +134,20 @@ void bootloader_after_init(void) { // data in the partition won't be found, because there is a typo in the key name { .namespace_name = "clowny_day", .key_name = "i8", .value_type = NVS_TYPE_I8 }, // ESP_ERR_NVS_NOT_FOUND // data in the partition won't be found, because there is typo in namespace name - { .namespace_name = "sunny_day", .key_name = "string_10_chars", .value_type = NVS_TYPE_STR, .value.str_val = { .buff_ptr = str_buff, .buff_len = 2 } }, + { .namespace_name = "sunny_day", .key_name = "string_10_chars", .value_type = NVS_TYPE_STR, .value.str_val = { .buff_ptr = str_buff_10, .buff_len = 2 } }, // ESP_ERR_INVALID_SIZE // buffer is too small { .namespace_name = "sunny_day", .key_name = "u32", .value_type = NVS_TYPE_U32 }, // ESP_OK // this value will be read correctly - { .namespace_name = "sunny_day", .key_name = "u32", .value_type = NVS_TYPE_U32 } // ESP_ERR_NVS_NOT_FOUND + { .namespace_name = "sunny_day", .key_name = "u32", .value_type = NVS_TYPE_U32 }, // ESP_ERR_NVS_NOT_FOUND // this value won't be read as function doesn't support duplicate readings + { .namespace_name = "sunny_day", .key_name = "string_66_chars", .value_type = NVS_TYPE_STR, .value.str_val = { .buff_ptr = str_buff_66, .buff_len = 65 } } + // ESP_ERR_INVALID_SIZE + // buffer is just small }; size_t good_read_list_bad_results_count = sizeof(good_read_list_bad_results) / sizeof(good_read_list_bad_results[0]); - log_request_call_read_evaluate_output(nvs_partition_label, good_read_list_bad_results, good_read_list_bad_results_count); + log_request_call_read_evaluate_output(nvs_partition_label, good_read_list_bad_results, good_read_list_bad_results_count, sec_cfg); // --- This is the request structure for the read function showing all records found--- @@ -116,16 +158,23 @@ void bootloader_after_init(void) { // For NVS_TYPE_STR the value field is a structure with a pointer to the buffer and the buffer length is povided // In this case, the buffer is a stack allocated array of 10 characters plus space for the null terminator + ESP_EARLY_LOGI(TAG, "Trying to read the NVS partition correctly"); + nvs_bootloader_read_list_t good_read_list[] = { { .namespace_name = "sunny_day", .key_name = "u8", .value_type = NVS_TYPE_U8 }, { .namespace_name = "sunny_day", .key_name = "i32", .value_type = NVS_TYPE_I32 }, { .namespace_name = "cloudy_day", .key_name = "i8", .value_type = NVS_TYPE_I8 }, // mixed in different namespace { .namespace_name = "sunny_day", .key_name = "u16", .value_type = NVS_TYPE_U16 }, - { .namespace_name = "sunny_day", .key_name = "string_10_chars", .value_type = NVS_TYPE_STR, .value.str_val = { .buff_ptr = str_buff, .buff_len = STR_BUFF_LEN } } + { .namespace_name = "sunny_day", .key_name = "string_10_chars", .value_type = NVS_TYPE_STR, .value.str_val = { .buff_ptr = str_buff_10, .buff_len = STR_BUFF_LEN_10 } }, + { .namespace_name = "sunny_day", .key_name = "string_66_chars", .value_type = NVS_TYPE_STR, .value.str_val = { .buff_ptr = str_buff_66, .buff_len = STR_BUFF_LEN_66 } } }; size_t good_read_list_count = sizeof(good_read_list) / sizeof(good_read_list[0]); - log_request_call_read_evaluate_output(nvs_partition_label, good_read_list, good_read_list_count); + log_request_call_read_evaluate_output(nvs_partition_label, good_read_list, good_read_list_count, sec_cfg); + +#if CONFIG_NVS_ENCRYPTION + nvs_bootloader_secure_deinit(); +#endif /* CONFIG_NVS_ENCRYPTION */ ESP_LOGI(TAG, "Finished bootloader part"); } diff --git a/examples/storage/nvs_bootloader/main/CMakeLists.txt b/examples/storage/nvs_bootloader/main/CMakeLists.txt index d8d0fb7a76..47e59caa22 100644 --- a/examples/storage/nvs_bootloader/main/CMakeLists.txt +++ b/examples/storage/nvs_bootloader/main/CMakeLists.txt @@ -1,3 +1,13 @@ idf_component_register(SRCS "bootloader_hooks_example_main.c" INCLUDE_DIRS ".") -nvs_create_partition_image(nvs ../nvs_data.csv FLASH_IN_PROJECT) + +if(NOT CONFIG_NVS_ENCRYPTION) + nvs_create_partition_image(nvs ../nvs_data.csv FLASH_IN_PROJECT) +else() + if(CONFIG_NVS_SEC_KEY_PROTECT_USING_FLASH_ENC) + esptool_py_flash_to_partition(flash "nvs_key" ${PROJECT_DIR}/main/encryption_keys.bin) + esptool_py_flash_to_partition(flash "nvs" ${PROJECT_DIR}/main/nvs_encrypted.bin) + else() # NVS Encryption using HMAC (CONFIG_NVS_SEC_KEY_PROTECT_USING_HMAC) + esptool_py_flash_to_partition(flash "nvs" ${PROJECT_DIR}/main/nvs_encrypted_hmac.bin) + endif() +endif() diff --git a/examples/storage/nvs_bootloader/main/encryption_keys.bin b/examples/storage/nvs_bootloader/main/encryption_keys.bin new file mode 100644 index 0000000000..9ef4439d8c --- /dev/null +++ b/examples/storage/nvs_bootloader/main/encryption_keys.bin @@ -0,0 +1 @@ +"""""""""""""""""""""""""""""""",< \ No newline at end of file diff --git a/examples/storage/nvs_bootloader/main/nvs_enc_hmac_key.bin b/examples/storage/nvs_bootloader/main/nvs_enc_hmac_key.bin new file mode 100644 index 0000000000..2ea3dec3e1 --- /dev/null +++ b/examples/storage/nvs_bootloader/main/nvs_enc_hmac_key.bin @@ -0,0 +1,2 @@ + +  \ No newline at end of file diff --git a/examples/storage/nvs_bootloader/main/nvs_encrypted.bin b/examples/storage/nvs_bootloader/main/nvs_encrypted.bin new file mode 100644 index 0000000000..890d720300 Binary files /dev/null and b/examples/storage/nvs_bootloader/main/nvs_encrypted.bin differ diff --git a/examples/storage/nvs_bootloader/main/nvs_encrypted_hmac.bin b/examples/storage/nvs_bootloader/main/nvs_encrypted_hmac.bin new file mode 100644 index 0000000000..e413d5108e Binary files /dev/null and b/examples/storage/nvs_bootloader/main/nvs_encrypted_hmac.bin differ diff --git a/examples/storage/nvs_bootloader/nvs_data.csv b/examples/storage/nvs_bootloader/nvs_data.csv index 5079deb33a..dbf277668b 100644 --- a/examples/storage/nvs_bootloader/nvs_data.csv +++ b/examples/storage/nvs_bootloader/nvs_data.csv @@ -10,6 +10,8 @@ i16,data,i16,-20000 u32,data,u32,4294967295 i32,data,i32,-2147483648 string_10_chars,data,string,"Text_67890" +# adding the below entry to read non-multiple lengths of XTS-AES block size +string_66_chars,data,string,"Text_67890_Text_67890_Text_67890_0_Text_67890_Text_67890_Text_6789" # defines cloudy_day namespace to show it can be read at once with sunny_day one cloudy_day,namespace,, i8,data,i8,-13 diff --git a/examples/storage/nvs_bootloader/pytest_nvs_bootloader.py b/examples/storage/nvs_bootloader/pytest_nvs_bootloader.py index 66e2a50286..562693141c 100644 --- a/examples/storage/nvs_bootloader/pytest_nvs_bootloader.py +++ b/examples/storage/nvs_bootloader/pytest_nvs_bootloader.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 import pytest from pytest_embedded import Dut @@ -9,5 +9,27 @@ from pytest_embedded import Dut def test_nvs_bootloader_example(dut: Dut) -> None: # Expect to read hooks messages and data from NVS partition dut.expect_exact('Before reading from NVS partition') + dut.expect_exact('Result data. Return code: ESP_OK') + dut.expect_exact('u8 U8 255') + dut.expect_exact('i32 I32 -2147483648') + dut.expect_exact('i8 I8 -13') + dut.expect_exact('u16 U16 65535') + dut.expect_exact('string_10_chars STR Text_67890') + dut.expect_exact('string_66_chars STR Text_67890_Text_67890_Text_67890_0_Text_67890_Text_67890_Text_6789') dut.expect_exact('Finished bootloader part') dut.expect_exact('User application is loaded and running.') + + +@pytest.mark.esp32c3 +@pytest.mark.nvs_encr_hmac +@pytest.mark.parametrize('config', ['nvs_enc_hmac'], indirect=True) +def test_nvs_bootloader_example_nvs_encr_hmac(dut: Dut) -> None: + test_nvs_bootloader_example(dut) + + +@pytest.mark.esp32 +@pytest.mark.esp32c3 +@pytest.mark.flash_encryption +@pytest.mark.parametrize('config', ['nvs_enc_flash_enc'], indirect=True) +def test_nvs_bootloader_example_flash_enc(dut: Dut) -> None: + test_nvs_bootloader_example(dut) diff --git a/examples/storage/nvs_bootloader/sdkconfig.ci.default b/examples/storage/nvs_bootloader/sdkconfig.ci.default new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/storage/nvs_bootloader/sdkconfig.ci.nvs_enc_flash_enc b/examples/storage/nvs_bootloader/sdkconfig.ci.nvs_enc_flash_enc new file mode 100644 index 0000000000..34dc45f65a --- /dev/null +++ b/examples/storage/nvs_bootloader/sdkconfig.ci.nvs_enc_flash_enc @@ -0,0 +1,13 @@ +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_ENCRYPTION=y +CONFIG_NVS_SEC_KEY_PROTECT_USING_FLASH_ENC=y + +CONFIG_PARTITION_TABLE_SINGLE_APP_ENCRYPTED_NVS=y diff --git a/examples/storage/nvs_bootloader/sdkconfig.ci.nvs_enc_hmac b/examples/storage/nvs_bootloader/sdkconfig.ci.nvs_enc_hmac new file mode 100644 index 0000000000..b0b02f4a85 --- /dev/null +++ b/examples/storage/nvs_bootloader/sdkconfig.ci.nvs_enc_hmac @@ -0,0 +1,12 @@ +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_ENCRYPTION=y +CONFIG_NVS_SEC_KEY_PROTECT_USING_HMAC=y +CONFIG_NVS_SEC_HMAC_EFUSE_KEY_ID=0 diff --git a/examples/storage/nvs_bootloader/sdkconfig.defaults b/examples/storage/nvs_bootloader/sdkconfig.defaults index bb48f6522e..5b4203d232 100644 --- a/examples/storage/nvs_bootloader/sdkconfig.defaults +++ b/examples/storage/nvs_bootloader/sdkconfig.defaults @@ -1,4 +1,4 @@ # The size of bootloader has been increased, so the partition table offset needs adjustment -CONFIG_PARTITION_TABLE_OFFSET=0xA000 +CONFIG_PARTITION_TABLE_OFFSET=0xB000 CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y CONFIG_BOOTLOADER_LOG_LEVEL=2 diff --git a/examples/storage/nvs_bootloader/sdkconfig.defaults.esp32c2 b/examples/storage/nvs_bootloader/sdkconfig.defaults.esp32c2 new file mode 100644 index 0000000000..5e3a3c88f4 --- /dev/null +++ b/examples/storage/nvs_bootloader/sdkconfig.defaults.esp32c2 @@ -0,0 +1,2 @@ +CONFIG_IDF_TARGET="esp32c2" +CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL_BOOTLOADER=y