security: Added example for HMAC-based NVS encr-keys protection scheme

This commit is contained in:
Laukik Hase 2023-03-09 15:59:08 +05:30
parent 72f703ccd4
commit 3aa6f97c72
No known key found for this signature in database
GPG Key ID: 11C571361F51A199
9 changed files with 374 additions and 0 deletions

View File

@ -5,3 +5,11 @@ examples/security/flash_encryption:
- if: IDF_TARGET in ["esp32s2", "esp32s3", "esp32c6", "esp32h2", "esp32c2"]
temporary: true
reason: lack of runners
examples/security/nvs_encryption_hmac:
disable:
- if: SOC_HMAC_SUPPORTED != 1
disable_test:
- if: IDF_TARGET not in ["esp32c3"]
temporary: true
reason: lack of runners

View File

@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(nvs_encryption_hmac)

View File

@ -0,0 +1,81 @@
| Supported Targets | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 |
| ----------------- | -------- | -------- | -------- | -------- | -------- |
# NVS Encryption with HMAC-based encryption key protection scheme
## Overview
This example demonstrates NVS encryption using the HMAC peripheral, wherein the encryption keys are derived from the HMAC key burnt in eFuse. Since the derivation of the encryption keys occurs at runtime, they are not stored in the flash. Thus, this feature does not require a separate `nvs_keys` partition and _also does not require flash encryption enabled_.
## How to use the example
### Hardware Required
This example can be executed on any development board with a supported Espressif SOC chip - possessing a built-in HMAC peripheral (see `Supported Targets` table above).
### Configure the project
Before the project configuration and build, be sure to set the correct chip target using `idf.py set-target <chip_name>`.
Open the project configuration menu (`idf.py menuconfig`).
#### Configure the eFuse key ID storing the HMAC key
- Set the eFuse key ID storing the HMAC key at `Component config → NVS Security Provider → eFuse key ID storing the HMAC key`.
The HMAC key stored at this key block will be used to generate the encryption keys for the default NVS partition (`nvs`), initialised with `nvs_flash_init()`. Note that the example will fail to build without setting this config option to the correct value (the default value is out of range).
- Users can program their own HMAC key in the configured block before running the example - refer below snippet. The example checks if the configured block is empty or already programmed with an HMAC key - if empty, a new key is generated at runtime and stored in the block, or else the provided key is used. While burning the key prior to flashing the app, please make sure that the config value is set to the eFuse block holding the HMAC key.
```shell
# Burning the HMAC-key in eFuse block 0 - key ID 0
espefuse.py -p PORT burn_key BLOCK_KEY0 hmac_key_file.bin HMAC_UP
```
### Build and Flash
Build the project and flash it to the board, then run the monitor tool to view the serial output:
```
idf.py -p PORT flash monitor
```
(To exit the serial monitor, type `Ctrl-]`.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
### Example Output
```log
I (300) nvs_sec_provider: NVS Encryption - Registering HMAC-based scheme...
I (308) app_start: Starting scheduler on CPU0
I (313) main_task: Started on CPU0
I (313) main_task: Calling app_main()
I (313) example: Initialising the default NVS partition
I (333) nvs: NVS partition "nvs" is encrypted.
I (603) example: Initialising the custom NVS partition
I (613) example: NVS partition "custom_nvs" is encrypted.
I (623) example: Key: u8_key | Val: 255
I (623) example: Key: i8_key | Val: -128
I (623) example: Key: u16_key | Val: 65535
I (633) example: Key: u32_key | Val: 4294967295
I (633) example: Key: i32_key | Val: -2147483648
I (643) example: Key: str_key | Val: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Fusce quis risus justo.
Suspendisse egestas in nisi sit amet auctor.
Pellentesque rhoncus dictum sodales.
In justo erat, viverra at interdum eget, interdum vel dui.
I (663) custom_nvs: 0x3ffc5f5c fe ff ff ff 00 00 00 00 fe ff ff ff ff ff ff ff |................|
I (673) custom_nvs: 0x3ffc5f6c ff ff ff ff ff ff ff ff ff ff ff ff 84 2d ba b9 |.............-..|
I (683) custom_nvs: 0x3ffc5f7c aa aa aa fa ff ff ff ff ff ff ff ff ff ff ff ff |................|
I (693) custom_nvs: 0x3ffc5f8c ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
I (703) custom_nvs: 0x3ffc5f9c ca 8b c3 bb 2d c2 33 d6 6b d4 a7 3d 31 0e 9c 36 |....-.3.k..=1..6|
I (713) custom_nvs: 0x3ffc5fac 39 7f bc d4 5c 6d f8 98 de 0a 90 50 21 23 ff 04 |9...\m.....P!#..|
I (723) custom_nvs: 0x3ffc5fbc ce f9 23 6f 2c d6 07 08 2d 0e d2 f2 a5 af 5a 2e |..#o,...-.....Z.|
I (733) custom_nvs: 0x3ffc5fcc c9 61 bd fc 96 fc 12 87 1b 8c cb fb 51 2c ed a2 |.a..........Q,..|
...
...
...
I (1133) custom_nvs: 0x3ffc624c ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
I (1143) main_task: Returned from app_main()
```

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "main.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,205 @@
/*
* NVS Encryption with HMAC-based encryption key protection scheme example
*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <inttypes.h>
#include "esp_flash.h"
#include "esp_partition.h"
#include "esp_hmac.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nvs_flash.h"
#include "nvs_sec_provider.h"
#define CUSTOM_NVS_PART_LABEL "custom_nvs"
#define CUSTOM_NVS_PART_NAMESPACE "storage"
#define CUSTOM_NVS_PART_DUMP_SIZE (512 + 16)
static const char* str_val = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n"
"Fusce quis risus justo.\n"
"Suspendisse egestas in nisi sit amet auctor.\n"
"Pellentesque rhoncus dictum sodales.\n"
"In justo erat, viverra at interdum eget, interdum vel dui.\n";
static const char* TAG = "example";
static esp_err_t example_custom_nvs_part_init(const char *label)
{
esp_err_t ret = ESP_FAIL;
#if defined(CONFIG_NVS_ENCRYPTION) && defined(CONFIG_NVS_SEC_KEY_PROTECT_USING_HMAC)
nvs_sec_cfg_t cfg = {};
nvs_sec_scheme_t *sec_scheme_handle = NULL;
nvs_sec_config_hmac_t sec_scheme_cfg = NVS_SEC_PROVIDER_CFG_HMAC_DEFAULT();
ret = nvs_sec_provider_register_hmac(&sec_scheme_cfg, &sec_scheme_handle);
if (ret != ESP_OK) {
return ret;
}
ret = nvs_flash_read_security_cfg_v2(sec_scheme_handle, &cfg);
if (ret != ESP_OK) {
/* We shall not generate keys here as that must have been done in default NVS partition initialization case */
ESP_LOGE(TAG, "Failed to read NVS security cfg: [0x%02X] (%s)", ret, esp_err_to_name(ret));
return ret;
}
ret = nvs_flash_secure_init_partition(label, &cfg);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "NVS partition \"%s\" is encrypted.", label);
}
memset(&cfg, 0x00, sizeof(nvs_sec_cfg_t));
#else
ret = nvs_flash_init_partition(label);
#endif
return ret;
}
static esp_err_t example_custom_nvs_part_write(const char *label, const char *namespace)
{
nvs_handle_t my_handle;
esp_err_t err = ESP_FAIL;
// Open
err = nvs_open_from_partition(label, namespace, NVS_READWRITE, &my_handle);
if (err != ESP_OK) return err;
// Write
err = nvs_set_u8(my_handle, "u8_key", UINT8_MAX);
if (err != ESP_OK) goto exit;
err = nvs_set_i8(my_handle, "i8_key", INT8_MIN);
if (err != ESP_OK) goto exit;
err = nvs_set_u16(my_handle, "u16_key", UINT16_MAX);
if (err != ESP_OK) goto exit;
err = nvs_set_u32(my_handle, "u32_key", UINT32_MAX);
if (err != ESP_OK) goto exit;
err = nvs_set_i32(my_handle, "i32_key", INT32_MIN);
if (err != ESP_OK) goto exit;
err = nvs_set_str(my_handle, "str_key", str_val);
if (err != ESP_OK) goto exit;
// Commit
err = nvs_commit(my_handle);
if (err != ESP_OK) goto exit;
exit:
// Close
nvs_close(my_handle);
return err;
}
static esp_err_t example_custom_nvs_part_read(const char *label, const char *namespace)
{
nvs_handle_t my_handle;
esp_err_t err = ESP_FAIL;
// Open
err = nvs_open_from_partition(label, namespace, NVS_READWRITE, &my_handle);
if (err != ESP_OK) return err;
// Write
uint8_t u8_val = 0;
err = nvs_get_u8(my_handle, "u8_key", &u8_val);
if (err != ESP_OK) goto exit;
ESP_LOGI(TAG, "Key: u8_key | Val: %" PRIu8, u8_val);
int8_t i8_val = 0;
err = nvs_get_i8(my_handle, "i8_key", &i8_val);
if (err != ESP_OK) goto exit;
ESP_LOGI(TAG, "Key: i8_key | Val: %" PRIi8, i8_val);
uint16_t u16_val = 0;
err = nvs_get_u16(my_handle, "u16_key", &u16_val);
if (err != ESP_OK) goto exit;
ESP_LOGI(TAG, "Key: u16_key | Val: %" PRIu16, u16_val);
uint32_t u32_val = 0;
err = nvs_get_u32(my_handle, "u32_key", &u32_val);
if (err != ESP_OK) goto exit;
ESP_LOGI(TAG, "Key: u32_key | Val: %" PRIu32, u32_val);
int32_t i32_val = 0;
err = nvs_get_i32(my_handle, "i32_key", &i32_val);
if (err != ESP_OK) goto exit;
ESP_LOGI(TAG, "Key: i32_key | Val: %" PRIi32, i32_val);
size_t str_val_len = 0;
err = nvs_get_str(my_handle, "str_key", NULL, &str_val_len);
if (err != ESP_OK) goto exit;
char* str_key_val = malloc(str_val_len);
assert(str_val);
err = nvs_get_str(my_handle, "str_key", str_key_val, &str_val_len);
if (err != ESP_OK) goto cleanup;
ESP_LOGI(TAG, "Key: str_key | Val: %s", str_key_val);
cleanup:
free(str_key_val);
exit:
// Close
nvs_close(my_handle);
return err;
}
void dump_custom_nvs_partition(const char *label, size_t len)
{
const esp_partition_t* partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, label);
assert(partition);
uint8_t *read_data = calloc(len, sizeof(uint8_t));
assert(read_data != NULL);
ESP_ERROR_CHECK(esp_partition_read(partition, 0, read_data, len));
ESP_LOG_BUFFER_HEXDUMP(label, read_data, len, ESP_LOG_INFO);
free(read_data);
}
void app_main(void)
{
ESP_LOGI(TAG, "Initialising the default NVS partition");
/* Initialising the default NVS partition */
ESP_ERROR_CHECK(nvs_flash_init());
/* Erasing the custom NVS partition */
ESP_ERROR_CHECK(nvs_flash_erase_partition(CUSTOM_NVS_PART_LABEL));
ESP_LOGI(TAG, "Initialising the custom NVS partition");
/* Initialize the custom NVS partition with encryption enabled */
esp_err_t ret = example_custom_nvs_part_init(CUSTOM_NVS_PART_LABEL);
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase_partition(CUSTOM_NVS_PART_LABEL));
ret = example_custom_nvs_part_init(CUSTOM_NVS_PART_LABEL);
}
ESP_ERROR_CHECK(ret);
ret = example_custom_nvs_part_write(CUSTOM_NVS_PART_LABEL, CUSTOM_NVS_PART_NAMESPACE);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to write NVS partition (%s | %s): %s", CUSTOM_NVS_PART_LABEL, CUSTOM_NVS_PART_NAMESPACE, esp_err_to_name(ret));
};
ret = example_custom_nvs_part_read(CUSTOM_NVS_PART_LABEL, CUSTOM_NVS_PART_NAMESPACE);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to read NVS partition (%s | %s): %s", CUSTOM_NVS_PART_LABEL, CUSTOM_NVS_PART_NAMESPACE, esp_err_to_name(ret));
};
dump_custom_nvs_partition(CUSTOM_NVS_PART_LABEL, CUSTOM_NVS_PART_DUMP_SIZE);
}

