Merge pull request #9464 from gilles-peskine-arm/psa-keystore-dynamic-development

dynamically sized key store
diff --git a/ChangeLog.d/dynamic-keystore.txt b/ChangeLog.d/dynamic-keystore.txt
index d576dcd..c6aac3c 100644
--- a/ChangeLog.d/dynamic-keystore.txt
+++ b/ChangeLog.d/dynamic-keystore.txt
@@ -1,3 +1,9 @@
+Features
+   * When the new compilation option MBEDTLS_PSA_KEY_STORE_DYNAMIC is enabled,
+     the number of volatile PSA keys is virtually unlimited, at the expense
+     of increased code size. This option is off by default, but enabled in
+     the default mbedtls_config.h. Fixes #9216.
+
 Bugfix
    * Fix interference between PSA volatile keys and built-in keys
      when MBEDTLS_PSA_CRYPTO_BUILTIN_KEYS is enabled and
diff --git a/include/mbedtls/mbedtls_config.h b/include/mbedtls/mbedtls_config.h
index e3589ea..4a5f21a 100644
--- a/include/mbedtls/mbedtls_config.h
+++ b/include/mbedtls/mbedtls_config.h
@@ -1233,6 +1233,23 @@
 //#define MBEDTLS_PSA_CRYPTO_SPM
 
 /**
+ * \def MBEDTLS_PSA_KEY_STORE_DYNAMIC
+ *
+ * Dynamically resize the PSA key store to accommodate any number of
+ * volatile keys (until the heap memory is exhausted).
+ *
+ * If this option is disabled, the key store has a fixed size
+ * #MBEDTLS_PSA_KEY_SLOT_COUNT for volatile keys and loaded persistent keys
+ * together.
+ *
+ * This option has no effect when #MBEDTLS_PSA_CRYPTO_C is disabled.
+ *
+ * Module:  library/psa_crypto.c
+ * Requires: MBEDTLS_PSA_CRYPTO_C
+ */
+#define MBEDTLS_PSA_KEY_STORE_DYNAMIC
+
+/**
  * Uncomment to enable p256-m. This is an alternative implementation of
  * key generation, ECDH and (randomized) ECDSA on the curve SECP256R1.
  * Compared to the default implementation:
@@ -3825,9 +3842,13 @@
 
 /** \def MBEDTLS_PSA_KEY_SLOT_COUNT
  *
- * The maximum amount of PSA keys simultaneously in memory. This counts all
+ * When #MBEDTLS_PSA_KEY_STORE_DYNAMIC is disabled,
+ * the maximum amount of PSA keys simultaneously in memory. This counts all
  * volatile keys, plus loaded persistent keys.
  *
+ * When #MBEDTLS_PSA_KEY_STORE_DYNAMIC is enabled,
+ * the maximum number of loaded persistent keys.
+ *
  * Currently, persistent keys do not need to be loaded all the time while
  * a multipart operation is in progress, only while the operation is being
  * set up. This may change in future versions of the library.
diff --git a/tests/scripts/components-configuration-crypto.sh b/tests/scripts/components-configuration-crypto.sh
index f4744a8..37b610f 100644
--- a/tests/scripts/components-configuration-crypto.sh
+++ b/tests/scripts/components-configuration-crypto.sh
@@ -2069,6 +2069,40 @@
     scripts/config.py set MBEDTLS_DEPRECATED_REMOVED
 }
 
+component_test_full_block_cipher_psa_dispatch_static_keystore () {
+    msg "build: full + PSA dispatch in block_cipher with static keystore"
+    # Check that the static key store works well when CTR_DRBG uses a
+    # PSA key for AES.
+    scripts/config.py unset MBEDTLS_PSA_KEY_STORE_DYNAMIC
+
+    loc_accel_list="ALG_ECB_NO_PADDING \
+                    KEY_TYPE_AES KEY_TYPE_ARIA KEY_TYPE_CAMELLIA"
+
+    # Configure
+    # ---------
+
+    common_block_cipher_dispatch 1
+
+    # Build
+    # -----
+
+    helper_libtestdriver1_make_drivers "$loc_accel_list"
+
+    helper_libtestdriver1_make_main "$loc_accel_list"
+
+    # Make sure disabled components were not re-enabled by accident (additive
+    # config)
+    not grep mbedtls_aes_ library/aes.o
+    not grep mbedtls_aria_ library/aria.o
+    not grep mbedtls_camellia_ library/camellia.o
+
+    # Run the tests
+    # -------------
+
+    msg "test: full + PSA dispatch in block_cipher with static keystore"
+    make test
+}
+
 component_test_full_block_cipher_psa_dispatch () {
     msg "build: full + PSA dispatch in block_cipher"
 
@@ -2593,6 +2627,16 @@
     make test
 }
 
+component_test_full_static_keystore () {
+    msg "build: full config - MBEDTLS_PSA_KEY_STORE_DYNAMIC"
+    scripts/config.py full
+    scripts/config.py unset MBEDTLS_PSA_KEY_STORE_DYNAMIC
+    make CC=clang CFLAGS="$ASAN_CFLAGS -Os" LDFLAGS="$ASAN_CFLAGS"
+
+    msg "test: full config - MBEDTLS_PSA_KEY_STORE_DYNAMIC"
+    make test
+}
+
 component_test_psa_crypto_drivers () {
     msg "build: full + test drivers dispatching to builtins"
     scripts/config.py full
diff --git a/tf-psa-crypto/core/psa_crypto.c b/tf-psa-crypto/core/psa_crypto.c
index fd05128..d1c93fd 100644
--- a/tf-psa-crypto/core/psa_crypto.c
+++ b/tf-psa-crypto/core/psa_crypto.c
@@ -1210,15 +1210,15 @@
         case PSA_SLOT_PENDING_DELETION:
             /* In this state psa_wipe_key_slot() must only be called if the
              * caller is the last reader. */
