mbedtls/library/psa_crypto_slot_management.c
Paul Elliott 838886da64 Protect the key slot management initialised flag
Use the global data mutex, as the key slot mutex has to be held in some
of the functions where we are testing the flag, and we already hold the
global data mutex when calling the functions where the flag is set.

Signed-off-by: Paul Elliott <paul.elliott@arm.com>
2024-03-13 12:39:02 +00:00

687 lines
22 KiB
C

/*
* PSA crypto layer on top of Mbed TLS crypto
*/
/*
* Copyright The Mbed TLS Contributors
* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
*/
#include "common.h"
#if defined(MBEDTLS_PSA_CRYPTO_C)
#include "psa/crypto.h"
#include "psa_crypto_core.h"
#include "psa_crypto_driver_wrappers_no_static.h"
#include "psa_crypto_slot_management.h"
#include "psa_crypto_storage.h"
#if defined(MBEDTLS_PSA_CRYPTO_SE_C)
#include "psa_crypto_se.h"
#endif
#include <stdlib.h>
#include <string.h>
#include "mbedtls/platform.h"
#if defined(MBEDTLS_THREADING_C)
#include "mbedtls/threading.h"
#endif
typedef struct {
psa_key_slot_t key_slots[MBEDTLS_PSA_KEY_SLOT_COUNT];
uint8_t key_slots_initialized;
} psa_global_data_t;
static psa_global_data_t global_data;
static uint8_t psa_get_key_slots_initialized(void)
{
uint8_t initialized;
#if defined(MBEDTLS_THREADING_C)
mbedtls_mutex_lock(&mbedtls_threading_psa_globaldata_mutex);
#endif /* defined(MBEDTLS_THREADING_C) */
initialized = global_data.key_slots_initialized;
#if defined(MBEDTLS_THREADING_C)
mbedtls_mutex_unlock(&mbedtls_threading_psa_globaldata_mutex);
#endif /* defined(MBEDTLS_THREADING_C) */
return initialized;
}
int psa_is_valid_key_id(mbedtls_svc_key_id_t key, int vendor_ok)
{
psa_key_id_t key_id = MBEDTLS_SVC_KEY_ID_GET_KEY_ID(key);
if ((PSA_KEY_ID_USER_MIN <= key_id) &&
(key_id <= PSA_KEY_ID_USER_MAX)) {
return 1;
}
if (vendor_ok &&
(PSA_KEY_ID_VENDOR_MIN <= key_id) &&
(key_id <= PSA_KEY_ID_VENDOR_MAX)) {
return 1;
}
return 0;
}
/** Get the description in memory of a key given its identifier and lock it.
*
* The descriptions of volatile keys and loaded persistent keys are
* stored in key slots. This function returns a pointer to the key slot
* containing the description of a key given its identifier.
*
* The function searches the key slots containing the description of the key
* with \p key identifier. The function does only read accesses to the key
* slots. The function does not load any persistent key thus does not access
* any storage.
*
* For volatile key identifiers, only one key slot is queried as a volatile
* key with identifier key_id can only be stored in slot of index
* ( key_id - #PSA_KEY_ID_VOLATILE_MIN ).
*
* On success, the function locks the key slot. It is the responsibility of
* the caller to unlock the key slot when it does not access it anymore.
*
* If multi-threading is enabled, the caller must hold the
* global key slot mutex.
*
* \param key Key identifier to query.
* \param[out] p_slot On success, `*p_slot` contains a pointer to the
* key slot containing the description of the key
* identified by \p key.
*
* \retval #PSA_SUCCESS
* The pointer to the key slot containing the description of the key
* identified by \p key was returned.
* \retval #PSA_ERROR_INVALID_HANDLE
* \p key is not a valid key identifier.
* \retval #PSA_ERROR_DOES_NOT_EXIST
* There is no key with key identifier \p key in the key slots.
*/
static psa_status_t psa_get_and_lock_key_slot_in_memory(
mbedtls_svc_key_id_t key, psa_key_slot_t **p_slot)
{
psa_status_t status = PSA_ERROR_CORRUPTION_DETECTED;
psa_key_id_t key_id = MBEDTLS_SVC_KEY_ID_GET_KEY_ID(key);
size_t slot_idx;
psa_key_slot_t *slot = NULL;
if (psa_key_id_is_volatile(key_id)) {
slot = &global_data.key_slots[key_id - PSA_KEY_ID_VOLATILE_MIN];
/* Check if both the PSA key identifier key_id and the owner
* identifier of key match those of the key slot. */
if ((slot->state == PSA_SLOT_FULL) &&
(mbedtls_svc_key_id_equal(key, slot->attr.id))) {
status = PSA_SUCCESS;
} else {
status = PSA_ERROR_DOES_NOT_EXIST;
}
} else {
if (!psa_is_valid_key_id(key, 1)) {
return PSA_ERROR_INVALID_HANDLE;
}
for (slot_idx = 0; slot_idx < MBEDTLS_PSA_KEY_SLOT_COUNT; slot_idx++) {
slot = &global_data.key_slots[slot_idx];
/* Only consider slots which are in a full state. */
if ((slot->state == PSA_SLOT_FULL) &&
(mbedtls_svc_key_id_equal(key, slot->attr.id))) {
break;
}
}
status = (slot_idx < MBEDTLS_PSA_KEY_SLOT_COUNT) ?
PSA_SUCCESS : PSA_ERROR_DOES_NOT_EXIST;
}
if (status == PSA_SUCCESS) {
status = psa_register_read(slot);
if (status == PSA_SUCCESS) {
*p_slot = slot;
}
}
return status;
}
psa_status_t psa_initialize_key_slots(void)
{
/* Nothing to do: program startup and psa_wipe_all_key_slots() both
* guarantee that the key slots are initialized to all-zero, which
* means that all the key slots are in a valid, empty state. The global
* data mutex is already held when calling this function, so no need to
* lock it here, to set the flag. */
global_data.key_slots_initialized = 1;
return PSA_SUCCESS;
}
void psa_wipe_all_key_slots(void)
{
size_t slot_idx;
for (slot_idx = 0; slot_idx < MBEDTLS_PSA_KEY_SLOT_COUNT; slot_idx++) {
psa_key_slot_t *slot = &global_data.key_slots[slot_idx];
slot->registered_readers = 1;
slot->state = PSA_SLOT_PENDING_DELETION;
(void) psa_wipe_key_slot(slot);
}
/* The global data mutex is already held when calling this function. */
global_data.key_slots_initialized = 0;
}
psa_status_t psa_reserve_free_key_slot(psa_key_id_t *volatile_key_id,
psa_key_slot_t **p_slot)
{
psa_status_t status = PSA_ERROR_CORRUPTION_DETECTED;
size_t slot_idx;
psa_key_slot_t *selected_slot, *unused_persistent_key_slot;
if (!psa_get_key_slots_initialized()) {
status = PSA_ERROR_BAD_STATE;
goto error;
}
selected_slot = unused_persistent_key_slot = NULL;
for (slot_idx = 0; slot_idx < MBEDTLS_PSA_KEY_SLOT_COUNT; slot_idx++) {
psa_key_slot_t *slot = &global_data.key_slots[slot_idx];
if (slot->state == PSA_SLOT_EMPTY) {
selected_slot = slot;
break;
}
if ((unused_persistent_key_slot == NULL) &&
(slot->state == PSA_SLOT_FULL) &&
(!psa_key_slot_has_readers(slot)) &&
(!PSA_KEY_LIFETIME_IS_VOLATILE(slot->attr.lifetime))) {
unused_persistent_key_slot = slot;
}
}
/*
* If there is no unused key slot and there is at least one unlocked key
* slot containing the description of a persistent key, recycle the first
* such key slot we encountered. If we later need to operate on the
* persistent key we are evicting now, we will reload its description from
* storage.
*/
if ((selected_slot == NULL) &&
(unused_persistent_key_slot != NULL)) {
selected_slot = unused_persistent_key_slot;
psa_register_read(selected_slot);
status = psa_wipe_key_slot(selected_slot);
if (status != PSA_SUCCESS) {
goto error;
}
}
if (selected_slot != NULL) {
status = psa_key_slot_state_transition(selected_slot, PSA_SLOT_EMPTY,
PSA_SLOT_FILLING);
if (status != PSA_SUCCESS) {
goto error;
}
*volatile_key_id = PSA_KEY_ID_VOLATILE_MIN +
((psa_key_id_t) (selected_slot - global_data.key_slots));
*p_slot = selected_slot;
return PSA_SUCCESS;
}
status = PSA_ERROR_INSUFFICIENT_MEMORY;
error:
*p_slot = NULL;
*volatile_key_id = 0;
return status;
}
#if defined(MBEDTLS_PSA_CRYPTO_STORAGE_C)
static psa_status_t psa_load_persistent_key_into_slot(psa_key_slot_t *slot)
{
psa_status_t status = PSA_SUCCESS;
uint8_t *key_data = NULL;
size_t key_data_length = 0;
status = psa_load_persistent_key(&slot->attr,
&key_data, &key_data_length);
if (status != PSA_SUCCESS) {
goto exit;
}
#if defined(MBEDTLS_PSA_CRYPTO_SE_C)
/* Special handling is required for loading keys associated with a
* dynamically registered SE interface. */
const psa_drv_se_t *drv;
psa_drv_se_context_t *drv_context;
if (psa_get_se_driver(slot->attr.lifetime, &drv, &drv_context)) {
psa_se_key_data_storage_t *data;
if (key_data_length != sizeof(*data)) {
status = PSA_ERROR_DATA_INVALID;
goto exit;
}
data = (psa_se_key_data_storage_t *) key_data;
status = psa_copy_key_material_into_slot(
slot, data->slot_number, sizeof(data->slot_number));
goto exit;
}
#endif /* MBEDTLS_PSA_CRYPTO_SE_C */
status = psa_copy_key_material_into_slot(slot, key_data, key_data_length);
if (status != PSA_SUCCESS) {
goto exit;
}
exit:
psa_free_persistent_key_data(key_data, key_data_length);
return status;
}
#endif /* MBEDTLS_PSA_CRYPTO_STORAGE_C */
#if defined(MBEDTLS_PSA_CRYPTO_BUILTIN_KEYS)
static psa_status_t psa_load_builtin_key_into_slot(psa_key_slot_t *slot)
{
psa_status_t status = PSA_ERROR_CORRUPTION_DETECTED;
psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
psa_key_lifetime_t lifetime = PSA_KEY_LIFETIME_VOLATILE;
psa_drv_slot_number_t slot_number = 0;
size_t key_buffer_size = 0;
size_t key_buffer_length = 0;
if (!psa_key_id_is_builtin(
MBEDTLS_SVC_KEY_ID_GET_KEY_ID(slot->attr.id))) {
return PSA_ERROR_DOES_NOT_EXIST;
}
/* Check the platform function to see whether this key actually exists */
status = mbedtls_psa_platform_get_builtin_key(
slot->attr.id, &lifetime, &slot_number);
if (status != PSA_SUCCESS) {
return status;
}
/* Set required key attributes to ensure get_builtin_key can retrieve the
* full attributes. */
psa_set_key_id(&attributes, slot->attr.id);
psa_set_key_lifetime(&attributes, lifetime);
/* Get the full key attributes from the driver in order to be able to
* calculate the required buffer size. */
status = psa_driver_wrapper_get_builtin_key(
slot_number, &attributes,
NULL, 0, NULL);
if (status != PSA_ERROR_BUFFER_TOO_SMALL) {
/* Builtin keys cannot be defined by the attributes alone */
if (status == PSA_SUCCESS) {
status = PSA_ERROR_CORRUPTION_DETECTED;
}
return status;
}
/* If the key should exist according to the platform, then ask the driver
* what its expected size is. */
status = psa_driver_wrapper_get_key_buffer_size(&attributes,
&key_buffer_size);
if (status != PSA_SUCCESS) {
return status;
}
/* Allocate a buffer of the required size and load the builtin key directly
* into the (now properly sized) slot buffer. */
status = psa_allocate_buffer_to_slot(slot, key_buffer_size);
if (status != PSA_SUCCESS) {
return status;
}
status = psa_driver_wrapper_get_builtin_key(
slot_number, &attributes,
slot->key.data, slot->key.bytes, &key_buffer_length);
if (status != PSA_SUCCESS) {
goto exit;
}
/* Copy actual key length and core attributes into the slot on success */
slot->key.bytes = key_buffer_length;
slot->attr = attributes;
exit:
if (status != PSA_SUCCESS) {
psa_remove_key_data_from_memory(slot);
}
return status;
}
#endif /* MBEDTLS_PSA_CRYPTO_BUILTIN_KEYS */
psa_status_t psa_get_and_lock_key_slot(mbedtls_svc_key_id_t key,
psa_key_slot_t **p_slot)
{
psa_status_t status = PSA_ERROR_CORRUPTION_DETECTED;
*p_slot = NULL;
if (!psa_get_key_slots_initialized()) {
return PSA_ERROR_BAD_STATE;
}
#if defined(MBEDTLS_THREADING_C)
/* We need to set status as success, otherwise CORRUPTION_DETECTED
* would be returned if the lock fails. */
status = PSA_SUCCESS;
/* If the key is persistent and not loaded, we cannot unlock the mutex
* between checking if the key is loaded and setting the slot as FULL,
* as otherwise another thread may load and then destroy the key
* in the meantime. */
PSA_THREADING_CHK_RET(mbedtls_mutex_lock(
&mbedtls_threading_key_slot_mutex));
#endif
/*
* On success, the pointer to the slot is passed directly to the caller
* thus no need to unlock the key slot here.
*/
status = psa_get_and_lock_key_slot_in_memory(key, p_slot);
if (status != PSA_ERROR_DOES_NOT_EXIST) {
#if defined(MBEDTLS_THREADING_C)
PSA_THREADING_CHK_RET(mbedtls_mutex_unlock(
&mbedtls_threading_key_slot_mutex));
#endif
return status;
}
/* Loading keys from storage requires support for such a mechanism */
#if defined(MBEDTLS_PSA_CRYPTO_STORAGE_C) || \
defined(MBEDTLS_PSA_CRYPTO_BUILTIN_KEYS)
psa_key_id_t volatile_key_id;
status = psa_reserve_free_key_slot(&volatile_key_id, p_slot);
if (status != PSA_SUCCESS) {
#if defined(MBEDTLS_THREADING_C)
PSA_THREADING_CHK_RET(mbedtls_mutex_unlock(
&mbedtls_threading_key_slot_mutex));
#endif
return status;
}
(*p_slot)->attr.id = key;
(*p_slot)->attr.lifetime = PSA_KEY_LIFETIME_PERSISTENT;
status = PSA_ERROR_DOES_NOT_EXIST;
#if defined(MBEDTLS_PSA_CRYPTO_BUILTIN_KEYS)
/* Load keys in the 'builtin' range through their own interface */
status = psa_load_builtin_key_into_slot(*p_slot);
#endif /* MBEDTLS_PSA_CRYPTO_BUILTIN_KEYS */
#if defined(MBEDTLS_PSA_CRYPTO_STORAGE_C)
if (status == PSA_ERROR_DOES_NOT_EXIST) {
status = psa_load_persistent_key_into_slot(*p_slot);
}
#endif /* defined(MBEDTLS_PSA_CRYPTO_STORAGE_C) */
if (status != PSA_SUCCESS) {
psa_wipe_key_slot(*p_slot);
if (status == PSA_ERROR_DOES_NOT_EXIST) {
status = PSA_ERROR_INVALID_HANDLE;
}
} else {
/* Add implicit usage flags. */
psa_extend_key_usage_flags(&(*p_slot)->attr.policy.usage);
psa_key_slot_state_transition((*p_slot), PSA_SLOT_FILLING,
PSA_SLOT_FULL);
status = psa_register_read(*p_slot);
}
#else /* MBEDTLS_PSA_CRYPTO_STORAGE_C || MBEDTLS_PSA_CRYPTO_BUILTIN_KEYS */
status = PSA_ERROR_INVALID_HANDLE;
#endif /* MBEDTLS_PSA_CRYPTO_STORAGE_C || MBEDTLS_PSA_CRYPTO_BUILTIN_KEYS */
#if defined(MBEDTLS_THREADING_C)
PSA_THREADING_CHK_RET(mbedtls_mutex_unlock(
&mbedtls_threading_key_slot_mutex));
#endif
return status;
}
psa_status_t psa_unregister_read(psa_key_slot_t *slot)
{
if (slot == NULL) {
return PSA_SUCCESS;
}
if ((slot->state != PSA_SLOT_FULL) &&
(slot->state != PSA_SLOT_PENDING_DELETION)) {
return PSA_ERROR_CORRUPTION_DETECTED;
}
/* If we are the last reader and the slot is marked for deletion,
* we must wipe the slot here. */
if ((slot->state == PSA_SLOT_PENDING_DELETION) &&
(slot->registered_readers == 1)) {
return psa_wipe_key_slot(slot);
}
if (psa_key_slot_has_readers(slot)) {
slot->registered_readers--;
return PSA_SUCCESS;
}
/*
* As the return error code may not be handled in case of multiple errors,
* do our best to report if there are no registered readers. Assert with
* MBEDTLS_TEST_HOOK_TEST_ASSERT that there are registered readers:
* if the MBEDTLS_TEST_HOOKS configuration option is enabled and
* the function is called as part of the execution of a test suite, the
* execution of the test suite is stopped in error if the assertion fails.
*/
MBEDTLS_TEST_HOOK_TEST_ASSERT(psa_key_slot_has_readers(slot));
return PSA_ERROR_CORRUPTION_DETECTED;
}
psa_status_t psa_unregister_read_under_mutex(psa_key_slot_t *slot)
{
psa_status_t status = PSA_ERROR_CORRUPTION_DETECTED;
#if defined(MBEDTLS_THREADING_C)
/* We need to set status as success, otherwise CORRUPTION_DETECTED
* would be returned if the lock fails. */
status = PSA_SUCCESS;
PSA_THREADING_CHK_RET(mbedtls_mutex_lock(
&mbedtls_threading_key_slot_mutex));
#endif
status = psa_unregister_read(slot);
#if defined(MBEDTLS_THREADING_C)
PSA_THREADING_CHK_RET(mbedtls_mutex_unlock(
&mbedtls_threading_key_slot_mutex));
#endif
return status;
}
psa_status_t psa_validate_key_location(psa_key_lifetime_t lifetime,
psa_se_drv_table_entry_t **p_drv)
{
if (psa_key_lifetime_is_external(lifetime)) {
#if defined(MBEDTLS_PSA_CRYPTO_SE_C)
/* Check whether a driver is registered against this lifetime */
psa_se_drv_table_entry_t *driver = psa_get_se_driver_entry(lifetime);
if (driver != NULL) {
if (p_drv != NULL) {
*p_drv = driver;
}
return PSA_SUCCESS;
}
#else /* MBEDTLS_PSA_CRYPTO_SE_C */
(void) p_drv;
#endif /* MBEDTLS_PSA_CRYPTO_SE_C */
/* Key location for external keys gets checked by the wrapper */
return PSA_SUCCESS;
} else {
/* Local/internal keys are always valid */
return PSA_SUCCESS;
}
}
psa_status_t psa_validate_key_persistence(psa_key_lifetime_t lifetime)
{
if (PSA_KEY_LIFETIME_IS_VOLATILE(lifetime)) {
/* Volatile keys are always supported */
return PSA_SUCCESS;
} else {
/* Persistent keys require storage support */
#if defined(MBEDTLS_PSA_CRYPTO_STORAGE_C)
if (PSA_KEY_LIFETIME_IS_READ_ONLY(lifetime)) {
return PSA_ERROR_INVALID_ARGUMENT;
} else {
return PSA_SUCCESS;
}
#else /* MBEDTLS_PSA_CRYPTO_STORAGE_C */
return PSA_ERROR_NOT_SUPPORTED;
#endif /* !MBEDTLS_PSA_CRYPTO_STORAGE_C */
}
}
psa_status_t psa_open_key(mbedtls_svc_key_id_t key, psa_key_handle_t *handle)
{
#if defined(MBEDTLS_PSA_CRYPTO_STORAGE_C) || \
defined(MBEDTLS_PSA_CRYPTO_BUILTIN_KEYS)
psa_status_t status;
psa_key_slot_t *slot;
status = psa_get_and_lock_key_slot(key, &slot);
if (status != PSA_SUCCESS) {
*handle = PSA_KEY_HANDLE_INIT;
if (status == PSA_ERROR_INVALID_HANDLE) {
status = PSA_ERROR_DOES_NOT_EXIST;
}
return status;
}
*handle = key;
return psa_unregister_read_under_mutex(slot);
#else /* MBEDTLS_PSA_CRYPTO_STORAGE_C || MBEDTLS_PSA_CRYPTO_BUILTIN_KEYS */
(void) key;
*handle = PSA_KEY_HANDLE_INIT;
return PSA_ERROR_NOT_SUPPORTED;
#endif /* MBEDTLS_PSA_CRYPTO_STORAGE_C || MBEDTLS_PSA_CRYPTO_BUILTIN_KEYS */
}
psa_status_t psa_close_key(psa_key_handle_t handle)
{
psa_status_t status = PSA_ERROR_CORRUPTION_DETECTED;
psa_key_slot_t *slot;
if (psa_key_handle_is_null(handle)) {
return PSA_SUCCESS;
}
#if defined(MBEDTLS_THREADING_C)
/* We need to set status as success, otherwise CORRUPTION_DETECTED
* would be returned if the lock fails. */
status = PSA_SUCCESS;
PSA_THREADING_CHK_RET(mbedtls_mutex_lock(
&mbedtls_threading_key_slot_mutex));
#endif
status = psa_get_and_lock_key_slot_in_memory(handle, &slot);
if (status != PSA_SUCCESS) {
if (status == PSA_ERROR_DOES_NOT_EXIST) {
status = PSA_ERROR_INVALID_HANDLE;
}
#if defined(MBEDTLS_THREADING_C)
PSA_THREADING_CHK_RET(mbedtls_mutex_unlock(
&mbedtls_threading_key_slot_mutex));
#endif
return status;
}
if (slot->registered_readers == 1) {
status = psa_wipe_key_slot(slot);
} else {
status = psa_unregister_read(slot);
}
#if defined(MBEDTLS_THREADING_C)
PSA_THREADING_CHK_RET(mbedtls_mutex_unlock(
&mbedtls_threading_key_slot_mutex));
#endif
return status;
}
psa_status_t psa_purge_key(mbedtls_svc_key_id_t key)
{
psa_status_t status = PSA_ERROR_CORRUPTION_DETECTED;
psa_key_slot_t *slot;
#if defined(MBEDTLS_THREADING_C)
/* We need to set status as success, otherwise CORRUPTION_DETECTED
* would be returned if the lock fails. */
status = PSA_SUCCESS;
PSA_THREADING_CHK_RET(mbedtls_mutex_lock(
&mbedtls_threading_key_slot_mutex));
#endif
status = psa_get_and_lock_key_slot_in_memory(key, &slot);
if (status != PSA_SUCCESS) {
#if defined(MBEDTLS_THREADING_C)
PSA_THREADING_CHK_RET(mbedtls_mutex_unlock(
&mbedtls_threading_key_slot_mutex));
#endif
return status;
}
if ((!PSA_KEY_LIFETIME_IS_VOLATILE(slot->attr.lifetime)) &&
(slot->registered_readers == 1)) {
status = psa_wipe_key_slot(slot);
} else {
status = psa_unregister_read(slot);
}
#if defined(MBEDTLS_THREADING_C)
PSA_THREADING_CHK_RET(mbedtls_mutex_unlock(
&mbedtls_threading_key_slot_mutex));
#endif
return status;
}
void mbedtls_psa_get_stats(mbedtls_psa_stats_t *stats)
{
size_t slot_idx;
memset(stats, 0, sizeof(*stats));
for (slot_idx = 0; slot_idx < MBEDTLS_PSA_KEY_SLOT_COUNT; slot_idx++) {
const psa_key_slot_t *slot = &global_data.key_slots[slot_idx];
if (psa_key_slot_has_readers(slot)) {
++stats->locked_slots;
}
if (slot->state == PSA_SLOT_EMPTY) {
++stats->empty_slots;
continue;
}
if (PSA_KEY_LIFETIME_IS_VOLATILE(slot->attr.lifetime)) {
++stats->volatile_slots;
} else {
psa_key_id_t id = MBEDTLS_SVC_KEY_ID_GET_KEY_ID(slot->attr.id);
++stats->persistent_slots;
if (id > stats->max_open_internal_key_id) {
stats->max_open_internal_key_id = id;
}
}
if (PSA_KEY_LIFETIME_GET_LOCATION(slot->attr.lifetime) !=
PSA_KEY_LOCATION_LOCAL_STORAGE) {
psa_key_id_t id = MBEDTLS_SVC_KEY_ID_GET_KEY_ID(slot->attr.id);
++stats->external_slots;
if (id > stats->max_open_external_key_id) {
stats->max_open_external_key_id = id;
}
}
}
}
#endif /* MBEDTLS_PSA_CRYPTO_C */