Add freeze functionality to EVP_SKEYMGMT_fetch

Add unit tests to check functionality

Reviewed-by: Neil Horman <nhorman@openssl.org>
Reviewed-by: Norbert Pocs <norbertp@openssl.org>
MergeDate: Fri Apr 17 11:12:10 2026
(Merged from https://github.com/openssl/openssl/pull/30642)
diff --git a/crypto/evp/evp_local.h b/crypto/evp/evp_local.h
index 5653eec..2f002a2 100644
--- a/crypto/evp/evp_local.h
+++ b/crypto/evp/evp_local.h
@@ -231,6 +231,7 @@
 
 struct evp_skeymgmt_st {
     int name_id;
+    int origin;
     char *type_name;
     const char *description;
     OSSL_PROVIDER *prov;
@@ -467,3 +468,4 @@
 int evp_kem_fetch_all(OSSL_LIB_CTX *ctx);
 int evp_asym_cipher_fetch_all(OSSL_LIB_CTX *ctx);
 int evp_keyexch_fetch_all(OSSL_LIB_CTX *ctx);
+int evp_skeymgmt_fetch_all(OSSL_LIB_CTX *ctx);
diff --git a/crypto/evp/skeymgmt_meth.c b/crypto/evp/skeymgmt_meth.c
index a46bbe3..c225151 100644
--- a/crypto/evp/skeymgmt_meth.c
+++ b/crypto/evp/skeymgmt_meth.c
@@ -7,6 +7,7 @@
  * https://www.openssl.org/source/license.html
  */
 
+#include <string.h>
 #include <openssl/crypto.h>
 #include <openssl/core_dispatch.h>
 #include <openssl/evp.h>
@@ -17,6 +18,55 @@
 #include "crypto/evp.h"
 #include "evp_local.h"
 
+static void evp_skeymgmt_free_int(EVP_SKEYMGMT *skeymgmt)
+{
+    OPENSSL_free(skeymgmt->type_name);
+    ossl_provider_free(skeymgmt->prov);
+    CRYPTO_FREE_REF(&skeymgmt->refcnt);
+    OPENSSL_free(skeymgmt);
+}
+
+static void *evp_skeymgmt_dup_frozen(void *vin)
+{
+    EVP_SKEYMGMT *in = vin;
+    EVP_SKEYMGMT *out;
+
+    out = OPENSSL_malloc(sizeof(*out));
+    if (out == NULL)
+        return NULL;
+    memcpy(out, in, sizeof(*out));
+    if (!CRYPTO_NEW_REF(&out->refcnt, 1))
+        goto err;
+    out->type_name = OPENSSL_strdup(in->type_name);
+    if (out->type_name == NULL)
+        goto err;
+    out->origin = EVP_ORIG_FROZEN;
+    if (out->prov == NULL || !ossl_provider_up_ref(out->prov)) {
+        OPENSSL_free(out->type_name);
+        goto err;
+    }
+    return out;
+
+err:
+    CRYPTO_FREE_REF(&out->refcnt);
+    OPENSSL_free(out);
+    return NULL;
+}
+
+static void evp_skeymgmt_frozen_free(void *vin)
+{
+    EVP_SKEYMGMT *skeymgmt = vin;
+    int ref = 0;
+
+    if (skeymgmt == NULL || skeymgmt->origin != EVP_ORIG_FROZEN)
+        return;
+
+    CRYPTO_DOWN_REF(&skeymgmt->refcnt, &ref);
+    if (ref > 0)
+        return;
+    evp_skeymgmt_free_int(skeymgmt);
+}
+
 void *evp_skeymgmt_generate(const EVP_SKEYMGMT *skeymgmt, const OSSL_PARAM params[])
 {
     void *provctx = ossl_provider_ctx(EVP_SKEYMGMT_get0_provider(skeymgmt));
@@ -137,8 +187,8 @@
         skeymgmt_from_algorithm,
         (int (*)(void *))EVP_SKEYMGMT_up_ref,
         (void (*)(void *))EVP_SKEYMGMT_free,
-        NULL,
-        NULL);
+        evp_skeymgmt_dup_frozen,
+        evp_skeymgmt_frozen_free);
 }
 
 EVP_SKEYMGMT *EVP_SKEYMGMT_fetch(OSSL_LIB_CTX *ctx, const char *algorithm,
@@ -147,14 +197,28 @@
     return evp_generic_fetch(ctx, OSSL_OP_SKEYMGMT, algorithm, properties,
         skeymgmt_from_algorithm,
         (int (*)(void *))EVP_SKEYMGMT_up_ref,
-        (void (*)(void *))EVP_SKEYMGMT_free, NULL, NULL);
+        (void (*)(void *))EVP_SKEYMGMT_free,
+        evp_skeymgmt_dup_frozen,
+        evp_skeymgmt_frozen_free);
+}
+
+int evp_skeymgmt_fetch_all(OSSL_LIB_CTX *ctx)
+{
+    return evp_generic_fetch_all(ctx,
+        OSSL_OP_SKEYMGMT,
+        skeymgmt_from_algorithm,
+        (int (*)(void *))EVP_SKEYMGMT_up_ref,
+        (void (*)(void *))EVP_SKEYMGMT_free,
+        evp_skeymgmt_dup_frozen,
+        evp_skeymgmt_frozen_free);
 }
 
 int EVP_SKEYMGMT_up_ref(EVP_SKEYMGMT *skeymgmt)
 {
     int ref = 0;
 
-    CRYPTO_UP_REF(&skeymgmt->refcnt, &ref);
+    if (skeymgmt->origin == EVP_ORIG_DYNAMIC)
+        CRYPTO_UP_REF(&skeymgmt->refcnt, &ref);
     return 1;
 }
 