-            if (slot->registered_readers != 1) {
-                MBEDTLS_TEST_HOOK_TEST_ASSERT(slot->registered_readers == 1);
+            if (slot->var.occupied.registered_readers != 1) {
+                MBEDTLS_TEST_HOOK_TEST_ASSERT(slot->var.occupied.registered_readers == 1);
                 status = PSA_ERROR_CORRUPTION_DETECTED;
             }
             break;
         case PSA_SLOT_FILLING:
             /* In this state registered_readers must be 0. */
-            if (slot->registered_readers != 0) {
-                MBEDTLS_TEST_HOOK_TEST_ASSERT(slot->registered_readers == 0);
+            if (slot->var.occupied.registered_readers != 0) {
+                MBEDTLS_TEST_HOOK_TEST_ASSERT(slot->var.occupied.registered_readers == 0);
                 status = PSA_ERROR_CORRUPTION_DETECTED;
             }
             break;
@@ -1232,6 +1232,11 @@
             status = PSA_ERROR_CORRUPTION_DETECTED;
     }
 
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+    size_t slice_index = slot->slice_index;
+#endif /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
+
+
     /* Multipart operations may still be using the key. This is safe
      * because all multipart operation objects are independent from
      * the key slot: if they need to access the key after the setup
@@ -1242,6 +1247,17 @@
      * zeroize because the metadata is not particularly sensitive.
      * This memset also sets the slot's state to PSA_SLOT_EMPTY. */
     memset(slot, 0, sizeof(*slot));
+
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+    /* If the slot is already corrupted, something went deeply wrong,
+     * like a thread still using the slot or a stray pointer leading
+     * to the slot's memory being used for another object. Let the slot
+     * leak rather than make the corruption worse. */
+    if (status == PSA_SUCCESS) {
+        status = psa_free_key_slot(slice_index, slot);
+    }
+#endif /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
+
     return status;
 }
 
@@ -1753,8 +1769,6 @@
     psa_se_drv_table_entry_t **p_drv)
 {
     psa_status_t status;
-    psa_key_id_t volatile_key_id;
-    psa_key_slot_t *slot;
 
     (void) method;
     *p_drv = NULL;
@@ -1764,11 +1778,16 @@
         return status;
     }
 
+    int key_is_volatile = PSA_KEY_LIFETIME_IS_VOLATILE(attributes->lifetime);
+    psa_key_id_t volatile_key_id;
+
 #if defined(MBEDTLS_THREADING_C)
     PSA_THREADING_CHK_RET(mbedtls_mutex_lock(
                               &mbedtls_threading_key_slot_mutex));
 #endif
-    status = psa_reserve_free_key_slot(&volatile_key_id, p_slot);
+    status = psa_reserve_free_key_slot(
+        key_is_volatile ? &volatile_key_id : NULL,
+        p_slot);
 #if defined(MBEDTLS_THREADING_C)
     PSA_THREADING_CHK_RET(mbedtls_mutex_unlock(
                               &mbedtls_threading_key_slot_mutex));
@@ -1776,7 +1795,7 @@
     if (status != PSA_SUCCESS) {
         return status;
     }
-    slot = *p_slot;
+    psa_key_slot_t *slot = *p_slot;
 
     /* We're storing the declared bit-size of the key. It's up to each
      * creation mechanism to verify that this information is correct.
@@ -1787,7 +1806,7 @@
      * definition. */
 
     slot->attr = *attributes;
