diff --git a/components/nvs_flash/CMakeLists.txt b/components/nvs_flash/CMakeLists.txt index 281ff63d8d..eb8057afe2 100644 --- a/components/nvs_flash/CMakeLists.txt +++ b/components/nvs_flash/CMakeLists.txt @@ -1,11 +1,9 @@ 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") - if(CONFIG_NVS_ENCRYPTION) - list(APPEND srcs "src/nvs_bootloader_aes.c" - "src/nvs_bootloader_xts_aes.c") - endif() set(requires "esp_partition") idf_component_register(SRCS "${srcs}" 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_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/src/nvs_bootloader.c b/components/nvs_flash/src/nvs_bootloader.c index d6eca44a25..9347cfd3ba 100644 --- a/components/nvs_flash/src/nvs_bootloader.c +++ b/components/nvs_flash/src/nvs_bootloader.c @@ -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; } @@ -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,44 +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; - if (data_offset & 3 || block_len & 3 || (intptr_t) block & 3) { - /* 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 satisfies the above conditions - WORD_ALIGNED_ATTR uint8_t bootloader_flash_read_buffer[BOOTLOADER_FLASH_READ_LEN] = { 0 }; - - 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; - - /* 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, data_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, data_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, data_offset, block, block_len); - } - return ret; + return nvs_bootloader_partition_read_string_value(partition, data_offset, block, block_len); } // validates item's header @@ -637,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_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/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..052abda446 --- /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 > 5 +#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..c7f592dd43 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 @@ -112,8 +113,8 @@ ESP_SYSTEM_INIT_FN(nvs_sec_provider_register_flash_enc_scheme, SECONDARY, BIT(0) 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 -------------------