View File

@ -0,0 +1,4 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, , 24K,
factory, app, factory, , 1M,
custom_nvs, data, nvs, , 24K,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 24K
3 factory app factory 1M
4 custom_nvs data nvs 24K

View File

@ -0,0 +1,45 @@
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import logging
import os
import pytest
from pytest_embedded_idf.dut import IdfDut
STR_KEY_VAL = ['Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
'Fusce quis risus justo.',
'Suspendisse egestas in nisi sit amet auctor.',
'Pellentesque rhoncus dictum sodales.',
'In justo erat, viverra at interdum eget, interdum vel dui.']
ENCR_TEXT_ARR = ['fe ff ff ff 00 00 00 00 fe ff ff ff ff ff ff ff',
'ca 8b c3 bb 2d c2 33 d6 6b d4 a7 3d 31 0e 9c 36',
'bd c1 2a 10 87 44 5e 1c 4b 2c 7c 5d ac 97 48 63']
@pytest.mark.esp32c3
@pytest.mark.nvs_encr_hmac
@pytest.mark.parametrize('config', ['nvs_encr_hmac'], indirect=True)
def test_nvs_flash_encr_keys_hmac(dut: IdfDut) -> None:
# Logging example binary details
binary_file = os.path.join(dut.app.binary_path, 'nvs_encryption_hmac.bin')
bin_size = os.path.getsize(binary_file)
logging.info('nvs_encryption_hmac_bin_size : {}KB'.format(bin_size // 1024))
# Start test and verify serial output
dut.expect('NVS partition "nvs" is encrypted.', timeout=30)
dut.expect('NVS partition "custom_nvs" is encrypted', timeout=30)
dut.expect('Key: u8_key | Val: 255', timeout=30)
dut.expect('Key: i8_key | Val: -128', timeout=30)
dut.expect('Key: u16_key | Val: 65535', timeout=30)
dut.expect('Key: u32_key | Val: 4294967295', timeout=30)
dut.expect('Key: i32_key | Val: -2147483648', timeout=30)
for string in STR_KEY_VAL:
dut.expect(string, timeout=30)
for encr_txt in ENCR_TEXT_ARR:
dut.expect(encr_txt, timeout=30)
dut.expect('Returned from app_main()', timeout=30)

View File

@ -0,0 +1,16 @@
# Partition Table
CONFIG_PARTITION_TABLE_OFFSET=0x9000
# NOTE: The runner for this example has flash-encryption enabled
# Flash Encryption
CONFIG_SECURE_FLASH_ENC_ENABLED=y
CONFIG_SECURE_FLASH_ENCRYPTION_MODE_DEVELOPMENT=y
CONFIG_SECURE_BOOT_ALLOW_ROM_BASIC=y
CONFIG_SECURE_BOOT_ALLOW_JTAG=y
CONFIG_SECURE_FLASH_UART_BOOTLOADER_ALLOW_ENC=y
CONFIG_SECURE_FLASH_UART_BOOTLOADER_ALLOW_DEC=y
CONFIG_SECURE_FLASH_UART_BOOTLOADER_ALLOW_CACHE=y
CONFIG_SECURE_FLASH_REQUIRE_ALREADY_ENABLED=y
# NVS Encryption
CONFIG_NVS_SEC_HMAC_EFUSE_KEY_ID=0

View File

@ -0,0 +1,7 @@
# This example uses an extra partition to demonstrate encrypted/non-encrypted reads/writes.
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions_example.csv"
CONFIG_NVS_ENCRYPTION=y
CONFIG_NVS_SEC_KEY_PROTECT_USING_HMAC=y