-    if (PSA_KEY_LIFETIME_IS_VOLATILE(slot->attr.lifetime)) {
+    if (key_is_volatile) {
 #if !defined(MBEDTLS_PSA_CRYPTO_KEY_ID_ENCODES_OWNER)
         slot->attr.id = volatile_key_id;
 #else
diff --git a/tf-psa-crypto/core/psa_crypto_core.h b/tf-psa-crypto/core/psa_crypto_core.h
index e7ff151..21e7559 100644
--- a/tf-psa-crypto/core/psa_crypto_core.h
+++ b/tf-psa-crypto/core/psa_crypto_core.h
@@ -59,6 +59,8 @@
  * and metadata for one key.
  */
 typedef struct {
+    /* This field is accessed in a lot of places. Putting it first
+     * reduces the code size. */
     psa_key_attributes_t attr;
 
     /*
@@ -78,35 +80,77 @@
      * slots that are in a suitable state for the function.
      * For example, psa_get_and_lock_key_slot_in_memory, which finds a slot
      * containing a given key ID, will only check slots whose state variable is
-     * PSA_SLOT_FULL. */
+     * PSA_SLOT_FULL.
+     */
     psa_key_slot_state_t state;
 
-    /*
-     * Number of functions registered as reading the material in the key slot.
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+    /* The index of the slice containing this slot.
+     * This field must be filled if the slot contains a key
+     * (including keys being created or destroyed), and can be either
+     * filled or 0 when the slot is free.
      *
-     * Library functions must not write directly to registered_readers
-     *
-     * A function must call psa_register_read(slot) before reading the current
-     * contents of the slot for an operation.
-     * They then must call psa_unregister_read(slot) once they have finished
-     * reading the current contents of the slot. If the key slot mutex is not
-     * held (when mutexes are enabled), this call must be done via a call to
-     * psa_unregister_read_under_mutex(slot).
-     * A function must call psa_key_slot_has_readers(slot) to check if
-     * the slot is in use for reading.
-     *
-     * This counter is used to prevent resetting the key slot while the library
-     * may access it. For example, such control is needed in the following
-     * scenarios:
-     * . In case of key slot starvation, all key slots contain the description
-     *   of a key, and the library asks for the description of a persistent
-     *   key not present in the key slots, the key slots currently accessed by
-     *   the library cannot be reclaimed to free a key slot to load the
-     *   persistent key.
-     * . In case of a multi-threaded application where one thread asks to close
-     *   or purge or destroy a key while it is in use by the library through
-     *   another thread. */
-    size_t registered_readers;
+     * In most cases, the slice index can be deduced from the key identifer.
+     * We keep it in a separate field for robustness (it reduces the chance
+     * that a coding mistake in the key store will result in accessing the
+     * wrong slice), and also so that it's available even on code paths
+     * during creation or destruction where the key identifier might not be
+     * filled in.
+     * */
+    uint8_t slice_index;
+#endif /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
+
+    union {
+        struct {
+            /* The index of the next slot in the free list for this
+             * slice, relative * to the next array element.
+             *
+             * That is, 0 means the next slot, 1 means the next slot
+             * but one, etc. -1 would mean the slot itself. -2 means
+             * the previous slot, etc.
+             *
+             * If this is beyond the array length, the free list ends with the
+             * current element.
+             *
+             * The reason for this strange encoding is that 0 means the next
+             * element. This way, when we allocate a slice and initialize it
+             * to all-zero, the slice is ready for use, with a free list that
+             * consists of all the slots in order.
+             */
+            int32_t next_free_relative_to_next;
+        } free;
+
+        struct {
+            /*
+             * Number of functions registered as reading the material in the key slot.
+             *
+             * Library functions must not write directly to registered_readers
+             *
+             * A function must call psa_register_read(slot) before reading
+             * the current contents of the slot for an operation.
+             * They then must call psa_unregister_read(slot) once they have
+             * finished reading the current contents of the slot. If the key
+             * slot mutex is not held (when mutexes are enabled), this call
+             * must be done via a call to
+             * psa_unregister_read_under_mutex(slot).
+             * A function must call psa_key_slot_has_readers(slot) to check if
+             * the slot is in use for reading.
+             *
+             * This counter is used to prevent resetting the key slot while
+             * the library may access it. For example, such control is needed
+             * in the following scenarios:
+             * . In case of key slot starvation, all key slots contain the
+             *   description of a key, and the library asks for the
+             *   description of a persistent key not present in the
+             *   key slots, the key slots currently accessed by the
+             *   library cannot be reclaimed to free a key slot to load
+             *   the persistent key.
+             * . In case of a multi-threaded application where one thread
+             *   asks to close or purge or destroy a key while it is in use
+             *   by the library through another thread. */
+            size_t registered_readers;
+        } occupied;
+    } var;
 
     /* Dynamically allocated key data buffer.
      * Format as specified in psa_export_key(). */
@@ -169,7 +213,7 @@
  */
 static inline int psa_key_slot_has_readers(const psa_key_slot_t *slot)
 {
-    return slot->registered_readers > 0;
+    return slot->var.occupied.registered_readers > 0;
 }
 
 #if defined(MBEDTLS_PSA_CRYPTO_SE_C)
diff --git a/tf-psa-crypto/core/psa_crypto_slot_management.c b/tf-psa-crypto/core/psa_crypto_slot_management.c
index 9b297c9..9850d8c 100644
--- a/tf-psa-crypto/core/psa_crypto_slot_management.c
+++ b/tf-psa-crypto/core/psa_crypto_slot_management.c
@@ -58,17 +58,140 @@
 
 
 
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+
+/* Dynamic key store.
+ *
+ * The key store consists of multiple slices.
+ *
+ * The volatile keys are stored in variable-sized tables called slices.
+ * Slices are allocated on demand and deallocated when possible.
+ * The size of slices increases exponentially, so the average overhead
+ * (number of slots that are allocated but not used) is roughly
+ * proportional to the number of keys (with a factor that grows
+ * when the key store is fragmented).
+ *
+ * One slice is dedicated to the cache of persistent and built-in keys.
+ * For simplicity, they are separated from volatile keys. This cache
+ * slice has a fixed size and has the slice index KEY_SLOT_CACHE_SLICE_INDEX,
+ * located after the slices for volatile keys.
+ */
+
+/* Size of the last slice containing the cache of persistent and built-in keys. */
+#define PERSISTENT_KEY_CACHE_COUNT MBEDTLS_PSA_KEY_SLOT_COUNT
+
+/* Volatile keys are stored in slices 0 through
+ * (KEY_SLOT_VOLATILE_SLICE_COUNT - 1) inclusive.
+ * Each slice is twice the size of the previous slice.
+ * Volatile key identifiers encode the slice number as follows:
+ *     bits 30..31:  0b10 (mandated by the PSA Crypto specification).
+ *     bits 25..29:  slice index (0...KEY_SLOT_VOLATILE_SLICE_COUNT-1)
+ *     bits 0..24:   slot index in slice
+ */
+#define KEY_ID_SLOT_INDEX_WIDTH 25u
+#define KEY_ID_SLICE_INDEX_WIDTH 5u
+
+#define KEY_SLOT_VOLATILE_SLICE_BASE_LENGTH 16u
+#define KEY_SLOT_VOLATILE_SLICE_COUNT 22u
+#define KEY_SLICE_COUNT (KEY_SLOT_VOLATILE_SLICE_COUNT + 1u)
+#define KEY_SLOT_CACHE_SLICE_INDEX KEY_SLOT_VOLATILE_SLICE_COUNT
+
+
+/* Check that the length of the largest slice (calculated as
+ * KEY_SLICE_LENGTH_MAX below) does not overflow size_t. We use
+ * an indirect method in case the calculation of KEY_SLICE_LENGTH_MAX
+ * itself overflows uintmax_t: if (BASE_LENGTH << c)
+ * overflows size_t then BASE_LENGTH > SIZE_MAX >> c.
+ */
+#if (KEY_SLOT_VOLATILE_SLICE_BASE_LENGTH >              \
+     SIZE_MAX >> (KEY_SLOT_VOLATILE_SLICE_COUNT - 1))
+#error "Maximum slice length overflows size_t"
+#endif
+
+#if KEY_ID_SLICE_INDEX_WIDTH + KEY_ID_SLOT_INDEX_WIDTH > 30
+#error "Not enough room in volatile key IDs for slice index and slot index"
+#endif
+#if KEY_SLOT_VOLATILE_SLICE_COUNT > (1 << KEY_ID_SLICE_INDEX_WIDTH)
+#error "Too many slices to fit the slice index in a volatile key ID"
+#endif
+#define KEY_SLICE_LENGTH_MAX                                            \
+    (KEY_SLOT_VOLATILE_SLICE_BASE_LENGTH << (KEY_SLOT_VOLATILE_SLICE_COUNT - 1))
+#if KEY_SLICE_LENGTH_MAX > 1 << KEY_ID_SLOT_INDEX_WIDTH
+#error "Not enough room in volatile key IDs for a slot index in the largest slice"
+#endif
+#if KEY_ID_SLICE_INDEX_WIDTH > 8
+#error "Slice index does not fit in uint8_t for psa_key_slot_t::slice_index"
+#endif
+
+
+/* Calculate the volatile key id to use for a given slot.
+ * This function assumes valid parameter values. */
+static psa_key_id_t volatile_key_id_of_index(size_t slice_idx,
+                                             size_t slot_idx)
+{
+    /* We assert above that the slice and slot indexes fit in separate
+     * bit-fields inside psa_key_id_t, which is a 32-bit type per the
+     * PSA Cryptography specification. */
+    return (psa_key_id_t) (0x40000000u |
+                           (slice_idx << KEY_ID_SLOT_INDEX_WIDTH) |
+                           slot_idx);
+}
+
+/* Calculate the slice containing the given volatile key.
+ * This function assumes valid parameter values. */
+static size_t slice_index_of_volatile_key_id(psa_key_id_t key_id)
+{
+    size_t mask = (1LU << KEY_ID_SLICE_INDEX_WIDTH) - 1;
+    return (key_id >> KEY_ID_SLOT_INDEX_WIDTH) & mask;
+}
+
+/* Calculate the index of the slot containing the given volatile key.
+ * This function assumes valid parameter values. */
+static size_t slot_index_of_volatile_key_id(psa_key_id_t key_id)
+{
+    return key_id & ((1LU << KEY_ID_SLOT_INDEX_WIDTH) - 1);
+}
+
+/* In global_data.first_free_slot_index, use this special value to
+ * indicate that the slice is full. */
+#define FREE_SLOT_INDEX_NONE ((size_t) -1)
+
+#if defined(MBEDTLS_TEST_HOOKS)
+size_t psa_key_slot_volatile_slice_count(void)
+{
+    return KEY_SLOT_VOLATILE_SLICE_COUNT;
+}
+#endif
+
+#else /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
+
+/* Static key store.
+ *
+ * All the keys (volatile or persistent) are in a single slice.
+ * We only use slices as a concept to allow some differences between
+ * static and dynamic key store management to be buried in auxiliary
+ * functions.
+ */
+
+#define PERSISTENT_KEY_CACHE_COUNT MBEDTLS_PSA_KEY_SLOT_COUNT
+#define KEY_SLICE_COUNT 1u
+#define KEY_SLOT_CACHE_SLICE_INDEX 0
+
+#endif /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
+
+
 typedef struct {
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+    psa_key_slot_t *key_slices[KEY_SLICE_COUNT];
+    size_t first_free_slot_index[KEY_SLOT_VOLATILE_SLICE_COUNT];
+#else /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
     psa_key_slot_t key_slots[MBEDTLS_PSA_KEY_SLOT_COUNT];
+#endif /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
     uint8_t key_slots_initialized;
 } psa_global_data_t;
 
 static psa_global_data_t global_data;
 
-MBEDTLS_STATIC_ASSERT(ARRAY_LENGTH(global_data.key_slots) <=
-                      PSA_KEY_ID_VOLATILE_MAX - PSA_KEY_ID_VOLATILE_MIN + 1,
-                      "The key slot array is larger than the volatile key ID range");
-
 static uint8_t psa_get_key_slots_initialized(void)
 {
     uint8_t initialized;
@@ -86,6 +209,125 @@
     return initialized;
 }
 
+
+
+/** The length of the given slice in the key slot table.
+ *
+ * \param slice_idx     The slice number. It must satisfy
+ *                      0 <= slice_idx < KEY_SLICE_COUNT.
+ *
+ * \return              The number of elements in the given slice.
+ */
+static inline size_t key_slice_length(size_t slice_idx);
+
+/** Get a pointer to the slot where the given volatile key is located.
+ *
+ * \param key_id        The key identifier. It must be a valid volatile key
+ *                      identifier.
+ * \return              A pointer to the only slot that the given key
+ *                      can be in. Note that the slot may be empty or
+ *                      contain a different key.
+ */
+static inline psa_key_slot_t *get_volatile_key_slot(psa_key_id_t key_id);
+
+/** Get a pointer to an entry in the persistent key cache.
+ *
+ * \param slot_idx      The index in the table. It must satisfy
+ *                      0 <= slot_idx < PERSISTENT_KEY_CACHE_COUNT.
+ * \return              A pointer to the slot containing the given
+ *                      persistent key cache entry.
+ */
+static inline psa_key_slot_t *get_persistent_key_slot(size_t slot_idx);
+
+/** Get a pointer to a slot given by slice and index.
+ *
+ * \param slice_idx     The slice number. It must satisfy
+ *                      0 <= slice_idx < KEY_SLICE_COUNT.
+ * \param slot_idx      An index in the given slice. It must satisfy
+ *                      0 <= slot_idx < key_slice_length(slice_idx).
+ *
+ * \return              A pointer to the given slot.
+ */
+static inline psa_key_slot_t *get_key_slot(size_t slice_idx, size_t slot_idx);
+
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+
+#if defined(MBEDTLS_TEST_HOOKS)
+size_t (*mbedtls_test_hook_psa_volatile_key_slice_length)(size_t slice_idx) = NULL;
+#endif
+
+static inline size_t key_slice_length(size_t slice_idx)
+{
+    if (slice_idx == KEY_SLOT_CACHE_SLICE_INDEX) {
+        return PERSISTENT_KEY_CACHE_COUNT;
+    } else {
+#if defined(MBEDTLS_TEST_HOOKS)
+        if (mbedtls_test_hook_psa_volatile_key_slice_length != NULL) {
+            return mbedtls_test_hook_psa_volatile_key_slice_length(slice_idx);
+        }
+#endif
+        return KEY_SLOT_VOLATILE_SLICE_BASE_LENGTH << slice_idx;
+    }
+}
+
+static inline psa_key_slot_t *get_volatile_key_slot(psa_key_id_t key_id)
+{
+    size_t slice_idx = slice_index_of_volatile_key_id(key_id);
+    if (slice_idx >= KEY_SLOT_VOLATILE_SLICE_COUNT) {
+        return NULL;
+    }
+    size_t slot_idx = slot_index_of_volatile_key_id(key_id);
+    if (slot_idx >= key_slice_length(slice_idx)) {
+        return NULL;
+    }
+    psa_key_slot_t *slice = global_data.key_slices[slice_idx];
+    if (slice == NULL) {
+        return NULL;
+    }
+    return &slice[slot_idx];
+}
+
+static inline psa_key_slot_t *get_persistent_key_slot(size_t slot_idx)
+{
+    return &global_data.key_slices[KEY_SLOT_CACHE_SLICE_INDEX][slot_idx];
+}
+
+static inline psa_key_slot_t *get_key_slot(size_t slice_idx, size_t slot_idx)
+{
+    return &global_data.key_slices[slice_idx][slot_idx];
+}
+
+#else /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
+
+static inline size_t key_slice_length(size_t slice_idx)
+{
+    (void) slice_idx;
+    return ARRAY_LENGTH(global_data.key_slots);
+}
+
+static inline psa_key_slot_t *get_volatile_key_slot(psa_key_id_t key_id)
+{
+    MBEDTLS_STATIC_ASSERT(ARRAY_LENGTH(global_data.key_slots) <=
+                          PSA_KEY_ID_VOLATILE_MAX - PSA_KEY_ID_VOLATILE_MIN + 1,
+                          "The key slot array is larger than the volatile key ID range");
+    return &global_data.key_slots[key_id - PSA_KEY_ID_VOLATILE_MIN];
+}
+
+static inline psa_key_slot_t *get_persistent_key_slot(size_t slot_idx)
+{
+    return &global_data.key_slots[slot_idx];
+}
+
+static inline psa_key_slot_t *get_key_slot(size_t slice_idx, size_t slot_idx)
+{
+    (void) slice_idx;
+    return &global_data.key_slots[slot_idx];
+}
+
+#endif /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
+
+
+
 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);
@@ -147,12 +389,13 @@
     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];
