psa: mgmt: Add key slot reuse

When looking for an empty key slot to store
the description of a key, if all key slots
are in use, reuse the first encountered
and unaccessed key slot containing the
description of a permanent key.

Signed-off-by: Ronald Cron <ronald.cron@arm.com>
diff --git a/library/psa_crypto_slot_management.c b/library/psa_crypto_slot_management.c
index 9271e14..5a1fc74 100644
--- a/library/psa_crypto_slot_management.c
+++ b/library/psa_crypto_slot_management.c
@@ -173,27 +173,62 @@
 psa_status_t psa_get_empty_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, *unaccessed_permanent_key_slot;
 
     if( ! global_data.key_slots_initialized )
-        return( PSA_ERROR_BAD_STATE );
-
-    for( slot_idx = PSA_KEY_SLOT_COUNT; slot_idx > 0; slot_idx-- )
     {
-        *p_slot = &global_data.key_slots[ slot_idx - 1 ];
-        if( ! psa_is_key_slot_occupied( *p_slot ) )
-        {
-            *volatile_key_id = PSA_KEY_ID_VOLATILE_MIN +
-                               ( (psa_key_id_t)slot_idx ) - 1;
-
-            psa_increment_key_slot_access_count( *p_slot );
-
-            return( PSA_SUCCESS );
-        }
+        status = PSA_ERROR_BAD_STATE;
+        goto error;
     }
 
+    selected_slot = unaccessed_permanent_key_slot = NULL;
+    for( slot_idx = 0; slot_idx < PSA_KEY_SLOT_COUNT; slot_idx++ )
+    {
+        psa_key_slot_t *slot = &global_data.key_slots[ slot_idx ];
+        if( ! psa_is_key_slot_occupied( slot ) )
+        {
+            selected_slot = slot;
+            break;
+        }
+
+        if( ( unaccessed_permanent_key_slot == NULL ) &&
+            ( ! PSA_KEY_LIFETIME_IS_VOLATILE( slot->attr.lifetime ) ) &&
+            ( ! psa_is_key_slot_accessed( slot ) ) )
+            unaccessed_permanent_key_slot = slot;
+    }
+
+    /*
+     * If there is no unused key slot and there is at least one unaccessed key
+     * slot containing the description of a permament key, recycle the first
+     * such key slot we encountered. If we need later on to operate on the
+     * permanent key we evict now, we will reload its description from storage.
+     */
+    if( ( selected_slot == NULL ) &&
+        ( unaccessed_permanent_key_slot != NULL ) )
+    {
+        selected_slot = unaccessed_permanent_key_slot;
+        selected_slot->access_count = 1;
+        psa_wipe_key_slot( selected_slot );
+    }
+
+    if( selected_slot != NULL )
+    {
+        *volatile_key_id = PSA_KEY_ID_VOLATILE_MIN +
+            ( (psa_key_id_t)( selected_slot - global_data.key_slots ) );
+        *p_slot = selected_slot;
+        psa_increment_key_slot_access_count( selected_slot );
+
+        return( PSA_SUCCESS );
+    }
+    status = PSA_ERROR_INSUFFICIENT_MEMORY;
+
+error:
     *p_slot = NULL;
-    return( PSA_ERROR_INSUFFICIENT_MEMORY );
+    *volatile_key_id = 0;
+
+    return( status );
 }
 
 #if defined(MBEDTLS_PSA_CRYPTO_STORAGE_C)
diff --git a/tests/suites/test_suite_psa_crypto_slot_management.data b/tests/suites/test_suite_psa_crypto_slot_management.data
index 2533425..d2d6c01 100644
--- a/tests/suites/test_suite_psa_crypto_slot_management.data
+++ b/tests/suites/test_suite_psa_crypto_slot_management.data
@@ -186,3 +186,23 @@
 
 Open many transient keys
 many_transient_keys:42
+
+# Eviction from a key slot to be able to import a new permanent key.
+Key slot eviction to import a new permanent key
+key_slot_eviction_to_import_new_key:PSA_KEY_LIFETIME_PERSISTENT
+
+# Eviction from a key slot to be able to import a new volatile key.
+Key slot eviction to import a new volatile key
+key_slot_eviction_to_import_new_key:PSA_KEY_LIFETIME_VOLATILE
+
+# Check that non reusable key slots are not deleted/overwritten in case of key
+# slot starvation:
+# . An attempt to access a permanent key while all RAM key slots are occupied
+#   by volatile keys fails and does not lead to volatile key data to be
+#   spoiled.
+# . With all key slot in use with one containing a permanent key, an attempt
+#   to copy the permanent key fails (the permanent key slot cannot be reclaimed
+#   as it is accessed by the copy process) without the permament key data and
+#   volatile key data being spoiled.
+Non reusable key slots integrity in case of key slot starvation
+non_reusable_key_slots_integrity_in_case_of_key_slot_starvation
diff --git a/tests/suites/test_suite_psa_crypto_slot_management.function b/tests/suites/test_suite_psa_crypto_slot_management.function
index 66bf0a4..94bcade 100644
--- a/tests/suites/test_suite_psa_crypto_slot_management.function
+++ b/tests/suites/test_suite_psa_crypto_slot_management.function
@@ -877,3 +877,189 @@
 }
 /* END_CASE */
 
