Merge branch 'feat/psa_its_custom_backend_v6.1' into 'release/v6.1'

Support custom storage backend for persistent PSA keys (v6.1)

See merge request espressif/esp-idf!49151
This commit is contained in:
Mahavir Jain
2026-07-03 11:34:23 +05:30
20 changed files with 1389 additions and 34 deletions

View File

@@ -167,6 +167,114 @@ The new mbedTLS configuration system is organized into logical categories for ea
X.509 certificate parsing, validation, and certificate bundle management.
PSA ITS Custom Storage Backend
-------------------------------
ESP-IDF's PSA Internal Trusted Storage (ITS) implementation uses NVS as its default backend for storing persistent PSA Crypto keys. The custom storage backend feature allows routing a reserved range of PSA key IDs to a user-provided storage implementation, while all other keys continue using NVS.
This is useful when:
- Certain keys need to be stored on a different filesystem (FATFS, SPIFFS, littlefs)
- Keys require hardware-protected encryption (e.g., via TEE secure storage)
- Different storage partitions are needed for different key categories
Enabling the Custom Backend
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Enable the feature via ``menuconfig`` under ``Component Config`` > ``mbedTLS``:
- :ref:`CONFIG_MBEDTLS_PSA_ITS_CUSTOM_STORAGE_BACKEND`: Enable the custom storage backend
- :ref:`CONFIG_MBEDTLS_PSA_ITS_CUSTOM_BACKEND_UID_MIN`: Start of the custom key ID range (default ``0x30000000``)
- :ref:`CONFIG_MBEDTLS_PSA_ITS_CUSTOM_BACKEND_UID_MAX`: End of the custom key ID range (default ``0x3FFFFFFF``)
PSA key IDs within the configured range are routed to the registered backend. All other key IDs (and internal PSA data such as the random seed) continue using the default NVS backend.
Implementing a Custom Backend
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Implement the ``esp_psa_its_custom_ops_t`` callback structure and register it before using PSA Crypto with keys in the custom range:
.. code-block:: c
#include "esp_psa_its.h"
static psa_status_t my_set(void *ctx, const psa_storage_uid_t uid,
const uint32_t data_length, const void *p_data,
const psa_storage_create_flags_t create_flags)
{
/* Store the blob identified by uid */
}
static psa_status_t my_get(void *ctx, const psa_storage_uid_t uid,
const uint32_t data_offset, const uint32_t data_length,
void *p_data, size_t *p_data_length)
{
/* Retrieve the blob identified by uid */
}
static psa_status_t my_get_info(void *ctx, const psa_storage_uid_t uid,
struct psa_storage_info_t *p_info)
{
/* Return size and flags for the blob identified by uid */
}
static psa_status_t my_remove(void *ctx, const psa_storage_uid_t uid)
{
/* Delete the blob identified by uid */
}
static esp_psa_its_custom_ops_t my_ops = {
.set = my_set,
.get = my_get,
.get_info = my_get_info,
.remove = my_remove,
.ctx = NULL, /* optional user context */
};
/* Register before using PSA keys in the custom range */
esp_psa_its_register_custom_backend(&my_ops);
The callback signatures mirror the PSA ITS API. Each callback receives the raw ``psa_storage_uid_t`` (not a string), allowing the implementation to make routing decisions based on the numeric key ID. The ``ctx`` pointer is passed as the first argument to every callback.
.. note::
Only persistent keys flow through the ITS layer. PSA requires ``psa_set_key_id()`` for persistent keys, so the application always controls which key IDs it assigns and thus which range they fall into.
The backend implementation is responsible for enforcing ``psa_storage_create_flags_t`` semantics if needed.
Working with the PSA Key Blob Format
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The byte stream that flows through ``psa_its_set()`` / ``psa_its_get()`` is the PSA persistent key blob format documented in the Mbed TLS `storage specification <https://github.com/Mbed-TLS/TF-PSA-Crypto/blob/development/docs/architecture/mbed-crypto-storage-specification.md>`__. Backends that store the blob verbatim do not need to look inside it. Backends that strip the header on write (to save space) or synthesise the blob on read (for example, to expose a pre-provisioned hardware key) need to construct or parse it themselves.
ESP-IDF provides two helpers in ``esp_psa_key_file.h`` for this:
- :cpp:func:`esp_psa_key_file_pack` — assemble a key blob from a ``psa_key_attributes_t`` structure and raw key material bytes.
- :cpp:func:`esp_psa_key_file_unpack` — parse a key blob back into attributes and a pointer into the key material section.
- :cpp:func:`esp_psa_key_file_size` — return the total blob size for a given material length.
These helpers implement the documented byte layout directly and do not depend on any internal Mbed TLS function. For example, a backend that stores only the inner key bytes can rebuild the blob on read:
.. code-block:: c
#include "esp_psa_key_file.h"
psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT;
psa_set_key_lifetime(&attr, PSA_KEY_LIFETIME_PERSISTENT);
psa_set_key_type(&attr, PSA_KEY_TYPE_AES);
psa_set_key_bits(&attr, key_data_len * 8);
psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT);
psa_set_key_algorithm(&attr, PSA_ALG_CBC_NO_PADDING);
size_t blob_size = esp_psa_key_file_size(key_data_len);
uint8_t *blob = calloc(1, blob_size);
size_t written = 0;
esp_psa_key_file_pack(&attr, key_data, key_data_len, blob, blob_size, &written);
/* blob now holds the full PSA persistent key file; copy the requested
* window into p_data per psa_its_get()'s offset/length arguments. */
For a complete working example using a custom NVS namespace as the custom backend, refer to :example:`security/psa_its_custom_backend`.
Application Examples
--------------------