+        slot = get_volatile_key_slot(key_id);
 
         /* 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))) {
+        if (slot != NULL &&
+            slot->state == PSA_SLOT_FULL &&
+            mbedtls_svc_key_id_equal(key, slot->attr.id)) {
             status = PSA_SUCCESS;
         } else {
             status = PSA_ERROR_DOES_NOT_EXIST;
@@ -162,8 +405,8 @@
             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];
+        for (slot_idx = 0; slot_idx < PERSISTENT_KEY_CACHE_COUNT; slot_idx++) {
+            slot = get_persistent_key_slot(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))) {
@@ -186,29 +429,169 @@
 
 psa_status_t psa_initialize_key_slots(void)
 {
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+    global_data.key_slices[KEY_SLOT_CACHE_SLICE_INDEX] =
+        mbedtls_calloc(PERSISTENT_KEY_CACHE_COUNT,
+                       sizeof(*global_data.key_slices[KEY_SLOT_CACHE_SLICE_INDEX]));
+    if (global_data.key_slices[KEY_SLOT_CACHE_SLICE_INDEX] == NULL) {
+        return PSA_ERROR_INSUFFICIENT_MEMORY;
+    }
+#else /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
     /* 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. */
+#endif /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
+
     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);
+    for (size_t slice_idx = 0; slice_idx < KEY_SLICE_COUNT; slice_idx++) {
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+        if (global_data.key_slices[slice_idx] == NULL) {
+            continue;
+        }
+#endif  /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
+        for (size_t slot_idx = 0; slot_idx < key_slice_length(slice_idx); slot_idx++) {
+            psa_key_slot_t *slot = get_key_slot(slice_idx, slot_idx);
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+            /* When MBEDTLS_PSA_KEY_STORE_DYNAMIC is disabled, calling
+             * psa_wipe_key_slot() on an unused slot is useless, but it
+             * happens to work (because we flip the state to PENDING_DELETION).
+             *
+             * When MBEDTLS_PSA_KEY_STORE_DYNAMIC is enabled,
+             * psa_wipe_key_slot() needs to have a valid slice_index
+             * field, but that value might not be correct in a
+             * free slot, so we must not call it.
+             *
+             * Bypass the call to psa_wipe_key_slot() if the slot is empty,
+             * but only if MBEDTLS_PSA_KEY_STORE_DYNAMIC is enabled, to save
+             * a few bytes of code size otherwise.
+             */
+            if (slot->state == PSA_SLOT_EMPTY) {
+                continue;
+            }
+#endif
+            slot->var.occupied.registered_readers = 1;
+            slot->state = PSA_SLOT_PENDING_DELETION;
+            (void) psa_wipe_key_slot(slot);
+        }
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+        mbedtls_free(global_data.key_slices[slice_idx]);
+        global_data.key_slices[slice_idx] = NULL;
+#endif  /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
     }
