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

This commit is contained in:
harshal.patil
2026-07-03 10:25:48 +05:30
parent 175d0d844b
commit 3c4586abff
8 changed files with 638 additions and 2 deletions
+5 -1
View File
@@ -36,7 +36,8 @@ if(NOT ${IDF_TARGET} STREQUAL "linux")
endif()
endif()
set(mbedtls_srcs "port/esp_mem.c")
set(mbedtls_srcs "port/esp_mem.c"
"port/psa_crypto_storage/esp_psa_key_file.c")
set(mbedtls_include_dirs
"port/include"
"mbedtls/include"
@@ -57,6 +58,7 @@ endif()
list(APPEND mbedtls_include_dirs "${COMPONENT_DIR}/port/psa_driver/include")
list(APPEND mbedtls_include_dirs "${COMPONENT_DIR}/port/psa_crypto_storage/include")
idf_component_register(SRCS "${mbedtls_srcs}"
INCLUDE_DIRS "${mbedtls_include_dirs}"
@@ -240,6 +242,7 @@ if(NOT ${IDF_TARGET} STREQUAL "linux")
target_link_libraries(tfpsacrypto PRIVATE "$<$<TARGET_EXISTS:idf::nvs_flash>:idf::nvs_flash>")
# Define compile definition to indicate ESP-IDF PSA ITS implementation is available
target_compile_definitions(tfpsacrypto PUBLIC "$<$<TARGET_EXISTS:idf::nvs_flash>:ESP_PSA_ITS_AVAILABLE>")
target_include_directories(tfpsacrypto PRIVATE "${COMPONENT_DIR}/port/psa_crypto_storage/include")
else()
# For v1: check if component is in build before adding source and linking
idf_build_get_property(build_components BUILD_COMPONENTS)
@@ -248,6 +251,7 @@ if(NOT ${IDF_TARGET} STREQUAL "linux")
idf_component_get_property(nvs_flash_lib nvs_flash COMPONENT_LIB)
target_link_libraries(tfpsacrypto PRIVATE ${nvs_flash_lib})
target_compile_definitions(tfpsacrypto PUBLIC ESP_PSA_ITS_AVAILABLE)
target_include_directories(tfpsacrypto PRIVATE "${COMPONENT_DIR}/port/psa_crypto_storage/include")
endif()
endif()
endif()
+32
View File
@@ -30,6 +30,38 @@ menu "mbedTLS"
which is added through vfs component for ESP32 based targets or by
the host system when the target is Linux.
config MBEDTLS_PSA_ITS_CUSTOM_STORAGE_BACKEND
bool "Enable custom storage backend for PSA ITS"
default n
help
Enable support for registering a custom storage backend that
handles PSA ITS operations for keys in a reserved UID range.
When enabled, users can call esp_psa_its_register_custom_backend()
to route storage operations for UIDs in the configured range to
their own implementation. UIDs outside the range continue using
the default NVS backend.
config MBEDTLS_PSA_ITS_CUSTOM_BACKEND_UID_MIN
hex "Minimum UID for custom backend range"
depends on MBEDTLS_PSA_ITS_CUSTOM_STORAGE_BACKEND
default 0x30000000
range 0x00000001 0x3FFFFFFF
help
The minimum UID value (inclusive) that will be routed to the
custom storage backend. Must be within the PSA user key ID
range (0x00000001 - 0x3FFFFFFF).
config MBEDTLS_PSA_ITS_CUSTOM_BACKEND_UID_MAX
hex "Maximum UID for custom backend range"
depends on MBEDTLS_PSA_ITS_CUSTOM_STORAGE_BACKEND
default 0x3FFFFFFF
range MBEDTLS_PSA_ITS_CUSTOM_BACKEND_UID_MIN 0x3FFFFFFF
help
The maximum UID value (inclusive) that will be routed to the
custom storage backend. Must be >= the minimum UID and within
the PSA user key ID range (0x00000001 - 0x3FFFFFFF); the lower
bound is enforced by Kconfig.
config MBEDTLS_THREADING_C
bool "Enable the threading abstraction layer"
default y
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*
@@ -23,8 +23,28 @@
#include "nvs_flash.h"
#include "esp_log.h"
#include "sdkconfig.h"
#if CONFIG_MBEDTLS_PSA_ITS_CUSTOM_STORAGE_BACKEND
#include "esp_psa_its.h"
#endif
static const char *TAG = "esp_psa_its";
#if CONFIG_MBEDTLS_PSA_ITS_CUSTOM_STORAGE_BACKEND
/* Single registered custom backend (NULL when none registered) */
static const esp_psa_its_custom_ops_t *s_custom_ops = NULL;
/**
* Check if a UID falls within the custom backend range.
*/
static inline bool uid_in_custom_range(psa_storage_uid_t uid)
{
return (uid >= CONFIG_MBEDTLS_PSA_ITS_CUSTOM_BACKEND_UID_MIN &&
uid <= CONFIG_MBEDTLS_PSA_ITS_CUSTOM_BACKEND_UID_MAX);
}
#endif /* CONFIG_MBEDTLS_PSA_ITS_CUSTOM_STORAGE_BACKEND */
/* NVS namespace for PSA ITS */
#define PSA_ITS_NVS_NAMESPACE "psa_its"
@@ -106,6 +126,15 @@ psa_status_t psa_its_get_info(psa_storage_uid_t uid,
return PSA_ERROR_INVALID_ARGUMENT;
}
#if CONFIG_MBEDTLS_PSA_ITS_CUSTOM_STORAGE_BACKEND
if (uid_in_custom_range(uid)) {
if (s_custom_ops == NULL || s_custom_ops->get_info == NULL) {
return PSA_ERROR_STORAGE_FAILURE;
}
return s_custom_ops->get_info(s_custom_ops->ctx, uid, p_info);
}
#endif
/* Convert UID to NVS key */
uid_to_nvs_key(uid, nvs_key);
@@ -188,6 +217,16 @@ psa_status_t psa_its_get(psa_storage_uid_t uid,
return PSA_ERROR_INVALID_ARGUMENT;
}
#if CONFIG_MBEDTLS_PSA_ITS_CUSTOM_STORAGE_BACKEND
if (uid_in_custom_range(uid)) {
if (s_custom_ops == NULL || s_custom_ops->get == NULL) {
return PSA_ERROR_STORAGE_FAILURE;
}
return s_custom_ops->get(s_custom_ops->ctx, uid, data_offset,
data_length, p_data, p_data_length);
}
#endif
/* Convert UID to NVS key */
uid_to_nvs_key(uid, nvs_key);
@@ -297,6 +336,16 @@ psa_status_t psa_its_set(psa_storage_uid_t uid,
return PSA_ERROR_INVALID_ARGUMENT;
}
#if CONFIG_MBEDTLS_PSA_ITS_CUSTOM_STORAGE_BACKEND
if (uid_in_custom_range(uid)) {
if (s_custom_ops == NULL || s_custom_ops->set == NULL) {
return PSA_ERROR_STORAGE_FAILURE;
}
return s_custom_ops->set(s_custom_ops->ctx, uid, data_length,
p_data, create_flags);
}
#endif
/* Convert UID to NVS key */
uid_to_nvs_key(uid, nvs_key);
@@ -383,6 +432,15 @@ psa_status_t psa_its_remove(psa_storage_uid_t uid)
psa_its_entry_t *existing_entry = NULL;
psa_status_t status = PSA_ERROR_STORAGE_FAILURE;
#if CONFIG_MBEDTLS_PSA_ITS_CUSTOM_STORAGE_BACKEND
if (uid_in_custom_range(uid)) {
if (s_custom_ops == NULL || s_custom_ops->remove == NULL) {
return PSA_ERROR_STORAGE_FAILURE;
}
return s_custom_ops->remove(s_custom_ops->ctx, uid);
}
#endif
/* Convert UID to NVS key */
uid_to_nvs_key(uid, nvs_key);
@@ -451,3 +509,30 @@ exit:
nvs_close(handle);
return status;
}
#if CONFIG_MBEDTLS_PSA_ITS_CUSTOM_STORAGE_BACKEND
psa_status_t esp_psa_its_register_custom_backend(const esp_psa_its_custom_ops_t *ops)
{
if (ops == NULL || ops->set == NULL || ops->get == NULL ||
ops->get_info == NULL || ops->remove == NULL) {
return PSA_ERROR_INVALID_ARGUMENT;
}
if (s_custom_ops != NULL) {
return PSA_ERROR_NOT_PERMITTED;
}
s_custom_ops = ops;
return PSA_SUCCESS;
}
psa_status_t esp_psa_its_unregister_custom_backend(void)
{
if (s_custom_ops == NULL) {
return PSA_ERROR_DOES_NOT_EXIST;
}
s_custom_ops = NULL;
return PSA_SUCCESS;
}
#endif
@@ -0,0 +1,81 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*
* Thin wrapper around the internal tf-psa-crypto storage helpers
* (psa_format_key_data_for_storage / psa_parse_key_data_from_storage and
* the fork-side psa_persistent_key_storage_blob_size).
*
* The PSA persistent key file format is documented and stable per the
* Mbed TLS storage specification; the C functions that produce/consume it
* are not (they're in tf-psa-crypto/core/, not the public install root).
* This wrapper isolates user code from the internal-symbol coupling: if
* the upstream functions change shape or get removed, only this file
* needs updating — esp_psa_key_file_{size,pack,unpack} stay the same.
*/
#include "esp_psa_key_file.h"
#include "psa/crypto.h"
#include "psa_crypto_storage.h"
size_t esp_psa_key_file_size(size_t material_len)
{
return psa_persistent_key_storage_blob_size(material_len);
}
psa_status_t esp_psa_key_file_pack(const psa_key_attributes_t *attrs,
const uint8_t *material,
size_t material_len,
uint8_t *out_buf,
size_t out_buf_size,
size_t *out_len)
{
if (attrs == NULL || out_buf == NULL || out_len == NULL ||
(material == NULL && material_len != 0)) {
return PSA_ERROR_INVALID_ARGUMENT;
}
const size_t total = psa_persistent_key_storage_blob_size(material_len);
if (out_buf_size < total) {
return PSA_ERROR_BUFFER_TOO_SMALL;
}
psa_format_key_data_for_storage(material, material_len, attrs, out_buf);
*out_len = total;
return PSA_SUCCESS;
}
psa_status_t esp_psa_key_file_unpack(const uint8_t *blob,
size_t blob_len,
psa_key_attributes_t *attrs,
const uint8_t **material,
size_t *material_len)
{
if (blob == NULL || attrs == NULL || material == NULL || material_len == NULL) {
return PSA_ERROR_INVALID_ARGUMENT;
}
uint8_t *upstream_material = NULL;
size_t upstream_material_len = 0;
psa_status_t status = psa_parse_key_data_from_storage(blob, blob_len,
&upstream_material,
&upstream_material_len,
attrs);
if (status != PSA_SUCCESS) {
return status;
}
/* Upstream allocates and copies the material into a fresh buffer.
* Discard the copy and return a pointer into the caller's blob — the
* material section starts immediately after the fixed-size header, so
* the bytes are identical. Preserves the zero-copy public contract. */
*material = (upstream_material_len == 0) ? NULL
: blob + (blob_len - upstream_material_len);
*material_len = upstream_material_len;
psa_free_persistent_key_data(upstream_material, upstream_material_len);
return PSA_SUCCESS;
}
@@ -0,0 +1,126 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*
* PSA ITS custom storage backend API.
*
* When CONFIG_MBEDTLS_PSA_ITS_CUSTOM_STORAGE_BACKEND is enabled, users can register
* a custom storage backend for a reserved range of PSA key IDs. UIDs within
* the configured range are routed to the registered backend; all other UIDs
* continue using the default NVS backend.
*/
#pragma once
#include <stdint.h>
#include <stddef.h>
#include "psa/internal_trusted_storage.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Custom storage backend operations for PSA ITS.
*
* Implement this structure to provide a custom storage backend for PSA ITS
* keys in the configured UID range. Callback signatures mirror the PSA ITS
* API with an additional user-provided context pointer.
*
* The user's implementation is opaque to the framework — it may internally
* route to any number of storage systems (FATFS, SPIFFS, secure elements,
* etc.) based on the UID or any other criteria.
*/
typedef struct {
/**
* @brief Store data for the given UID.
*
* @param ctx User-provided context pointer
* @param uid Storage UID (key ID)
* @param data_length Length of the data to store
* @param p_data Pointer to the data buffer
* @param create_flags Storage flags (e.g., PSA_STORAGE_FLAG_WRITE_ONCE)
* @return PSA status code
*/
psa_status_t (*set)(void *ctx,
psa_storage_uid_t uid,
uint32_t data_length,
const void *p_data,
psa_storage_create_flags_t create_flags);
/**
* @brief Retrieve data for the given UID.
*
* @param ctx User-provided context pointer
* @param uid Storage UID (key ID)
* @param data_offset Byte offset within the stored data
* @param data_length Number of bytes to retrieve
* @param p_data Output buffer for the data
* @param p_data_length On success, set to the number of bytes written
* @return PSA status code
*/
psa_status_t (*get)(void *ctx,
psa_storage_uid_t uid,
uint32_t data_offset,
uint32_t data_length,
void *p_data,
size_t *p_data_length);
/**
* @brief Retrieve metadata for the given UID.
*
* @param ctx User-provided context pointer
* @param uid Storage UID (key ID)
* @param p_info Output structure for size and flags
* @return PSA status code
*/
psa_status_t (*get_info)(void *ctx,
psa_storage_uid_t uid,
struct psa_storage_info_t *p_info);
/**
* @brief Remove data for the given UID.
*
* @param ctx User-provided context pointer
* @param uid Storage UID (key ID)
* @return PSA status code
*/
psa_status_t (*remove)(void *ctx,
psa_storage_uid_t uid);
/** User-provided context pointer, passed as the first argument to all callbacks. */
void *ctx;
} esp_psa_its_custom_ops_t;
/**
* @brief Register a custom storage backend for the configured UID range.
*
* Only one custom backend may be registered at a time. The UID range is
* determined by CONFIG_MBEDTLS_PSA_ITS_CUSTOM_BACKEND_UID_MIN (inclusive) and
* CONFIG_MBEDTLS_PSA_ITS_CUSTOM_BACKEND_UID_MAX (inclusive).
*
* @param ops Pointer to the operations structure. Must remain valid for
* the lifetime of the registration. All four function pointers
* (set, get, get_info, remove) must be non-NULL.
*
* @return PSA_SUCCESS on success
* @return PSA_ERROR_INVALID_ARGUMENT if ops or any callback is NULL
* @return PSA_ERROR_NOT_PERMITTED if a backend is already registered
*/
psa_status_t esp_psa_its_register_custom_backend(const esp_psa_its_custom_ops_t *ops);
/**
* @brief Unregister the currently registered custom storage backend.
*
* After this call, UIDs in the custom range will return
* PSA_ERROR_STORAGE_FAILURE until a new backend is registered.
*
* @return PSA_SUCCESS on success
* @return PSA_ERROR_DOES_NOT_EXIST if no backend is registered
*/
psa_status_t esp_psa_its_unregister_custom_backend(void);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,92 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*
* Pack/unpack PSA persistent-key file blobs in the format documented at
* tf-psa-crypto/docs/architecture/mbed-crypto-storage-specification.md
* ("Key file format for Mbed TLS 2.25.0"), which is the stable storage
* contract for Mbed TLS 2.25 through < 4.
*
* Custom PSA ITS backends that synthesize blobs on read, or that strip the
* header on write to save space, use these helpers to convert between PSA
* attributes + raw key material and the documented blob format without
* depending on tf-psa-crypto internal functions.
*/
#pragma once
#include <stddef.h>
#include <stdint.h>
#include "psa/crypto.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Total blob size produced by esp_psa_key_file_pack() for a given key
* material length (i.e. fixed header size + @p material_len).
*/
size_t esp_psa_key_file_size(size_t material_len);
/**
* @brief Pack key attributes and material into a key file blob.
*
* @param[in] attrs Source attributes (lifetime, type, bits, policy).
* @param[in] material Key material. For transparent keys, the
* psa_export_key() output. For opaque keys, the
* driver-specific opaque blob.
* @param[in] material_len Length of @p material in bytes.
* @param[out] out_buf Destination buffer.
* @param[in] out_buf_size Size of @p out_buf. Must be at least
* esp_psa_key_file_size(material_len).
* @param[out] out_len Set to the number of bytes written.
*
* @retval PSA_SUCCESS
* @retval PSA_ERROR_INVALID_ARGUMENT Null pointer.
* @retval PSA_ERROR_BUFFER_TOO_SMALL @p out_buf_size is too small.
*/
psa_status_t esp_psa_key_file_pack(const psa_key_attributes_t *attrs,
const uint8_t *material,
size_t material_len,
uint8_t *out_buf,
size_t out_buf_size,
size_t *out_len);
/**
* @brief Parse a key file blob into key attributes and material.
*
* The returned @p material pointer points into @p blob and is valid only as
* long as @p blob itself is. No allocation occurs.
*
* Attribute fields described in the blob (lifetime, type, bits, usage,
* algorithm, enrollment algorithm) are set on @p attrs on success. Other
* fields (notably the key id) are left untouched, so the caller can set the
* id beforehand if needed.
*
* @param[in] blob Blob to parse.
* @param[in] blob_len Length of @p blob in bytes.
* @param[out] attrs Filled with parsed attributes on success.
* @param[out] material Set to point to the material section within @p blob,
* or NULL if the blob declares zero-length material.
* @param[out] material_len Length of the material section in bytes.
*
* @retval PSA_SUCCESS
* @retval PSA_ERROR_INVALID_ARGUMENT Null pointer, or @p blob_len smaller than
* ESP_PSA_KEY_FILE_HEADER_SIZE.
* @retval PSA_ERROR_DATA_INVALID Bad magic, unsupported version, or the
* declared material length does not match
* the blob size (the spec rejects trailing
* data on load).
*/
psa_status_t esp_psa_key_file_unpack(const uint8_t *blob,
size_t blob_len,
psa_key_attributes_t *attrs,
const uint8_t **material,
size_t *material_len);
#ifdef __cplusplus
}
#endif