+/* BEGIN_CASE depends_on:MBEDTLS_PSA_CRYPTO_STORAGE_C */
+void key_slot_eviction_to_import_new_key( int lifetime_arg )
+{
+    psa_key_lifetime_t lifetime = (psa_key_lifetime_t)lifetime_arg;
+    size_t i;
+    psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
+    uint8_t exported[sizeof( size_t )];
+    size_t exported_length;
+    mbedtls_svc_key_id_t key, returned_key_id;
+
+    PSA_ASSERT( psa_crypto_init( ) );
+
+    psa_set_key_usage_flags( &attributes, PSA_KEY_USAGE_EXPORT );
+    psa_set_key_algorithm( &attributes, 0 );
+    psa_set_key_type( &attributes, PSA_KEY_TYPE_RAW_DATA );
+
+    /*
+     * Create PSA_KEY_SLOT_COUNT persistent keys.
+     */
+    for( i = 0; i < PSA_KEY_SLOT_COUNT; i++ )
+    {
+        key = mbedtls_svc_key_id_make( i, i + 1 );
+        psa_set_key_id( &attributes, key );
+        PSA_ASSERT( psa_import_key( &attributes,
+                                    (uint8_t *) &i, sizeof( i ),
+                                    &returned_key_id ) );
+       TEST_ASSERT( mbedtls_svc_key_id_equal( returned_key_id, key ) );
+    }
+
+    /*
+     * Create a new persistent or volatile key. When creating the key,
+     * one of the description of the previously created persistent key
+     * is removed from the RAM key slots. This makes room to store its
+     * description in RAM.
+     */
+    i = PSA_KEY_SLOT_COUNT;
+    key = mbedtls_svc_key_id_make( i, i + 1 );
+    psa_set_key_id( &attributes, key );
+
+    if( lifetime == PSA_KEY_LIFETIME_VOLATILE )
+        psa_set_key_lifetime( &attributes, PSA_KEY_LIFETIME_VOLATILE );
+
+    PSA_ASSERT( psa_import_key( &attributes,
+                                (uint8_t *) &i, sizeof( i ),
+                                &returned_key_id ) );
+    if( lifetime != PSA_KEY_LIFETIME_VOLATILE )
+        TEST_ASSERT( mbedtls_svc_key_id_equal( returned_key_id, key ) );
+
+    /*
+     * Check that we can export all ( PSA_KEY_SLOT_COUNT + 1 ) keys,
+     * that they have the expected value and destroy them. In that process,
+     * the description of the persistent key that was evicted from the RAM
+     * slots when creating the last key is restored in a RAM slot to export
+     * its value.
+     */
+    for( i = 0; i <= PSA_KEY_SLOT_COUNT; i++ )
+    {
+        if( i < PSA_KEY_SLOT_COUNT )
+            key = mbedtls_svc_key_id_make( i, i + 1 );
+        else
+            key = returned_key_id;
+
+        PSA_ASSERT( psa_export_key( key,
+                                    exported, sizeof( exported ),
+                                    &exported_length ) );
+        ASSERT_COMPARE( exported, exported_length,
+                        (uint8_t *) &i, sizeof( i ) );
+        PSA_ASSERT( psa_destroy_key( key ) );
+    }
+
+exit:
+    PSA_DONE( );
+}
+/* END_CASE */
+
+/* BEGIN_CASE depends_on:MBEDTLS_PSA_CRYPTO_STORAGE_C */
+void non_reusable_key_slots_integrity_in_case_of_key_slot_starvation( )
+{
+    psa_status_t status;
+    size_t i;
+    psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
+    uint8_t exported[sizeof( size_t )];
+    size_t exported_length;
+    mbedtls_svc_key_id_t permanent_key = MBEDTLS_SVC_KEY_ID_INIT;
+    mbedtls_svc_key_id_t permanent_key2 = MBEDTLS_SVC_KEY_ID_INIT;
+    mbedtls_svc_key_id_t returned_key_id = MBEDTLS_SVC_KEY_ID_INIT;
+    mbedtls_svc_key_id_t *keys = NULL;
+
+    TEST_ASSERT( PSA_KEY_SLOT_COUNT >= 1 );
+
+    ASSERT_ALLOC( keys, PSA_KEY_SLOT_COUNT );
+    PSA_ASSERT( psa_crypto_init( ) );
+
+    psa_set_key_usage_flags( &attributes,
+                             PSA_KEY_USAGE_EXPORT | PSA_KEY_USAGE_COPY );
+    psa_set_key_algorithm( &attributes, 0 );
+    psa_set_key_type( &attributes, PSA_KEY_TYPE_RAW_DATA );
+
+    /*
+     * Create a permanent key
+     */
+    permanent_key = mbedtls_svc_key_id_make( 0x100, 0x205 );
+    psa_set_key_id( &attributes, permanent_key );
+    PSA_ASSERT( psa_import_key( &attributes,
+                                (uint8_t *) &permanent_key,
+                                sizeof( permanent_key ),
+                                &returned_key_id ) );
+    TEST_ASSERT( mbedtls_svc_key_id_equal( returned_key_id, permanent_key ) );
+
+    /*
+     * Create PSA_KEY_SLOT_COUNT volatile keys
+     */
+    psa_set_key_lifetime( &attributes, PSA_KEY_LIFETIME_VOLATILE );
+    for( i = 0; i < PSA_KEY_SLOT_COUNT; i++ )
+    {
+        PSA_ASSERT( psa_import_key( &attributes,
+                                    (uint8_t *) &i, sizeof( i ),
+                                    &keys[i]) );
+    }
+    psa_reset_key_attributes( &attributes );
+
+    /*
+     * Check that we cannot access the persistent key as all slots are
+     * occupied by volatile keys and the implementation needs to load the
+     * persistent key description in a slot to be able to access it.
+     */
+    status = psa_get_key_attributes( permanent_key, &attributes );
+    TEST_EQUAL( status, PSA_ERROR_INSUFFICIENT_MEMORY );
+
+    /*
+     * Check we can export the volatile key created last and that it has the
+     * expected value. Then, destroy it.
+     */
+    PSA_ASSERT( psa_export_key( keys[PSA_KEY_SLOT_COUNT - 1],
+                                exported, sizeof( exported ),
+                                &exported_length ) );
+    i = PSA_KEY_SLOT_COUNT - 1;
+    ASSERT_COMPARE( exported, exported_length, (uint8_t *) &i, sizeof( i ) );
+    PSA_ASSERT( psa_destroy_key( keys[PSA_KEY_SLOT_COUNT - 1] ) );
+
+    /*
+     * Check that we can now access the persistent key again.
+     */
+    PSA_ASSERT( psa_get_key_attributes( permanent_key, &attributes ) );
+    TEST_ASSERT( mbedtls_svc_key_id_equal( attributes.core.id,
+                                           permanent_key ) );
+
+    /*
+     * Check that we cannot copy the persistent key as all slots are occupied
+     * by the permanent key and the volatile keys and the slot containing the
+     * permanent key cannot be reclaimed as it contains the key to copy.
+     */
+    permanent_key2 = mbedtls_svc_key_id_make( 0x100, 0x204 );
+    psa_set_key_id( &attributes, permanent_key2 );
+    status = psa_copy_key( permanent_key, &attributes, &returned_key_id );
+    TEST_EQUAL( status, PSA_ERROR_INSUFFICIENT_MEMORY );
+
+    /*
+     * Check we can export the remaining volatile keys and that they have the
+     * expected values.
+     */
+    for( i = 0; i < ( PSA_KEY_SLOT_COUNT - 1 ); i++ )
+    {
+        PSA_ASSERT( psa_export_key( keys[i],
+                                    exported, sizeof( exported ),
+                                    &exported_length ) );
+        ASSERT_COMPARE( exported, exported_length,
+                        (uint8_t *) &i, sizeof( i ) );
+        PSA_ASSERT( psa_destroy_key( keys[i] ) );
+    }
+
+    /*
+     * Check we can export the persistent key and that it have the expected
+     * value.
+     */
+
+    PSA_ASSERT( psa_export_key( permanent_key, exported, sizeof( exported ),
+                                &exported_length ) );
+    ASSERT_COMPARE( exported, exported_length,
+                    (uint8_t *) &permanent_key, sizeof( permanent_key ) );
+exit:
+    psa_destroy_key( permanent_key );
+    PSA_DONE( );
+    mbedtls_free( keys );
+}
+/* END_CASE */