+
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+    for (size_t slice_idx = 0; slice_idx < KEY_SLOT_VOLATILE_SLICE_COUNT; slice_idx++) {
+        global_data.first_free_slot_index[slice_idx] = 0;
+    }
+#endif  /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
+
     /* The global data mutex is already held when calling this function. */
     global_data.key_slots_initialized = 0;
 }
 
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+
+static psa_status_t psa_allocate_volatile_key_slot(psa_key_id_t *key_id,
+                                                   psa_key_slot_t **p_slot)
+{
+    size_t slice_idx;
+    for (slice_idx = 0; slice_idx < KEY_SLOT_VOLATILE_SLICE_COUNT; slice_idx++) {
+        if (global_data.first_free_slot_index[slice_idx] != FREE_SLOT_INDEX_NONE) {
+            break;
+        }
+    }
+    if (slice_idx == KEY_SLOT_VOLATILE_SLICE_COUNT) {
+        return PSA_ERROR_INSUFFICIENT_MEMORY;
+    }
+
+    if (global_data.key_slices[slice_idx] == NULL) {
+        global_data.key_slices[slice_idx] =
+            mbedtls_calloc(key_slice_length(slice_idx),
+                           sizeof(psa_key_slot_t));
+        if (global_data.key_slices[slice_idx] == NULL) {
+            return PSA_ERROR_INSUFFICIENT_MEMORY;
+        }
+    }
+    psa_key_slot_t *slice = global_data.key_slices[slice_idx];
+
+    size_t slot_idx = global_data.first_free_slot_index[slice_idx];
+    *key_id = volatile_key_id_of_index(slice_idx, slot_idx);
+
+    psa_key_slot_t *slot = &slice[slot_idx];
+    size_t next_free = slot_idx + 1 + slot->var.free.next_free_relative_to_next;
+    if (next_free >= key_slice_length(slice_idx)) {
+        next_free = FREE_SLOT_INDEX_NONE;
+    }
+    global_data.first_free_slot_index[slice_idx] = next_free;
+    /* The .next_free field is not meaningful when the slot is not free,
+     * so give it the same content as freshly initialized memory. */
+    slot->var.free.next_free_relative_to_next = 0;
+
+    psa_status_t status = psa_key_slot_state_transition(slot,
+                                                        PSA_SLOT_EMPTY,
+                                                        PSA_SLOT_FILLING);
+    if (status != PSA_SUCCESS) {
+        /* The only reason for failure is if the slot state was not empty.
+         * This indicates that something has gone horribly wrong.
+         * In this case, we leave the slot out of the free list, and stop
+         * modifying it. This minimizes any further corruption. The slot
+         * is a memory leak, but that's a lesser evil. */
+        return status;
+    }
+
+    *p_slot = slot;
+    /* We assert at compile time that the slice index fits in uint8_t. */
+    slot->slice_index = (uint8_t) slice_idx;
+    return PSA_SUCCESS;
+}
+
+psa_status_t psa_free_key_slot(size_t slice_idx,
+                               psa_key_slot_t *slot)
+{
+
+    if (slice_idx == KEY_SLOT_CACHE_SLICE_INDEX) {
+        /* This is a cache entry. We don't maintain a free list, so
+         * there's nothing to do. */
+        return PSA_SUCCESS;
+    }
+    if (slice_idx >= KEY_SLOT_VOLATILE_SLICE_COUNT) {
+        return PSA_ERROR_CORRUPTION_DETECTED;
+    }
+
+    psa_key_slot_t *slice = global_data.key_slices[slice_idx];
+    psa_key_slot_t *slice_end = slice + key_slice_length(slice_idx);
+    if (slot < slice || slot >= slice_end) {
+        /* The slot isn't actually in the slice! We can't detect that
+         * condition for sure, because the pointer comparison itself is
+         * undefined behavior in that case. That same condition makes the
+         * subtraction to calculate the slot index also UB.
+         * Give up now to avoid causing further corruption.
+         */
+        return PSA_ERROR_CORRUPTION_DETECTED;
+    }
+    size_t slot_idx = slot - slice;
+
+    size_t next_free = global_data.first_free_slot_index[slice_idx];
+    if (next_free >= key_slice_length(slice_idx)) {
+        /* The slot was full. The newly freed slot thus becomes the
+         * end of the free list. */
+        next_free = key_slice_length(slice_idx);
+    }
+    global_data.first_free_slot_index[slice_idx] = slot_idx;
+    slot->var.free.next_free_relative_to_next =
+        (int32_t) next_free - (int32_t) slot_idx - 1;
+
+    return PSA_SUCCESS;
+}
+#endif /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
+
 psa_status_t psa_reserve_free_key_slot(psa_key_id_t *volatile_key_id,
                                        psa_key_slot_t **p_slot)
 {
@@ -221,9 +604,19 @@
         goto error;
     }
 
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+    if (volatile_key_id != NULL) {
+        return psa_allocate_volatile_key_slot(volatile_key_id, p_slot);
+    }
+#endif /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
+
+    /* With a dynamic key store, allocate an entry in the cache slice,
+     * applicable only to non-volatile keys that get cached in RAM.
+     * With a static key store, allocate an entry in the sole slice,
+     * applicable to all keys. */
     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];
