mirror of
https://github.com/espressif/esp-idf.git
synced 2026-08-11 17:52:00 +03:00
Merge branch 'feat/support_persistent_esp_rsa_ds_keys_v6.0' into 'release/v6.0'
Support persistent ESP RSA DS keys (v6.0) See merge request espressif/esp-idf!48523
This commit is contained in:
@@ -19,7 +19,7 @@ if(CONFIG_SECURE_TEE_ATTESTATION)
|
||||
list(APPEND srcs "test_esp_tee_att.c")
|
||||
endif()
|
||||
|
||||
set(mbedtls_test_srcs_dir "${idf_path}/components/mbedtls/test_apps/main")
|
||||
set(mbedtls_test_srcs_dir "${idf_path}/components/mbedtls/test_apps/mbedtls_ut/main")
|
||||
|
||||
#AES
|
||||
if(CONFIG_SOC_AES_SUPPORTED)
|
||||
|
||||
Submodule components/mbedtls/mbedtls updated: 8bfdb42530...6cc42afad3
@@ -11,10 +11,12 @@
|
||||
|
||||
#include "psa_crypto_driver_esp_rsa_ds.h"
|
||||
#include "psa_crypto_driver_esp_rsa_ds_contexts.h"
|
||||
#include "psa_crypto_driver_esp_opaque_common.h"
|
||||
#include "include/psa_crypto_driver_esp_rsa_ds_utilities.h"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_efuse.h"
|
||||
#include "esp_assert.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
#if SOC_KEY_MANAGER_SUPPORTED
|
||||
@@ -28,6 +30,281 @@ static const char *TAG = "PSA_RSA_DS";
|
||||
static SemaphoreHandle_t s_ds_lock = NULL;
|
||||
static int s_timeout_ms = 0;
|
||||
|
||||
/*
|
||||
* Per-source storage structs — internal to the driver.
|
||||
* These are what actually get persisted to NVS, not the user-facing esp_rsa_ds_opaque_key_t.
|
||||
*
|
||||
* All storage structs share a common prefix: [version][key_source]
|
||||
* so that the driver can identify the key source at operation time.
|
||||
*/
|
||||
typedef enum {
|
||||
ESP_RSA_DS_KEY_STORAGE_VERSION_INVALID = 0,
|
||||
ESP_RSA_DS_KEY_STORAGE_VERSION_V1 = 1,
|
||||
ESP_RSA_DS_KEY_STORAGE_VERSION_MAX = 2,
|
||||
} esp_rsa_ds_key_storage_version_t;
|
||||
|
||||
typedef enum {
|
||||
ESP_RSA_DS_KEY_SOURCE_EFUSE = 0,
|
||||
ESP_RSA_DS_KEY_SOURCE_KEY_MGR = 1,
|
||||
} esp_rsa_ds_key_source_t;
|
||||
|
||||
/* Storage structs use uint8_t for key_source instead of enum
|
||||
* to ensure stable serialized size across compilers. */
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint8_t version;
|
||||
uint8_t key_source; /* esp_rsa_ds_key_source_t */
|
||||
} esp_rsa_ds_common_key_storage_metadata_t;
|
||||
|
||||
ESP_STATIC_ASSERT(sizeof(esp_rsa_ds_common_key_storage_metadata_t) == 2 * sizeof(uint8_t),
|
||||
"esp_rsa_ds_common_key_storage_metadata_t must be exactly 2 bytes");
|
||||
|
||||
/* esp_ds_data_t is serialised verbatim into NVS as part of the persistent storage structs,
|
||||
* and its first field is an enum (esp_digital_signature_length_t). esp_ds.h documents that
|
||||
* "in IDF, the enum type length is the same as of type unsigned" — assert it here so a
|
||||
* future toolchain change that shrinks enums fails the build instead of silently making
|
||||
* existing stored DS keys unreadable. */
|
||||
ESP_STATIC_ASSERT(sizeof(esp_digital_signature_length_t) == sizeof(unsigned),
|
||||
"esp_digital_signature_length_t must be sized as unsigned for stable NVS layout of esp_ds_data_t");
|
||||
|
||||
/**
|
||||
* Pointer-based storage for volatile keys.
|
||||
* The caller must keep all referenced data valid until psa_destroy_key().
|
||||
* This avoids deep-copying esp_ds_data_t (~1600 bytes), preserving the
|
||||
* heap savings of mmap'd flash data from esp_secure_cert_mgr.
|
||||
*/
|
||||
typedef struct {
|
||||
esp_rsa_ds_common_key_storage_metadata_t metadata;
|
||||
esp_ds_data_ctx_t *ds_data_ctx;
|
||||
#if SOC_KEY_MANAGER_SUPPORTED
|
||||
esp_key_mgr_key_recovery_info_t *key_recovery_info;
|
||||
#endif /* SOC_KEY_MANAGER_SUPPORTED */
|
||||
} esp_rsa_ds_volatile_key_storage_t;
|
||||
|
||||
/**
|
||||
* Inline storage for persistent eFuse DS keys.
|
||||
* All data is deep-copied — no external references.
|
||||
*/
|
||||
typedef struct {
|
||||
esp_rsa_ds_common_key_storage_metadata_t metadata;
|
||||
uint8_t efuse_key_id;
|
||||
uint8_t reserved; /* explicit padding */
|
||||
uint16_t rsa_length_bits;
|
||||
uint8_t reserved2[2]; /* explicit padding for ds_data 4-byte alignment */
|
||||
esp_ds_data_t ds_data;
|
||||
} esp_rsa_ds_efuse_key_storage_t;
|
||||
|
||||
ESP_STATIC_ASSERT(offsetof(esp_rsa_ds_efuse_key_storage_t, ds_data) % 4 == 0,
|
||||
"ds_data must be 4-byte aligned in esp_rsa_ds_efuse_key_storage_t");
|
||||
|
||||
#if SOC_KEY_MANAGER_SUPPORTED
|
||||
/**
|
||||
* Inline storage for persistent Key Manager DS keys.
|
||||
* All data is deep-copied — no external references.
|
||||
*/
|
||||
typedef struct {
|
||||
esp_rsa_ds_common_key_storage_metadata_t metadata;
|
||||
uint16_t rsa_length_bits;
|
||||
esp_ds_data_t ds_data;
|
||||
esp_key_mgr_key_recovery_info_t key_recovery_info;
|
||||
} esp_rsa_ds_km_key_storage_t;
|
||||
|
||||
ESP_STATIC_ASSERT(offsetof(esp_rsa_ds_km_key_storage_t, ds_data) % 4 == 0,
|
||||
"ds_data must be 4-byte aligned in esp_rsa_ds_km_key_storage_t");
|
||||
ESP_STATIC_ASSERT(offsetof(esp_rsa_ds_km_key_storage_t, key_recovery_info) % 4 == 0,
|
||||
"key_recovery_info must be 4-byte aligned in esp_rsa_ds_km_key_storage_t");
|
||||
#endif /* SOC_KEY_MANAGER_SUPPORTED */
|
||||
|
||||
/**
|
||||
* @brief Read the key_source tag from any storage struct.
|
||||
*
|
||||
* All storage structs share the layout: [version(1)][key_source(1)][...]
|
||||
*/
|
||||
static inline esp_rsa_ds_key_source_t rsa_ds_storage_get_key_source(const uint8_t *key_buffer)
|
||||
{
|
||||
return (esp_rsa_ds_key_source_t)key_buffer[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculate the storage buffer size for an import operation.
|
||||
*
|
||||
* For volatile keys: returns pointer-based storage size (small).
|
||||
* For persistent keys: inspects the user struct to determine key source
|
||||
* and returns the corresponding inline storage struct size (large).
|
||||
*/
|
||||
static size_t esp_rsa_ds_get_storage_size(const esp_rsa_ds_opaque_key_t *key, bool persistent)
|
||||
{
|
||||
if (!persistent) {
|
||||
return sizeof(esp_rsa_ds_volatile_key_storage_t);
|
||||
}
|
||||
|
||||
#if SOC_KEY_MANAGER_SUPPORTED
|
||||
if (key->key_recovery_info) {
|
||||
return sizeof(esp_rsa_ds_km_key_storage_t);
|
||||
}
|
||||
#endif /* SOC_KEY_MANAGER_SUPPORTED */
|
||||
|
||||
(void)key;
|
||||
return sizeof(esp_rsa_ds_efuse_key_storage_t);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculate the expected storage size from an already-serialized storage buffer.
|
||||
*
|
||||
* @param key_buffer The storage buffer.
|
||||
* @param key_buffer_size Size of @p key_buffer in bytes.
|
||||
* @param persistent true if the key is persistent (inline data), false if volatile.
|
||||
* @param[out] expected_storage_size The expected minimum buffer size.
|
||||
*/
|
||||
static psa_status_t esp_rsa_ds_get_expected_storage_size(const uint8_t *key_buffer,
|
||||
size_t key_buffer_size,
|
||||
bool persistent,
|
||||
size_t *expected_storage_size)
|
||||
{
|
||||
if (key_buffer_size < sizeof(esp_rsa_ds_common_key_storage_metadata_t)) {
|
||||
return PSA_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (key_buffer[0] == ESP_RSA_DS_KEY_STORAGE_VERSION_INVALID || key_buffer[0] >= ESP_RSA_DS_KEY_STORAGE_VERSION_MAX) {
|
||||
return PSA_ERROR_DATA_INVALID;
|
||||
}
|
||||
|
||||
*expected_storage_size = 0;
|
||||
|
||||
if (!persistent) {
|
||||
*expected_storage_size = sizeof(esp_rsa_ds_volatile_key_storage_t);
|
||||
return PSA_SUCCESS;
|
||||
}
|
||||
|
||||
esp_rsa_ds_key_source_t key_source = rsa_ds_storage_get_key_source(key_buffer);
|
||||
|
||||
switch (key_source) {
|
||||
#if SOC_KEY_MANAGER_SUPPORTED
|
||||
case ESP_RSA_DS_KEY_SOURCE_KEY_MGR:
|
||||
*expected_storage_size = sizeof(esp_rsa_ds_km_key_storage_t);
|
||||
return PSA_SUCCESS;
|
||||
#endif /* SOC_KEY_MANAGER_SUPPORTED */
|
||||
case ESP_RSA_DS_KEY_SOURCE_EFUSE:
|
||||
*expected_storage_size = sizeof(esp_rsa_ds_efuse_key_storage_t);
|
||||
return PSA_SUCCESS;
|
||||
default:
|
||||
return PSA_ERROR_DATA_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Validate a storage buffer and extract the per-source DS parameters.
|
||||
*
|
||||
* Centralises the metadata + size validation and the per-source pointer/value extraction
|
||||
* shared by the sign-hash and asymmetric-decrypt entry points. On success, all out-params
|
||||
* are filled. On failure, out-params are left untouched.
|
||||
*
|
||||
* @param key_buffer The PSA storage buffer.
|
||||
* @param key_buffer_size Size of @p key_buffer in bytes.
|
||||
* @param is_persistent Persistence flag derived from the PSA key attributes.
|
||||
* @param[out] key_source Storage key source tag (eFuse / Key Manager).
|
||||
* @param[out] rsa_length_bits RSA key length in bits, taken from the storage struct.
|
||||
* @param[out] ds_data Pointer to the in-storage @c esp_ds_data_t (volatile: caller's
|
||||
* mmap'd flash buffer; persistent: the inline copy).
|
||||
* @param[out] hmac_key_id HMAC key id for the DS peripheral.
|
||||
* @param[out] km_ri Pointer to the @c esp_key_mgr_key_recovery_info_t to pass to
|
||||
* @c esp_key_mgr_activate_key, or NULL when the key source is
|
||||
* not Key Manager. Only present on KM-capable SoCs.
|
||||
*
|
||||
* @return PSA_SUCCESS, or a PSA error if validation fails.
|
||||
*/
|
||||
static psa_status_t esp_rsa_ds_extract_storage(
|
||||
const uint8_t *key_buffer,
|
||||
size_t key_buffer_size,
|
||||
bool is_persistent,
|
||||
esp_rsa_ds_key_source_t *key_source,
|
||||
uint16_t *rsa_length_bits,
|
||||
const esp_ds_data_t **ds_data,
|
||||
hmac_key_id_t *hmac_key_id
|
||||
#if SOC_KEY_MANAGER_SUPPORTED
|
||||
, esp_key_mgr_key_recovery_info_t **km_ri
|
||||
#endif /* SOC_KEY_MANAGER_SUPPORTED */
|
||||
)
|
||||
{
|
||||
size_t expected_storage_size = 0;
|
||||
psa_status_t status = esp_rsa_ds_get_expected_storage_size(key_buffer, key_buffer_size,
|
||||
is_persistent, &expected_storage_size);
|
||||
if (status != PSA_SUCCESS) {
|
||||
return PSA_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (key_buffer_size < expected_storage_size) {
|
||||
return PSA_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
esp_rsa_ds_key_source_t src = rsa_ds_storage_get_key_source(key_buffer);
|
||||
uint16_t bits = 0;
|
||||
const esp_ds_data_t *data = NULL;
|
||||
hmac_key_id_t hmac_id = 0;
|
||||
#if SOC_KEY_MANAGER_SUPPORTED
|
||||
esp_key_mgr_key_recovery_info_t *ri = NULL;
|
||||
#endif /* SOC_KEY_MANAGER_SUPPORTED */
|
||||
|
||||
if (!is_persistent) {
|
||||
const esp_rsa_ds_volatile_key_storage_t *ptr_st =
|
||||
(const esp_rsa_ds_volatile_key_storage_t *)key_buffer;
|
||||
bits = ptr_st->ds_data_ctx->rsa_length_bits;
|
||||
data = ptr_st->ds_data_ctx->esp_ds_data;
|
||||
hmac_id = ptr_st->ds_data_ctx->efuse_key_id;
|
||||
#if SOC_KEY_MANAGER_SUPPORTED
|
||||
if (src == ESP_RSA_DS_KEY_SOURCE_KEY_MGR) {
|
||||
hmac_id = HMAC_KEY_KM;
|
||||
ri = ptr_st->key_recovery_info;
|
||||
}
|
||||
#endif /* SOC_KEY_MANAGER_SUPPORTED */
|
||||
} else {
|
||||
switch (src) {
|
||||
case ESP_RSA_DS_KEY_SOURCE_EFUSE: {
|
||||
const esp_rsa_ds_efuse_key_storage_t *efuse_st =
|
||||
(const esp_rsa_ds_efuse_key_storage_t *)key_buffer;
|
||||
bits = efuse_st->rsa_length_bits;
|
||||
data = &efuse_st->ds_data;
|
||||
hmac_id = efuse_st->efuse_key_id;
|
||||
break;
|
||||
}
|
||||
#if SOC_KEY_MANAGER_SUPPORTED
|
||||
case ESP_RSA_DS_KEY_SOURCE_KEY_MGR: {
|
||||
const esp_rsa_ds_km_key_storage_t *km_st =
|
||||
(const esp_rsa_ds_km_key_storage_t *)key_buffer;
|
||||
bits = km_st->rsa_length_bits;
|
||||
data = &km_st->ds_data;
|
||||
hmac_id = HMAC_KEY_KM;
|
||||
/* esp_key_mgr_activate_key() takes a non-const pointer for API compatibility
|
||||
* but does not modify the recovery info. The cast away const is therefore safe;
|
||||
* if that contract ever changes, copy the recovery info to a local first. */
|
||||
ri = (esp_key_mgr_key_recovery_info_t *)&km_st->key_recovery_info;
|
||||
break;
|
||||
}
|
||||
#endif /* SOC_KEY_MANAGER_SUPPORTED */
|
||||
default:
|
||||
return PSA_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
}
|
||||
|
||||
if (bits % 32 != 0 || bits < 1024 || bits > SOC_DS_SIGNATURE_MAX_BIT_LEN) {
|
||||
return PSA_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (data->rsa_length != (bits / 32) - 1) {
|
||||
return PSA_ERROR_DATA_INVALID;
|
||||
}
|
||||
|
||||
*key_source = src;
|
||||
*rsa_length_bits = bits;
|
||||
*ds_data = data;
|
||||
*hmac_key_id = hmac_id;
|
||||
#if SOC_KEY_MANAGER_SUPPORTED
|
||||
*km_ri = ri;
|
||||
#endif /* SOC_KEY_MANAGER_SUPPORTED */
|
||||
|
||||
return PSA_SUCCESS;
|
||||
}
|
||||
|
||||
void esp_rsa_ds_release_ds_lock(void);
|
||||
|
||||
static int esp_rsa_ds_pad(esp_rsa_ds_padding_t padding, psa_algorithm_t hash_alg, unsigned int hashlen,
|
||||
@@ -129,37 +406,48 @@ psa_status_t esp_rsa_ds_opaque_sign_hash_start(
|
||||
return PSA_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (key_buffer_size < sizeof(esp_rsa_ds_opaque_key_t)) {
|
||||
return PSA_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
bool is_persistent = esp_opaque_key_is_persistent(attributes);
|
||||
|
||||
if (!PSA_ALG_IS_RSA_PKCS1V15_SIGN(alg) && !PSA_ALG_IS_RSA_PSS(alg)) {
|
||||
return PSA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
operation->alg = alg;
|
||||
esp_rsa_ds_key_source_t key_source = ESP_RSA_DS_KEY_SOURCE_EFUSE;
|
||||
uint16_t rsa_length_bits = 0;
|
||||
const esp_ds_data_t *ds_data = NULL;
|
||||
hmac_key_id_t hmac_key_id = 0;
|
||||
#if SOC_KEY_MANAGER_SUPPORTED
|
||||
esp_key_mgr_key_recovery_info_t *km_ri = NULL;
|
||||
#endif /* SOC_KEY_MANAGER_SUPPORTED */
|
||||
|
||||
const esp_rsa_ds_opaque_key_t *opaque_key = (const esp_rsa_ds_opaque_key_t *)key_buffer;
|
||||
operation->esp_rsa_ds_opaque_key = opaque_key;
|
||||
|
||||
if (esp_rsa_ds_validate_opaque_key(opaque_key) != PSA_SUCCESS) {
|
||||
return PSA_ERROR_INVALID_ARGUMENT;
|
||||
psa_status_t status = esp_rsa_ds_extract_storage(
|
||||
key_buffer, key_buffer_size, is_persistent,
|
||||
&key_source, &rsa_length_bits, &ds_data, &hmac_key_id
|
||||
#if SOC_KEY_MANAGER_SUPPORTED
|
||||
, &km_ri
|
||||
#endif /* SOC_KEY_MANAGER_SUPPORTED */
|
||||
);
|
||||
if (status != PSA_SUCCESS) {
|
||||
return status;
|
||||
}
|
||||
|
||||
operation->alg = alg;
|
||||
operation->key_buffer = key_buffer;
|
||||
|
||||
if ((xSemaphoreTake(s_ds_lock, s_timeout_ms / portTICK_PERIOD_MS) != pdTRUE)) {
|
||||
return PSA_ERROR_GENERIC_ERROR;
|
||||
}
|
||||
|
||||
esp_rsa_ds_padding_t padding = ESP_RSA_DS_PADDING_INVALID;
|
||||
if (PSA_ALG_IS_RSA_PSS(operation->alg)) {
|
||||
if (PSA_ALG_IS_RSA_PSS(alg)) {
|
||||
padding = ESP_RSA_DS_PADDING_PSS;
|
||||
} else if (PSA_ALG_IS_RSA_PKCS1V15_SIGN(operation->alg)) {
|
||||
} else if (PSA_ALG_IS_RSA_PKCS1V15_SIGN(alg)) {
|
||||
padding = ESP_RSA_DS_PADDING_PKCS_V15;
|
||||
}
|
||||
|
||||
psa_algorithm_t hash_alg = PSA_ALG_SIGN_GET_HASH(operation->alg);
|
||||
psa_algorithm_t hash_alg = PSA_ALG_SIGN_GET_HASH(alg);
|
||||
|
||||
const size_t words_len = (opaque_key->ds_data_ctx->rsa_length_bits / 32);
|
||||
const size_t words_len = rsa_length_bits / 32;
|
||||
const size_t rsa_len_bytes = words_len * 4;
|
||||
operation->sig_buffer_size = rsa_len_bytes;
|
||||
operation->sig_buffer = NULL;
|
||||
@@ -170,7 +458,7 @@ psa_status_t esp_rsa_ds_opaque_sign_hash_start(
|
||||
return PSA_ERROR_INSUFFICIENT_MEMORY;
|
||||
}
|
||||
|
||||
psa_status_t status = esp_rsa_ds_pad(
|
||||
status = esp_rsa_ds_pad(
|
||||
padding, hash_alg, hash_length, hash, -1, em, rsa_len_bytes);
|
||||
if (status != PSA_SUCCESS) {
|
||||
goto error;
|
||||
@@ -188,24 +476,21 @@ psa_status_t esp_rsa_ds_opaque_sign_hash_start(
|
||||
sig_words[i] = SWAP_INT32(em_words[words_len - (i + 1)]);
|
||||
}
|
||||
|
||||
hmac_key_id_t hmac_key_id = opaque_key->ds_data_ctx->efuse_key_id;
|
||||
|
||||
#if SOC_KEY_MANAGER_SUPPORTED
|
||||
esp_key_mgr_key_recovery_info_t *km_key_recovery_info = operation->esp_rsa_ds_opaque_key->key_recovery_info;
|
||||
if (km_key_recovery_info) {
|
||||
err = esp_key_mgr_activate_key(km_key_recovery_info);
|
||||
if (key_source == ESP_RSA_DS_KEY_SOURCE_KEY_MGR) {
|
||||
err = esp_key_mgr_activate_key(km_ri);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to activate key: 0x%x", err);
|
||||
status = PSA_ERROR_INVALID_HANDLE;
|
||||
goto error;
|
||||
}
|
||||
hmac_key_id = HMAC_KEY_KM;
|
||||
operation->is_km_key_active = true;
|
||||
operation->km_ri = km_ri;
|
||||
}
|
||||
#endif /* SOC_KEY_MANAGER_SUPPORTED */
|
||||
|
||||
err = esp_ds_start_sign((const void *)operation->sig_buffer,
|
||||
opaque_key->ds_data_ctx->esp_ds_data,
|
||||
ds_data,
|
||||
hmac_key_id,
|
||||
&operation->esp_rsa_ds_ctx);
|
||||
if (err != ESP_OK) {
|
||||
@@ -239,7 +524,7 @@ psa_status_t esp_rsa_ds_opaque_sign_hash_complete(
|
||||
return PSA_ERROR_BAD_STATE;
|
||||
}
|
||||
|
||||
int expected_signature_size = operation->esp_rsa_ds_opaque_key->ds_data_ctx->rsa_length_bits / 8;
|
||||
size_t expected_signature_size = operation->sig_buffer_size;
|
||||
if (signature_size < expected_signature_size) {
|
||||
return PSA_ERROR_BUFFER_TOO_SMALL;
|
||||
}
|
||||
@@ -277,15 +562,15 @@ psa_status_t esp_rsa_ds_opaque_sign_hash_abort(
|
||||
return PSA_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (operation->esp_rsa_ds_opaque_key) {
|
||||
#if SOC_KEY_MANAGER_SUPPORTED
|
||||
esp_key_mgr_key_recovery_info_t *km_key_recovery_info = operation->esp_rsa_ds_opaque_key->key_recovery_info;
|
||||
if (km_key_recovery_info && operation->is_km_key_active) {
|
||||
esp_key_mgr_deactivate_key(km_key_recovery_info->key_type);
|
||||
operation->is_km_key_active = false;
|
||||
}
|
||||
if (operation->is_km_key_active) {
|
||||
esp_key_mgr_deactivate_key(operation->km_ri->key_type);
|
||||
operation->is_km_key_active = false;
|
||||
}
|
||||
#endif /* SOC_KEY_MANAGER_SUPPORTED */
|
||||
operation->esp_rsa_ds_opaque_key = NULL;
|
||||
|
||||
if (operation->key_buffer) {
|
||||
operation->key_buffer = NULL;
|
||||
}
|
||||
|
||||
if (operation->esp_rsa_ds_ctx) {
|
||||
@@ -362,33 +647,100 @@ psa_status_t esp_rsa_ds_opaque_import_key(
|
||||
return PSA_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (key_buffer_size < sizeof(esp_rsa_ds_opaque_key_t)) {
|
||||
return PSA_ERROR_BUFFER_TOO_SMALL;
|
||||
}
|
||||
|
||||
const esp_rsa_ds_opaque_key_t *opaque_key = (const esp_rsa_ds_opaque_key_t *)data;
|
||||
int ret = esp_rsa_ds_validate_opaque_key(opaque_key);
|
||||
if (ret != PSA_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Shallow copy: key buffer holds the context; esp_ds_data points to the caller's data.
|
||||
* The key material (esp_rsa_ds_opaque_key_t and the esp_ds_data_t it points to) must remain
|
||||
* valid until psa_destroy_key() is called on this key. */
|
||||
memcpy(key_buffer, opaque_key, sizeof(esp_rsa_ds_opaque_key_t));
|
||||
*key_buffer_length = sizeof(esp_rsa_ds_opaque_key_t);
|
||||
bool is_persistent = esp_opaque_key_is_persistent(attributes);
|
||||
|
||||
esp_rsa_ds_key_source_t key_source = ESP_RSA_DS_KEY_SOURCE_EFUSE;
|
||||
#if SOC_KEY_MANAGER_SUPPORTED
|
||||
if (opaque_key->key_recovery_info) {
|
||||
key_source = ESP_RSA_DS_KEY_SOURCE_KEY_MGR;
|
||||
}
|
||||
#endif /* SOC_KEY_MANAGER_SUPPORTED */
|
||||
|
||||
if (!is_persistent) {
|
||||
/* Volatile: store pointers only — caller keeps data alive.
|
||||
* This preserves the heap savings of mmap'd flash data from esp_secure_cert_mgr. */
|
||||
if (key_buffer_size < sizeof(esp_rsa_ds_volatile_key_storage_t)) {
|
||||
return PSA_ERROR_BUFFER_TOO_SMALL;
|
||||
}
|
||||
|
||||
esp_rsa_ds_volatile_key_storage_t *storage = (esp_rsa_ds_volatile_key_storage_t *)key_buffer;
|
||||
storage->metadata.version = ESP_RSA_DS_KEY_STORAGE_VERSION_V1;
|
||||
storage->metadata.key_source = key_source;
|
||||
storage->ds_data_ctx = opaque_key->ds_data_ctx;
|
||||
#if SOC_KEY_MANAGER_SUPPORTED
|
||||
storage->key_recovery_info = opaque_key->key_recovery_info;
|
||||
#endif /* SOC_KEY_MANAGER_SUPPORTED */
|
||||
*key_buffer_length = sizeof(esp_rsa_ds_volatile_key_storage_t);
|
||||
} else {
|
||||
/* Persistent: deep-copy all data into self-contained storage struct */
|
||||
#if SOC_KEY_MANAGER_SUPPORTED
|
||||
if (key_source == ESP_RSA_DS_KEY_SOURCE_KEY_MGR) {
|
||||
if (key_buffer_size < sizeof(esp_rsa_ds_km_key_storage_t)) {
|
||||
return PSA_ERROR_BUFFER_TOO_SMALL;
|
||||
}
|
||||
|
||||
esp_rsa_ds_km_key_storage_t *storage = (esp_rsa_ds_km_key_storage_t *)key_buffer;
|
||||
storage->metadata.version = ESP_RSA_DS_KEY_STORAGE_VERSION_V1;
|
||||
storage->metadata.key_source = ESP_RSA_DS_KEY_SOURCE_KEY_MGR;
|
||||
storage->rsa_length_bits = opaque_key->ds_data_ctx->rsa_length_bits;
|
||||
memcpy(&storage->ds_data, opaque_key->ds_data_ctx->esp_ds_data, sizeof(esp_ds_data_t));
|
||||
memcpy(&storage->key_recovery_info, opaque_key->key_recovery_info,
|
||||
sizeof(esp_key_mgr_key_recovery_info_t));
|
||||
*key_buffer_length = sizeof(esp_rsa_ds_km_key_storage_t);
|
||||
} else
|
||||
#endif /* SOC_KEY_MANAGER_SUPPORTED */
|
||||
{
|
||||
if (key_buffer_size < sizeof(esp_rsa_ds_efuse_key_storage_t)) {
|
||||
return PSA_ERROR_BUFFER_TOO_SMALL;
|
||||
}
|
||||
|
||||
esp_rsa_ds_efuse_key_storage_t *storage = (esp_rsa_ds_efuse_key_storage_t *)key_buffer;
|
||||
storage->metadata.version = ESP_RSA_DS_KEY_STORAGE_VERSION_V1;
|
||||
storage->metadata.key_source = ESP_RSA_DS_KEY_SOURCE_EFUSE;
|
||||
storage->efuse_key_id = opaque_key->ds_data_ctx->efuse_key_id;
|
||||
storage->reserved = 0;
|
||||
storage->rsa_length_bits = opaque_key->ds_data_ctx->rsa_length_bits;
|
||||
memset(storage->reserved2, 0, sizeof(storage->reserved2));
|
||||
memcpy(&storage->ds_data, opaque_key->ds_data_ctx->esp_ds_data, sizeof(esp_ds_data_t));
|
||||
*key_buffer_length = sizeof(esp_rsa_ds_efuse_key_storage_t);
|
||||
}
|
||||
}
|
||||
|
||||
*bits = opaque_key->ds_data_ctx->rsa_length_bits;
|
||||
return PSA_SUCCESS;
|
||||
}
|
||||
|
||||
size_t esp_rsa_ds_opaque_size_function(
|
||||
const psa_key_attributes_t *attributes,
|
||||
psa_key_type_t key_type,
|
||||
size_t key_bits)
|
||||
const uint8_t *data,
|
||||
size_t data_length)
|
||||
{
|
||||
(void)key_type;
|
||||
(void)key_bits;
|
||||
|
||||
return sizeof(esp_rsa_ds_opaque_key_t);
|
||||
bool is_persistent = esp_opaque_key_is_persistent(attributes);
|
||||
|
||||
if (!data || data_length < sizeof(esp_rsa_ds_opaque_key_t)) {
|
||||
/* Data too short to inspect the user struct — return the largest possible
|
||||
* size for the persistence flavor so import has enough room to write whichever
|
||||
* variant ends up being needed. import_key() does the real validation. */
|
||||
if (!is_persistent) {
|
||||
return sizeof(esp_rsa_ds_volatile_key_storage_t);
|
||||
}
|
||||
#if SOC_KEY_MANAGER_SUPPORTED
|
||||
return sizeof(esp_rsa_ds_km_key_storage_t);
|
||||
#else
|
||||
return sizeof(esp_rsa_ds_efuse_key_storage_t);
|
||||
#endif /* SOC_KEY_MANAGER_SUPPORTED */
|
||||
}
|
||||
|
||||
return esp_rsa_ds_get_storage_size((const esp_rsa_ds_opaque_key_t *)data, is_persistent);
|
||||
}
|
||||
|
||||
void esp_rsa_ds_opaque_set_session_timeout(int timeout_ms)
|
||||
@@ -416,8 +768,7 @@ psa_status_t esp_rsa_ds_opaque_asymmetric_decrypt(
|
||||
|
||||
esp_err_t err = ESP_FAIL;
|
||||
|
||||
if (!attributes || !key || key_length < sizeof(esp_rsa_ds_opaque_key_t) ||
|
||||
!input || input_length < 1 || !output || !output_length) {
|
||||
if (!attributes || !key || !input || input_length < 1 || !output || !output_length) {
|
||||
return PSA_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
@@ -425,14 +776,28 @@ psa_status_t esp_rsa_ds_opaque_asymmetric_decrypt(
|
||||
return PSA_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
const esp_rsa_ds_opaque_key_t *opaque_key = (const esp_rsa_ds_opaque_key_t *)key;
|
||||
bool is_persistent = esp_opaque_key_is_persistent(attributes);
|
||||
|
||||
if (esp_rsa_ds_validate_opaque_key(opaque_key) != PSA_SUCCESS) {
|
||||
return PSA_ERROR_INVALID_ARGUMENT;
|
||||
esp_rsa_ds_key_source_t key_source = ESP_RSA_DS_KEY_SOURCE_EFUSE;
|
||||
uint16_t rsa_length_bits = 0;
|
||||
const esp_ds_data_t *ds_data = NULL;
|
||||
hmac_key_id_t hmac_key_id = 0;
|
||||
#if SOC_KEY_MANAGER_SUPPORTED
|
||||
esp_key_mgr_key_recovery_info_t *km_ri = NULL;
|
||||
#endif /* SOC_KEY_MANAGER_SUPPORTED */
|
||||
|
||||
psa_status_t status = esp_rsa_ds_extract_storage(
|
||||
key, key_length, is_persistent,
|
||||
&key_source, &rsa_length_bits, &ds_data, &hmac_key_id
|
||||
#if SOC_KEY_MANAGER_SUPPORTED
|
||||
, &km_ri
|
||||
#endif /* SOC_KEY_MANAGER_SUPPORTED */
|
||||
);
|
||||
if (status != PSA_SUCCESS) {
|
||||
return status;
|
||||
}
|
||||
|
||||
size_t key_bits = opaque_key->ds_data_ctx->rsa_length_bits;
|
||||
if (input_length != (key_bits / 8)) {
|
||||
if (input_length != (rsa_length_bits / 8)) {
|
||||
return PSA_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
@@ -447,7 +812,7 @@ psa_status_t esp_rsa_ds_opaque_asymmetric_decrypt(
|
||||
return PSA_ERROR_GENERIC_ERROR;
|
||||
}
|
||||
|
||||
size_t ilen = key_bits / 8;
|
||||
size_t ilen = rsa_length_bits / 8;
|
||||
size_t data_len = ilen / 4;
|
||||
uint32_t *em_words = heap_caps_malloc_prefer(sizeof(uint32_t) * data_len, 1, MALLOC_CAP_32BIT | MALLOC_CAP_INTERNAL, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL);
|
||||
if (em_words == NULL) {
|
||||
@@ -459,49 +824,41 @@ psa_status_t esp_rsa_ds_opaque_asymmetric_decrypt(
|
||||
em_words[i] = SWAP_INT32(((uint32_t *)input)[(data_len) - (i + 1)]);
|
||||
}
|
||||
|
||||
esp_rsa_ds_opaque_sign_hash_operation_t operation = {0};
|
||||
operation.alg = alg;
|
||||
operation.esp_rsa_ds_opaque_key = opaque_key;
|
||||
operation.sig_buffer = em_words;
|
||||
|
||||
hmac_key_id_t hmac_key_id = opaque_key->ds_data_ctx->efuse_key_id;
|
||||
#if SOC_KEY_MANAGER_SUPPORTED
|
||||
esp_key_mgr_key_recovery_info_t *km_key_recovery_info = opaque_key->key_recovery_info;
|
||||
if (km_key_recovery_info) {
|
||||
err = esp_key_mgr_activate_key(km_key_recovery_info);
|
||||
bool is_km_key_active = false;
|
||||
if (key_source == ESP_RSA_DS_KEY_SOURCE_KEY_MGR) {
|
||||
err = esp_key_mgr_activate_key(km_ri);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to activate key: 0x%x", err);
|
||||
heap_caps_free(em_words);
|
||||
esp_rsa_ds_release_ds_lock();
|
||||
return PSA_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
hmac_key_id = HMAC_KEY_KM;
|
||||
operation.is_km_key_active = true;
|
||||
is_km_key_active = true;
|
||||
}
|
||||
#endif /* SOC_KEY_MANAGER_SUPPORTED */
|
||||
|
||||
esp_ds_context_t *ds_ctx = NULL;
|
||||
err = esp_ds_start_sign((const void *)em_words,
|
||||
opaque_key->ds_data_ctx->esp_ds_data,
|
||||
ds_data,
|
||||
hmac_key_id,
|
||||
&operation.esp_rsa_ds_ctx);
|
||||
&ds_ctx);
|
||||
if (err != ESP_OK) {
|
||||
heap_caps_free(em_words);
|
||||
#if SOC_KEY_MANAGER_SUPPORTED
|
||||
if (km_key_recovery_info && operation.is_km_key_active) {
|
||||
esp_key_mgr_deactivate_key(km_key_recovery_info->key_type);
|
||||
operation.is_km_key_active = false;
|
||||
}
|
||||
if (is_km_key_active) {
|
||||
esp_key_mgr_deactivate_key(km_ri->key_type);
|
||||
}
|
||||
#endif /* SOC_KEY_MANAGER_SUPPORTED */
|
||||
esp_rsa_ds_release_ds_lock();
|
||||
return PSA_ERROR_GENERIC_ERROR;
|
||||
}
|
||||
|
||||
err = esp_ds_finish_sign((void *)em_words, operation.esp_rsa_ds_ctx);
|
||||
err = esp_ds_finish_sign((void *)em_words, ds_ctx);
|
||||
|
||||
#if SOC_KEY_MANAGER_SUPPORTED
|
||||
if (km_key_recovery_info && operation.is_km_key_active) {
|
||||
esp_key_mgr_deactivate_key(km_key_recovery_info->key_type);
|
||||
operation.is_km_key_active = false;
|
||||
if (is_km_key_active) {
|
||||
esp_key_mgr_deactivate_key(km_ri->key_type);
|
||||
}
|
||||
#endif /* SOC_KEY_MANAGER_SUPPORTED */
|
||||
|
||||
|
||||
@@ -22,14 +22,13 @@ extern "C" {
|
||||
|
||||
#define PSA_KEY_LOCATION_ESP_RSA_DS ((psa_key_location_t) 0x800003)
|
||||
|
||||
/* IDF-15427: ESP-PSA driver does not support persistent RSA DS keys as of now */
|
||||
#if 0
|
||||
/* @brief Construct a lifetime for ESP RSA DS keys with default persistence */
|
||||
/**
|
||||
* @brief Construct a persistent lifetime for ESP RSA DS keys
|
||||
*/
|
||||
#define PSA_KEY_LIFETIME_ESP_RSA_DS \
|
||||
PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION( \
|
||||
PSA_KEY_PERSISTENCE_DEFAULT, \
|
||||
PSA_KEY_LOCATION_ESP_RSA_DS)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Construct a volatile lifetime for ESP RSA DS keys
|
||||
@@ -145,15 +144,23 @@ psa_status_t esp_rsa_ds_opaque_import_key(
|
||||
size_t *bits);
|
||||
|
||||
/**
|
||||
* @brief Return the size of the RSA DS opaque key in bytes
|
||||
* @brief Return the storage buffer size required for an RSA DS opaque key
|
||||
*
|
||||
* @param key_type Key type
|
||||
* @param key_bits Key bits
|
||||
* @return Size of the RSA DS opaque key in bytes
|
||||
* For volatile keys, returns a small pointer-based storage size.
|
||||
* For persistent keys, inspects the user-facing import data to determine
|
||||
* the key source and returns the corresponding inline storage struct size.
|
||||
*
|
||||
* @param attributes Key attributes (used to check persistence)
|
||||
* @param key_type Key type
|
||||
* @param data Import data (user-facing esp_rsa_ds_opaque_key_t)
|
||||
* @param data_length Length of import data
|
||||
* @return Size of the storage buffer in bytes, or 0 on error
|
||||
*/
|
||||
size_t esp_rsa_ds_opaque_size_function(
|
||||
const psa_key_attributes_t *attributes,
|
||||
psa_key_type_t key_type,
|
||||
size_t key_bits);
|
||||
const uint8_t *data,
|
||||
size_t data_length);
|
||||
|
||||
/**
|
||||
* @brief Set the timeout for the RSA DS session
|
||||
|
||||
@@ -35,9 +35,14 @@ typedef enum {
|
||||
* @brief ESP DS data context
|
||||
* This context is used to store the ESP DS data.
|
||||
*
|
||||
* When passed to psa_import_key() for PSA_KEY_LIFETIME_ESP_RSA_DS_VOLATILE, the key material
|
||||
* (this struct and the esp_ds_data_t pointed to by esp_ds_data) must remain valid
|
||||
* until psa_destroy_key() is called on the imported key.
|
||||
* For persistent keys (PSA_KEY_LIFETIME_ESP_RSA_DS), the driver deep-copies
|
||||
* all referenced data at import time. The caller's data does not need to
|
||||
* remain valid after psa_import_key() returns.
|
||||
*
|
||||
* For volatile keys (PSA_KEY_LIFETIME_ESP_RSA_DS_VOLATILE), the driver stores
|
||||
* pointers to the caller's data. This struct and the esp_ds_data_t pointed to
|
||||
* by esp_ds_data must remain valid until psa_destroy_key() is called.
|
||||
* This preserves the heap savings of mmap'd flash data from esp_secure_cert_mgr.
|
||||
*/
|
||||
typedef struct {
|
||||
esp_ds_data_t *esp_ds_data; /**< Pointer to the esp ds data */
|
||||
@@ -55,9 +60,10 @@ typedef struct {
|
||||
#if !(__DOXYGEN__) // No need to document these structures, these are internal to the driver
|
||||
/* The buffers are stored in the little-endian format */
|
||||
typedef struct {
|
||||
const esp_rsa_ds_opaque_key_t *esp_rsa_ds_opaque_key; /**< Pointer to the esp ds opaque key */
|
||||
const uint8_t *key_buffer; /**< Pointer to per-source storage struct in key slot */
|
||||
#if SOC_KEY_MANAGER_SUPPORTED
|
||||
bool is_km_key_active; /**< Flag indicating if the Key Manager key is active for this operation */
|
||||
esp_key_mgr_key_recovery_info_t *km_ri; /**< Pointer to the key recovery info for DS key */
|
||||
#endif /* SOC_KEY_MANAGER_SUPPORTED */
|
||||
psa_algorithm_t alg; /**< Algorithm used in the sign operation */
|
||||
uint32_t *sig_buffer; /**< Buffer to hold the signature */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps
|
||||
|
||||
components/mbedtls/test_apps:
|
||||
components/mbedtls/test_apps/mbedtls_ut:
|
||||
disable:
|
||||
- if: CONFIG_NAME == "aes_no_hw" and SOC_AES_SUPPORTED != 1
|
||||
- if: CONFIG_NAME == "psram" and SOC_SPIRAM_SUPPORTED != 1
|
||||
@@ -22,3 +22,18 @@ components/mbedtls/test_apps:
|
||||
- esp_driver_dma
|
||||
- esp_hal_dma
|
||||
- esp_mm
|
||||
- esp_hw_support
|
||||
|
||||
components/mbedtls/test_apps/persistent_storage_format:
|
||||
disable:
|
||||
- if: CONFIG_NAME not in ["hmac", "ecdsa"]
|
||||
reason: this app has no default config; only the hmac and ecdsa overlays are exercised
|
||||
- if: CONFIG_NAME == "hmac" and IDF_TARGET != "esp32c3"
|
||||
reason: nvs_encr_hmac runner (HMAC + RSA-DS persistent format consume) is esp32c3 only
|
||||
- if: CONFIG_NAME == "ecdsa" and IDF_TARGET != "esp32h2"
|
||||
reason: ECDSA persistent format consume runner is esp32h2 only
|
||||
depends_components:
|
||||
- mbedtls
|
||||
- esp_security
|
||||
- esp_hal_security
|
||||
- nvs_flash
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# Persistent storage format stability tests for the ESP PSA opaque drivers.
|
||||
#
|
||||
# This is a separate test project from `mbedtls_ut` because it depends on a
|
||||
# pre-flashed NVS fixture image, has its own partition layout (NVS is reserved
|
||||
# at a known offset), and is run by a dedicated pytest entry point.
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/test_apps/components")
|
||||
set(COMPONENTS main)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(persistent_storage_format_test)
|
||||
|
||||
# Hook the shared multi-driver NVS fixture into `idf.py flash` so every
|
||||
# CI runner gets a pre-populated NVS partition without manual steps.
|
||||
# The same .bin works across runners — each only references its driver's
|
||||
# key id from the fixture.
|
||||
set(NVS_FIXTURE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/fixtures/nvs_efuse_v1.bin")
|
||||
if(EXISTS ${NVS_FIXTURE})
|
||||
partition_table_get_partition_info(nvs_offset "--partition-name nvs" "offset")
|
||||
partition_table_get_partition_info(nvs_size "--partition-name nvs" "size")
|
||||
if(NOT nvs_offset OR NOT nvs_size)
|
||||
message(FATAL_ERROR "persistent_storage_format: nvs partition not found "
|
||||
"in partition table — fixture cannot be flashed.")
|
||||
endif()
|
||||
# NVS partition is plaintext-on-flash (IDF excludes it from auto flash
|
||||
# encryption). Force plaintext writes even when SECURE_FLASH_ENC is on,
|
||||
# otherwise the bytes get encrypted-on-write and NVS reads them as
|
||||
# garbage at runtime.
|
||||
esptool_py_flash_target_image(flash "nvs" ${nvs_offset} ${NVS_FIXTURE}
|
||||
ALWAYS_PLAINTEXT)
|
||||
message(STATUS "persistent_storage_format: will flash ${NVS_FIXTURE} "
|
||||
"to nvs @ ${nvs_offset} (size ${nvs_size}).")
|
||||
endif()
|
||||
@@ -0,0 +1,2 @@
|
||||
| Supported Targets | ESP32-C3 | ESP32-H2 |
|
||||
| ----------------- | -------- | -------- |
|
||||
@@ -0,0 +1,55 @@
|
||||
# Persistent storage format fixtures
|
||||
|
||||
A single committed NVS partition image, pre-populated with three persistent eFuse PSA keys — one per driver. Every CI runner flashes the same `.bin`; each runner's consume test only references its driver's key id (RSA-DS / HMAC / ECDSA) and ignores the other two.
|
||||
|
||||
Cross-platform format drift is caught by every runner — three independent signals on the same regression.
|
||||
|
||||
## File
|
||||
|
||||
| File | Storage version | Contains |
|
||||
|------|-----------------|----------|
|
||||
| `nvs_efuse_v1.bin` | v1 | 3 persistent eFuse keys: RSA-DS (id `0x1ADA1`), HMAC (id `0x1ADA2`), ECDSA SECP256R1 (id `0x1ADA3`) |
|
||||
|
||||
The key ids and the eFuse block/key-id assignments are declared in `../main/test_persistent_format.h`. They align with the existing volatile tests in `mbedtls_ut`, so each runner's already-burned eFuse key serves the persistent path too.
|
||||
|
||||
KM-source fixtures are intentionally absent — the per-key `esp_key_mgr_key_recovery_info_t` blob is HUK-wrapped by the deploying chip and meaningless on any other device. KM persistence is verified by the runtime deploy-then-import tests in `mbedtls_ut`.
|
||||
|
||||
## Regenerating
|
||||
|
||||
You need any chip with all three drivers in the build (typically ESP32-C5 or ESP32-P4 with DS + HMAC + ECDSA enabled).
|
||||
|
||||
The capture build needs all three drivers compiled in, which neither `sdkconfig.ci.hmac` nor `sdkconfig.ci.ecdsa` provides on its own. Create your own local overlay file (any path; the example below uses `sdkconfig.capture`) with the following contents, then point `SDKCONFIG_DEFAULTS` at it. Do NOT name it `sdkconfig.ci.*` — that prefix is auto-discovered by the CI app manifest and would add a build target the runners can't use.
|
||||
|
||||
```
|
||||
CONFIG_MBEDTLS_HARDWARE_RSA_DS_PERIPHERAL=y
|
||||
CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN=y
|
||||
CONFIG_MBEDTLS_HARDWARE_ECDSA_VERIFY=y
|
||||
```
|
||||
|
||||
```
|
||||
cd components/mbedtls/test_apps/persistent_storage_format
|
||||
|
||||
# 1. Erase NVS so the capture starts deterministic
|
||||
esptool --chip <CHIP> --port <PORT> erase_region --force <NVS_OFFSET> <NVS_SIZE>
|
||||
|
||||
# 2. Build, flash, and run ONLY the capture test
|
||||
idf.py set-target <CHIP>
|
||||
idf.py -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.capture" reconfigure
|
||||
idf.py -p <PORT> flash
|
||||
idf.py -p <PORT> monitor # send `[fixture_capture]` to Unity, wait for PASS
|
||||
|
||||
# 3. Read the partition out
|
||||
esptool --chip <CHIP> --port <PORT> read_flash <NVS_OFFSET> <NVS_SIZE> fixtures/nvs_efuse_v1.bin
|
||||
|
||||
# 4. Commit. If you bumped any storage version, rename to nvs_efuse_v2.bin
|
||||
# and keep nvs_efuse_v1.bin around — the v1 consume tests then prove
|
||||
# v2 firmware can still read v1 NVS blobs (backward compat).
|
||||
```
|
||||
|
||||
NVS offset/size live in `partitions.csv` — currently `0xA000` / `0x6000` (24 KB).
|
||||
|
||||
## When to regenerate
|
||||
|
||||
- After bumping any driver's persistent storage struct version. Add a new fixture file (`v2`, `v3`, …) AND keep the older fixtures around with their consume tests, so older-on-disk -> newer-firmware compatibility is proven.
|
||||
- After an mbedtls upgrade that changes PSA ITS framing. Rare; needs a release note.
|
||||
- Never just "to refresh." The file is meant to stay frozen so it detects regressions.
|
||||
Binary file not shown.
@@ -0,0 +1,14 @@
|
||||
idf_component_register(
|
||||
SRC_DIRS "."
|
||||
PRIV_INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES efuse cmock test_utils mbedtls esp_timer
|
||||
unity spi_flash esp_security nvs_flash
|
||||
WHOLE_ARCHIVE)
|
||||
|
||||
# The RSA-DS opaque driver is the unit under test for these format
|
||||
# stability checks; the linker wraps below let the sign path complete
|
||||
# without a real eFuse HMAC key on the runner. Mirrors mbedtls_ut.
|
||||
target_link_options(
|
||||
${COMPONENT_LIB} INTERFACE
|
||||
"-Wl,--wrap=esp_ds_finish_sign,--wrap=esp_ds_start_sign,--wrap=esp_efuse_get_key_purpose"
|
||||
)
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_newlib.h"
|
||||
#include "memory_checks.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "unity.h"
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
test_utils_record_free_mem();
|
||||
test_utils_set_leak_level(CONFIG_UNITY_CRITICAL_LEAK_LEVEL_GENERAL,
|
||||
ESP_LEAK_TYPE_CRITICAL, ESP_COMP_LEAK_GENERAL);
|
||||
test_utils_set_leak_level(CONFIG_UNITY_WARN_LEAK_LEVEL_GENERAL,
|
||||
ESP_LEAK_TYPE_WARNING, ESP_COMP_LEAK_GENERAL);
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
vTaskDelay(5);
|
||||
esp_reent_cleanup();
|
||||
TEST_ASSERT_MESSAGE(heap_caps_check_integrity(MALLOC_CAP_INVALID, true),
|
||||
"The test has corrupted the heap");
|
||||
test_utils_finish_and_evaluate_leaks(
|
||||
test_utils_get_leak_level(ESP_LEAK_TYPE_WARNING, ESP_COMP_LEAK_ALL),
|
||||
test_utils_get_leak_level(ESP_LEAK_TYPE_CRITICAL, ESP_COMP_LEAK_ALL));
|
||||
}
|
||||
|
||||
static void test_task(void *pvParameters)
|
||||
{
|
||||
vTaskDelay(2);
|
||||
unity_run_menu();
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_err_t err = nvs_flash_init();
|
||||
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
}
|
||||
xTaskCreatePinnedToCore(test_task, "testTask",
|
||||
CONFIG_UNITY_FREERTOS_STACK_SIZE, NULL,
|
||||
CONFIG_UNITY_FREERTOS_PRIORITY, NULL,
|
||||
CONFIG_UNITY_FREERTOS_CPU);
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Shared scaffolding for persistent-storage format-stability tests.
|
||||
*
|
||||
* - One capture test that imports a persistent eFuse key for every PSA
|
||||
* opaque driver compiled into the build (RSA-DS, HMAC, ECDSA), with
|
||||
* fixed key ids. Run once on a chip that has all three drivers in the
|
||||
* build; the resulting NVS partition is captured with esptool and
|
||||
* committed as fixtures/nvs_efuse_v1.bin.
|
||||
*
|
||||
* - The committed fixture is then flashed by every CI runner; each runner
|
||||
* only references its own driver's key id (see the per-driver consume
|
||||
* files), so a single shared NVS image works across heterogeneous
|
||||
* runners.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "unity.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "esp_efuse.h"
|
||||
#include "psa/crypto.h"
|
||||
|
||||
#ifdef CONFIG_MBEDTLS_HARDWARE_RSA_DS_PERIPHERAL
|
||||
#include "esp_ds.h"
|
||||
#include "hal/hmac_types.h"
|
||||
#include "psa_crypto_driver_esp_rsa_ds.h"
|
||||
#endif
|
||||
|
||||
#ifdef ESP_HMAC_OPAQUE_DRIVER_ENABLED
|
||||
#include "psa_crypto_driver_esp_hmac_opaque.h"
|
||||
#include "psa_crypto_driver_esp_hmac_opaque_contexts.h"
|
||||
#endif
|
||||
|
||||
#if CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN
|
||||
#include "psa_crypto_driver_esp_ecdsa.h"
|
||||
#include "psa_crypto_driver_esp_ecdsa_contexts.h"
|
||||
#endif
|
||||
|
||||
#include "test_persistent_format.h"
|
||||
|
||||
/* --- Dynamic-purpose wrap shared across all drivers' import validation. */
|
||||
static volatile esp_efuse_purpose_t s_purpose_override = ESP_EFUSE_KEY_PURPOSE_USER;
|
||||
|
||||
extern esp_efuse_purpose_t __real_esp_efuse_get_key_purpose(esp_efuse_block_t block);
|
||||
|
||||
esp_efuse_purpose_t __wrap_esp_efuse_get_key_purpose(esp_efuse_block_t block)
|
||||
{
|
||||
if (s_purpose_override != ESP_EFUSE_KEY_PURPOSE_USER) {
|
||||
return s_purpose_override;
|
||||
}
|
||||
return __real_esp_efuse_get_key_purpose(block);
|
||||
}
|
||||
|
||||
/* --- DS hardware wraps. Without these the DS sign path on the consume
|
||||
* side would touch the DS peripheral and fail for lack of a real
|
||||
* eFuse HMAC key. Compiled only when DS is in the build. */
|
||||
#ifdef CONFIG_MBEDTLS_HARDWARE_RSA_DS_PERIPHERAL
|
||||
int __wrap_esp_ds_start_sign(const void *message, const esp_ds_data_t *data,
|
||||
hmac_key_id_t key_id, esp_ds_context_t **esp_ds_ctx)
|
||||
{
|
||||
if (message == NULL || data == NULL || esp_ds_ctx == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
*esp_ds_ctx = malloc(sizeof(esp_ds_context_t));
|
||||
if (*esp_ds_ctx == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
int __wrap_esp_ds_finish_sign(void *sig, esp_ds_context_t *ctx)
|
||||
{
|
||||
free(ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* RSA-DS storage embeds the encrypted key blob; the capture test below
|
||||
* needs a syntactically-valid mock since the driver cross-validates
|
||||
* rsa_length_bits against ds_data.rsa_length. */
|
||||
static esp_ds_data_ctx_t *mock_ds_data_ctx(void)
|
||||
{
|
||||
esp_ds_data_ctx_t *ds = calloc(1, sizeof(esp_ds_data_ctx_t));
|
||||
if (!ds) {
|
||||
return NULL;
|
||||
}
|
||||
ds->esp_ds_data = calloc(1, sizeof(esp_ds_data_t));
|
||||
if (!ds->esp_ds_data) {
|
||||
free(ds);
|
||||
return NULL;
|
||||
}
|
||||
ds->rsa_length_bits = 2048;
|
||||
ds->efuse_key_id = ESP_PERSISTENT_FIXTURE_DS_EFUSE_KEY_ID;
|
||||
ds->esp_ds_data->rsa_length = (ds->rsa_length_bits / 32) - 1;
|
||||
return ds;
|
||||
}
|
||||
|
||||
static void free_mock_ds_data_ctx(esp_ds_data_ctx_t *ds)
|
||||
{
|
||||
if (ds) {
|
||||
free(ds->esp_ds_data);
|
||||
free(ds);
|
||||
}
|
||||
}
|
||||
#endif /* CONFIG_MBEDTLS_HARDWARE_RSA_DS_PERIPHERAL */
|
||||
|
||||
/* --- Capture test. Imports one persistent eFuse key per compiled driver,
|
||||
* using fixed ids. Run once; capture NVS via esptool; commit. */
|
||||
TEST_CASE("efuse persistent fixture capture v1 (one-shot, all drivers)",
|
||||
"[fixture_capture]")
|
||||
{
|
||||
/* Clean any leftover from prior runs so we capture a deterministic NVS. */
|
||||
psa_destroy_key(ESP_PERSISTENT_FIXTURE_DS_KEY_ID);
|
||||
psa_destroy_key(ESP_PERSISTENT_FIXTURE_HMAC_KEY_ID);
|
||||
psa_destroy_key(ESP_PERSISTENT_FIXTURE_ECDSA_KEY_ID);
|
||||
|
||||
#ifdef CONFIG_MBEDTLS_HARDWARE_RSA_DS_PERIPHERAL
|
||||
{
|
||||
esp_rsa_ds_opaque_key_t key = {0};
|
||||
key.ds_data_ctx = mock_ds_data_ctx();
|
||||
TEST_ASSERT_NOT_NULL(key.ds_data_ctx);
|
||||
|
||||
psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT;
|
||||
psa_set_key_type(&attr, PSA_KEY_TYPE_RSA_KEY_PAIR);
|
||||
psa_set_key_bits(&attr, key.ds_data_ctx->rsa_length_bits);
|
||||
psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_SIGN_HASH);
|
||||
psa_set_key_algorithm(&attr, PSA_ALG_RSA_PKCS1V15_SIGN(PSA_ALG_SHA_256));
|
||||
psa_set_key_lifetime(&attr, PSA_KEY_LIFETIME_ESP_RSA_DS);
|
||||
psa_set_key_id(&attr, ESP_PERSISTENT_FIXTURE_DS_KEY_ID);
|
||||
|
||||
s_purpose_override = ESP_EFUSE_KEY_PURPOSE_HMAC_DOWN_DIGITAL_SIGNATURE;
|
||||
psa_key_id_t kid;
|
||||
TEST_ASSERT_EQUAL(PSA_SUCCESS,
|
||||
psa_import_key(&attr, (const uint8_t *)&key, sizeof(key), &kid));
|
||||
s_purpose_override = ESP_EFUSE_KEY_PURPOSE_USER;
|
||||
TEST_ASSERT_EQUAL(ESP_PERSISTENT_FIXTURE_DS_KEY_ID, kid);
|
||||
TEST_ASSERT_EQUAL(PSA_SUCCESS, psa_purge_key(kid));
|
||||
|
||||
free_mock_ds_data_ctx(key.ds_data_ctx);
|
||||
psa_reset_key_attributes(&attr);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ESP_HMAC_OPAQUE_DRIVER_ENABLED
|
||||
{
|
||||
esp_hmac_opaque_key_t key = {
|
||||
.efuse_key_id = ESP_PERSISTENT_FIXTURE_HMAC_EFUSE_KEY_ID,
|
||||
};
|
||||
|
||||
psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT;
|
||||
psa_set_key_type(&attr, PSA_KEY_TYPE_HMAC);
|
||||
psa_set_key_bits(&attr, 256);
|
||||
psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_SIGN_MESSAGE | PSA_KEY_USAGE_VERIFY_MESSAGE);
|
||||
psa_set_key_algorithm(&attr, PSA_ALG_HMAC(PSA_ALG_SHA_256));
|
||||
psa_set_key_lifetime(&attr, PSA_KEY_LIFETIME_ESP_HMAC);
|
||||
psa_set_key_id(&attr, ESP_PERSISTENT_FIXTURE_HMAC_KEY_ID);
|
||||
|
||||
s_purpose_override = ESP_EFUSE_KEY_PURPOSE_HMAC_UP;
|
||||
psa_key_id_t kid;
|
||||
TEST_ASSERT_EQUAL(PSA_SUCCESS,
|
||||
psa_import_key(&attr, (const uint8_t *)&key, sizeof(key), &kid));
|
||||
s_purpose_override = ESP_EFUSE_KEY_PURPOSE_USER;
|
||||
TEST_ASSERT_EQUAL(ESP_PERSISTENT_FIXTURE_HMAC_KEY_ID, kid);
|
||||
TEST_ASSERT_EQUAL(PSA_SUCCESS, psa_purge_key(kid));
|
||||
|
||||
psa_reset_key_attributes(&attr);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN
|
||||
{
|
||||
esp_ecdsa_opaque_key_t key = {
|
||||
.curve = ESP_ECDSA_CURVE_SECP256R1,
|
||||
.efuse_block = ESP_PERSISTENT_FIXTURE_ECDSA_EFUSE_BLOCK,
|
||||
};
|
||||
|
||||
psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT;
|
||||
psa_set_key_type(&attr, PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1));
|
||||
psa_set_key_bits(&attr, 256);
|
||||
psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_SIGN_HASH);
|
||||
psa_set_key_algorithm(&attr, PSA_ALG_ECDSA(PSA_ALG_SHA_256));
|
||||
psa_set_key_lifetime(&attr, PSA_KEY_LIFETIME_ESP_ECDSA);
|
||||
psa_set_key_id(&attr, ESP_PERSISTENT_FIXTURE_ECDSA_KEY_ID);
|
||||
|
||||
s_purpose_override = ESP_EFUSE_KEY_PURPOSE_ECDSA_KEY;
|
||||
psa_key_id_t kid;
|
||||
TEST_ASSERT_EQUAL(PSA_SUCCESS,
|
||||
psa_import_key(&attr, (const uint8_t *)&key, sizeof(key), &kid));
|
||||
s_purpose_override = ESP_EFUSE_KEY_PURPOSE_USER;
|
||||
TEST_ASSERT_EQUAL(ESP_PERSISTENT_FIXTURE_ECDSA_KEY_ID, kid);
|
||||
TEST_ASSERT_EQUAL(PSA_SUCCESS, psa_purge_key(kid));
|
||||
|
||||
psa_reset_key_attributes(&attr);
|
||||
}
|
||||
#endif
|
||||
|
||||
printf("\n*** Fixture written; capture NVS partition with esptool now. ***\n\n");
|
||||
}
|
||||
|
||||
/* ====================================================================== *
|
||||
* Per-driver consume tests.
|
||||
*
|
||||
* The CI runner flashes fixtures/nvs_efuse_v1.bin to the NVS partition.
|
||||
* The persistent keys are already there with their fixed ids; each
|
||||
* driver's consume test only references its own id and ignores the rest,
|
||||
* so a single shared NVS image works across heterogeneous runners.
|
||||
*
|
||||
* If anything in the on-NVS format has drifted (storage struct, PSA
|
||||
* framing, NVS encoding) the corresponding op call returns
|
||||
* INVALID_ARGUMENT/DATA_INVALID and the test fails. Each driver gates
|
||||
* its test on the same CONFIG flag that pulls its driver into the build.
|
||||
* ====================================================================== */
|
||||
|
||||
#if CONFIG_MBEDTLS_HARDWARE_RSA_DS_PERIPHERAL
|
||||
TEST_CASE("rsa-ds persistent NVS fixture v1 sign",
|
||||
"[persistent_format][rsa_ds]")
|
||||
{
|
||||
uint8_t hash[32] = {0};
|
||||
size_t hash_length = 0;
|
||||
uint8_t input[7] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
|
||||
TEST_ASSERT_EQUAL(PSA_SUCCESS,
|
||||
psa_hash_compute(PSA_ALG_SHA_256, input, sizeof(input),
|
||||
hash, sizeof(hash), &hash_length));
|
||||
|
||||
uint8_t signature[256] = {0};
|
||||
size_t signature_length = 0;
|
||||
TEST_ASSERT_EQUAL_HEX32(PSA_SUCCESS,
|
||||
psa_sign_hash(ESP_PERSISTENT_FIXTURE_DS_KEY_ID,
|
||||
PSA_ALG_RSA_PKCS1V15_SIGN(PSA_ALG_SHA_256),
|
||||
hash, hash_length,
|
||||
signature, sizeof(signature), &signature_length));
|
||||
TEST_ASSERT_EQUAL(256, signature_length);
|
||||
|
||||
/* v15 padding-shape sanity, same as the volatile sign test. */
|
||||
TEST_ASSERT_EQUAL(0, memcmp(hash, signature + (256 - hash_length), hash_length));
|
||||
TEST_ASSERT_EQUAL(hash_length, signature[256 - hash_length - 1]);
|
||||
TEST_ASSERT_EQUAL(0x04, signature[256 - hash_length - 2]);
|
||||
TEST_ASSERT_EQUAL(0x00, signature[0]);
|
||||
|
||||
TEST_ASSERT_EQUAL(PSA_SUCCESS, psa_purge_key(ESP_PERSISTENT_FIXTURE_DS_KEY_ID));
|
||||
}
|
||||
#endif /* CONFIG_MBEDTLS_HARDWARE_RSA_DS_PERIPHERAL */
|
||||
|
||||
#ifdef ESP_HMAC_OPAQUE_DRIVER_ENABLED
|
||||
/* The runner has an eFuse HMAC key burned in
|
||||
* ESP_PERSISTENT_FIXTURE_HMAC_EFUSE_KEY_ID with HMAC_UP purpose; we do a
|
||||
* real mac_compute + mac_verify roundtrip. */
|
||||
static const uint8_t hmac_test_data[] = "Pretty long input message";
|
||||
|
||||
TEST_CASE("hmac efuse persistent NVS fixture v1 mac",
|
||||
"[persistent_format][hmac_efuse_key]")
|
||||
{
|
||||
uint8_t mac[32] = {0};
|
||||
size_t mac_length = 0;
|
||||
TEST_ASSERT_EQUAL_HEX32(PSA_SUCCESS,
|
||||
psa_mac_compute(ESP_PERSISTENT_FIXTURE_HMAC_KEY_ID,
|
||||
PSA_ALG_HMAC(PSA_ALG_SHA_256),
|
||||
hmac_test_data, sizeof(hmac_test_data) - 1,
|
||||
mac, sizeof(mac), &mac_length));
|
||||
TEST_ASSERT_EQUAL(32, mac_length);
|
||||
|
||||
TEST_ASSERT_EQUAL(PSA_SUCCESS,
|
||||
psa_mac_verify(ESP_PERSISTENT_FIXTURE_HMAC_KEY_ID,
|
||||
PSA_ALG_HMAC(PSA_ALG_SHA_256),
|
||||
hmac_test_data, sizeof(hmac_test_data) - 1,
|
||||
mac, mac_length));
|
||||
|
||||
TEST_ASSERT_EQUAL(PSA_SUCCESS, psa_purge_key(ESP_PERSISTENT_FIXTURE_HMAC_KEY_ID));
|
||||
}
|
||||
#endif /* ESP_HMAC_OPAQUE_DRIVER_ENABLED */
|
||||
|
||||
#if CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN
|
||||
/* SECP256R1 public key matching the SECP256R1 ECDSA key burned in
|
||||
* ESP_PERSISTENT_FIXTURE_ECDSA_EFUSE_BLOCK on the runner. Same constants
|
||||
* as mbedtls_ut/test_psa_ecdsa.c uses for its eFuse-key volatile tests. */
|
||||
static const uint8_t ecdsa256_pub_x[] = {
|
||||
0xa2, 0x8f, 0x52, 0x60, 0x20, 0x9b, 0x54, 0x3c,
|
||||
0x13, 0x2f, 0x51, 0xb1, 0x89, 0xbf, 0xc7, 0xfa,
|
||||
0x84, 0x5c, 0x56, 0x96, 0x2a, 0x00, 0x67, 0xdd,
|
||||
0x7c, 0x8c, 0x0f, 0x63, 0x8b, 0x76, 0x7f, 0xb9,
|
||||
};
|
||||
static const uint8_t ecdsa256_pub_y[] = {
|
||||
0xf6, 0x4c, 0x87, 0x5b, 0x5a, 0x9b, 0x59, 0x0a,
|
||||
0xc4, 0x53, 0x04, 0x72, 0x0d, 0x7c, 0xde, 0xac,
|
||||
0x7e, 0xad, 0x49, 0x8c, 0xf7, 0x5c, 0xc3, 0x1c,
|
||||
0x1e, 0x81, 0xf2, 0x47, 0x01, 0x74, 0x05, 0xd5,
|
||||
};
|
||||
|
||||
/* The runner has a SECP256R1 ECDSA key burned in
|
||||
* ESP_PERSISTENT_FIXTURE_ECDSA_EFUSE_BLOCK with ECDSA_KEY purpose. We
|
||||
* sign with the persistent key, then verify the resulting signature
|
||||
* with a freshly-imported transparent public key matching the burned
|
||||
* eFuse private key. The verify pass is the mathematical "compare the
|
||||
* signature" — it proves the persistent extract recovered the SAME
|
||||
* private key as the runner has burned. We don't rely on HW pubkey
|
||||
* export here, since some ECDSA-capable chips (e.g. esp32h2) lack it
|
||||
* for opaque keys. */
|
||||
TEST_CASE("ecdsa efuse persistent NVS fixture v1 sign and verify",
|
||||
"[persistent_format][ecdsa_efuse_key]")
|
||||
{
|
||||
psa_algorithm_t alg = PSA_ALG_ECDSA(PSA_ALG_SHA_256);
|
||||
uint8_t hash[32];
|
||||
memset(hash, 0xA5, sizeof(hash));
|
||||
|
||||
/* Sign on the persistent eFuse key. */
|
||||
uint8_t signature[64]; /* SECP256R1: r || s, 32 bytes each */
|
||||
size_t signature_length = 0;
|
||||
TEST_ASSERT_EQUAL_HEX32(PSA_SUCCESS,
|
||||
psa_sign_hash(ESP_PERSISTENT_FIXTURE_ECDSA_KEY_ID,
|
||||
alg, hash, sizeof(hash),
|
||||
signature, sizeof(signature), &signature_length));
|
||||
TEST_ASSERT_EQUAL(64, signature_length);
|
||||
|
||||
/* Import the matching transparent public key and verify. */
|
||||
uint8_t pub[65];
|
||||
pub[0] = 0x04; /* uncompressed point format */
|
||||
memcpy(pub + 1, ecdsa256_pub_x, sizeof(ecdsa256_pub_x));
|
||||
memcpy(pub + 1 + sizeof(ecdsa256_pub_x), ecdsa256_pub_y, sizeof(ecdsa256_pub_y));
|
||||
|
||||
psa_key_attributes_t pub_attr = PSA_KEY_ATTRIBUTES_INIT;
|
||||
psa_set_key_type(&pub_attr, PSA_KEY_TYPE_ECC_PUBLIC_KEY(PSA_ECC_FAMILY_SECP_R1));
|
||||
psa_set_key_usage_flags(&pub_attr, PSA_KEY_USAGE_VERIFY_HASH);
|
||||
psa_set_key_algorithm(&pub_attr, PSA_ALG_ECDSA(PSA_ALG_SHA_256));
|
||||
psa_set_key_bits(&pub_attr, 256);
|
||||
|
||||
psa_key_id_t pub_kid = 0;
|
||||
TEST_ASSERT_EQUAL(PSA_SUCCESS,
|
||||
psa_import_key(&pub_attr, pub, sizeof(pub), &pub_kid));
|
||||
|
||||
TEST_ASSERT_EQUAL_HEX32(PSA_SUCCESS,
|
||||
psa_verify_hash(pub_kid, PSA_ALG_ECDSA(PSA_ALG_SHA_256),
|
||||
hash, sizeof(hash), signature, signature_length));
|
||||
|
||||
TEST_ASSERT_EQUAL(PSA_SUCCESS, psa_destroy_key(pub_kid));
|
||||
psa_reset_key_attributes(&pub_attr);
|
||||
TEST_ASSERT_EQUAL(PSA_SUCCESS, psa_purge_key(ESP_PERSISTENT_FIXTURE_ECDSA_KEY_ID));
|
||||
}
|
||||
#endif /* CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN */
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Fixed key ids and eFuse block/id assignments baked into the committed
|
||||
* NVS fixture (fixtures/nvs_efuse_v1.bin). Bumping ANY of these values
|
||||
* means regenerating the fixture and bumping its filename's version.
|
||||
*
|
||||
* The eFuse block/id values are chosen to align with the existing
|
||||
* volatile tests on each driver's runner so the same physical eFuse
|
||||
* key the runner already has burned for the volatile test path serves
|
||||
* the persistent path too.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/* Persistent PSA key ids (chosen arbitrarily, must be non-zero). */
|
||||
#define ESP_PERSISTENT_FIXTURE_DS_KEY_ID 0x1ADA1U
|
||||
#define ESP_PERSISTENT_FIXTURE_HMAC_KEY_ID 0x1ADA2U
|
||||
#define ESP_PERSISTENT_FIXTURE_ECDSA_KEY_ID 0x1ADA3U
|
||||
|
||||
/* eFuse block / key-id assignments. The capture chip's import path uses
|
||||
* the dynamic-purpose wrap to satisfy validation; on the consume runners
|
||||
* these must match the eFuse blocks that runner has burned for the
|
||||
* relevant peripheral. */
|
||||
/* RSA-DS uses a distinct block from HMAC so the two persistent keys
|
||||
* don't visually share an eFuse id. The DS HW is wrapped at op time, so
|
||||
* the runner doesn't actually need anything burned at this block — any
|
||||
* non-conflicting value works. */
|
||||
#define ESP_PERSISTENT_FIXTURE_DS_EFUSE_KEY_ID 1 /* HMAC_KEY1; DS HW wrapped at op time */
|
||||
#define ESP_PERSISTENT_FIXTURE_HMAC_EFUSE_KEY_ID 0 /* HMAC_KEY0 — matches the nvs_encr_hmac runner's real burned key */
|
||||
#define ESP_PERSISTENT_FIXTURE_ECDSA_EFUSE_BLOCK 5 /* EFUSE_BLK_KEY1, matches SECP256R1_EFUSE_BLOCK in test_psa_ecdsa.c */
|
||||
@@ -0,0 +1,9 @@
|
||||
# Persistent-storage-format test partition layout.
|
||||
# CONFIG_PARTITION_TABLE_CUSTOM=y is needed for this file to be picked up.
|
||||
# The NVS partition is the target for the pre-flashed fixture images
|
||||
# under fixtures/.
|
||||
#
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0xA000, 0x6000,
|
||||
esp_secure_cert, 0x3F, , 0x10000, 0x2000,
|
||||
factory, app, factory, 0x20000, 1M,
|
||||
|
@@ -0,0 +1,36 @@
|
||||
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
#
|
||||
# Per-runner pytest entry points for the persistent-storage format
|
||||
# stability tests. Each runner flashes the SAME shared NVS fixture
|
||||
# (fixtures/nvs_efuse_v1.bin) — pre-populated with three persistent
|
||||
# eFuse keys, one per driver — and runs the consume tests for whichever
|
||||
# drivers it has hardware for. The capture-side test is tagged
|
||||
# [fixture_capture] (NOT [persistent_format]) so it is never picked up
|
||||
# by the normal CI groups.
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
|
||||
# nvs_encr_hmac runner (esp32c3) covers BOTH HMAC and RSA-DS:
|
||||
# - real eFuse HMAC key burned in block 0 with HMAC_UP purpose →
|
||||
# genuine psa_mac_compute roundtrip
|
||||
# - DS peripheral on c3, DS HW wrapped → RSA-DS consume runs without
|
||||
# needing a real HMAC-DOWN-DIGITAL-SIGNATURE eFuse key
|
||||
# A single runner exercises both drivers' persistent extract paths.
|
||||
@pytest.mark.nvs_encr_hmac
|
||||
@pytest.mark.parametrize('config', ['hmac'], indirect=True)
|
||||
@idf_parametrize('target', ['esp32c3'], indirect=['target'])
|
||||
def test_persistent_storage_format_hmac_and_rsa_ds(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases(group='persistent_format')
|
||||
|
||||
|
||||
# ECDSA runner (esp32h2) — has a SECP256R1 ECDSA key burned in
|
||||
# EFUSE_BLK_KEY1 with ECDSA_KEY purpose (matches mbedtls_ut's existing
|
||||
# ecdsa_sign volatile tests).
|
||||
@pytest.mark.ecdsa_efuse
|
||||
@pytest.mark.parametrize('config', ['ecdsa'], indirect=True)
|
||||
@idf_parametrize('target', ['esp32h2'], indirect=['target'])
|
||||
def test_persistent_storage_format_ecdsa(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases(group='ecdsa_efuse_key')
|
||||
@@ -0,0 +1,5 @@
|
||||
# ECDSA opaque persistent-format runner overlay (esp32h2).
|
||||
# The runner has a SECP256R1 ECDSA key burned in EFUSE_BLK_KEY1 with
|
||||
# ECDSA_KEY purpose. DS isn't on h2; HMAC opaque test runs on c3, not here.
|
||||
CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN=y
|
||||
CONFIG_MBEDTLS_HARDWARE_ECDSA_VERIFY=y
|
||||
@@ -0,0 +1,21 @@
|
||||
# HMAC + RSA-DS persistent-format runner overlay (nvs_encr_hmac runner,
|
||||
# esp32c3). The runner has:
|
||||
# - an eFuse HMAC key in block 0 with HMAC_UP purpose (used by the HMAC
|
||||
# consume test for a real psa_mac_compute roundtrip)
|
||||
# in test_persistent_format.c, so the RSA-DS consume test runs without
|
||||
# needing a real HMAC-DOWN-DIGITAL-SIGNATURE eFuse key on this runner.
|
||||
#
|
||||
# A single runner therefore exercises both the HMAC and RSA-DS persistent
|
||||
# extract paths against the same shared NVS fixture.
|
||||
|
||||
CONFIG_SECURE_FLASH_ENC_ENABLED=y
|
||||
CONFIG_SECURE_FLASH_ENCRYPTION_MODE_DEVELOPMENT=y
|
||||
CONFIG_SECURE_FLASH_REQUIRE_ALREADY_ENABLED=y
|
||||
CONFIG_SECURE_BOOT_ALLOW_ROM_BASIC=y
|
||||
CONFIG_SECURE_BOOT_ALLOW_JTAG=y
|
||||
CONFIG_SECURE_FLASH_UART_BOOTLOADER_ALLOW_ENC=y
|
||||
CONFIG_SECURE_FLASH_UART_BOOTLOADER_ALLOW_DEC=y
|
||||
CONFIG_SECURE_FLASH_UART_BOOTLOADER_ALLOW_CACHE=y
|
||||
CONFIG_PARTITION_TABLE_OFFSET=0x9000
|
||||
|
||||
CONFIG_MBEDTLS_HARDWARE_RSA_DS_PERIPHERAL=y
|
||||
@@ -0,0 +1,19 @@
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
|
||||
CONFIG_PARTITION_TABLE_OFFSET=0x9000
|
||||
|
||||
# NVS encryption disabled — fixture images are committed as plaintext NVS
|
||||
# bytes so they're portable across devices. With CONFIG_NVS_ENCRYPTION=y the
|
||||
# bootloader auto-routes through nvs_sec_provider which requires an eFuse
|
||||
# HMAC key; that's outside the scope of these format-stability tests.
|
||||
CONFIG_NVS_ENCRYPTION=n
|
||||
|
||||
# Common build sanity options (mirrored from mbedtls_ut)
|
||||
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
|
||||
CONFIG_COMPILER_WARN_WRITE_STRINGS=y
|
||||
CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y
|
||||
CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y
|
||||
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
|
||||
CONFIG_COMPILER_STACK_CHECK=y
|
||||
CONFIG_ESP_TASK_WDT_EN=y
|
||||
CONFIG_ESP_TASK_WDT_INIT=n
|
||||
@@ -113,8 +113,17 @@ To use the DS peripheral for signing or decryption in application code (outside
|
||||
|
||||
psa_destroy_key(key_id);
|
||||
|
||||
Example for SSL Mutual Authentication Using DS
|
||||
----------------------------------------------
|
||||
Persistent vs. Volatile RSA_DS PSA Keys
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The driver supports two PSA key lifetimes for RSA_DS keys:
|
||||
|
||||
- ``PSA_KEY_LIFETIME_ESP_RSA_DS_VOLATILE`` (used in the example above) stores only pointers to the caller-supplied ``esp_ds_data_ctx_t`` and any Key Manager recovery info in the PSA key slot. The referenced buffers must remain valid until :cpp:func:`psa_destroy_key` is called. This avoids deep-copying large blobs such as :cpp:type:`esp_ds_data_t` (≈1200-1600 bytes, chip-dependent) when they already live in mmap'd flash via ``esp_secure_cert_mgr``.
|
||||
|
||||
- ``PSA_KEY_LIFETIME_ESP_RSA_DS`` (persistent) deep-copies the encrypted key material into the PSA key slot at :cpp:func:`psa_import_key` time and PSA persists it to NVS together with the rest of the key attributes. The caller is free to release the import-time buffers once :cpp:func:`psa_import_key` returns; subsequent :cpp:func:`psa_sign_hash` / :cpp:func:`psa_asymmetric_decrypt` calls retrieve the bytes back from NVS automatically. Use this lifetime when the application wants the key to survive reboots without having to reload the ``esp_ds_data_ctx_t`` from external storage on every boot.
|
||||
|
||||
Example for SSL Mutual Authentication Using RSA_DS
|
||||
---------------------------------------------------
|
||||
|
||||
The SSL mutual authentication example that previously lived under ``examples/protocols/mqtt/ssl_ds`` is now shipped with the standalone `espressif/mqtt <https://components.espressif.com/components/espressif/mqtt>`__ component. Follow the component documentation to fetch the SSL DS example and build it together with ESP-MQTT. The example continues to use ``mqtt_client`` (implemented by ESP-MQTT) to connect to ``test.mosquitto.org`` over mutual-authenticated TLS, with the TLS portion handled by ESP-TLS.
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@ Flash Encryption Best Practices
|
||||
* - High
|
||||
- 72.4 %
|
||||
|
||||
.. [#] The above performance numbers have been calculated using the AES performance test of the mbedtls test application :component_file:`test_psa_aes_perf.c <mbedtls/test_apps/main/test_psa_aes_perf.c>`.
|
||||
.. [#] The above performance numbers have been calculated using the AES performance test of the mbedtls test application :component_file:`test_psa_aes_perf.c <mbedtls/test_apps/mbedtls_ut/main/test_psa_aes_perf.c>`.
|
||||
|
||||
Considering the above performance impact, ESP-IDF by-default does not enable the pseudo-round function to avoid any performance-related degrade. But it is recommended to enable the pseudo-round function for better security.
|
||||
|
||||
|
||||
@@ -113,8 +113,17 @@ TLS 连接所需的 DS 外设配置
|
||||
|
||||
psa_destroy_key(key_id);
|
||||
|
||||
使用 DS 外设进行 SSL 双向认证
|
||||
-----------------------------
|
||||
持久化与易失性 RSA_DS PSA 密钥
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
驱动支持两种 PSA 密钥生命周期:
|
||||
|
||||
- ``PSA_KEY_LIFETIME_ESP_RSA_DS_VOLATILE`` (上述示例中使用)只在 PSA 密钥 槽中保存指向调用方提供的 ``esp_ds_data_ctx_t`` 以及密钥管理器恢复信息的 指针。被引用的缓冲区必须保持有效,直到调用 :cpp:func:`psa_destroy_key` 为止。这样可避免对大块数据(例如 :cpp:type:`esp_ds_data_t`,约 1200–1600 字节,因芯片而异)进行深拷贝;当这些数据已经通过 ``esp_secure_cert_mgr`` 从 flash 中以 mmap 形式可用时,这一点尤其有用。
|
||||
|
||||
- ``PSA_KEY_LIFETIME_ESP_RSA_DS`` (持久化)在调用 :cpp:func:`psa_import_key` 时将加密的密钥数据深拷贝到 PSA 密钥槽中,并 由 PSA 与其他密钥属性一同持久化到 NVS。导入返回后,调用方即可释放原始 缓冲区;后续的 :cpp:func:`psa_sign_hash` 和 :cpp:func:`psa_asymmetric_decrypt` 调用会自动从 NVS 取回所需数据。 当应用希望密钥在重启后依然可用,且无需在每次启动时重新从外部存储 载入 ``esp_ds_data_ctx_t`` 时,应使用此生命周期。
|
||||
|
||||
使用 RSA_DS 外设进行 SSL 双向认证
|
||||
------------------------------------
|
||||
|
||||
此前位于 ``examples/protocols/mqtt/ssl_ds`` 目录下的 SSL 双向认证示例现已随独立的 `espressif/mqtt <https://components.espressif.com/components/espressif/mqtt>`__ 组件一同提供。请参照该组件文档获取 SSL DS 示例,并与 ESP-MQTT 一同构建。该示例仍使用 ``mqtt_client`` (由 ESP-MQTT 实现),通过双向认证 TLS 连接至 ``test.mosquitto.org``,其中 TLS 通信层仍由 ESP-TLS 实现。
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@ flash 加密最佳实践
|
||||
* - 高
|
||||
- 72.4 %
|
||||
|
||||
.. [#] 上述性能数据通过 mbedtls 测试应用中的 AES 性能测试 :component_file:`test_psa_aes_perf.c <mbedtls/test_apps/main/test_psa_aes_perf.c>` 计算得出。
|
||||
.. [#] 上述性能数据通过 mbedtls 测试应用中的 AES 性能测试 :component_file:`test_psa_aes_perf.c <mbedtls/test_apps/mbedtls_ut/main/test_psa_aes_perf.c>` 计算得出。
|
||||
|
||||
考虑到上述性能影响,ESP-IDF 默认关闭伪轮次功能,避免对相关性能造成影响。但如果需要更高的安全性,仍然建议启用。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user