Merge branch 'feature/storage_fatfs_encr_example' into 'master'

security: flash_encryption example extended with fatfs encryption

Closes IDF-7639

See merge request espressif/esp-idf!27729
This commit is contained in:
Martin Vychodil 2024-06-27 22:34:37 +08:00
commit 8d34993193
12 changed files with 608 additions and 58 deletions

View File

@ -12,6 +12,8 @@ This section contains reference of the high-level storage APIs. They are based o
- :doc:`FAT <fatfs>` is a standard file system which can be used in SPI flash or on SD/MMC cards
- :doc:`Wear Levelling <wear-levelling>` library implements a flash translation layer (FTL) suitable for SPI NOR flash. It is used as a container for FAT partitions in flash.
Topics related to :doc:`Storage Security <storage-security>` are described in separate section.
.. note::
It is suggested to use high-level APIs (``esp_partition`` or file system) instead of low-level driver APIs to access the SPI NOR flash.
@ -33,5 +35,47 @@ This section contains reference of the high-level storage APIs. They are based o
spiffs
vfs
wear-levelling
storage-security.rst
Code examples for this API section are provided in the :example:`storage` directory of ESP-IDF examples.
.. list-table:: Code examples for this API section
:widths: 25 75
:header-rows: 0
* - **Link**
- **Description**
* - :doc:`FAT <fatfs>`
-
* - :example:`<wear_levelling <storage/wear_levelling>`
- Demonstrates using FATFS over wear leveling on internal flash.
* - :example:`ext_flash_fatfs <storage/ext_flash_fatfs>`
- Demonstrates using FATFS over wear leveling on external flash.
* - :example:`fatfsgen <storage/fatfsgen>`
- Demonstrates the capabilities of Python-based tooling for FATFS images available on host computers.
* - :doc:`Non-Volatile Storage library (NVS) <nvs_flash>`
-
* - :example:`nvs_rw_blob <storage/nvs_rw_blob>`
- Shows the use of the C-style API to read and write blob data types in NVS flash.
* - :example:`nvs_rw_value <storage/nvs_rw_value>`
- Shows the use of the C-style API to read and write integer data types in NVS flash.
* - :example:`nvs_rw_value_cxx <storage/nvs_rw_value_cxx>`
- Shows the use of the C++-style API to read and write integer data types in NVS flash.
* - :example:`nvsgen <storage/nvsgen>`
- Demonstrates how to use the Python-based NVS image generation tool to create an NVS partition image from the contents of a CSV file.
* - :doc:`SPIFFS <spiffs>`
-
* - :example:`spiffs <storage/spiffs>`
- Shows the use of the SPIFFS API to initialize the filesystem and work with files using POSIX functions.
* - :example:`spiffsgen <storage/spiffsgen>`
- Demonstrates the capabilities of Python-based tooling for SPIFFS images available on host computers.
* - :doc:`Partitions API <partition>`
-
* - :example:`partition_api <storage/partition_api>`
- Provides an overview of API functions to look up particular partitions, perform basic I/O operations, and use partitions via CPU memory mapping.
* - :example:`parttool <storage/parttool>`
- Demonstrates the capabilities of Python-based tooling for partition images available on host computers.
* - :doc:`Virtual File System (VFS) <vfs>`
-
* - :example:`littlefs <storage/littlefs>`
- Shows the use of the LittleFS component to initialize the filesystem and work with a file using POSIX functions.
* - :example:`semihost_vfs <storage/semihost_vfs>`
- Demonstrates the use of the VFS API to let an ESP-based device access a file on a JTAG-connected host using POSIX functions.

View File

@ -0,0 +1,24 @@
Storage Security
================
:link_to_translation:`zh_CN:[中文]`
Overview of Available Resources
-------------------------------
Data privacy is achieved by using the :doc:`../../security/flash-encryption` feature. This mechanism is currently used by FATFS and LittleFS and is recommended for new storage type implementations based on the Partitions API.
NVS storage uses a proprietary :doc:`NVS encryption <nvs_encryption>` implementation.
Workflows focused on overall system security are described in the :doc:`Host Based Workflows <../../security/host-based-security-workflows>`.
Workflows related to the combination of multiple secured storage components in one project are presented in the :example:`Flash Encryption Example <security/flash_encryption>`.
.. list-table:: Relevant storage security examples
:widths: 25 75
:header-rows: 0
* - **Link**
- **Description**
* - :example:`nvs_encryption_hmac <security/nvs_encryption_hmac>`
- Demonstrates NVS encryption with an HMAC-based encryption key protection scheme.
* - :example:`flash_encryption <security/flash_encryption>`
- Provides a combined example showing the coexistence of NVS encryption, FATFS encryption, and encrypted custom data access via the Partitions API. Security related workflows for both development and production are also provided.

View File

@ -33,5 +33,6 @@
spiffs
vfs
wear-levelling
storage-security.rst
此部分 API 代码示例存放在 ESP-IDF 示例项目的 :example:`storage` 目录下。

View File

@ -0,0 +1 @@
.. include:: ../../../en/api-reference/storage/storage-security.rst

View File