+    for (slot_idx = 0; slot_idx < PERSISTENT_KEY_CACHE_COUNT; slot_idx++) {
+        psa_key_slot_t *slot = get_key_slot(KEY_SLOT_CACHE_SLICE_INDEX, slot_idx);
         if (slot->state == PSA_SLOT_EMPTY) {
             selected_slot = slot;
             break;
@@ -261,8 +654,18 @@
             goto error;
         }
 
-        *volatile_key_id = PSA_KEY_ID_VOLATILE_MIN +
-                           ((psa_key_id_t) (selected_slot - global_data.key_slots));
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+        selected_slot->slice_index = KEY_SLOT_CACHE_SLICE_INDEX;
+#endif /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
+
+#if !defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+        if (volatile_key_id != NULL) {
+            /* Refresh slot_idx, for when the slot is not the original
+             * selected_slot but rather unused_persistent_key_slot.  */
+            slot_idx = selected_slot - global_data.key_slots;
+            *volatile_key_id = PSA_KEY_ID_VOLATILE_MIN + slot_idx;
+        }
+#endif
         *p_slot = selected_slot;
 
         return PSA_SUCCESS;
@@ -271,7 +674,6 @@
 
 error:
     *p_slot = NULL;
-    *volatile_key_id = 0;
 
     return status;
 }