@@ -162,16 +226,13 @@
 {
     int ref = 0;
 
-    if (skeymgmt == NULL)
+    if (skeymgmt == NULL || skeymgmt->origin != EVP_ORIG_DYNAMIC)
         return;
 
     CRYPTO_DOWN_REF(&skeymgmt->refcnt, &ref);
     if (ref > 0)
         return;
-    OPENSSL_free(skeymgmt->type_name);
-    ossl_provider_free(skeymgmt->prov);
-    CRYPTO_FREE_REF(&skeymgmt->refcnt);
-    OPENSSL_free(skeymgmt);
+    evp_skeymgmt_free_int(skeymgmt);
 }
 
 const OSSL_PROVIDER *EVP_SKEYMGMT_get0_provider(const EVP_SKEYMGMT *skeymgmt)
diff --git a/crypto/property/property.c b/crypto/property/property.c
index f577c73..b086d0c 100644
--- a/crypto/property/property.c
+++ b/crypto/property/property.c
@@ -1178,7 +1178,8 @@
         || evp_kdf_fetch_all(store->ctx) <= 0
         || evp_kem_fetch_all(store->ctx) <= 0
         || evp_asym_cipher_fetch_all(store->ctx) <= 0
-        || evp_keyexch_fetch_all(store->ctx) <= 0)
+        || evp_keyexch_fetch_all(store->ctx) <= 0
+        || evp_skeymgmt_fetch_all(store->ctx) <= 0)
         goto err;
 
     ossl_sa_ALGORITHM_doall_arg(store->algs, &alg_freeze, &af);
diff --git a/test/evp_fetch_prov_test.c b/test/evp_fetch_prov_test.c
index 3225b8d..78116e0 100644
--- a/test/evp_fetch_prov_test.c
+++ b/test/evp_fetch_prov_test.c
@@ -1693,6 +1693,138 @@
     return test_explicit_EVP_KEYEXCH_fetch("DH");
 }
 
