Skip to content

Commit 3b7d71e

Browse files
committed
feat(examples): Extend the bootloader_nvs example to support encrypted NVS reads
1 parent cadd6b8 commit 3b7d71e

16 files changed

+261
-24
lines changed

examples/storage/.build-test-rules.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@ examples/storage/emmc:
1616
- if: IDF_TARGET == "esp32s3"
1717
reason: only support on esp32s3
1818

19+
examples/storage/nvs_bootloader:
20+
depends_components:
21+
- nvs_flash
22+
- nvs_sec_provider
23+
disable:
24+
- if: CONFIG_NAME == "nvs_enc_flash_enc" and (SOC_AES_SUPPORTED != 1 and ESP_ROM_HAS_MBEDTLS_CRYPTO_LIB != 1)
25+
- 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)))
26+
reason: As of now in such cases, we do not have any way to perform AES operations in the bootloader build
27+
1928
examples/storage/nvs_rw_blob:
2029
depends_components:
2130
- nvs_flash

examples/storage/nvs_bootloader/README.md

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33

44
# NVS Bootloader
55

6-
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
6+
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.
7+
8+
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.
79

810
## Usage of this example:
911

@@ -123,3 +125,116 @@ Below is a short explanation of files in the project folder.
123125
The example creates request/response array `read_list[]`, populates it with identifiers of the data to be read.
124126
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.
125127
Function `log_nvs_bootloader_read_list()`is used before and after reading from nvs to show request/response data to the console.
128+
129+
# Encrypted NVS Bootloader
130+
131+
This example is extended to support reading encrypted NVS partition when NVS encryption is enabled.
132+
133+
## Usage of this example:
134+
135+
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).
136+
137+
(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.)
138+
139+
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).
140+
We shall use the [nvs_partition_gen.py](../../../components/nvs_flash/nvs_partition_generator/nvs_partition_gen.py) script for the operations.
141+
142+
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`.
143+
144+
In case the data in `nvs_data.csv` is modified, these encrypted NVS partitions can be re-generated using the following commands:
145+
146+
1. NVS Encryption using the flash encryption scheme
147+
148+
```
149+
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
150+
```
151+
152+
2. NVS Encryption using the HMAC scheme
153+
154+
```
155+
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
156+
```
157+
158+
Build the application using configurations corresponding to the NVS encryption scheme that you have selected:
159+
160+
```
161+
idf.py set-target <target>
162+
163+
# For NVS encryption using flash encryption scheme
164+
cat sdkconfig.ci.nvs_enc_flash_enc >> sdkconfig
165+
166+
OR
167+
168+
# For NVS encryption using the HMAC scheme
169+
cat sdkconfig.ci.nvs_enc_hmac >> sdkconfig
170+
171+
idf.py build
172+
```
173+
174+
Then flash it and open the monitor with the following command:
175+
```
176+
idf.py flash monitor
177+
```
178+
179+
If everything went well, the console output should contain the same three blocks of log messages that are mentioned above.
180+
181+
### Running the example using QEMU
182+
183+
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.
184+
185+
#### Using the NVS encryption's flash encryption scheme
186+
187+
1. Configure the application with the corresponding configurations
188+
189+
```
190+
idf.py set-target <qemu-supported-targets>
191+
192+
cat sdkconfig.ci.nvs_enc_flash_enc >> sdkconfig
193+
194+
# Disable the below config as it was enabled as a CI related configuration, thus enabling flash encryption during boot-up in QEMU
195+
echo "CONFIG_SECURE_FLASH_REQUIRE_ALREADY_ENABLED=n" >> sdkconfig
196+
197+
```
198+
199+
2. Building the app
200+
201+
```
202+
idf.py build
203+
```
204+
205+
3. Running the app
206+
207+
```
208+
idf.py qemu monitor
209+
```
210+
211+
#### Using the NVS encryption's HMAC scheme
212+
213+
1. Build the application using the corresponding configurations
214+
215+
```
216+
idf.py set-target <qemu-supported-targets>
217+
218+
cat sdkconfig.ci.nvs_enc_hmac >> sdkconfig
219+
220+
# Disable the below config as it was enabled as a CI related configuration, thus enabling flash encryption during boot-up in QEMU
221+
echo "CONFIG_SECURE_FLASH_REQUIRE_ALREADY_ENABLED=n" >> sdkconfig
222+
```
223+
224+
2. Building the app
225+
226+
```
227+
idf.py build
228+
```
229+
230+
3. Burn the related HMAC key in the efuses
231+
232+
```
233+
idf.py qemu efuse-burn-key BLOCK_KEY0 main/nvs_enc_hmac_key.bin HMAC_UP
234+
```
235+
236+
4. Running the app
237+
238+
```
239+
idf.py qemu monitor
240+
```

examples/storage/nvs_bootloader/bootloader_components/nvs_bootloader_example/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
idf_component_register( SRCS "src/nvs_bootloader_example.c" "src/nvs_bootloader_example_utils.c"
1+
idf_component_register(SRCS "src/nvs_bootloader_example.c" "src/nvs_bootloader_example_utils.c"
22
INCLUDE_DIRS "include"
3-
REQUIRES "nvs_flash" )
3+
REQUIRES "nvs_flash" "nvs_sec_provider")
44

55
# We need to force GCC to integrate this static library into the
66
# bootloader link. Indeed, by default, as the hooks in the bootloader are weak,

examples/storage/nvs_bootloader/bootloader_components/nvs_bootloader_example/src/nvs_bootloader_example.c

Lines changed: 67 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -8,6 +8,8 @@
88
#include "nvs_bootloader.h"
99
#include "nvs_bootloader_example_utils.h"
1010

11+
#include "nvs_sec_provider.h"
12+
1113
static const char* TAG = "nvs_bootloader_example";
1214

1315
// Function used to tell the linker to include this file
@@ -21,14 +23,11 @@ void bootloader_before_init(void) {
2123
}
2224

2325

24-
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) {
25-
// log the request structure before the read to see the requested keys and namespaces
26-
// 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
27-
// it is useful for debugging the request structure
28-
log_nvs_bootloader_read_list(ESP_ERR_NOT_FINISHED, read_list, read_list_count);
29-
26+
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)
27+
{
28+
esp_err_t ret = ESP_FAIL;
3029
// call the read function
31-
esp_err_t ret = nvs_bootloader_read(nvs_partition_label, read_list_count, read_list);
30+
ret = nvs_bootloader_read(nvs_partition_label, read_list_count, read_list);
3231

3332
// Error code ESP_OK means that the read function was successful and individual, per record results are stored in the read_list
3433
if (ret == ESP_OK) {
@@ -62,10 +61,48 @@ void bootloader_after_init(void) {
6261
// we are going to read from the default nvs partition labelled 'nvs'
6362
const char* nvs_partition_label = "nvs";
6463

65-
#define STR_BUFF_LEN 10+1 // 10 characters + null terminator
66-
char str_buff[STR_BUFF_LEN];
64+
#define STR_BUFF_LEN_10 10+1 // 10 characters + null terminator
65+
#define STR_BUFF_LEN_66 66+1 // 66 characters + null terminator
66+
char str_buff_10[STR_BUFF_LEN_10];
67+
char str_buff_66[STR_BUFF_LEN_66];
68+
69+
nvs_sec_cfg_t* sec_cfg = NULL;
70+
71+
#if CONFIG_NVS_ENCRYPTION
72+
nvs_sec_cfg_t cfg = {};
73+
nvs_sec_scheme_t *sec_scheme_handle = NULL;
74+
#if CONFIG_NVS_SEC_KEY_PROTECT_USING_HMAC
75+
nvs_sec_config_hmac_t sec_scheme_cfg = NVS_SEC_PROVIDER_CFG_HMAC_DEFAULT();
76+
if (nvs_sec_provider_register_hmac(&sec_scheme_cfg, &sec_scheme_handle) != ESP_OK) {
77+
ESP_EARLY_LOGE(TAG, "Registering the HMAC scheme failed");
78+
return;
79+
}
80+
#elif CONFIG_NVS_SEC_KEY_PROTECT_USING_FLASH_ENC
81+
nvs_sec_config_flash_enc_t sec_scheme_cfg = NVS_SEC_PROVIDER_CFG_FLASH_ENC_DEFAULT();
82+
if (sec_scheme_cfg.nvs_keys_part == NULL) {
83+
ESP_EARLY_LOGE(TAG, "partition with subtype \"nvs_keys\" not found");
84+
}
85+
86+
if (nvs_sec_provider_register_flash_enc(&sec_scheme_cfg, &sec_scheme_handle) != ESP_OK) {
87+
ESP_EARLY_LOGE(TAG, "Registering the Flash Encryption scheme failed");
88+
return;
89+
}
90+
#endif /* CONFIG_NVS_SEC_KEY_PROTECT_USING_HMAC */
91+
92+
if (nvs_bootloader_read_security_cfg(sec_scheme_handle, &cfg) != ESP_OK) {
93+
ESP_EARLY_LOGE(TAG, "Reading the NVS security configuration failed");
94+
return;
95+
}
96+
97+
if (nvs_bootloader_secure_init(&cfg) != ESP_OK) {
98+
ESP_LOGE(TAG, "Secure initialization of NVS failed");
99+
return;
100+
}
101+
#endif /* CONFIG_NVS_ENCRYPTION*/
67102

68103
// --- This is the request structure for the read function showing validation errors - function will return ESP_ERR_INVALID_ARG ---
104+
ESP_EARLY_LOGI(TAG, "Trying to read the NVS partition by passing invalid arguments");
105+
69106
nvs_bootloader_read_list_t bad_read_list_indicate_problems[] = {
70107
{ .namespace_name = "sunny_day", .key_name = "u8", .value_type = NVS_TYPE_U8 }, // ESP_ERR_NVS_NOT_FOUND
71108
// this is correct request, not found is expected default result code
@@ -75,37 +112,42 @@ void bootloader_after_init(void) {
75112
// too long key name
76113
{ .namespace_name = "clowny_day", .key_name = "blobeee", .value_type = NVS_TYPE_BLOB }, // ESP_ERR_INVALID_ARG
77114
// not supported data type
78-
{ .namespace_name = "sunny_day", .key_name = "string_10_chars", .value_type = NVS_TYPE_STR, .value.str_val = { .buff_ptr = str_buff, .buff_len = 0 } },
115+
{ .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 } },
79116
// ESP_ERR_INVALID_SIZE
80117
// buffer size is 0
81-
{ .namespace_name = "sunny_day", .key_name = "string_10_chars", .value_type = NVS_TYPE_STR, .value.str_val = { .buff_ptr = NULL, .buff_len = 10 } }
118+
{ .namespace_name = "sunny_day", .key_name = "string_66_chars", .value_type = NVS_TYPE_STR, .value.str_val = { .buff_ptr = NULL, .buff_len = 66 } }
82119
// ESP_ERR_INVALID_SIZE
83120
// buffer pointer is invalid
84121
};
85122

86123
size_t bad_read_list_indicate_problems_count = sizeof(bad_read_list_indicate_problems) / sizeof(bad_read_list_indicate_problems[0]);
87-
log_request_call_read_evaluate_output(nvs_partition_label, bad_read_list_indicate_problems, bad_read_list_indicate_problems_count);
124+
log_request_call_read_evaluate_output(nvs_partition_label, bad_read_list_indicate_problems, bad_read_list_indicate_problems_count, sec_cfg);
88125

89126
// --- This is the request structure for the read function showing runtime errors - function will return ESP_OK ---
90127
// but some records will have result_code set to ESP_ERR_NVS_NOT_FOUND, ESP_ERR_NVS_TYPE_MISMATCH, ESP_ERR_INVALID_SIZE
128+
ESP_EARLY_LOGI(TAG, "Trying to read the NVS partition by expecting incorrect data");
129+
91130
nvs_bootloader_read_list_t good_read_list_bad_results[] = {
92131
{ .namespace_name = "sunny_day", .key_name = "u8", .value_type = NVS_TYPE_I8 }, // ESP_ERR_NVS_TYPE_MISMATCH
93132
// data in the partition is of different type (NVS_TYPE_U8)
94133
{ .namespace_name = "sunny_day", .key_name = "i32_", .value_type = NVS_TYPE_I32 }, // ESP_ERR_NVS_NOT_FOUND
95134
// data in the partition won't be found, because there is a typo in the key name
96135
{ .namespace_name = "clowny_day", .key_name = "i8", .value_type = NVS_TYPE_I8 }, // ESP_ERR_NVS_NOT_FOUND
97136
// data in the partition won't be found, because there is typo in namespace name
98-
{ .namespace_name = "sunny_day", .key_name = "string_10_chars", .value_type = NVS_TYPE_STR, .value.str_val = { .buff_ptr = str_buff, .buff_len = 2 } },
137+
{ .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 } },
99138
// ESP_ERR_INVALID_SIZE
100139
// buffer is too small
101140
{ .namespace_name = "sunny_day", .key_name = "u32", .value_type = NVS_TYPE_U32 }, // ESP_OK
102141
// this value will be read correctly
103-
{ .namespace_name = "sunny_day", .key_name = "u32", .value_type = NVS_TYPE_U32 } // ESP_ERR_NVS_NOT_FOUND
142+
{ .namespace_name = "sunny_day", .key_name = "u32", .value_type = NVS_TYPE_U32 }, // ESP_ERR_NVS_NOT_FOUND
104143
// this value won't be read as function doesn't support duplicate readings
144+
{ .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 } }
145+
// ESP_ERR_INVALID_SIZE
146+
// buffer is just small
105147
};
106148

107149
size_t good_read_list_bad_results_count = sizeof(good_read_list_bad_results) / sizeof(good_read_list_bad_results[0]);
108-
log_request_call_read_evaluate_output(nvs_partition_label, good_read_list_bad_results, good_read_list_bad_results_count);
150+
log_request_call_read_evaluate_output(nvs_partition_label, good_read_list_bad_results, good_read_list_bad_results_count, sec_cfg);
109151

110152

111153
// --- This is the request structure for the read function showing all records found---
@@ -116,16 +158,23 @@ void bootloader_after_init(void) {
116158
// For NVS_TYPE_STR the value field is a structure with a pointer to the buffer and the buffer length is povided
117159
// In this case, the buffer is a stack allocated array of 10 characters plus space for the null terminator
118160

161+
ESP_EARLY_LOGI(TAG, "Trying to read the NVS partition correctly");
162+
119163
nvs_bootloader_read_list_t good_read_list[] = {
120164
{ .namespace_name = "sunny_day", .key_name = "u8", .value_type = NVS_TYPE_U8 },
121165
{ .namespace_name = "sunny_day", .key_name = "i32", .value_type = NVS_TYPE_I32 },
122166
{ .namespace_name = "cloudy_day", .key_name = "i8", .value_type = NVS_TYPE_I8 }, // mixed in different namespace
123167
{ .namespace_name = "sunny_day", .key_name = "u16", .value_type = NVS_TYPE_U16 },
124-
{ .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 } }
168+
{ .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 } },
169+
{ .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 } }
125170
};
126171

127172
size_t good_read_list_count = sizeof(good_read_list) / sizeof(good_read_list[0]);
128-
log_request_call_read_evaluate_output(nvs_partition_label, good_read_list, good_read_list_count);
173+
log_request_call_read_evaluate_output(nvs_partition_label, good_read_list, good_read_list_count, sec_cfg);
174+
175+
#if CONFIG_NVS_ENCRYPTION
176+
nvs_bootloader_secure_deinit();
177+
#endif /* CONFIG_NVS_ENCRYPTION */
129178

130179
ESP_LOGI(TAG, "Finished bootloader part");
131180
}
Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
11
idf_component_register(SRCS "bootloader_hooks_example_main.c"
22
INCLUDE_DIRS ".")
3-
nvs_create_partition_image(nvs ../nvs_data.csv FLASH_IN_PROJECT)
3+
4+
if(NOT CONFIG_NVS_ENCRYPTION)
5+
nvs_create_partition_image(nvs ../nvs_data.csv FLASH_IN_PROJECT)
6+
else()
7+
if(CONFIG_NVS_SEC_KEY_PROTECT_USING_FLASH_ENC)
8+
esptool_py_flash_to_partition(flash "nvs_key" ${PROJECT_DIR}/main/encryption_keys.bin)
9+
esptool_py_flash_to_partition(flash "nvs" ${PROJECT_DIR}/main/nvs_encrypted.bin)
10+
else() # NVS Encryption using HMAC (CONFIG_NVS_SEC_KEY_PROTECT_USING_HMAC)
11+
esptool_py_flash_to_partition(flash "nvs" ${PROJECT_DIR}/main/nvs_encrypted_hmac.bin)
12+
endif()
13+
endif()

0 commit comments

Comments
 (0)