View File

@@ -167,6 +167,114 @@ ESP-IDF 为 Mbed TLS 提供了基于预设的配置系统,用于简化设置
X.509 证书解析、验证和证书包管理。
PSA ITS 自定义存储后端
-----------------------
ESP-IDF 的 PSA 内部可信存储 (Internal Trusted Storage, ITS) 实现默认使用 NVS 作为持久化 PSA Crypto 密钥的存储后端。自定义存储后端功能允许将保留范围内的 PSA 密钥 ID 路由到用户提供的存储实现,而其他密钥继续使用 NVS。
适用场景包括:
- 某些密钥需要存储在不同的文件系统中(如 FATFS、SPIFFS、littlefs
- 密钥需要硬件保护的加密存储(例如通过 TEE 安全存储)
- 不同类别的密钥需要使用不同的存储分区
启用自定义后端
^^^^^^^^^^^^^^^
通过 ``menuconfig````Component Config`` > ``mbedTLS`` 中启用该功能:
- :ref:`CONFIG_MBEDTLS_PSA_ITS_CUSTOM_STORAGE_BACKEND`:启用自定义存储后端
- :ref:`CONFIG_MBEDTLS_PSA_ITS_CUSTOM_BACKEND_UID_MIN`:自定义密钥 ID 范围的起始值(默认 ``0x30000000``
- :ref:`CONFIG_MBEDTLS_PSA_ITS_CUSTOM_BACKEND_UID_MAX`:自定义密钥 ID 范围的结束值(默认 ``0x3FFFFFFF``
配置范围内的 PSA 密钥 ID 会被路由到已注册的后端。其他所有密钥 ID以及随机种子等 PSA 内部数据)继续使用默认的 NVS 后端。
实现自定义后端
^^^^^^^^^^^^^^^
实现 ``esp_psa_its_custom_ops_t`` 回调结构体,并在使用自定义范围内的 PSA Crypto 密钥之前进行注册:
.. code-block:: c
#include "esp_psa_its.h"
static psa_status_t my_set(void *ctx, const psa_storage_uid_t uid,
const uint32_t data_length, const void *p_data,
const psa_storage_create_flags_t create_flags)
{
/* 存储 uid 对应的 blob */
}
static psa_status_t my_get(void *ctx, const psa_storage_uid_t uid,
const uint32_t data_offset, const uint32_t data_length,
void *p_data, size_t *p_data_length)
{
/* 读取 uid 对应的 blob */
}
static psa_status_t my_get_info(void *ctx, const psa_storage_uid_t uid,
struct psa_storage_info_t *p_info)
{
/* 返回 uid 对应 blob 的大小和标志位 */
}
static psa_status_t my_remove(void *ctx, const psa_storage_uid_t uid)
{
/* 删除 uid 对应的 blob */
}
static esp_psa_its_custom_ops_t my_ops = {
.set = my_set,
.get = my_get,
.get_info = my_get_info,
.remove = my_remove,
.ctx = NULL, /* 可选的用户上下文 */
};
/* 在使用自定义范围内的 PSA 密钥之前注册 */
esp_psa_its_register_custom_backend(&my_ops);
回调函数的签名与 PSA ITS API 保持一致。每个回调接收原始的 ``psa_storage_uid_t``\ (而非字符串),允许实现根据数值型密钥 ID 进行路由决策。``ctx`` 指针会作为第一个参数传递给每个回调。
.. note::
只有持久化密钥会经过 ITS 层。PSA 要求持久化密钥必须调用 ``psa_set_key_id()``,因此应用程序始终可以控制分配哪些密钥 ID进而决定它们落在哪个范围内。
如有需要,后端实现需自行处理 ``psa_storage_create_flags_t`` 的语义。
处理 PSA 密钥文件格式
^^^^^^^^^^^^^^^^^^^^^^
通过 ``psa_its_set()`` / ``psa_its_get()`` 传输的字节流采用 Mbed TLS `存储规范 <https://github.com/Mbed-TLS/TF-PSA-Crypto/blob/development/docs/architecture/mbed-crypto-storage-specification.md>`__\ 中描述的 PSA 持久密钥文件格式。原样存储该 blob 的后端无需查看其内容;在写入时剥离头部以节省空间,或在读取时合成 blob 的后端(例如,将预配置的硬件密钥暴露为持久 PSA 密钥)则需要自行构造或解析。
为此ESP-IDF 在 ``esp_psa_key_file.h`` 中提供以下辅助函数:
- :cpp:func:`esp_psa_key_file_pack` —— 将 ``psa_key_attributes_t`` 结构和原始密钥字节封装为密钥 blob。
- :cpp:func:`esp_psa_key_file_unpack` —— 将密钥 blob 解析回属性以及指向密钥字节段的指针。
- :cpp:func:`esp_psa_key_file_size` —— 根据给定的密钥字节长度返回 blob 的总大小。
这些辅助函数直接实现规范文档中描述的字节布局,不依赖任何 Mbed TLS 内部函数。例如,仅存储内部密钥字节的后端可以在读取时重建 blob
.. code-block:: c
#include "esp_psa_key_file.h"
psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT;
psa_set_key_lifetime(&attr, PSA_KEY_LIFETIME_PERSISTENT);
psa_set_key_type(&attr, PSA_KEY_TYPE_AES);
psa_set_key_bits(&attr, key_data_len * 8);
psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT);
psa_set_key_algorithm(&attr, PSA_ALG_CBC_NO_PADDING);
size_t blob_size = esp_psa_key_file_size(key_data_len);
uint8_t *blob = calloc(1, blob_size);
size_t written = 0;
esp_psa_key_file_pack(&attr, key_data, key_data_len, blob, blob_size, &written);
/* blob 现在包含完整的 PSA 持久密钥文件;按 psa_its_get() 的 offset/length
* 参数将所请求的窗口复制到 p_data。 */
完整的可运行示例(使用自定义 NVS 命名空间作为自定义后端)请参考 :example:`security/psa_its_custom_backend`
应用示例
--------