feat(mbedtls): Support custom storage backend for persistent PSA keys

This commit is contained in:
harshal.patil
2026-07-02 16:00:23 +05:30
parent ffaf55cd6b
commit 6634a0b620
8 changed files with 618 additions and 2 deletions
@@ -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`。
应用示例
--------