Update copyright year
[openssl.git] / crypto / evp / keymgmt_lib.c
index 763982e58f7e18247761f0bff9443942fbeebbf7..27b9f6b907e382b5ba1de303b825077760e8f4d7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019-2020 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 2019-2021 The OpenSSL Project Authors. All Rights Reserved.
  *
  * Licensed under the Apache License 2.0 (the "License").  You may not use
  * this file except in compliance with the License.  You can obtain a copy
@@ -87,7 +87,7 @@ int evp_keymgmt_util_export(const EVP_PKEY *pk, int selection,
 void *evp_keymgmt_util_export_to_provider(EVP_PKEY *pk, EVP_KEYMGMT *keymgmt)
 {
     struct evp_keymgmt_util_try_import_data_st import_data;
-    size_t i = 0;
+    OP_CACHE_ELEM *op;
 
     /* Export to where? */
     if (keymgmt == NULL)
@@ -101,11 +101,23 @@ void *evp_keymgmt_util_export_to_provider(EVP_PKEY *pk, EVP_KEYMGMT *keymgmt)
     if (pk->keymgmt == keymgmt)
         return pk->keydata;
 
-    /* If this key is already exported to |keymgmt|, no more to do */
-    i = evp_keymgmt_util_find_operation_cache_index(pk, keymgmt);
-    if (i < OSSL_NELEM(pk->operation_cache)
-        && pk->operation_cache[i].keymgmt != NULL)
-        return pk->operation_cache[i].keydata;
+    CRYPTO_THREAD_read_lock(pk->lock);
+    /*
+     * If the provider native "origin" hasn't changed since last time, we
+     * try to find our keymgmt in the operation cache.  If it has changed
+     * and our keymgmt isn't found, we will clear the cache further down.
+     */
+    if (pk->dirty_cnt == pk->dirty_cnt_copy) {
+        /* If this key is already exported to |keymgmt|, no more to do */
+        op = evp_keymgmt_util_find_operation_cache(pk, keymgmt);
+        if (op != NULL && op->keymgmt != NULL) {
+            void *ret = op->keydata;
+
+            CRYPTO_THREAD_unlock(pk->lock);
+            return ret;
+        }
+    }
+    CRYPTO_THREAD_unlock(pk->lock);
 
     /* If the "origin" |keymgmt| doesn't support exporting, give up */
     /*
@@ -115,15 +127,6 @@ void *evp_keymgmt_util_export_to_provider(EVP_PKEY *pk, EVP_KEYMGMT *keymgmt)
     if (pk->keymgmt->export == NULL)
         return NULL;
 
-    /* Check that we have found an empty slot in the export cache */
-    /*
-     * TODO(3.0) Right now, we assume we have ample space.  We will have to
-     * think about a cache aging scheme, though, if |i| indexes outside the
-     * array.
-     */
-    if (!ossl_assert(i < OSSL_NELEM(pk->operation_cache)))
-        return NULL;
-
     /*
      * Make sure that the type of the keymgmt to export to matches the type
      * of the "origin"
@@ -153,53 +156,111 @@ void *evp_keymgmt_util_export_to_provider(EVP_PKEY *pk, EVP_KEYMGMT *keymgmt)
         return NULL;
     }
 
+    CRYPTO_THREAD_write_lock(pk->lock);
+    /* Check to make sure some other thread didn't get there first */
+    op = evp_keymgmt_util_find_operation_cache(pk, keymgmt);
+    if (op != NULL && op->keydata != NULL) {
+        void *ret = op->keydata;
+
+        CRYPTO_THREAD_unlock(pk->lock);
+
+        /*
+         * Another thread seemms to have already exported this so we abandon
+         * all the work we just did.
+         */
+        evp_keymgmt_freedata(keymgmt, import_data.keydata);
+
+        return ret;
+    }
+
+    /*
+     * If the dirty counter changed since last time, then clear the
+     * operation cache.  In that case, we know that |i| is zero.
+     */
+    if (pk->dirty_cnt != pk->dirty_cnt_copy)
+        evp_keymgmt_util_clear_operation_cache(pk, 0);
+
     /* Add the new export to the operation cache */
-    if (!evp_keymgmt_util_cache_keydata(pk, i, keymgmt, import_data.keydata)) {
+    if (!evp_keymgmt_util_cache_keydata(pk, keymgmt, import_data.keydata)) {
         evp_keymgmt_freedata(keymgmt, import_data.keydata);
         return NULL;
     }
 
+    /* Synchronize the dirty count */
+    pk->dirty_cnt_copy = pk->dirty_cnt;
+
+    CRYPTO_THREAD_unlock(pk->lock);
+
     return import_data.keydata;
 }
 
-void evp_keymgmt_util_clear_operation_cache(EVP_PKEY *pk)
+static void op_cache_free(OP_CACHE_ELEM *e)
 {
-    size_t i, end = OSSL_NELEM(pk->operation_cache);
+    evp_keymgmt_freedata(e->keymgmt, e->keydata);
+    EVP_KEYMGMT_free(e->keymgmt);
+    OPENSSL_free(e);
+}
 
+int evp_keymgmt_util_clear_operation_cache(EVP_PKEY *pk, int locking)
+{
     if (pk != NULL) {
-        for (i = 0; i < end && pk->operation_cache[i].keymgmt != NULL; i++) {
-            EVP_KEYMGMT *keymgmt = pk->operation_cache[i].keymgmt;
-            void *keydata = pk->operation_cache[i].keydata;
-
-            pk->operation_cache[i].keymgmt = NULL;
-            pk->operation_cache[i].keydata = NULL;
-            evp_keymgmt_freedata(keymgmt, keydata);
-            EVP_KEYMGMT_free(keymgmt);
-        }
+        if (locking && pk->lock != NULL && !CRYPTO_THREAD_write_lock(pk->lock))
+            return 0;
+        sk_OP_CACHE_ELEM_pop_free(pk->operation_cache, op_cache_free);
+        pk->operation_cache = NULL;
+        if (locking && pk->lock != NULL)
+            CRYPTO_THREAD_unlock(pk->lock);
     }
+
+    return 1;
 }
 
-size_t evp_keymgmt_util_find_operation_cache_index(EVP_PKEY *pk,
-                                                   EVP_KEYMGMT *keymgmt)
+OP_CACHE_ELEM *evp_keymgmt_util_find_operation_cache(EVP_PKEY *pk,
+                                                     EVP_KEYMGMT *keymgmt)
 {
-    size_t i, end = OSSL_NELEM(pk->operation_cache);
+    int i, end = sk_OP_CACHE_ELEM_num(pk->operation_cache);
+    OP_CACHE_ELEM *p;
 
-    for (i = 0; i < end && pk->operation_cache[i].keymgmt != NULL; i++) {
-        if (keymgmt == pk->operation_cache[i].keymgmt)
-            break;
+    /*
+     * A comparison and sk_P_CACHE_ELEM_find() are avoided to not cause
+     * problems when we've only a read lock.
+     */
+    for (i = 0; i < end; i++) {
+        p = sk_OP_CACHE_ELEM_value(pk->operation_cache, i);
+        if (keymgmt == p->keymgmt)
+            return p;
     }
-
-    return i;
+    return NULL;
 }
 
-int evp_keymgmt_util_cache_keydata(EVP_PKEY *pk, size_t index,
+int evp_keymgmt_util_cache_keydata(EVP_PKEY *pk,
                                    EVP_KEYMGMT *keymgmt, void *keydata)
 {
+    OP_CACHE_ELEM *p = NULL;
+
     if (keydata != NULL) {
-        if (!EVP_KEYMGMT_up_ref(keymgmt))
+        if (pk->operation_cache == NULL) {
+            pk->operation_cache = sk_OP_CACHE_ELEM_new_null();
+            if (pk->operation_cache == NULL)
+                return 0;
+        }
+
+        p = OPENSSL_malloc(sizeof(*p));
+        if (p == NULL)
+            return 0;
+        p->keydata = keydata;
+        p->keymgmt = keymgmt;
+
+        if (!EVP_KEYMGMT_up_ref(keymgmt)) {
+            OPENSSL_free(p);
             return 0;
-        pk->operation_cache[index].keydata = keydata;
-        pk->operation_cache[index].keymgmt = keymgmt;
+        }
+
+        if (!sk_OP_CACHE_ELEM_push(pk->operation_cache, p)) {
+            EVP_KEYMGMT_free(keymgmt);
+            OPENSSL_free(p);
+            return 0;
+        }
     }
     return 1;
 }