feat(nvs_flash): Support reading encrypted NVS partitions in the bootloader

This commit is contained in:
harshal.patil 2024-12-30 19:01:22 +05:30
parent 37de702e97
commit cadd6b8bcc
No known key found for this signature in database
GPG Key ID: 67334E837530B75C
9 changed files with 499 additions and 118 deletions

View File

@ -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}"

View File

@ -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 <stdint.h>
#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

View File

@ -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" {

View File

@ -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 <esp_rom_crc.h>
#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);
}

View File

@ -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)

View File

@ -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

View File

@ -0,0 +1,212 @@
/*
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#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

View File

@ -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) {

View File

@ -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
-------------------