mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 03:00:34 +03:00
feat(mbedtls): Support custom storage backend for persistent PSA keys
This commit is contained in:
@@ -39,7 +39,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"
|
||||
@@ -60,6 +61,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}"
|
||||
@@ -243,6 +245,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)
|
||||
@@ -251,6 +254,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()
|
||||
|
||||
@@ -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,70 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "esp_psa_key_file.h"
|
||||
|
||||
#include "psa/crypto.h"
|
||||
#include "psa_crypto_storage.h"
|
||||
|
||||
size_t esp_psa_key_file_size(const 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,
|
||||
const size_t material_len,
|
||||
uint8_t *out_buf,
|
||||
const 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,
|
||||
const 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,122 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
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,
|
||||
const psa_storage_uid_t uid,
|
||||
const uint32_t data_length,
|
||||
const void *p_data,
|
||||
const 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,
|
||||
const psa_storage_uid_t uid,
|
||||
const uint32_t data_offset,
|
||||
const 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,
|
||||
const 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,
|
||||
const 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,87 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* 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(const 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,
|
||||
const size_t material_len,
|
||||
uint8_t *out_buf,
|
||||
const 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,
|
||||
const size_t blob_len,
|
||||
psa_key_attributes_t *attrs,
|
||||
const uint8_t **material,
|
||||
size_t *material_len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user