mirror of
https://github.com/espressif/esp-idf
synced 2025-03-09 09:09:10 -04:00
feat(nvs_flash): Support reading encrypted NVS partitions in the bootloader
This commit is contained in:
parent
37de702e97
commit
cadd6b8bcc
@ -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}"
|
||||
|
@ -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
|
||||
|
@ -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" {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
212
components/nvs_sec_provider/nvs_bootloader_sec_provider.c
Normal file
212
components/nvs_sec_provider/nvs_bootloader_sec_provider.c
Normal 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
|
@ -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) {
|
||||
|
@ -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
|
||||
-------------------
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user