@ -3,11 +3,27 @@
# Flash Encryption
The example checks if the flash encryption feature is enabled/disabled and if enabled prints the flash encryption mode (DEVELOPMENT / RELEASE) and FLASH_CRYPT_CNT (for ESP32) or SPI_BOOT_CRYPT_CNT (for ESP32-S2 and newer targets) eFuse value.
The example demonstrates the flash encryption application by providing a code using FATFS and NVS partitions on a device with the flash encryption enabled.
As the flash encryption can be enabled either in Development or Release mode, a guidance of how to use either mode is provided.
The example also demonstrates writing and reading encrypted partitions in flash.
The example code checks if the flash encryption feature is enabled/disabled and if enabled, it prints a status information of all the eFuses related to the flash encryption mode (Development/Release) and FLASH_CRYPT_CNT (for ESP32) or SPI_BOOT_CRYPT_CNT (for ESP32-S2 and newer targets).
## How to use example
The example also demonstrates:
1. Writing and reading encrypted partitions in the flash memory.
2. Initialization of FATFS, formatting, writing and reading file. Both encrypted as well as non-encrypted.
3. Initialization of encrypted NVS partition. Both auto-generated as well as pre-generated NVS key scenarios are presented.
4. Flashing the example in Development as well as Release mode.
### NVS example
The example demonstrates default and custom NVS partition initialisation when the flash encryption is enabled. From the code perspective the use of NVS API is transparent regardless of the flash encryption mode.
### FATFS example
FATFS example function finds non-encrypted partition `fat_not_encr`, erases it, creates FATFS, formats and mounts the file system. Then it creates file `/spiflash/inner.txt` using `fopen`, writes test string using `fprintf` and closes the file using `fclose`. Then it uses `fopen` and `fgets` to read the string back to verify it is correctly written. The last step of the example on unencrypted partition uses `esp_flash_read` to locate the test string in the underlying partition without involving potential cryptographic layer. The test string offset from the beginning of the partition is returned to the second part of the example.
The second part of the example repeats FATFS related steps on the encrypted partition named `fat_encrypted`. The same sequence of steps as in the non encrypted partition means, that the file data should be present on the same offset in the partition. Therefore, direct partition reading using `esp_flash_read` uses the offset returned by the first part of example. Depending on encryption settings in the menuconfig, the test string is then read in the visible form `(SECURE_FLASH_ENC_ENABLED not set)` or in encrypted form `(SECURE_FLASH_ENC_ENABLED=y)`
## How to use the example
### Hardware Required
@ -16,67 +32,211 @@ The example also demonstrates writing and reading encrypted partitions in flash.
```
idf.py menuconfig
```
#### Configuration for flash encryption
* Enable the flash encryption mode (Development or Release) under Security Features. Default usage mode is Development (recommended during test and development phase).
Note: After enabling flash encryption, the bootloader size increases, which means that the offset of the partition table must be changed to 0x9000 from 0x8000 to prevent the bootloader from overlapping with the partition table. In this example, the default offset of the partition table is 0x9000.
#### Flash encryption configuration
Enable the flash encryption mode (Development or Release) under (`menuconfig -> Security Features`). Default usage mode is Development (recommended during test and development phase).
For better security, the NVS encryption is enabled by default when the flash encryption is enabled. If you choose to disable the NVS encryption, you can skip the NVS configuration step given below.
Note: After enabling flash encryption, the bootloader size increases, which means that default offset of the partition table (`menuconfig -> Partition Table -> Offset of partition table`) has to be increased from default value of 0x8000 to prevent the bootloader from overlapping with the partition table. In this example, the offset of the partition table is incereased to 0xD000.
If you chose the Release mode, for better security, you can also disable UART ROM download mode using (`menuconfig -> Security features -> UART ROM download mode -> Permanently disabled`)
Note: This option, after first start of application, makes the device forever inaccessible using UART. Use it with care.
This example demonstrates working with several non-default partitions, therefore a custom partition configuration file is selected. The custom file option (`menuconfig -> Partition Table -> Partition table = Custom partition table CSV`) and the name of file is set in (`menuconfig -> Partition Table -> Custom partition CSV file`). The additional entries in the custom partition table CSV file on top of default ones are described in the NVS and FATFS related subsections below.
For better security, the NVS encryption (`menuconfig -> Component config -> NVS -> Enable NVS encryption`) is enabled by default when the flash encryption is enabled in the menuconfig. If you decide to disable the NVS encryption, you can skip the NVS configuration step given below.
#### Configuration for NVS encryption
For using NVS encryption, the partition table must contain the [NVS key partition](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/nvs_flash.html#nvs-key-partition). Two partition tables containing the NVS keys partition are provided for NVS encryption under the partition table option . They can be selected with the project configuration menu (`menuconfig -> Partition Table`). This particular example uses a custom partition table as it requires a `storage` partition along with the `nvs_keys` partition.
With the NVS encryption is enabled, the partition table must contain additional [NVS key partition](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/nvs_flash.html#nvs-key-partition) atop of all the NVS partitions holding data. This example uses two NVS partitions (`storage`, `nvs`) and one NVS key partition `nvs_keys`. All the partitions required are already preconfigured in `partitions_example.csv`
The configuration for NVS encryption involves generating the XTS encryption keys in the [NVS key partition](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/nvs_flash.html#nvs-key-partition) partition. It can be done with one of the following method.
The partition configuration for the NVS encryption involves generating NVS encryption keys to be stored in [NVS key partition](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/nvs_flash.html#nvs-key-partition). The NVS encryption keys can be either automatically generated by ESP32-based chip during the first run of the application, or generated and flashed to the device by the application developer.
1. Generate the XTS encryption keys on the ESP chip:
Automatic generation of NVS encryption keys on ESP32-based chips:
When the NVS encryption is enabled, the `nvs_flash_init` API function can internally generate the XTS encryption keys on the ESP32-based chip. The API function finds the first [NVS key partition](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/nvs_flash.html#nvs-key-partition), i.e. a partition of type `data` and subtype `nvs_keys` and when this partition is empty, the API function automatically generates NVS keys. (Consult the [`nvs_flash_init`](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/nvs_flash.html#_CPPv414nvs_flash_initv) API documentation in the ESP-IDF programming guide for more details).
When NVS encryption is enabled the `nvs_flash_init` API function can internally generate the XTS encryption keys on the ESP chip. The API function finds the first [NVS key partition](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/nvs_flash.html#nvs-key-partition) i.e. a partition of type `data` and subtype `nvs_keys`.
Then the API function automatically generates and stores the
nvs keys in that partition. New keys are generated and stored only when the respective key partition is empty. (Consult the [`nvs_flash_init`](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/nvs_flash.html#_CPPv414nvs_flash_initv) API documentation in the ESP-IDF programming guide for more details).
**Please note that `nvs_keys` partition has to be completely erased before starting the application. Otherwise the application may generate `ESP_ERR_NVS_CORRUPT_KEY_PART` error code assuming that `nvs_keys` partition was not empty and contains malformed data.**
**Please note that `nvs_keys` partition must be completely erased before starting the application. Otherwise the application may generate `ESP_ERR_NVS_CORRUPT_KEY_PART` error code assuming that `nvs_keys` partition was not empty and contains malformatted data.**
Flash pre-generated NVS encryption keys:
This method is useful when application developer requires presence of known NVS encryption keys on a device. I.e. to allow easier analysis of the NVS partition on a host computer.
For convenience, file `sample_encryption_keys.bin` with a sample of pre-generated NVS encryption keys is provided.
2. Use pre-generated XTS encryption keys:
This method will be required by the user when the `XTS encryption keys` in [NVS key partition](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/nvs_flash.html#nvs-key-partition) are not generated by the application.
The pre generated `Sample XTS encryption keys` can be stored on the flash with help of the following two commands
#### Configuration for FATFS encryption
FATFS encryption example uses two additional partitions in the partition table. Both partitions are of type `data` and subtype `fat`. The first partition `fat_encrypted` has the encrypted flag set, the second partition `fat_not_encr` has the encryption flag off. Both partitions are defined in the partition configuration file `partitions_example.csv`
i) Build and flash the partition table:
```
idf.py partition-table partition-table-flash
```
ii) Store the `sample_encryption_keys.bin` in the `nvs_key`partition (on the flash) with the help of [parttool.py](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/partition-tables.html#partition-tool-parttool-py):
```
parttool.py --port /dev/ttyUSB0 --partition-table-offset 0x9000 write_partition --partition-name="nvs_key" --input sample_encryption_keys.bin
```
The sample [NVS key partition](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/nvs_flash.html#nvs-key-partition) partition used in this example is generated with the help of [NVS Partition Generator Utility](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/nvs_partition_gen.html#nvs-partition-generator-utility)
.
### Build and Flash
When building the project and flashing it to the board FOR THE FIRST TIME after enabling flash encryption feature in menuconfig, run following command to program the target and monitor the output:
### Building
```
idf.py -p PORT flash monitor
idf.py build
```
### Flashing
There are two flash encryption modes: Development and Release.
ESP32-based device's bootloader in the Development mode can encrypt the data to be flashed, whilst the bootloader in the Release mode cannot. Therefore the data to be flashed in Release mode has to be encrypted on a host system in advance. The workflows for both options are described below.
Prerequisites:
- Flashing port of connected ESP32-based device is stored in the environment variable PORT, e.g. `export PORT=/dev/cu.usbserial-14320`
- Current directory is set to the example root, e.g. `esp-idf/examples/security/flash_encryption`
#### Development mode
1. Build and flash the partition table:
```
idf.py --port $PORT partition-table partition-table-flash
```
2. If you want use pre-generated NVS encryption keys, flash the `sample_encryption_keys.bin` into the `nvs_key`partition (on the flash) with the help of [parttool.py](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/partition-tables.html#partition-tool-parttool-py):
```
parttool.py --port $PORT --partition-table-offset 0xD000 write_partition --partition-name="nvs_key" --input sample_encryption_keys.bin
```
The sample [NVS key partition](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/nvs_flash.html#nvs-key-partition) partition used in this example is generated with the help of [NVS Partition Generator Utility](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/nvs_partition_gen.html#nvs-partition-generator-utility)
3. When building the project and flashing it to the board FOR THE FIRST TIME (after enabling the flash encryption feature in menuconfig), run the following command to program the target and monitor the output:
```
idf.py --port $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.
When reprogramming the device subsequently use following command for encrypted write of new plaintext application:
4. When reprogramming the device, subsequently use the following command for encrypted write of the new non-encrypted application's binary:
```
idf.py -p PORT encrypted-app-flash monitor
idf.py --port $PORT encrypted-app-flash monitor
```
Please note above command programs only the app partition. In order to reprogram all partitions (bootloader, partition table and application) in encrypted form use:
5. Please note that above-mentioned command programs only the 'app' partition. In order to reprogram all the partitions (bootloader, partition table and application) and let the bootloader encrypt them on the device, use:
```
idf.py -p PORT encrypted-flash monitor
idf.py --port $PORT encrypted-flash monitor
```
#### Release mode
Full example including host generated NVS encryption keys and pre-encryption of the flash on the host is provided for ESP32 ECO3.
Steps:
1. Generate an unique flash encryption key on a host
```
espsecure.py generate_flash_encryption_key example_flash_encryption_key.bin
```
Alternatively, if you want to further develop the workflow, it might be useful to use all-zeroes "easy to restore" flash encryption key instead. However, never use this key in a production!
```
dd if=/dev/zero of=example_flash_encryption_key.bin bs=1 count=32
```
2. NVS encryption key
This workflow uses NVS encryption key provided by the example repository. It will be flashed to the `nvs_key` partition.
File name is `sample_encryption_keys.bin`
3. Prepare data partitions to be flashed
Developers requiring pre-initialized partitions can use NVS Partition Generator Utility for NVS or FAT File System Generator for FAT filesystem.
This example assumes empty partitions.
```
dd if=/dev/zero bs=1 count=0x1000 of=build/storage.bin
dd if=/dev/zero bs=1 count=0x6000 of=build/nvs.bin
dd if=/dev/zero bs=1 count=0x6000 of=build/custom_nvs.bin
dd if=/dev/zero bs=1 count=0x96000 of=build/fat_encrypted.bin
dd if=/dev/zero bs=1 count=0x96000 of=build/fat_not_encr.bin
```
4. Encrypt flash artefacts on a host using flash encryption key.
First, figure out the offsets of particular artefacts in the flash memory, as it is one of the inputs to the encryption algorithm.
Lookup the section listing partition table in output of the command `idf.py build` i.e.
```
Partition table binary generated. Contents:
*******************************************************************************
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs,data,nvs,0xe000,24K,
storage,data,255,0x14000,4K,encrypted
factory,app,factory,0x20000,1M,
nvs_key,data,nvs_keys,0x120000,4K,encrypted
custom_nvs,data,nvs,0x121000,24K,
fat_encrypted,data,fat,0x127000,600K,encrypted
fat_not_encr,data,fat,0x1bd000,600K,
*******************************************************************************
```
The offsets of the bootloader, application code and the partition table binaries can be found in `build/flash_args`
```
0x1000 bootloader/bootloader.bin
0x20000 flash_encryption.bin
0xd000 partition_table/partition-table.bin
```
From the example application perspective it is necessary to encrypt every artefact except for 'nvs', 'custom_nvs' and 'fat_not_encr' partitions, as their content is expected to be plaintext.
```
espsecure.py encrypt_flash_data --keyfile example_flash_encryption_key.bin --output build/bootloader_encrypted.bin --address 0x1000 build/bootloader/bootloader.bin
espsecure.py encrypt_flash_data --keyfile example_flash_encryption_key.bin --output build/flash_encryption_encrypted.bin --address 0x20000 build/flash_encryption.bin
espsecure.py encrypt_flash_data --keyfile example_flash_encryption_key.bin --output build/partition_table_encrypted.bin --address 0xd000 build/partition_table/partition-table.bin
espsecure.py encrypt_flash_data --keyfile example_flash_encryption_key.bin --output build/storage_encrypted.bin --address 0x14000 build/storage.bin
espsecure.py encrypt_flash_data --keyfile example_flash_encryption_key.bin --output build/sample_encryption_keys_encrypted.bin --address 0x120000 sample_encryption_keys.bin
espsecure.py encrypt_flash_data --keyfile example_flash_encryption_key.bin --output build/fat_encrypted_encrypted.bin --address 0x127000 build/fat_encrypted.bin
```
5. Merge all the flash artefacts into one file
```
esptool.py --chip ESP32 merge_bin -o build\merged_encrypted_flash.bin --flash_mode dio --flash_size 4MB \
0x1000 build/bootloader_encrypted.bin \
0xd000 build/partition_table_encrypted.bin \
0xe000 build/nvs.bin \
0x14000 build/storage_encrypted.bin \
0x20000 build/flash_encryption_encrypted.bin \
0x120000 build/sample_encryption_keys_encrypted.bin \
0x121000 build/nvs.bin \
0x127000 build/fat_encrypted_encrypted.bin \
0x1bd000 build/fat_not_encr.bin
```
Ignore the warning `Warning: Image file at 0x1000 doesn't look like an image file, so not changing any flash settings.`
Output flash binary file is `build\merged_encrypted_flash.bin`
6. Burn the encryption key and set the efuses in ESP32-based device
This step can be performed only once. If the workflow is using non-zero flash encryption key generated in step 1, keep the encryption key file on a safe place for future application updates.
```
espefuse.py --chip esp32 --do-not-confirm --port $PORT burn_key flash_encryption example_flash_encryption_key.bin
espefuse.py --chip esp32 --do-not-confirm --port $PORT burn_efuse FLASH_CRYPT_CONFIG 0xf
espefuse.py --chip esp32 --do-not-confirm --port $PORT burn_efuse FLASH_CRYPT_CNT 127
```
7. Burn security eFuses
WARNING: This step can be performed only once!
```
espefuse.py --chip esp32 --do-not-confirm --port $PORT burn_efuse DISABLE_DL_ENCRYPT 0x1
espefuse.py --chip esp32 --do-not-confirm --port $PORT burn_efuse DISABLE_DL_DECRYPT 0x1
espefuse.py --chip esp32 --do-not-confirm --port $PORT burn_efuse DISABLE_DL_CACHE 0x1
espefuse.py --chip esp32 --do-not-confirm --port $PORT burn_efuse JTAG_DISABLE 0x1
```
8. Flash the image to the ESP32-based device
```
python -m esptool --chip esp32 --port $PORT -b 460800 write_flash --force --flash_mode dio \
--flash_size 4MB --flash_freq 40m \
0x0000 build\merged_encrypted_flash.bin
```
9. Monitor the output, then reset the device
```
idf.py --port $PORT monitor
```
10. You can verify the efuse settings using
```
espefuse.py --chip esp32 --port $PORT summary
```
For subsequent application update, only steps 4,5,8 are required.
## Example Output
### Development mode
When running the example without enabling flash encryption, the output would be as follows (on ESP32):
```
@ -94,8 +254,25 @@ I (398) example: 0x3ffb4db0 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f
Reading with spi_flash_read:
I (408) example: 0x3ffb4da0 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
I (418) example: 0x3ffb4db0 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f |................|
....
I (509) example: FAT partition "fat_not_encr" is not encrypted. Size is (0x96000 bytes)
....
I (3429) example: Read partition using esp_flash_read until test string is found
I (3649) example: 0x3ffb4f90 74 68 65 20 71 75 69 63 6b 20 62 72 6f 77 6e 20 |the quick brown |
I (3649) example: 0x3ffb4fa0 66 6f 78 20 6a 75 6d 70 65 64 20 6f 76 65 72 20 |fox jumped over |
I (3659) example: 0x3ffb4fb0 74 68 65 20 6c 61 7a 79 20 64 6f 67 |the lazy dog|
I (3669) example: Test string was found at offset (0x7000)
....
I (3679) example: FAT partition "fat_encrypted" is not encrypted. Size is (0x96000 bytes)
....
I (7099) example: Read partition using esp_flash_read at expected offset (0x7000)
I (7099) example: 0x3ffb4f84 74 68 65 20 71 75 69 63 6b 20 62 72 6f 77 6e 20 |the quick brown |
I (7099) example: 0x3ffb4f94 66 6f 78 20 6a 75 6d 70 65 64 20 6f 76 65 72 20 |fox jumped over |
I (7109) example: 0x3ffb4fa4 74 68 65 20 6c 61 7a 79 20 64 6f 67 |the lazy dog|
I (7119) example: Data matches test string
```
Note: Above, the partition `fat_encrypted` has flag `encrypted` set in partition table, but it's actual state
is `not encrypted` until the flash encryption is enabled in security part of menuconfig as demonstrated below.
After enabling flash encryption in Development mode, the output shows the process of enabling the flash encryption:
```
@ -132,19 +309,78 @@ I (471) example: 0x3ffb4db0 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f
Reading with spi_flash_read:
I (491) example: 0x3ffb4b30 35 9b f2 07 b4 6d 40 89 28 b4 1e 22 98 7b 4a 36 |5....m@.(..".{J6|
I (491) example: 0x3ffb4b40 ba 89 81 67 77 a3 60 5e 0a e7 51 01 b3 58 c2 f6 |...gw.`^..Q..X..|
....
I (583) example: FAT partition "fat_not_encr" is not encrypted. Size is (0x96000 bytes)
....
I (3863) example: Read partition using esp_flash_read until test string is found
I (4083) example: 0x3ffb4fb0 74 68 65 20 71 75 69 63 6b 20 62 72 6f 77 6e 20 |the quick brown |
I (4083) example: 0x3ffb4fc0 66 6f 78 20 6a 75 6d 70 65 64 20 6f 76 65 72 20 |fox jumped over |
I (4093) example: 0x3ffb4fd0 74 68 65 20 6c 61 7a 79 20 64 6f 67 |the lazy dog|
I (4103) example: Test string was found at offset (0x7000)
....
I (4113) example: FAT partition "fat_encrypted" is encrypted. Size is (0x96000 bytes)
....
I (8033) example: Read partition using esp_flash_read at expected offset (0x7000)
I (8033) example: 0x3ffb4fa4 a7 ea d5 a7 ed cf f6 f7 4a a2 54 a0 4f 92 73 7b |........J.T.O.s{|
I (8043) example: 0x3ffb4fb4 63 eb 5d fc 14 b9 da 3b f2 be d0 94 de eb b2 dc |c.]....;........|
I (8053) example: 0x3ffb4fc4 38 aa 14 62 b7 23 61 7d b6 34 43 53 |8..b.#a}.4CS|
I (8063) example: Data does not match test string
```
If the NVS encryption is enabled, then the output will show the status of the encrypted partition as follows
```
I (667) example_nvs: NVS partition "nvs" is encrypted.
```
### Release mode
In the Release mode no in-place encryption happens after the first reset, as the bootloader assumes the flashed data is already encrypted. The log below shows the run of application after successfully finished flashing.
```
Example to check Flash Encryption status
This is esp32 chip with 2 CPU core(s), WiFi/BT/BLE, silicon revision v1.0, 4MB external flash
FLASH_CRYPT_CNT eFuse value is 127
Flash encryption feature is enabled in RELEASE mode
Erasing partition "storage" (0x1000 bytes)
Writing data with esp_partition_write:
I (483) example: 0x3ffb5000 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
I (483) example: 0x3ffb5010 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f |................|
Reading with esp_partition_read:
I (493) example: 0x3ffb4fe0 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
I (503) example: 0x3ffb4ff0 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f |................|
Reading with esp_flash_read:
I (513) example: 0x3ffb4fe0 16 0a 38 06 c8 29 31 32 b2 1f 5a ca da e9 47 99 |..8..)12..Z...G.|
I (523) example: 0x3ffb4ff0 1d ca d6 6d 55 ad 31 57 d3 d9 df 77 43 f9 4c ca |...mU.1W...wC.L.|
....
I (543) example_fatfs: FAT partition "fat_not_encr" is not encrypted. Size is (0x96000 bytes)
....
I (3573) example_fatfs: Read partition using esp_flash_read until test string is found
I (3793) example_fatfs: 0x3ffb4fc0 74 68 65 20 71 75 69 63 6b 20 62 72 6f 77 6e 20 |the quick brown |
I (3803) example_fatfs: 0x3ffb4fd0 66 6f 78 20 6a 75 6d 70 65 64 20 6f 76 65 72 20 |fox jumped over |
I (3803) example_fatfs: 0x3ffb4fe0 74 68 65 20 6c 61 7a 79 20 64 6f 67 |the lazy dog|
I (3813) example_fatfs: Test string was found at offset (0x7000)
....
I (3823) example_fatfs: FAT partition "fat_encrypted" is encrypted. Size is (0x96000 bytes)
....
I (7303) example_fatfs: Read partition using esp_flash_read at expected offset (0x7000)
I (7303) example_fatfs: 0x3ffb4fb4 3b 8f f4 e1 c1 f4 73 43 d9 28 3f 57 f3 2b d2 b0 |;.....sC.(?W.+..|
I (7313) example_fatfs: 0x3ffb4fc4 41 7a 8f 09 81 1d 92 51 74 8a ee 2a c3 64 8f 75 |Az.....Qt..*.d.u|
I (7323) example_fatfs: 0x3ffb4fd4 61 fe 08 b6 b4 0c cb 08 5a b8 65 29 |a.......Z.e)|
I (7333) example_fatfs: Data does not match test string
```
If the NVS encryption is enabled, then the output will show the status of the encrypted partition as follows
```
I (7373) nvs: NVS partition "nvs" is encrypted.
I (7413) example: NVS partition "custom_nvs" is encrypted.
```
## Troubleshooting
It is also possible to use esptool.py utility to read the eFuse values and check if flash encryption is enabled or not
```
python $IDF_PATH/components/esptool_py/esptool/espefuse.py --port PORT summary
python $IDF_PATH/components/esptool_py/esptool/espefuse.py --port $PORT summary
```
If FLASH_CRYPT_CNT (for ESP32) or SPI_BOOT_CRYPT_CNT (for ESP32-S2 and newer targets) eFuse value is non-zero flash encryption is enabled
If FLASH_CRYPT_CNT (for ESP32) or SPI_BOOT_CRYPT_CNT (for ESP32-S2 and newer targets) eFuse value is non-zero, the flash encryption is enabled

View File

@ -1,2 +1,3 @@
idf_component_register(SRCS "flash_encrypt_main.c"
"flash_encrypt_fatfs.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,192 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* Flash encryption Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <string.h>
#include "esp_flash.h"
#include "esp_partition.h"
#include "esp_vfs.h"
#include "esp_vfs_fat.h"
#include "sdkconfig.h"
#include "flash_encrypt_fatfs.h"
static size_t example_fatfs_partition_test(const esp_partition_t* partition, const size_t text_data_offset);
static const char* TAG = "example_fatfs";
void example_read_write_fatfs(void)
{
const esp_partition_t* partition = NULL;
// Not encrypted partition test
partition = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, CUSTOM_FAT_PART_NAME_NE);
assert(partition);
size_t open_test_string_offset = example_fatfs_partition_test(partition, SIZE_MAX);
assert(open_test_string_offset != SIZE_MAX);
// Encrypted partition test
partition = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, CUSTOM_FAT_PART_NAME_E);
assert(partition);
example_fatfs_partition_test(partition, open_test_string_offset);
}
// Performs fatfs test on the partition.
// Erases, formats, writes and reads text file containing "test string".
// Then:
// If parameter text_data_offset == SIZE_MAX, it tries to find test string on the partition using
// esp_partition_read. Returns offset from the beginning of the partition if "test string" is found, otherwise returns SIZE_MAX
// If parameter text_data_offset != SIZE_MAX, it compares the flash content at the text_data_offset with the "test string"
// If data matches, returns text_data_offset, if it doesn't match, returns SIZE_MAX
static size_t example_fatfs_partition_test(const esp_partition_t* partition, const size_t text_data_offset)
{
const char* TEST_FAT_STRING = "the quick brown fox jumped over the lazy dog";
// Mount path for the partition
const char *base_path = "/spiflash";
// Handle of the wear levelling library instance
wl_handle_t s_wl_handle = WL_INVALID_HANDLE;
esp_err_t err = ESP_FAIL;
ESP_LOGI(TAG, "FAT partition \"%s\" is %sencrypted. Size is (0x%" PRIx32 " bytes) ",
partition->label,
(partition->encrypted) ? "" : "not ",
(long unsigned int)partition->size
);
ESP_LOGI(TAG, "Erasing partition");
ESP_ERROR_CHECK(esp_partition_erase_range(partition, 0, partition->size));
ESP_LOGI(TAG, "Formatting FAT filesystem");
err = esp_vfs_fat_spiflash_format_rw_wl(base_path, partition->label);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to format FATFS (%s)", esp_err_to_name(err));
return SIZE_MAX;
}
ESP_LOGI(TAG, "Mounting FAT filesystem");
// To mount device we need name of device partition, define base_path
// and allow format partition in case if it is new one and was not formatted before
const esp_vfs_fat_mount_config_t mount_config = {
.max_files = 4,
.format_if_mount_failed = false,
.allocation_unit_size = CONFIG_WL_SECTOR_SIZE
};
err = esp_vfs_fat_spiflash_mount_rw_wl(base_path, partition->label, &mount_config, &s_wl_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to mount FATFS (%s)", esp_err_to_name(err));
return SIZE_MAX;
}
const size_t RD_BUFF_LEN = 64;
char read_buffer[RD_BUFF_LEN];
char *device_filename;
device_filename = "/spiflash/inner.txt";
// Open file for writing
ESP_LOGI(TAG, "Opening file");
FILE *f;
f = fopen(device_filename, "wb");
if (f == NULL) {
ESP_LOGE(TAG, "Failed to open file for writing");
return SIZE_MAX;
}
fprintf(f, TEST_FAT_STRING);
fclose(f);
ESP_LOGI(TAG, "Written to file: '%s'", TEST_FAT_STRING);
// Open file for reading
ESP_LOGI(TAG, "Reading file");
f = fopen(device_filename, "rb");
if (f == NULL) {
ESP_LOGE(TAG, "Failed to open file for reading");
return SIZE_MAX;
}
fgets(read_buffer, sizeof(read_buffer), f);
fclose(f);
// strip newline
char *pos = strchr(read_buffer, '\n');
if (pos) {
*pos = '\0';
}
ESP_LOGI(TAG, "Read from file: '%s'", read_buffer);
// Unmount FATFS
ESP_LOGI(TAG, "Unmounting FAT filesystem");
ESP_ERROR_CHECK(esp_vfs_fat_spiflash_unmount_rw_wl(base_path, s_wl_handle));
if (text_data_offset == SIZE_MAX) {
// try to find the TEST_FAT_STRING on the partition using esp_flash_read read.
ESP_LOGI(TAG, "Read partition using esp_flash_read until test string is found");
size_t read_offset = 0;
size_t read_len = RD_BUFF_LEN;
void* text_addr = NULL;
assert(partition->size > RD_BUFF_LEN);
assert(RD_BUFF_LEN > strlen(TEST_FAT_STRING));
// read from partition until it's end
while (true) {
ESP_ERROR_CHECK(esp_flash_read(NULL, read_buffer, partition->address + read_offset, read_len));
// try to find characters, break the loop if found
// buffer is read_buffer, len is read_len
text_addr = memmem(read_buffer, read_len, TEST_FAT_STRING, strlen(TEST_FAT_STRING));
if (text_addr != NULL) {
ESP_LOG_BUFFER_HEXDUMP(TAG, text_addr, strlen(TEST_FAT_STRING), ESP_LOG_INFO);
// calculate offset from the beginning of the partition
size_t test_str_part_offset = (text_addr - (void*)read_buffer) + read_offset;
ESP_LOGI(TAG, "Test string was found at offset (0x%" PRIx32 ")", (long unsigned int)test_str_part_offset);
return test_str_part_offset;
}
// advance read buffer by the RD_BUFF_LEN - strlen(TEST_FAT_STRING)
read_offset += (RD_BUFF_LEN - strlen(TEST_FAT_STRING));
if ((read_offset + strlen(TEST_FAT_STRING)) > partition->size) {
// remaining unread space is not long enough to hold the searched string
break;
} else {
// remaining unread space is either the size of buffer or just the rest up to the end of partition
read_len = (partition->size >= (read_offset + RD_BUFF_LEN)) ? RD_BUFF_LEN : partition->size - read_offset;
}
}
} else {
// offset, where the expected test string should be is in text_data_offset. Try to read it, compare and report
ESP_LOGI(TAG, "Read partition using esp_flash_read at expected offset (0x%" PRIx32 ") ", (long unsigned int)text_data_offset);
assert(text_data_offset <= partition->size);
assert((text_data_offset + strlen(TEST_FAT_STRING)) <= partition->size);
// read from flash
ESP_ERROR_CHECK(esp_flash_read(NULL, read_buffer, partition->address + text_data_offset, strlen(TEST_FAT_STRING)));
ESP_LOG_BUFFER_HEXDUMP(TAG, read_buffer, strlen(TEST_FAT_STRING), ESP_LOG_INFO);
if (memcmp(read_buffer, TEST_FAT_STRING, strlen(TEST_FAT_STRING)) == 0) {
ESP_LOGI(TAG, "Data matches test string");
return text_data_offset;
}
ESP_LOGI(TAG, "Data does not match test string");
}
return SIZE_MAX;
}

View File

@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* Flash encryption Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#define CUSTOM_FAT_PART_NAME_NE "fat_not_encr"
#define CUSTOM_FAT_PART_NAME_E "fat_encrypted"
void example_read_write_fatfs(void);

View File

@ -19,6 +19,8 @@
#include "esp_efuse_table.h"
#include "nvs_flash.h"
#include "flash_encrypt_fatfs.h"
static void example_print_chip_info(void);
static void example_print_flash_encryption_status(void);
static void example_read_write_flash(void);
@ -35,6 +37,23 @@ static const char* TAG = "example";
#define TARGET_CRYPT_CNT_WIDTH 3
#endif
// return true, if partitions necessary to demonstrate fatfs encryption are present in the flash
static bool can_perform_fatfs_example(void)
{
const esp_partition_t *fatfs_ne = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, CUSTOM_FAT_PART_NAME_NE);
const esp_partition_t *fatfs_e = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, CUSTOM_FAT_PART_NAME_E);
if ((fatfs_ne != NULL) && (fatfs_e != NULL)) {
ESP_LOGI(TAG, "Partitions %s and %s for FATFS example are present", CUSTOM_FAT_PART_NAME_NE, CUSTOM_FAT_PART_NAME_E);
return true;
}
return false;
}
static esp_err_t example_custom_nvs_part_init(const char *name)
{
#if CONFIG_NVS_ENCRYPTION
@ -71,6 +90,11 @@ void app_main(void)
example_print_chip_info();
example_print_flash_encryption_status();
example_read_write_flash();
if (can_perform_fatfs_example()) {
example_read_write_fatfs();
}
/* Initialize the default NVS partition */
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
@ -95,15 +119,15 @@ static void example_print_chip_info(void)
uint32_t flash_size;
esp_chip_info(&chip_info);
printf("This is %s chip with %d CPU core(s), WiFi%s%s, ",
CONFIG_IDF_TARGET,
chip_info.cores,
(chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "",
(chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : "");
CONFIG_IDF_TARGET,
chip_info.cores,
(chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "",
(chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : "");
unsigned major_rev = chip_info.revision / 100;
unsigned minor_rev = chip_info.revision % 100;
printf("silicon revision v%d.%d, ", major_rev, minor_rev);
if(esp_flash_get_size(NULL, &flash_size) != ESP_OK) {
if (esp_flash_get_size(NULL, &flash_size) != ESP_OK) {
printf("Get flash size failed");
return;
}
@ -111,7 +135,6 @@ static void example_print_chip_info(void)
(chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external");
}
static void example_print_flash_encryption_status(void)
{
uint32_t flash_crypt_cnt = 0;
@ -123,15 +146,14 @@ static void example_print_flash_encryption_status(void)
printf("Flash encryption feature is disabled\n");
} else {
printf("Flash encryption feature is enabled in %s mode\n",
mode == ESP_FLASH_ENC_MODE_DEVELOPMENT ? "DEVELOPMENT" : "RELEASE");
mode == ESP_FLASH_ENC_MODE_DEVELOPMENT ? "DEVELOPMENT" : "RELEASE");
}
}
static void example_read_write_flash(void)
{
const esp_partition_t* partition = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage");
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage");
assert(partition);
printf("Erasing partition \"%s\" (0x%" PRIx32 " bytes)\n", partition->label, partition->size);

View File

@ -1,9 +1,12 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, , 0x6000,
nvs, data, nvs, , 0x6000,
# Extra partition to demonstrate reading/writing of encrypted flash
storage, data, 0xff, , 0x1000, encrypted
factory, app, factory, , 1M,
storage, data, 0xff, , 0x1000, encrypted
factory, app, factory, , 1M,
# nvs_key partition contains the key that encrypts the NVS partition named nvs. The nvs_key partition needs to be encrypted.
nvs_key, data, nvs_keys, , 0x1000, encrypted,
nvs_key, data, nvs_keys, , 0x1000, encrypted,
# Custom NVS data partition
custom_nvs, data, nvs, , 0x6000,
custom_nvs, data, nvs, , 0x6000,
# FATFS partitions, one non-encrypted, one encrypted
fat_encrypted, data, fat, , 600k, encrypted
fat_not_encr, data, fat, , 600k,

1 # Name, Type, SubType, Offset, Size, Flags
2 nvs, data, nvs, , 0x6000, nvs, data, nvs, , 0x6000,
3 # Extra partition to demonstrate reading/writing of encrypted flash
4 storage, data, 0xff, , 0x1000, encrypted storage, data, 0xff, , 0x1000, encrypted
5 factory, app, factory, , 1M, factory, app, factory, , 1M,
6 # nvs_key partition contains the key that encrypts the NVS partition named nvs. The nvs_key partition needs to be encrypted.
7 nvs_key, data, nvs_keys, , 0x1000, encrypted, nvs_key, data, nvs_keys, , 0x1000, encrypted,
8 # Custom NVS data partition
9 custom_nvs, data, nvs, , 0x6000, custom_nvs, data, nvs, , 0x6000,
10 # FATFS partitions, one non-encrypted, one encrypted
11 fat_encrypted, data, fat, , 600k, encrypted
12 fat_not_encr, data, fat, , 600k,

View File

@ -53,6 +53,12 @@ def _test_flash_encryption(dut: Dut) -> None:
plain_hex_str,
'with esp_flash_read',
expected_str,
'FAT partition "fat_not_encr" is not encrypted.',
'Read partition using esp_flash_read until test string is found',
'Test string was found at offset',
'FAT partition "fat_encrypted" is encrypted.',
'Read partition using esp_flash_read at expected offset',
'Data does not match test string',
# The status of NVS encryption for the "nvs" partition
'NVS partition "nvs" is encrypted.',
# The status of NVS encryption for the "custom_nvs" partition

View File

@ -1,5 +1,7 @@
# This example uses an extra partition to demonstrate encrypted/non-encrypted reads/writes.
# This example uses an extra partitions 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_PARTITION_TABLE_OFFSET=0xD000
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y