+static int test_aes_skey(OSSL_LIB_CTX *libctx, const char *propq)
+{
+    unsigned char raw_key[16] = { 0 };
+    EVP_SKEY *skey = NULL;
+    int ret = 0;
+
+    if (!TEST_ptr(skey = EVP_SKEY_import_raw_key(libctx, "AES", raw_key,
+                      sizeof(raw_key), propq)))
+        goto err;
+
+    ret = 1;
+err:
+    EVP_SKEY_free(skey);
+    return ret;
+}
+
+static int test_skeymgmt(OSSL_LIB_CTX *libctx, const char *propq,
+    EVP_SKEYMGMT *skeymgmt, const char *name)
+{
+    return TEST_ptr(skeymgmt)
+        && TEST_ptr(EVP_SKEYMGMT_get0_provider(skeymgmt))
+        && TEST_true(EVP_SKEYMGMT_is_a(skeymgmt, name))
+        && TEST_true(test_aes_skey(libctx, propq));
+}
+
+static int test_EVP_SKEYMGMT_fetch_freeze(void)
+{
+#if defined(OPENSSL_NO_CACHED_FETCH)
+    /*
+     * Test does not make sense if cached fetch is disabled.
+     * There's nothing to freeze, and test will fail.
+     */
+    return 1;
+#endif
+
+    EVP_SKEYMGMT *skeymgmt = NULL;
+    int ret = 0;
+    OSSL_LIB_CTX *ctx = NULL;
+    OSSL_PROVIDER *prov[2] = { NULL, NULL };
+
+    if (use_default_ctx == 0 && !load_providers(&ctx, prov))
+        goto err;
+
+    if (!TEST_ptr(skeymgmt = EVP_SKEYMGMT_fetch(ctx, "AES", NULL))
+        || !TEST_true(test_skeymgmt(ctx, NULL, skeymgmt, "AES"))
+        || !TEST_int_ne(skeymgmt->origin, EVP_ORIG_FROZEN))
+        goto err;
+    EVP_SKEYMGMT_free(skeymgmt);
+    skeymgmt = NULL;
+
+    if (!TEST_int_eq(OSSL_LIB_CTX_freeze(ctx, "?fips=true"), 1)
+        || !TEST_ptr(skeymgmt = EVP_SKEYMGMT_fetch(ctx, "AES", NULL))
+        || !TEST_true(test_skeymgmt(ctx, NULL, skeymgmt, "AES"))
+        || !TEST_int_eq(skeymgmt->origin, EVP_ORIG_FROZEN))
+        goto err;
+    /* Technically, frozen version doesn't need to be freed */
+    EVP_SKEYMGMT_free(skeymgmt);
+
+    if (!TEST_ptr(skeymgmt = EVP_SKEYMGMT_fetch(ctx, "AES", "?fips=true"))
+        || !TEST_true(test_skeymgmt(ctx, "?fips=true", skeymgmt, "AES"))
+        || !TEST_int_eq(skeymgmt->origin, EVP_ORIG_FROZEN))
+        goto err;
+    EVP_SKEYMGMT_free(skeymgmt);
+
+    /*
+     * A mismatched propq should use the regular fetch path rather than the
+     * frozen fast path.
+     */
+    if (!TEST_ptr(skeymgmt = EVP_SKEYMGMT_fetch(ctx, "AES", "?provider=default"))
+        || !TEST_true(test_skeymgmt(ctx, "?provider=default", skeymgmt, "AES"))
+        || !TEST_int_ne(skeymgmt->origin, EVP_ORIG_FROZEN))
+        goto err;
+
+    ret = 1;
+err:
+    EVP_SKEYMGMT_free(skeymgmt);
+    unload_providers(&ctx, prov);
+    return ret;
+}
+
+static int test_implicit_EVP_SKEYMGMT_fetch(void)
+{
+    OSSL_LIB_CTX *ctx = NULL;
+    OSSL_PROVIDER *prov[] = { NULL, NULL };
+    EVP_SKEYMGMT *skeymgmt = NULL;
+    int ret = 0;
+
+    if (use_default_ctx == 0 && !TEST_true(load_providers(&ctx, prov)))
+        goto err;
+
+    if (!TEST_ptr(skeymgmt = EVP_SKEYMGMT_fetch(ctx, "AES", NULL)))
+        goto err;
+
+    ret = 1;
+err:
+    EVP_SKEYMGMT_free(skeymgmt);
+    unload_providers(&ctx, prov);
+    return ret;
+}
+
+static int test_explicit_EVP_SKEYMGMT_fetch(const char *id)
+{
+    OSSL_LIB_CTX *ctx = NULL;
+    EVP_SKEYMGMT *skeymgmt = NULL;
+    OSSL_PROVIDER *prov[] = { NULL, NULL };
+    int ret = 0;
+
+    if (use_default_ctx == 0 && !TEST_true(load_providers(&ctx, prov)))
+        goto err;
+
+    skeymgmt = EVP_SKEYMGMT_fetch(ctx, id, fetch_property);
+    if (expected_fetch_result != 0) {
+        if (!TEST_true(EVP_SKEYMGMT_up_ref(skeymgmt)))
+            goto err;
+        /* Ref count should now be 2. Release first one here */
+        EVP_SKEYMGMT_free(skeymgmt);
+    } else {
+        if (!TEST_ptr_null(skeymgmt))
+            goto err;
+    }
+    ret = 1;
+err:
+    EVP_SKEYMGMT_free(skeymgmt);
+    unload_providers(&ctx, prov);
+    return ret;
+}
+
+static int test_explicit_EVP_SKEYMGMT_fetch_by_name(void)
+{
+    return test_explicit_EVP_SKEYMGMT_fetch("AES");
+}
+
 int setup_tests(void)
 {
     OPTION_CHOICE o;
@@ -1761,6 +1893,10 @@
         ADD_TEST(test_EVP_KEYEXCH_fetch_freeze);
         ADD_TEST(test_implicit_EVP_KEYEXCH_fetch);
         ADD_TEST(test_explicit_EVP_KEYEXCH_fetch_by_name);
+    } else if (strcmp(alg, "skeymgmt") == 0) {
+        ADD_TEST(test_EVP_SKEYMGMT_fetch_freeze);
+        ADD_TEST(test_implicit_EVP_SKEYMGMT_fetch);
+        ADD_TEST(test_explicit_EVP_SKEYMGMT_fetch_by_name);
     } else {
         TEST_error("Unknown fetch type: %s", alg);
         return 0;
diff --git a/test/recipes/30-test_evp_fetch_prov.t b/test/recipes/30-test_evp_fetch_prov.t
index 388be86..85c7b79 100644
--- a/test/recipes/30-test_evp_fetch_prov.t
+++ b/test/recipes/30-test_evp_fetch_prov.t
@@ -21,7 +21,7 @@
 
 my $no_fips = disabled('fips') || ($ENV{NO_FIPS} // 0);
 
-my @types = ( "digest", "cipher", "rand", "mac", "kmgmt", "kem", "kdf", "asymcipher", "evp_keyexch" );
+my @types = ( "digest", "cipher", "rand", "mac", "kmgmt", "kem", "kdf", "asymcipher", "evp_keyexch", "skeymgmt" );
 
 my @testdata = (
     { config    => srctop_file("test", "default.cnf"),