@@ -430,9 +832,8 @@
     /* 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);
+    status = psa_reserve_free_key_slot(NULL, p_slot);
     if (status != PSA_SUCCESS) {
 #if defined(MBEDTLS_THREADING_C)
         PSA_THREADING_CHK_RET(mbedtls_mutex_unlock(
@@ -500,12 +901,12 @@
     /* 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)) {
+        (slot->var.occupied.registered_readers == 1)) {
         return psa_wipe_key_slot(slot);
     }
 
     if (psa_key_slot_has_readers(slot)) {
-        slot->registered_readers--;
+        slot->var.occupied.registered_readers--;
         return PSA_SUCCESS;
     }
 
@@ -639,7 +1040,7 @@
         return status;
     }
 
-    if (slot->registered_readers == 1) {
+    if (slot->var.occupied.registered_readers == 1) {
         status = psa_wipe_key_slot(slot);
     } else {
         status = psa_unregister_read(slot);
@@ -674,7 +1075,7 @@
     }
 
     if ((!PSA_KEY_LIFETIME_IS_VOLATILE(slot->attr.lifetime)) &&
-        (slot->registered_readers == 1)) {
+        (slot->var.occupied.registered_readers == 1)) {
         status = psa_wipe_key_slot(slot);
     } else {
         status = psa_unregister_read(slot);
@@ -689,34 +1090,39 @@
 
 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;
+    for (size_t slice_idx = 0; slice_idx < KEY_SLICE_COUNT; slice_idx++) {
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+        if (global_data.key_slices[slice_idx] == NULL) {
             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;
+#endif  /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
+        for (size_t slot_idx = 0; slot_idx < key_slice_length(slice_idx); slot_idx++) {
+            const psa_key_slot_t *slot = get_key_slot(slice_idx, slot_idx);
+            if (slot->state == PSA_SLOT_EMPTY) {
+                ++stats->empty_slots;
+                continue;
             }
-        }
-        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;
+            if (psa_key_slot_has_readers(slot)) {
+                ++stats->locked_slots;
+            }
+            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;
+                }
             }
         }
     }
diff --git a/tf-psa-crypto/core/psa_crypto_slot_management.h b/tf-psa-crypto/core/psa_crypto_slot_management.h
index 88b7c83..af1208e 100644
--- a/tf-psa-crypto/core/psa_crypto_slot_management.h
+++ b/tf-psa-crypto/core/psa_crypto_slot_management.h
@@ -17,8 +17,10 @@
  *
  *  The first #MBEDTLS_PSA_KEY_SLOT_COUNT identifiers of the implementation
  *  range of key identifiers are reserved for volatile key identifiers.
- *  A volatile key identifier is equal to #PSA_KEY_ID_VOLATILE_MIN plus the
- *  index of the key slot containing the volatile key definition.
+ *
+ *  If \c id is a a volatile key identifier, #PSA_KEY_ID_VOLATILE_MIN - \c id
+ *  indicates the key slot containing the volatile key definition. See
+ *  psa_crypto_slot_management.c for details.
  */
 
 /** The minimum value for a volatile key identifier.
@@ -27,8 +29,12 @@
 
 /** The maximum value for a volatile key identifier.
  */
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+#define PSA_KEY_ID_VOLATILE_MAX (MBEDTLS_PSA_KEY_ID_BUILTIN_MIN - 1)
+#else /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
 #define PSA_KEY_ID_VOLATILE_MAX                                 \
     (PSA_KEY_ID_VOLATILE_MIN + MBEDTLS_PSA_KEY_SLOT_COUNT - 1)
+#endif /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
 
 /** Test whether a key identifier is a volatile key identifier.
  *
@@ -94,6 +100,24 @@
  */
 psa_status_t psa_initialize_key_slots(void);
 
