[openssl] master update

Richard Levitte levitte at openssl.org
Sat Feb 29 04:41:48 UTC 2020


The branch master has been updated
       via  e32c608e0733d5b295c9aa119153133413c5d744 (commit)
       via  e33b132a1ae744614bb7cf568d197574dbafe1bb (commit)
       via  badf51c869d687f934e817f3bb4653acec0088ca (commit)
       via  3c6ed9555c7735c24d5f59c8b4ab7b9c4d807c77 (commit)
      from  49119647639b0b3ecd4db3d99b653653b41d1d20 (commit)


- Log -----------------------------------------------------------------
commit e32c608e0733d5b295c9aa119153133413c5d744
Author: Richard Levitte <levitte at openssl.org>
Date:   Mon Feb 24 19:15:47 2020 +0100

    DOCS: Add and modify docs for internal EVP_KEYMGMT utility functions
    
    Reviewed-by: Shane Lontis <shane.lontis at oracle.com>
    (Merged from https://github.com/openssl/openssl/pull/11148)

commit e33b132a1ae744614bb7cf568d197574dbafe1bb
Author: Richard Levitte <levitte at openssl.org>
Date:   Mon Feb 24 14:36:09 2020 +0100

    DOCS: Add internal docs for EVP_PKEY and the export functions
    
    Functions covered:
    
    - evp_pkey_export_to_provider()
    - evp_pkey_upgrade_to_provider()
    
    Reviewed-by: Shane Lontis <shane.lontis at oracle.com>
    (Merged from https://github.com/openssl/openssl/pull/11148)

commit badf51c869d687f934e817f3bb4653acec0088ca
Author: Richard Levitte <levitte at openssl.org>
Date:   Thu Feb 20 22:55:41 2020 +0100

    EVP: Add evp_pkey_upgrade_to_provider(), for EVP_PKEY upgrades
    
    This function "upgrades" a key from a legacy key container to a
    provider side key container.
    
    Reviewed-by: Shane Lontis <shane.lontis at oracle.com>
    (Merged from https://github.com/openssl/openssl/pull/11148)

commit 3c6ed9555c7735c24d5f59c8b4ab7b9c4d807c77
Author: Richard Levitte <levitte at openssl.org>
Date:   Thu Feb 20 20:26:16 2020 +0100

    Rethink the EVP_PKEY cache of provider side keys
    
    The role of this cache was two-fold:
    
    1.  It was a cache of key copies exported to providers with which an
        operation was initiated.
    2.  If the EVP_PKEY didn't have a legacy key, item 0 of the cache was
        the corresponding provider side origin, while the rest was the
        actual cache.
    
    This dual role for item 0 made the code a bit confusing, so we now
    make a separate keymgmt / keydata pair outside of that cache, which is
    the provider side "origin" key.
    
    A hard rule is that an EVP_PKEY cannot hold a legacy "origin" and a
    provider side "origin" at the same time.
    
    Reviewed-by: Shane Lontis <shane.lontis at oracle.com>
    (Merged from https://github.com/openssl/openssl/pull/11148)

-----------------------------------------------------------------------

Summary of changes:
 crypto/asn1/i2d_pr.c                               |   2 +-
 crypto/evp/exchange.c                              |  13 +-
 crypto/evp/keymgmt_lib.c                           | 174 +++++++-------
 crypto/evp/m_sigver.c                              |   9 +-
 crypto/evp/p_lib.c                                 | 259 ++++++++++++++-------
 crypto/evp/pmeth_check.c                           |  20 +-
 crypto/evp/pmeth_fn.c                              |   9 +-
 crypto/evp/pmeth_lib.c                             |   3 +-
 crypto/evp/signature.c                             |   9 +-
 crypto/serializer/serializer_pkey.c                |   6 +-
 crypto/x509/x_pubkey.c                             |   4 +-
 .../man3/evp_keymgmt_util_export_to_provider.pod   |  42 +++-
 doc/internal/man3/evp_pkey_export_to_provider.pod  |  75 ++++++
 doc/internal/man3/evp_pkey_make_provided.pod       |  63 -----
 doc/internal/man7/EVP_PKEY.pod                     |  56 +++++
 include/crypto/evp.h                               |  61 +++--
 test/keymgmt_internal_test.c                       |   3 +-
 17 files changed, 520 insertions(+), 288 deletions(-)
 create mode 100644 doc/internal/man3/evp_pkey_export_to_provider.pod
 delete mode 100644 doc/internal/man3/evp_pkey_make_provided.pod
 create mode 100644 doc/internal/man7/EVP_PKEY.pod

diff --git a/crypto/asn1/i2d_pr.c b/crypto/asn1/i2d_pr.c
index dd2a82da74..73b4461306 100644
--- a/crypto/asn1/i2d_pr.c
+++ b/crypto/asn1/i2d_pr.c
@@ -30,7 +30,7 @@ int i2d_PrivateKey(const EVP_PKEY *a, unsigned char **pp)
         }
         return ret;
     }
-    if (a->pkeys[0].keymgmt != NULL) {
+    if (a->keymgmt != NULL) {
         const char *serprop = OSSL_SERIALIZER_PrivateKey_TO_DER_PQ;
         OSSL_SERIALIZER_CTX *ctx =
             OSSL_SERIALIZER_CTX_new_by_EVP_PKEY(a, serprop);
diff --git a/crypto/evp/exchange.c b/crypto/evp/exchange.c
index 142a820651..ec5ba03f09 100644
--- a/crypto/evp/exchange.c
+++ b/crypto/evp/exchange.c
@@ -200,10 +200,13 @@ int EVP_PKEY_derive_init(EVP_PKEY_CTX *ctx)
     if (ctx->engine != NULL || ctx->keytype == NULL)
         goto legacy;
 
-    /* Ensure that the key is provided.  If not, go legacy */
+    /*
+     * Ensure that the key is provided, either natively, or as a cached export.
+     *  If not, go legacy
+     */
     tmp_keymgmt = ctx->keymgmt;
-    provkey = evp_pkey_make_provided(ctx->pkey, ctx->libctx,
-                                     &tmp_keymgmt, ctx->propquery);
+    provkey = evp_pkey_export_to_provider(ctx->pkey, ctx->libctx,
+                                          &tmp_keymgmt, ctx->propquery);
     if (provkey == NULL)
         goto legacy;
     if (!EVP_KEYMGMT_up_ref(tmp_keymgmt)) {
@@ -309,8 +312,8 @@ int EVP_PKEY_derive_set_peer(EVP_PKEY_CTX *ctx, EVP_PKEY *peer)
         return -2;
     }
 
-    provkey = evp_pkey_make_provided(peer, ctx->libctx, &ctx->keymgmt,
-                                     ctx->propquery);
+    provkey = evp_pkey_export_to_provider(peer, ctx->libctx, &ctx->keymgmt,
+                                          ctx->propquery);
     /*
      * If making the key provided wasn't possible, legacy may be able to pick
      * it up
diff --git a/crypto/evp/keymgmt_lib.c b/crypto/evp/keymgmt_lib.c
index cb30405166..a88d65dc5e 100644
--- a/crypto/evp/keymgmt_lib.c
+++ b/crypto/evp/keymgmt_lib.c
@@ -47,31 +47,48 @@ void *evp_keymgmt_util_export_to_provider(EVP_PKEY *pk, EVP_KEYMGMT *keymgmt)
 {
     void *keydata = NULL;
     struct import_data_st import_data;
-    size_t i, j;
+    size_t i = 0;
 
     /* Export to where? */
     if (keymgmt == NULL)
         return NULL;
 
     /* If we have an unassigned key, give up */
-    if (pk->pkeys[0].keymgmt == NULL)
+    if (pk->keymgmt == NULL)
         return NULL;
 
+    /* If |keymgmt| matches the "origin" |keymgmt|, no more to do */
+    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;
+
+    /* If the "origin" |keymgmt| doesn't support exporting, give up */
     /*
-     * See if we have exported to this provider already.
-     * If we have, return immediately.
+     * TODO(3.0) consider an evp_keymgmt_export() return value that indicates
+     * that the method is unsupported.
      */
-    i = evp_keymgmt_util_find_pkey_cache_index(pk, keymgmt);
+    if (pk->keymgmt->export == NULL)
+        return NULL;
 
-    /* If we're already exported to the given keymgmt, no more to do */
-    if (keymgmt == pk->pkeys[i].keymgmt)
-        return pk->pkeys[i].keydata;
+    /* 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 already cached keymgmt
+     * of the "origin"
      */
-    if (!ossl_assert(match_type(pk->pkeys[0].keymgmt, keymgmt)))
+    if (!ossl_assert(match_type(pk->keymgmt, keymgmt)))
         return NULL;
 
     /* Create space to import data into */
@@ -89,109 +106,90 @@ void *evp_keymgmt_util_export_to_provider(EVP_PKEY *pk, EVP_KEYMGMT *keymgmt)
     import_data.keymgmt = keymgmt;
     import_data.selection = OSSL_KEYMGMT_SELECT_ALL;
 
-    for (j = 0; j < i && pk->pkeys[j].keymgmt != NULL; j++) {
-        EVP_KEYMGMT *exp_keymgmt = pk->pkeys[j].keymgmt;
-        void *exp_keydata = pk->pkeys[j].keydata;
-
-        /*
-         * TODO(3.0) consider an evp_keymgmt_export() return value that
-         * indicates that the method is unsupported.
-         */
-        if (exp_keymgmt->export == NULL)
-            continue;
-
-        /*
-         * The export function calls the callback (try_import), which does
-         * the import for us.  If successful, we're done.
-         */
-        if (evp_keymgmt_export(exp_keymgmt, exp_keydata,
-                               OSSL_KEYMGMT_SELECT_ALL,
-                               &try_import, &import_data))
-            break;
-
+    /*
+     * The export function calls the callback (try_import), which does the
+     * import for us.  If successful, we're done.
+     */
+    if (!evp_keymgmt_export(pk->keymgmt, pk->keydata, OSSL_KEYMGMT_SELECT_ALL,
+                            &try_import, &import_data)) {
         /* If there was an error, bail out */
         evp_keymgmt_freedata(keymgmt, keydata);
         return NULL;
     }
 
-    /*
-     * 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->pkeys)))
+    /* Add the new export to the operation cache */
+    if (!evp_keymgmt_util_cache_keydata(pk, i, keymgmt, keydata)) {
+        evp_keymgmt_freedata(keymgmt, keydata);
         return NULL;
-
-    evp_keymgmt_util_cache_pkey(pk, i, keymgmt, keydata);
+    }
 
     return keydata;
 }
 
-void evp_keymgmt_util_clear_pkey_cache(EVP_PKEY *pk)
+void evp_keymgmt_util_clear_operation_cache(EVP_PKEY *pk)
 {
-    size_t i, end = OSSL_NELEM(pk->pkeys);
+    size_t i, end = OSSL_NELEM(pk->operation_cache);
 
     if (pk != NULL) {
-        for (i = 0; i < end && pk->pkeys[i].keymgmt != NULL; i++) {
-            EVP_KEYMGMT *keymgmt = pk->pkeys[i].keymgmt;
-            void *keydata = pk->pkeys[i].keydata;
+        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->pkeys[i].keymgmt = NULL;
-            pk->pkeys[i].keydata = NULL;
+            pk->operation_cache[i].keymgmt = NULL;
+            pk->operation_cache[i].keydata = NULL;
             evp_keymgmt_freedata(keymgmt, keydata);
             EVP_KEYMGMT_free(keymgmt);
         }
-
-        pk->cache.size = 0;
-        pk->cache.bits = 0;
-        pk->cache.security_bits = 0;
     }
 }
 
-size_t evp_keymgmt_util_find_pkey_cache_index(EVP_PKEY *pk,
-                                              EVP_KEYMGMT *keymgmt)
+size_t evp_keymgmt_util_find_operation_cache_index(EVP_PKEY *pk,
+                                                   EVP_KEYMGMT *keymgmt)
 {
-    size_t i, end = OSSL_NELEM(pk->pkeys);
+    size_t i, end = OSSL_NELEM(pk->operation_cache);
 
-    for (i = 0; i < end && pk->pkeys[i].keymgmt != NULL; i++) {
-        if (keymgmt == pk->pkeys[i].keymgmt)
+    for (i = 0; i < end && pk->operation_cache[i].keymgmt != NULL; i++) {
+        if (keymgmt == pk->operation_cache[i].keymgmt)
             break;
     }
 
     return i;
 }
 
-void evp_keymgmt_util_cache_pkey(EVP_PKEY *pk, size_t index,
-                                 EVP_KEYMGMT *keymgmt, void *keydata)
+int evp_keymgmt_util_cache_keydata(EVP_PKEY *pk, size_t index,
+                                   EVP_KEYMGMT *keymgmt, void *keydata)
 {
     if (keydata != NULL) {
-        EVP_KEYMGMT_up_ref(keymgmt);
-        pk->pkeys[index].keydata = keydata;
-        pk->pkeys[index].keymgmt = keymgmt;
-
-        /*
-         * Cache information about the key object.  Only needed for the
-         * "original" provider side key.
-         *
-         * This services functions like EVP_PKEY_size, EVP_PKEY_bits, etc
-         */
-        if (index == 0) {
-            int bits = 0;
-            int security_bits = 0;
-            int size = 0;
-            OSSL_PARAM params[4];
-
-            params[0] = OSSL_PARAM_construct_int(OSSL_PKEY_PARAM_BITS, &bits);
-            params[1] = OSSL_PARAM_construct_int(OSSL_PKEY_PARAM_SECURITY_BITS,
-                                                 &security_bits);
-            params[2] = OSSL_PARAM_construct_int(OSSL_PKEY_PARAM_MAX_SIZE,
-                                                 &size);
-            params[3] = OSSL_PARAM_construct_end();
-            if (evp_keymgmt_get_params(keymgmt, keydata, params)) {
-                pk->cache.size = size;
-                pk->cache.bits = bits;
-                pk->cache.security_bits = security_bits;
-            }
+        if (!EVP_KEYMGMT_up_ref(keymgmt))
+            return 0;
+        pk->operation_cache[index].keydata = keydata;
+        pk->operation_cache[index].keymgmt = keymgmt;
+    }
+    return 1;
+}
+
+void evp_keymgmt_util_cache_keyinfo(EVP_PKEY *pk)
+{
+    /*
+     * Cache information about the provider "origin" key.
+     *
+     * This services functions like EVP_PKEY_size, EVP_PKEY_bits, etc
+     */
+    if (pk->keymgmt != NULL) {
+        int bits = 0;
+        int security_bits = 0;
+        int size = 0;
+        OSSL_PARAM params[4];
+
+        params[0] = OSSL_PARAM_construct_int(OSSL_PKEY_PARAM_BITS, &bits);
+        params[1] = OSSL_PARAM_construct_int(OSSL_PKEY_PARAM_SECURITY_BITS,
+                                             &security_bits);
+        params[2] = OSSL_PARAM_construct_int(OSSL_PKEY_PARAM_MAX_SIZE, &size);
+        params[3] = OSSL_PARAM_construct_end();
+        if (evp_keymgmt_get_params(pk->keymgmt, pk->keydata, params)) {
+            pk->cache.size = size;
+            pk->cache.bits = bits;
+            pk->cache.security_bits = security_bits;
         }
     }
 }
@@ -202,14 +200,16 @@ void *evp_keymgmt_util_fromdata(EVP_PKEY *target, EVP_KEYMGMT *keymgmt,
     void *keydata = evp_keymgmt_newdata(keymgmt);
 
     if (keydata != NULL) {
-        if (!evp_keymgmt_import(keymgmt, keydata, selection, params)) {
+        if (!evp_keymgmt_import(keymgmt, keydata, selection, params)
+            || !EVP_KEYMGMT_up_ref(keymgmt)) {
             evp_keymgmt_freedata(keymgmt, keydata);
             return NULL;
         }
 
-
-        evp_keymgmt_util_clear_pkey_cache(target);
-        evp_keymgmt_util_cache_pkey(target, 0, keymgmt, keydata);
+        evp_keymgmt_util_clear_operation_cache(target);
+        target->keymgmt = keymgmt;
+        target->keydata = keydata;
+        evp_keymgmt_util_cache_keyinfo(target);
     }
 
     return keydata;
diff --git a/crypto/evp/m_sigver.c b/crypto/evp/m_sigver.c
index 1ea5669c02..b6c66722ec 100644
--- a/crypto/evp/m_sigver.c
+++ b/crypto/evp/m_sigver.c
@@ -64,10 +64,13 @@ static int do_sigver_init(EVP_MD_CTX *ctx, EVP_PKEY_CTX **pctx,
     if (locpctx->keytype == NULL)
         goto legacy;
 
-    /* Ensure that the key is provided.  If not, go legacy */
+    /*
+     * Ensure that the key is provided, either natively, or as a cached export.
+     *  If not, go legacy
+     */
     tmp_keymgmt = locpctx->keymgmt;
-    provkey = evp_pkey_make_provided(locpctx->pkey, locpctx->libctx,
-                                     &tmp_keymgmt, locpctx->propquery);
+    provkey = evp_pkey_export_to_provider(locpctx->pkey, locpctx->libctx,
+                                          &tmp_keymgmt, locpctx->propquery);
     if (provkey == NULL)
         goto legacy;
     if (!EVP_KEYMGMT_up_ref(tmp_keymgmt)) {
diff --git a/crypto/evp/p_lib.c b/crypto/evp/p_lib.c
index 2ffddf5d0a..621d99d171 100644
--- a/crypto/evp/p_lib.c
+++ b/crypto/evp/p_lib.c
@@ -713,7 +713,7 @@ int EVP_PKEY_print_params(BIO *out, const EVP_PKEY *pkey,
 static int legacy_asn1_ctrl_to_param(EVP_PKEY *pkey, int op,
                                      int arg1, void *arg2)
 {
-    if (pkey->pkeys[0].keymgmt == NULL)
+    if (pkey->keymgmt == NULL)
         return 0;
     switch (op) {
     case ASN1_PKEY_CTRL_DEFAULT_MD_NID:
@@ -768,9 +768,7 @@ int EVP_PKEY_get_default_digest_name(EVP_PKEY *pkey,
                                              mdmandatory,
                                              sizeof(mdmandatory));
         params[2] = OSSL_PARAM_construct_end();
-        if (!evp_keymgmt_get_params(pkey->pkeys[0].keymgmt,
-                                    pkey->pkeys[0].keydata,
-                                    params))
+        if (!evp_keymgmt_get_params(pkey->keymgmt, pkey->keydata, params))
             return 0;
         if (mdmandatory[0] != '\0') {
             OPENSSL_strlcpy(mdname, mdmandatory, mdname_sz);
@@ -868,22 +866,40 @@ int EVP_PKEY_up_ref(EVP_PKEY *pkey)
     return ((i > 1) ? 1 : 0);
 }
 
-static void evp_pkey_free_it(EVP_PKEY *x)
+#ifndef FIPS_MODE
+static void evp_pkey_free_legacy(EVP_PKEY *x)
 {
-    /* internal function; x is never NULL */
-
-    evp_keymgmt_util_clear_pkey_cache(x);
-
-    if (x->ameth && x->ameth->pkey_free) {
-        x->ameth->pkey_free(x);
+    if (x->ameth != NULL) {
+        if (x->ameth->pkey_free)
+            x->ameth->pkey_free(x);
         x->pkey.ptr = NULL;
+        x->ameth = NULL;
     }
-#if !defined(OPENSSL_NO_ENGINE) && !defined(FIPS_MODE)
+# ifndef OPENSSL_NO_ENGINE
     ENGINE_finish(x->engine);
     x->engine = NULL;
     ENGINE_finish(x->pmeth_engine);
     x->pmeth_engine = NULL;
+# endif
+    x->type = x->save_type = EVP_PKEY_NONE;
+}
+#endif  /* FIPS_MODE */
+
+static void evp_pkey_free_it(EVP_PKEY *x)
+{
+    /* internal function; x is never NULL */
+
+    evp_keymgmt_util_clear_operation_cache(x);
+#ifndef FIPS_MODE
+    evp_pkey_free_legacy(x);
 #endif
+
+    if (x->keymgmt != NULL) {
+        evp_keymgmt_freedata(x->keymgmt, x->keydata);
+        EVP_KEYMGMT_free(x->keymgmt);
+        x->keymgmt = NULL;
+        x->keydata = NULL;
+    }
 }
 
 void EVP_PKEY_free(EVP_PKEY *x)
@@ -917,8 +933,9 @@ int EVP_PKEY_size(const EVP_PKEY *pkey)
     return 0;
 }
 
-void *evp_pkey_make_provided(EVP_PKEY *pk, OPENSSL_CTX *libctx,
-                             EVP_KEYMGMT **keymgmt, const char *propquery)
+void *evp_pkey_export_to_provider(EVP_PKEY *pk, OPENSSL_CTX *libctx,
+                                  EVP_KEYMGMT **keymgmt,
+                                  const char *propquery)
 {
     EVP_KEYMGMT *allocated_keymgmt = NULL;
     EVP_KEYMGMT *tmp_keymgmt = NULL;
@@ -927,59 +944,22 @@ void *evp_pkey_make_provided(EVP_PKEY *pk, OPENSSL_CTX *libctx,
     if (pk == NULL)
         return NULL;
 
-    if (keymgmt != NULL) {
-        tmp_keymgmt = *keymgmt;
-        *keymgmt = NULL;
-    }
-
 #ifndef FIPS_MODE
-    /*
-     * If there is an underlying legacy key and it has changed, invalidate
-     * the cache of provider keys.
-     */
     if (pk->pkey.ptr != NULL) {
-        EVP_KEYMGMT *legacy_keymgmt = NULL;
-
-        /*
-         * If there is no dirty counter, this key can't be used with
-         * providers.
-         */
-        if (pk->ameth->dirty_cnt == NULL)
-            goto end;
-
-        /*
-         * If no keymgmt was given by the caller, we set it to the first
-         * that's cached, to become the keymgmt to re-export to if needed,
-         * or to have a token keymgmt to return on success.  Further checks
-         * are done further down.
-         *
-         * We need to carefully save the pointer somewhere other than in
-         * tmp_keymgmt, so the EVP_KEYMGMT_up_ref() below doesn't mistakenly
-         * increment the reference counter of a keymgmt given by the caller.
-         */
-        if (tmp_keymgmt == NULL)
-            legacy_keymgmt = pk->pkeys[0].keymgmt;
-
-        /*
-         * If the dirty counter changed since last time, we make sure to
-         * hold on to the keymgmt we just got (if we got one), then clear
-         * the cache.
-         */
-        if (pk->ameth->dirty_cnt(pk) != pk->dirty_cnt_copy) {
-            if (legacy_keymgmt != NULL && !EVP_KEYMGMT_up_ref(legacy_keymgmt))
-                goto end;
-            evp_keymgmt_util_clear_pkey_cache(pk);
-        }
-
         /*
-         * |legacy_keymgmt| was only given a value if |tmp_keymgmt| is
-         * NULL.
+         * If the legacy key doesn't have an dirty counter or export function,
+         * give up
          */
-        if (legacy_keymgmt != NULL)
-            tmp_keymgmt = legacy_keymgmt;
+        if (pk->ameth->dirty_cnt == NULL || pk->ameth->export_to == NULL)
+            return NULL;
     }
 #endif
 
+    if (keymgmt != NULL) {
+        tmp_keymgmt = *keymgmt;
+        *keymgmt = NULL;
+    }
+
     /* If no keymgmt was given or found, get a default keymgmt */
     if (tmp_keymgmt == NULL) {
         EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_pkey(libctx, pk, propquery);
@@ -990,35 +970,41 @@ void *evp_pkey_make_provided(EVP_PKEY *pk, OPENSSL_CTX *libctx,
         EVP_PKEY_CTX_free(ctx);
     }
 
+    /* If there's still no keymgmt to be had, give up */
     if (tmp_keymgmt == NULL)
         goto end;
 
 #ifndef FIPS_MODE
     if (pk->pkey.ptr != NULL) {
-        size_t i;
+        size_t i = 0;
 
         /*
-         * Find our keymgmt in the cache.  If it's present, it means that
-         * export has already been done.  We take token copies of the
-         * cached pointers, to have token success values to return.
-         *
-         * 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 the legacy "origin" hasn't changed since last time, we try
+         * to find our keymgmt in the operation cache.  If it has changed,
+         * |i| remains zero, and we will clear the cache further down.
          */
-        i = evp_keymgmt_util_find_pkey_cache_index(pk, tmp_keymgmt);
-        if (!ossl_assert(i < OSSL_NELEM(pk->pkeys)))
-            goto end;
-        if (pk->pkeys[i].keymgmt != NULL) {
-            keydata = pk->pkeys[i].keydata;
-            goto end;
+        if (pk->ameth->dirty_cnt(pk) == pk->dirty_cnt_copy) {
+            i = evp_keymgmt_util_find_operation_cache_index(pk, tmp_keymgmt);
+
+            /*
+             * If |tmp_keymgmt| is present in the operation cache, it means
+             * that export doesn't need to be redone.  In that case, we take
+             * token copies of the cached pointers, to have token success
+             * values to return.
+             */
+            if (i < OSSL_NELEM(pk->operation_cache)
+                && pk->operation_cache[i].keymgmt != NULL) {
+                keydata = pk->operation_cache[i].keydata;
+                goto end;
+            }
         }
 
         /*
-         * If we still don't have a keymgmt at this point, or the legacy
-         * key doesn't have an export function, just bail out.
+         * 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 (pk->ameth->export_to == NULL)
+        if (!ossl_assert(i < OSSL_NELEM(pk->operation_cache)))
             goto end;
 
         /* Make sure that the keymgmt key type matches the legacy NID */
@@ -1034,7 +1020,27 @@ void *evp_pkey_make_provided(EVP_PKEY *pk, OPENSSL_CTX *libctx,
             goto end;
         }
 
-        evp_keymgmt_util_cache_pkey(pk, i, tmp_keymgmt, keydata);
+        /*
+         * If the dirty counter changed since last time, then clear the
+         * operation cache.  In that case, we know that |i| is zero.  Just
+         * in case this is a re-export, we increment then decrement the
+         * keymgmt reference counter.
+         */
+        if (!EVP_KEYMGMT_up_ref(tmp_keymgmt)) { /* refcnt++ */
+            evp_keymgmt_freedata(tmp_keymgmt, keydata);
+            keydata = NULL;
+            goto end;
+        }
+        if (pk->ameth->dirty_cnt(pk) != pk->dirty_cnt_copy)
+            evp_keymgmt_util_clear_operation_cache(pk);
+        EVP_KEYMGMT_free(tmp_keymgmt); /* refcnt-- */
+
+        /* Add the new export to the operation cache */
+        if (!evp_keymgmt_util_cache_keydata(pk, i, tmp_keymgmt, keydata)) {
+            evp_keymgmt_freedata(tmp_keymgmt, keydata);
+            keydata = NULL;
+            goto end;
+        }
 
         /* Synchronize the dirty count */
         pk->dirty_cnt_copy = pk->ameth->dirty_cnt(pk);
@@ -1059,3 +1065,100 @@ void *evp_pkey_make_provided(EVP_PKEY *pk, OPENSSL_CTX *libctx,
     EVP_KEYMGMT_free(allocated_keymgmt);
     return keydata;
 }
+
+#ifndef FIPS_MODE
+/*
+ * This differs from exporting in that it releases the legacy key and assigns
+ * the export keymgmt and keydata to the "origin" provider side key instead
+ * of the operation cache.
+ */
+void *evp_pkey_upgrade_to_provider(EVP_PKEY *pk, OPENSSL_CTX *libctx,
+                                   EVP_KEYMGMT **keymgmt,
+                                   const char *propquery)
+{
+    EVP_KEYMGMT *allocated_keymgmt = NULL;
+    EVP_KEYMGMT *tmp_keymgmt = NULL;
+    void *keydata = NULL;
+
+    if (pk == NULL)
+        return NULL;
+
+    /*
+     * If this key is already "upgraded", this function shouldn't have been
+     * called.
+     */
+    if (!ossl_assert(pk->keymgmt == NULL))
+        return NULL;
+
+    if (keymgmt != NULL) {
+        tmp_keymgmt = *keymgmt;
+        *keymgmt = NULL;
+    }
+
+    /* If the key isn't a legacy one, bail out, but with proper values */
+    if (pk->pkey.ptr == NULL) {
+        tmp_keymgmt = pk->keymgmt;
+        keydata = pk->keydata;
+    } else {
+        /* If the legacy key doesn't have an export function, give up */
+        if (pk->ameth->export_to == NULL)
+            return NULL;
+
+        /* If no keymgmt was given, get a default keymgmt */
+        if (tmp_keymgmt == NULL) {
+            EVP_PKEY_CTX *ctx =
+                EVP_PKEY_CTX_new_from_pkey(libctx, pk, propquery);
+
+            if (ctx != NULL && ctx->keytype != NULL)
+                tmp_keymgmt = allocated_keymgmt =
+                    EVP_KEYMGMT_fetch(ctx->libctx, ctx->keytype, propquery);
+            EVP_PKEY_CTX_free(ctx);
+        }
+
+        /* If we still don't have a keymgmt, give up */
+        if (tmp_keymgmt == NULL)
+            goto end;
+
+        /* Make sure that the keymgmt key type matches the legacy NID */
+        if (!ossl_assert(EVP_KEYMGMT_is_a(tmp_keymgmt, OBJ_nid2sn(pk->type))))
+            goto end;
+
+        if ((keydata = evp_keymgmt_newdata(tmp_keymgmt)) == NULL)
+            goto end;
+
+        if (!pk->ameth->export_to(pk, keydata, tmp_keymgmt)
+            || !EVP_KEYMGMT_up_ref(tmp_keymgmt)) {
+            evp_keymgmt_freedata(tmp_keymgmt, keydata);
+            keydata = NULL;
+            goto end;
+        }
+
+        /*
+         * Clear the operation cache, all the legacy data, as well as the
+         * dirty counters
+         */
+        evp_pkey_free_legacy(pk);
+        pk->dirty_cnt_copy = 0;
+
+        evp_keymgmt_util_clear_operation_cache(pk);
+        pk->keymgmt = tmp_keymgmt;
+        pk->keydata = keydata;
+        evp_keymgmt_util_cache_keyinfo(pk);
+    }
+
+ end:
+    /*
+     * If nothing was upgraded, |tmp_keymgmt| might point at a freed
+     * EVP_KEYMGMT, so we clear it to be safe.  It shouldn't be useful for
+     * the caller either way in that case.
+     */
+    if (keydata == NULL)
+        tmp_keymgmt = NULL;
+
+    if (keymgmt != NULL)
+        *keymgmt = tmp_keymgmt;
+
+    EVP_KEYMGMT_free(allocated_keymgmt);
+    return keydata;
+}
+#endif  /* FIPS_MODE */
diff --git a/crypto/evp/pmeth_check.c b/crypto/evp/pmeth_check.c
index 1186ad2b12..c02353d5ea 100644
--- a/crypto/evp/pmeth_check.c
+++ b/crypto/evp/pmeth_check.c
@@ -28,8 +28,8 @@ int EVP_PKEY_public_check(EVP_PKEY_CTX *ctx)
         return 0;
     }
 
-    keymgmt = pkey->pkeys[0].keymgmt;
-    key = pkey->pkeys[0].keydata;
+    keymgmt = pkey->keymgmt;
+    key = pkey->keydata;
 
     if (key != NULL && keymgmt != NULL)
         return evp_keymgmt_validate(keymgmt, key,
@@ -61,8 +61,8 @@ int EVP_PKEY_param_check(EVP_PKEY_CTX *ctx)
         return 0;
     }
 
-    keymgmt = pkey->pkeys[0].keymgmt;
-    key = pkey->pkeys[0].keydata;
+    keymgmt = pkey->keymgmt;
+    key = pkey->keydata;
 
     if (key != NULL && keymgmt != NULL)
         return evp_keymgmt_validate(keymgmt, key,
@@ -94,8 +94,8 @@ int EVP_PKEY_private_check(EVP_PKEY_CTX *ctx)
         return 0;
     }
 
-    keymgmt = pkey->pkeys[0].keymgmt;
-    key = pkey->pkeys[0].keydata;
+    keymgmt = pkey->keymgmt;
+    key = pkey->keydata;
 
     if (key != NULL && keymgmt != NULL)
         return evp_keymgmt_validate(keymgmt, key,
@@ -115,8 +115,8 @@ int EVP_PKEY_pairwise_check(EVP_PKEY_CTX *ctx)
         return 0;
     }
 
-    keymgmt = pkey->pkeys[0].keymgmt;
-    key = pkey->pkeys[0].keydata;
+    keymgmt = pkey->keymgmt;
+    key = pkey->keydata;
 
     if (key != NULL && keymgmt != NULL)
         return evp_keymgmt_validate(keymgmt, key, OSSL_KEYMGMT_SELECT_KEYPAIR);
@@ -135,8 +135,8 @@ int EVP_PKEY_check(EVP_PKEY_CTX *ctx)
         return 0;
     }
 
-    keymgmt = pkey->pkeys[0].keymgmt;
-    key = pkey->pkeys[0].keydata;
+    keymgmt = pkey->keymgmt;
+    key = pkey->keydata;
 
     if (key != NULL && keymgmt != NULL)
         return evp_keymgmt_validate(keymgmt, key, OSSL_KEYMGMT_SELECT_ALL);
diff --git a/crypto/evp/pmeth_fn.c b/crypto/evp/pmeth_fn.c
index 0b5af8e136..ca0790fcd6 100644
--- a/crypto/evp/pmeth_fn.c
+++ b/crypto/evp/pmeth_fn.c
@@ -41,10 +41,13 @@ static int evp_pkey_asym_cipher_init(EVP_PKEY_CTX *ctx, int operation)
     if (ctx->keytype == NULL || ctx->engine != NULL)
         goto legacy;
 
-    /* Ensure that the key is provided.  If not, go legacy */
+    /*
+     * Ensure that the key is provided, either natively, or as a cached export.
+     *  If not, go legacy
+     */
     tmp_keymgmt = ctx->keymgmt;
-    provkey = evp_pkey_make_provided(ctx->pkey, ctx->libctx,
-                                     &tmp_keymgmt, ctx->propquery);
+    provkey = evp_pkey_export_to_provider(ctx->pkey, ctx->libctx,
+                                          &tmp_keymgmt, ctx->propquery);
     if (provkey == NULL)
         goto legacy;
     if (!EVP_KEYMGMT_up_ref(tmp_keymgmt)) {
diff --git a/crypto/evp/pmeth_lib.c b/crypto/evp/pmeth_lib.c
index cb64b95bf6..c82a543857 100644
--- a/crypto/evp/pmeth_lib.c
+++ b/crypto/evp/pmeth_lib.c
@@ -160,8 +160,7 @@ static EVP_PKEY_CTX *int_ctx_new(OPENSSL_CTX *libctx,
         /* If we have an engine, something went wrong somewhere... */
         if (!ossl_assert(e == NULL))
             return NULL;
-        name = evp_first_name(pkey->pkeys[0].keymgmt->prov,
-                              pkey->pkeys[0].keymgmt->name_id);
+        name = evp_first_name(pkey->keymgmt->prov, pkey->keymgmt->name_id);
         /*
          * TODO: I wonder if the EVP_PKEY should have the name and propquery
          * that were used when building it....  /RL
diff --git a/crypto/evp/signature.c b/crypto/evp/signature.c
index ca9d91efc5..3dfd4041e7 100644
--- a/crypto/evp/signature.c
+++ b/crypto/evp/signature.c
@@ -342,10 +342,13 @@ static int evp_pkey_signature_init(EVP_PKEY_CTX *ctx, int operation)
     if (ctx->keytype == NULL)
         goto legacy;
 
-    /* Ensure that the key is provided.  If not, go legacy */
+    /*
+     * Ensure that the key is provided, either natively, or as a cached export.
+     *  If not, go legacy
+     */
     tmp_keymgmt = ctx->keymgmt;
-    provkey = evp_pkey_make_provided(ctx->pkey, ctx->libctx,
-                                     &tmp_keymgmt, ctx->propquery);
+    provkey = evp_pkey_export_to_provider(ctx->pkey, ctx->libctx,
+                                          &tmp_keymgmt, ctx->propquery);
     if (provkey == NULL)
         goto legacy;
     if (!EVP_KEYMGMT_up_ref(tmp_keymgmt)) {
diff --git a/crypto/serializer/serializer_pkey.c b/crypto/serializer/serializer_pkey.c
index 3b0cc3ac86..1b6df1da53 100644
--- a/crypto/serializer/serializer_pkey.c
+++ b/crypto/serializer/serializer_pkey.c
@@ -267,8 +267,8 @@ static int serializer_write_cb(const OSSL_PARAM params[], void *arg)
 static int serializer_EVP_PKEY_to_bio(OSSL_SERIALIZER_CTX *ctx, BIO *out)
 {
     const EVP_PKEY *pkey = ctx->object;
-    void *keydata = pkey->pkeys[0].keydata;
-    EVP_KEYMGMT *keymgmt = pkey->pkeys[0].keymgmt;
+    void *keydata = pkey->keydata;
+    EVP_KEYMGMT *keymgmt = pkey->keymgmt;
 
     /*
      * OSSL_SERIALIZER_CTX_new() creates a context, even when the
@@ -306,7 +306,7 @@ OSSL_SERIALIZER_CTX *OSSL_SERIALIZER_CTX_new_by_EVP_PKEY(const EVP_PKEY *pkey,
 {
     OSSL_SERIALIZER_CTX *ctx = NULL;
     OSSL_SERIALIZER *ser = NULL;
-    EVP_KEYMGMT *keymgmt = pkey->pkeys[0].keymgmt;
+    EVP_KEYMGMT *keymgmt = pkey->keymgmt;
     int selection = OSSL_KEYMGMT_SELECT_ALL;
 
     if (!ossl_assert(pkey != NULL && propquery != NULL)) {
diff --git a/crypto/x509/x_pubkey.c b/crypto/x509/x_pubkey.c
index f79a57cb7d..f643170b45 100644
--- a/crypto/x509/x_pubkey.c
+++ b/crypto/x509/x_pubkey.c
@@ -91,7 +91,7 @@ int X509_PUBKEY_set(X509_PUBKEY **x, EVP_PKEY *pkey)
             X509err(X509_F_X509_PUBKEY_SET, X509_R_METHOD_NOT_SUPPORTED);
             goto error;
         }
-    } else if (pkey->pkeys[0].keymgmt != NULL) {
+    } else if (pkey->keymgmt != NULL) {
         BIO *bmem = BIO_new(BIO_s_mem());
         const char *serprop = OSSL_SERIALIZER_PUBKEY_TO_DER_PQ;
         OSSL_SERIALIZER_CTX *sctx =
@@ -270,7 +270,7 @@ int i2d_PUBKEY(const EVP_PKEY *a, unsigned char **pp)
             xpk->pkey = NULL;
         }
         X509_PUBKEY_free(xpk);
-    } else if (a->pkeys[0].keymgmt != NULL) {
+    } else if (a->keymgmt != NULL) {
         const char *serprop = OSSL_SERIALIZER_PUBKEY_TO_DER_PQ;
         OSSL_SERIALIZER_CTX *ctx =
             OSSL_SERIALIZER_CTX_new_by_EVP_PKEY(a, serprop);
diff --git a/doc/internal/man3/evp_keymgmt_util_export_to_provider.pod b/doc/internal/man3/evp_keymgmt_util_export_to_provider.pod
index 2c8b7b2f24..545625e7ba 100644
--- a/doc/internal/man3/evp_keymgmt_util_export_to_provider.pod
+++ b/doc/internal/man3/evp_keymgmt_util_export_to_provider.pod
@@ -3,8 +3,10 @@
 =head1 NAME
 
 evp_keymgmt_util_export_to_provider,
-evp_keymgmt_util_clear_pkey_cache,
-evp_keymgmt_util_cache_pkey,
+evp_keymgmt_util_find_operation_cache_index,
+evp_keymgmt_util_clear_operation_cache,
+evp_keymgmt_util_cache_keydata,
+evp_keymgmt_util_cache_keyinfo,
 evp_keymgmt_util_fromdata
 - internal KEYMGMT utility functions
 
@@ -13,9 +15,12 @@ evp_keymgmt_util_fromdata
  #include "crypto/evp.h"
 
  void *evp_keymgmt_util_export_to_provider(EVP_PKEY *pk, EVP_KEYMGMT *keymgmt);
- void evp_keymgmt_util_clear_pkey_cache(EVP_PKEY *pk);
- void evp_keymgmt_util_cache_pkey(EVP_PKEY *pk, size_t index,
-                                  EVP_KEYMGMT *keymgmt, void *keydata);
+ size_t evp_keymgmt_util_find_operation_cache_index(EVP_PKEY *pk,
+                                                    EVP_KEYMGMT *keymgmt);
+ void evp_keymgmt_util_clear_operation_cache(EVP_PKEY *pk);
+ void evp_keymgmt_util_cache_keydata(EVP_PKEY *pk, size_t index,
+                                     EVP_KEYMGMT *keymgmt, void *keydata);
+ void evp_keymgmt_util_cache_keyinfo(EVP_PKEY *pk);
  void *evp_keymgmt_util_fromdata(EVP_PKEY *target, EVP_KEYMGMT *keymgmt,
                                  int selection, const OSSL_PARAM params[]);
 
@@ -27,16 +32,24 @@ via a B<EVP_KEYMGMT> interface, if this hasn't already been done.
 It maintains a cache of provider key references in I<pk> to keep track
 of all provider side keys.
 
-To export a legacy key, use L<evp_pkey_make_provided(3)> instead, as
-this function deals purely with provider side keys and will not care
-to look at any legacy key.
+To export a legacy key, use L<evp_pkey_export_to_provider(3)> instead,
+as this function ignores any legacy key data.
 
-evp_keymgmt_util_clear_pkey_cache() can be used to explicitly clear
-the cache of provider key references.
+evp_keymgmt_util_find_operation_cache_index() finds the location if
+I<keymgmt> in I<pk>'s cache of provided keys for operations.  If
+I<keymgmt> is NULL or couldn't be found in the cache, it finds the
+first empty slot instead if there is any.
 
-evp_keymgmt_util_cache_pkey() can be used to assign a provider key
+evp_keymgmt_util_clear_operation_cache() can be used to explicitly
+clear the cache of operation key references.
+
+evp_keymgmt_util_cache_keydata() can be used to assign a provider key
 object to a specific cache slot in the given I<target>.
-I<Use with extreme care>.
+I<Use extreme care>.
+
+evp_keymgmt_util_cache_keyinfo() can be used to get all kinds of
+information from the provvider "origin" and save it in I<pk>'s
+information cache.
 
 evp_keymgmt_util_fromdata() can be used to add key object data to a
 given key I<target> via a B<EVP_KEYMGMT> interface.  This is used as a
@@ -48,6 +61,11 @@ evp_keymgmt_export_to_provider() and evp_keymgmt_util_fromdata()
 return a pointer to the appropriate provider side key (created or
 found again), or NULL on error.
 
+evp_keymgmt_util_find_operation_cache_index() returns the index of the
+operation cache slot.  If I<keymgmt> is NULL, or if there is no slot
+with a match for I<keymgmt>, the index of the first empty slot is
+returned, or the maximum number of slots if there isn't an empty one.
+
 =head1 NOTES
 
 "Legacy key" is the term used for any key that has been assigned to an
diff --git a/doc/internal/man3/evp_pkey_export_to_provider.pod b/doc/internal/man3/evp_pkey_export_to_provider.pod
new file mode 100644
index 0000000000..31e8ad02e4
--- /dev/null
+++ b/doc/internal/man3/evp_pkey_export_to_provider.pod
@@ -0,0 +1,75 @@
+=pod
+
+=head1 NAME
+
+evp_pkey_export_to_provider, evp_pkey_upgrade_to_provider
+- internal EVP_PKEY support functions for providers
+
+=head1 SYNOPSIS
+
+ /* Only for EVP source */
+ #include "evp_local.h"
+
+ void *evp_pkey_export_to_provider(EVP_PKEY *pk, OPENSSL_CTX *libctx,
+                                   EVP_KEYMGMT **keymgmt,
+                                   const char *propquery);
+ void *evp_pkey_upgrade_to_provider(EVP_PKEY *pk, OPENSSL_CTX *libctx,
+                                    EVP_KEYMGMT **keymgmt,
+                                    const char *propquery);
+
+=head1 DESCRIPTION
+
+This manual uses the term "origin", which is explained in internal
+L<EVP_PKEY(7)>.
+
+evp_pkey_export_to_provider() exports the "origin" key contained in I<pk>
+to its operation cache to make it suitable for an B<EVP_KEYMGMT> given either
+with I<*keymgmt> or with an implicit fetch using I<libctx> (NULL means the
+default context), the name of the legacy type of I<pk>, and the I<propquery>
+(NULL means the default property query settings).
+
+If I<keymgmt> isn't NULL but I<*keymgmt> is, and the "origin" was successfully
+exported, then I<*keymgmt> is assigned the implicitly fetched B<EVP_KEYMGMT>.
+
+evp_pkey_upgrade_to_provider() exports the legacy "origin" key contained in
+I<pk> to it's provider side counterpart, then clears the legacy "origin" key
+along with other legacy data, and resets all the caches.  Otherwise, it works
+like evp_pkey_export_to_provider().
+
+I<evp_pkey_upgrade_to_provider() must be used with great care, only if there's
+no other way.>
+Most of the time, it's sufficient to use evp_pkey_export_to_provider(), but in
+case the key needs modification with data coming from a provided key, the key
+will need an upgrade.
+
+=head1 RETURN VALUES
+
+evp_pkey_export_to_provider() and evp_pkey_upgrade_to_provider() both return
+the provider key data that was exported if the "origin" was successfully
+exported to its target.  Otherwise, NULL is returned.
+
+=head1 NOTES
+
+Some functions calling evp_pkey_export_to_provider() or
+evp_pkey_upgrade_to_provider() may have received a const key, and may
+therefore have to cast the key to non-const form to call this function.  Since
+B<EVP_PKEY> is always dynamically allocated, this is OK.
+
+=head1 SEE ALSO
+
+L<OPENSSL_CTX(3)>, L<EVP_KEYMGMT(3)>
+
+=head1 HISTORY
+
+The functions described here were all added in OpenSSL 3.0.
+
+=head1 COPYRIGHT
+
+Copyright 2020 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
+in the file LICENSE in the source distribution or at
+L<https://www.openssl.org/source/license.html>.
+
+=cut
diff --git a/doc/internal/man3/evp_pkey_make_provided.pod b/doc/internal/man3/evp_pkey_make_provided.pod
deleted file mode 100644
index 3eb17e707b..0000000000
--- a/doc/internal/man3/evp_pkey_make_provided.pod
+++ /dev/null
@@ -1,63 +0,0 @@
-=pod
-
-=head1 NAME
-
-evp_pkey_make_provided - internal EVP_PKEY support functions for providers
-
-=head1 SYNOPSIS
-
- /* Only for EVP source */
- #include "evp_local.h"
-
- void *evp_pkey_make_provided(EVP_PKEY *pk, OPENSSL_CTX *libctx,
-                              EVP_KEYMGMT **keymgmt, const char *propquery);
-
-=head1 DESCRIPTION
-
-evp_pkey_make_provided() ensures that the B<EVP_PKEY> I<pk> is provided within
-the library context I<libctx> (NULL means the default context).  I<keymgmt>
-may point at a reference to a B<EVP_KEYMGMT>, and works as an input/output
-parameter.
-As input to this function, it can be used to specify a B<EVP_KEYMGMT> to be
-used for exporting.  If not (I<*keymgmt> is NULL), then this function will
-fetch an B<EVP_KEYMGMT> implicitly, using I<propquery> as property query string.
-As output from this function, I<*keymgmt> will be assigned the B<EVP_KEYMGMT>
-that was used, if the export was successful, otherwise it will be assigned NULL.
-
-If I<pk> has an assigned legacy key, a check is done to see if any of
-its key material has changed since last export, by comparing the
-result of the legacy key's dirty_cnt() method with a copy of that
-result from last time evp_pkey_make_provided() was run with this
-B<EVP_PKEY>.
-If it has, the cache of already exported keys is cleared, and a new
-export is made with the new legacy key material.
-
-=head1 RETURN VALUES
-
-evp_pkey_make_provided() returns the provider key data that was exported if
-I<pk> was successfully provided.  Otherwise, NULL is returned.
-
-=head1 NOTES
-
-Some functions calling evp_pkey_make_provided() may have received a const
-key, and may therefore have to cast the key to non-const form to call this
-function.  Since B<EVP_PKEY> is always dynamically allocated, this is OK.
-
-=head1 SEE ALSO
-
-L<OPENSSL_CTX(3)>, L<EVP_KEYMGMT(3)>
-
-=head1 HISTORY
-
-The functions described here were all added in OpenSSL 3.0.
-
-=head1 COPYRIGHT
-
-Copyright 2020 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
-in the file LICENSE in the source distribution or at
-L<https://www.openssl.org/source/license.html>.
-
-=cut
diff --git a/doc/internal/man7/EVP_PKEY.pod b/doc/internal/man7/EVP_PKEY.pod
new file mode 100644
index 0000000000..a37ca9eecc
--- /dev/null
+++ b/doc/internal/man7/EVP_PKEY.pod
@@ -0,0 +1,56 @@
+=pod
+
+=head1 NAME
+
+EVP_PKEY - an internal description
+
+=head1 SYNOPSIS
+
+ #include "crypto/evp.h"
+
+ struct evp_pkey_st;
+
+=head1 DESCRIPTION
+
+I<This is not a complete description yet>
+
+B<EVP_PKEY> is a complex type that's essentially a container for
+private/public key key pairs, but has had other uses as well.
+
+=for comment "uses" could as well be "abuses"...
+
+It can contain the legacy form of keys -- i.e. pointers to the low
+level key types, such as B<RSA>, B<DSA> and B<EC> --, but also the
+provided form of keys -- i.e. pointers to provider side key data.
+Those two forms are mutually exclusive; an B<EVP_PKEY> instance can't
+contain both a key in legacy form and in provided form.  Regardless of
+form, this key is commonly refered to as the "origin".
+
+An B<EVP_PKEY> also contains a cache of provider side copies of the
+key, each adapted for the provider that is going to use that copy to
+perform some operation.
+For a legacy "origin", the B<EVP_PKEY_ASN1_METHOD>'s functions
+export_to() and dirty_cnt() must be implemented for such caching to be
+possible.  For a provider side "origin", the B<EVP_KEYMGMT>'s function
+OP_keymgmt_export() must be implemented.  In all cases, the receiving
+B<EVP_KEYMGMT> must have an implemented OP_keygmt_import().
+
+If such caching isn't supported, the operations that can be performed
+with that key are limited to the same backend as the "origin" key
+(ENGINE for legacy "origin" keys, provider for provider side "origin"
+keys).
+
+=head1 SEE ALSO
+
+L<provider-keymgmt(7)>
+
+=head1 COPYRIGHT
+
+Copyright 2020 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
+in the file LICENSE in the source distribution or at
+L<https://www.openssl.org/source/license.html>.
+
+=cut
diff --git a/include/crypto/evp.h b/include/crypto/evp.h
index 1724a12c7c..ddba4083e9 100644
--- a/include/crypto/evp.h
+++ b/include/crypto/evp.h
@@ -504,6 +504,11 @@ struct evp_pkey_st {
     /* == Legacy attributes == */
     int type;
     int save_type;
+
+    /*
+     * Legacy key "origin" is composed of a pointer to an EVP_PKEY_ASN1_METHOD,
+     * a pointer to a low level key and possibly a pointer to an engine.
+     */
     const EVP_PKEY_ASN1_METHOD *ameth;
     ENGINE *engine;
     ENGINE *pmeth_engine; /* If not NULL public key ENGINE to use */
@@ -531,20 +536,41 @@ struct evp_pkey_st {
     int save_parameters;
 
     /* == Provider attributes == */
+
+    /*
+     * Provider keydata "origin" is composed of a pointer to an EVP_KEYMGMT
+     * and a pointer to the provider side key data.  This is never used at
+     * the same time as the legacy key data above.
+     */
+    EVP_KEYMGMT *keymgmt;
+    void *keydata;
+    /*
+     * If any libcrypto code does anything that may modify the keydata
+     * contents, this dirty counter must be incremented.
+     */
+    size_t dirty_cnt;
+
     /*
-     * To support transparent export/import between providers that support
-     * the methods for it, and still not having to do the export/import
-     * every time a key object is changed, we maintain a cache of imported
-     * key objects, indexed by keymgmt address.  pkeys[0] is *always* the
-     * "original" data unless we have a legacy key attached.
+     * To support transparent execution of operation in backends other
+     * than the "origin" key, we support transparent export/import to
+     * those providers, and maintain a cache of the imported keydata,
+     * so we don't need to redo the export/import every time we perform
+     * the same operation in that same provider.
+     * This requires that the "origin" backend (whether it's a legacy or a
+     * provider "origin") implements exports, and that the target provider
+     * has an EVP_KEYMGMT that implements import.
+     *
+     * The cache limit is set at 10 different providers using the same
+     * "origin".  It's probably over the top, but is preferable to too
+     * few.
      */
     struct {
         EVP_KEYMGMT *keymgmt;
         void *keydata;
-    } pkeys[10];
+    } operation_cache[10];
     /*
-     * If there is a legacy key assigned to this structure, we keep
-     * a copy of that key's dirty count.
+     * We keep a copy of that "origin"'s dirty count, so we know if the
+     * operation cache needs flushing.
      */
     size_t dirty_cnt_copy;
 
@@ -574,18 +600,23 @@ void openssl_add_all_ciphers_int(void);
 void openssl_add_all_digests_int(void);
 void evp_cleanup_int(void);
 void evp_app_cleanup_int(void);
-void *evp_pkey_make_provided(EVP_PKEY *pk, OPENSSL_CTX *libctx,
-                             EVP_KEYMGMT **keymgmt, const char *propquery);
+void *evp_pkey_export_to_provider(EVP_PKEY *pk, OPENSSL_CTX *libctx,
+                                  EVP_KEYMGMT **keymgmt,
+                                  const char *propquery);
+void *evp_pkey_upgrade_to_provider(EVP_PKEY *pk, OPENSSL_CTX *libctx,
+                                   EVP_KEYMGMT **keymgmt,
+                                   const char *propquery);
 
 /*
  * KEYMGMT utility functions
  */
 void *evp_keymgmt_util_export_to_provider(EVP_PKEY *pk, EVP_KEYMGMT *keymgmt);
-size_t evp_keymgmt_util_find_pkey_cache_index(EVP_PKEY *pk,
-                                              EVP_KEYMGMT *keymgmt);
-void evp_keymgmt_util_clear_pkey_cache(EVP_PKEY *pk);
-void evp_keymgmt_util_cache_pkey(EVP_PKEY *pk, size_t index,
-                                 EVP_KEYMGMT *keymgmt, void *keydata);
+size_t evp_keymgmt_util_find_operation_cache_index(EVP_PKEY *pk,
+                                                   EVP_KEYMGMT *keymgmt);
+void evp_keymgmt_util_clear_operation_cache(EVP_PKEY *pk);
+int evp_keymgmt_util_cache_keydata(EVP_PKEY *pk, size_t index,
+                                   EVP_KEYMGMT *keymgmt, void *keydata);
+void evp_keymgmt_util_cache_keyinfo(EVP_PKEY *pk);
 void *evp_keymgmt_util_fromdata(EVP_PKEY *target, EVP_KEYMGMT *keymgmt,
                                 int selection, const OSSL_PARAM params[]);
 
diff --git a/test/keymgmt_internal_test.c b/test/keymgmt_internal_test.c
index 77a4afa490..5ef238ccf1 100644
--- a/test/keymgmt_internal_test.c
+++ b/test/keymgmt_internal_test.c
@@ -207,7 +207,8 @@ static int test_pass_rsa(FIXTURE *fixture)
         || !TEST_ptr_ne(km1, km2))
         goto err;
 
-    if (!TEST_ptr(evp_pkey_make_provided(pk, NULL, &km1, NULL))
+    if (!TEST_ptr(evp_pkey_export_to_provider(pk, NULL, &km1, NULL))
+        || !TEST_ptr(evp_pkey_upgrade_to_provider(pk, NULL, &km1, NULL))
         || !TEST_ptr(provkey = evp_keymgmt_util_export_to_provider(pk, km2)))
         goto err;
 


More information about the openssl-commits mailing list