+#if defined(MBEDTLS_TEST_HOOKS) && defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+/* Allow test code to customize the key slice length. We use this in tests
+ * that exhaust the key store to reach a full key store in reasonable time
+ * and memory.
+ *
+ * The length of each slice must be between 1 and
+ * (1 << KEY_ID_SLOT_INDEX_WIDTH) inclusive.
+ *
+ * The length for a given slice index must not change while
+ * the key store is initialized.
+ */
+extern size_t (*mbedtls_test_hook_psa_volatile_key_slice_length)(
+    size_t slice_idx);
+
+/* The number of volatile key slices. */
+size_t psa_key_slot_volatile_slice_count(void);
+#endif
+
 /** Delete all data from key slots in memory.
  * This function is not thread safe, it wipes every key slot regardless of
  * state and reader count. It should only be called when no slot is in use.
@@ -113,13 +137,22 @@
  * If multi-threading is enabled, the caller must hold the
  * global key slot mutex.
  *
- * \param[out] volatile_key_id   On success, volatile key identifier
- *                               associated to the returned slot.
+ * \param[out] volatile_key_id   - If null, reserve a cache slot for
+ *                                 a persistent or built-in key.
+ *                               - If non-null, allocate a slot for
+ *                                 a volatile key. On success,
+ *                                 \p *volatile_key_id is the
+ *                                 identifier corresponding to the
+ *                                 returned slot. It is the caller's
+ *                                 responsibility to set this key identifier
+ *                                 in the attributes.
  * \param[out] p_slot            On success, a pointer to the slot.
  *
  * \retval #PSA_SUCCESS \emptydescription
  * \retval #PSA_ERROR_INSUFFICIENT_MEMORY
  *         There were no free key slots.
+ *         When #MBEDTLS_PSA_KEY_STORE_DYNAMIC is enabled, there was not
+ *         enough memory to allocate more slots.
  * \retval #PSA_ERROR_BAD_STATE \emptydescription
  * \retval #PSA_ERROR_CORRUPTION_DETECTED
  *         This function attempted to operate on a key slot which was in an
@@ -128,6 +161,29 @@
 psa_status_t psa_reserve_free_key_slot(psa_key_id_t *volatile_key_id,
                                        psa_key_slot_t **p_slot);
 
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+/** Return a key slot to the free list.
+ *
+ * Call this function when a slot obtained from psa_reserve_free_key_slot()
+ * is no longer in use.
+ *
+ * If multi-threading is enabled, the caller must hold the
+ * global key slot mutex.
+ *
+ * \param slice_idx             The slice containing the slot.
+ *                              This is `slot->slice_index` when the slot
+ *                              is obtained from psa_reserve_free_key_slot().
+ * \param slot                  The key slot.
+ *
+ * \retval #PSA_SUCCESS \emptydescription
+ * \retval #PSA_ERROR_CORRUPTION_DETECTED
+ *         This function attempted to operate on a key slot which was in an
+ *         unexpected state.
+ */
+psa_status_t psa_free_key_slot(size_t slice_idx,
+                               psa_key_slot_t *slot);
+#endif /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */
+
 /** Change the state of a key slot.
  *
  * This function changes the state of the key slot from expected_state to
@@ -174,10 +230,10 @@
 static inline psa_status_t psa_register_read(psa_key_slot_t *slot)
 {
     if ((slot->state != PSA_SLOT_FULL) ||
-        (slot->registered_readers >= SIZE_MAX)) {
+        (slot->var.occupied.registered_readers >= SIZE_MAX)) {
         return PSA_ERROR_CORRUPTION_DETECTED;
     }
-    slot->registered_readers++;
+    slot->var.occupied.registered_readers++;
 
     return PSA_SUCCESS;
 }
diff --git a/tf-psa-crypto/tests/suites/test_suite_psa_crypto_slot_management.data b/tf-psa-crypto/tests/suites/test_suite_psa_crypto_slot_management.data
index af3b946..f379dba 100644
--- a/tf-psa-crypto/tests/suites/test_suite_psa_crypto_slot_management.data
+++ b/tf-psa-crypto/tests/suites/test_suite_psa_crypto_slot_management.data
@@ -129,9 +129,9 @@
 # writing, this happens in builds where AES uses a PSA driver and the
 # PSA RNG uses AES-CTR_DRBG through the PSA AES.
 # Pick a key id that's in the middle of the volatile key ID range.
-# That works out both when MBEDTLS_PSA_KEY_SLOT_DYNAMIC is enabled and
+# That works out both when MBEDTLS_PSA_KEY_STORE_DYNAMIC is enabled and
 # volatile key IDs are assigned starting with the lowest value, and when
-# MBEDTLS_PSA_KEY_SLOT_DYNAMIC is disabled and volatile key IDs are assigned
+# MBEDTLS_PSA_KEY_STORE_DYNAMIC is disabled and volatile key IDs are assigned
 # starting with the highest values.
 open_fail:(PSA_KEY_ID_VOLATILE_MIN + PSA_KEY_ID_VOLATILE_MAX) / 2:PSA_ERROR_DOES_NOT_EXIST
 
@@ -228,6 +228,11 @@
 Key slot count: maximum
 many_transient_keys:MBEDTLS_PSA_KEY_SLOT_COUNT - MBEDTLS_TEST_PSA_INTERNAL_KEYS
 
+Key slot count: dynamic: more than MBEDTLS_PSA_KEY_SLOT_COUNT
+depends_on:MBEDTLS_PSA_KEY_STORE_DYNAMIC
+# Check that MBEDTLS_PSA_KEY_SLOT_COUNT doesn't apply to volatile keys.
+many_transient_keys:MBEDTLS_PSA_KEY_SLOT_COUNT + 1
+
 Key slot count: try to overfill, destroy first
 fill_key_store:0
 
diff --git a/tf-psa-crypto/tests/suites/test_suite_psa_crypto_slot_management.function b/tf-psa-crypto/tests/suites/test_suite_psa_crypto_slot_management.function
index f679f2e..604c4bd 100644
--- a/tf-psa-crypto/tests/suites/test_suite_psa_crypto_slot_management.function
+++ b/tf-psa-crypto/tests/suites/test_suite_psa_crypto_slot_management.function
@@ -98,10 +98,30 @@
     return 0;
 }
 
-/* Currently, there is always a maximum number of volatile keys that can
- * realistically be reached in tests. When we add configurations where this
- * is not true, undefine the macro in such configurations. */
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC)
+#if defined(MBEDTLS_TEST_HOOKS)
+/* Artificially restrictable dynamic key store */
+#define KEY_SLICE_1_LENGTH 4
+#define KEY_SLICE_2_LENGTH 10
+static size_t tiny_key_slice_length(size_t slice_idx)
+{
+    switch (slice_idx) {
+        case 1: return KEY_SLICE_1_LENGTH;
+        case 2: return KEY_SLICE_2_LENGTH;
+        default: return 1;
+    }
+}
+#define MAX_VOLATILE_KEYS                       \
+    (KEY_SLICE_1_LENGTH + KEY_SLICE_2_LENGTH +  \
+     psa_key_slot_volatile_slice_count() - 2)
+
+#else  /* Effectively unbounded dynamic key store */
+#undef MAX_VOLATILE_KEYS
+#endif
+
+#else  /* Static key store */
 #define MAX_VOLATILE_KEYS MBEDTLS_PSA_KEY_SLOT_COUNT
+#endif
 
 /* END_HEADER */
 
@@ -867,6 +887,10 @@
     uint8_t exported[sizeof(size_t)];
     size_t exported_length;
 
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC) && defined(MBEDTLS_TEST_HOOKS)
+    mbedtls_test_hook_psa_volatile_key_slice_length = &tiny_key_slice_length;
+#endif
+
     PSA_ASSERT(psa_crypto_init());
 
     mbedtls_psa_stats_t stats;
@@ -949,6 +973,9 @@
 exit:
     PSA_DONE();
     mbedtls_free(keys);
+#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC) && defined(MBEDTLS_TEST_HOOKS)
+    mbedtls_test_hook_psa_volatile_key_slice_length = NULL;
+#endif
 }
 /* END_CASE */
 
@@ -1028,7 +1055,7 @@
 }
 /* END_CASE */
 
-/* BEGIN_CASE depends_on:MBEDTLS_PSA_CRYPTO_STORAGE_C */
+/* BEGIN_CASE depends_on:MBEDTLS_PSA_CRYPTO_STORAGE_C:!MBEDTLS_PSA_KEY_STORE_DYNAMIC */
 void non_reusable_key_slots_integrity_in_case_of_key_slot_starvation()
 {
     psa_status_t status;
@@ -1068,7 +1095,14 @@
     TEST_ASSERT(mbedtls_svc_key_id_equal(returned_key_id, persistent_key));
 
     /*
-     * Create the maximum available number of volatile keys
+     * Create the maximum available number of keys that are locked in
+     * memory. This can be:
+     * - volatile keys, when MBEDTLS_PSA_KEY_STORE_DYNAMIC is disabled;
+     * - opened persistent keys (could work, but not currently implemented
+     *   in this test function);
+     * - keys in use by another thread (we don't do this because it would
+     *   be hard to arrange and we can't control how long the keys are
+     *   locked anyway).
      */
     psa_set_key_lifetime(&attributes, PSA_KEY_LIFETIME_VOLATILE);
     for (i = 0; i < available_key_slots; i++) {