Commit 796fbbfe40 for openssl.org

commit 796fbbfe40bbc3ac8cc330f47443c96e33a9993c
Author: Viktor Dukhovni <openssl-users@dukhovni.org>
Date:   Wed May 20 22:59:19 2026 +1000

    Fix EVP_PKEY_dup() for ML-KEM keys

    ossl_ml_kem_key_dup() left the (PUB|PRIV) selection case
    unhandled, so EVP_PKEY_dup() silently returned NULL for
    ML-KEM-512/768/1024.  add_storage() also zeroed the duplicated
    rho_pkhash, leaving the dup unequal to the original.

    Add a parameterised dup sweep to test/endecode_test.c covering
    every supported public-key algorithm in three shapes: full
    keypair, public-only, and embryonic (parameters-only).

    While here, stop endecode_test from silently passing when key
    generation fails: setup_tests() now returns its accumulated
    status, MAKE_*KEYS no longer short-circuits, and each
    ADD_TEST_SUITE is now conditional on keygen success.  Guard the
    explicit-EC-curve tests with OPENSSL_NO_EC_EXPLICIT_CURVES.

    Reviewed-by: Neil Horman <nhorman@openssl.org>
    Reviewed-by: Bob Beck <beck@openssl.org>
    MergeDate: Thu Jun 18 08:02:28 2026
    (Merged from https://github.com/openssl/openssl/pull/31252)

diff --git a/crypto/ml_kem/ml_kem.c b/crypto/ml_kem/ml_kem.c
index 8fa5db9cc8..c8e6188f67 100644
--- a/crypto/ml_kem/ml_kem.c
+++ b/crypto/ml_kem/ml_kem.c
@@ -1537,7 +1537,8 @@ end:
  * The caller should only store private data in `priv` *after* a successful
  * (non-zero) return from this function.
  */
-static __owur int add_storage(scalar *pub, scalar *priv, int private, ML_KEM_KEY *key)
+static __owur int add_storage(scalar *pub, scalar *priv,
+    int private, int dup, ML_KEM_KEY *key)
 {
     int rank = key->vinfo->rank;

@@ -1552,9 +1553,11 @@ static __owur int add_storage(scalar *pub, scalar *priv, int private, ML_KEM_KEY
     }

     /*
-     * We're adding key material, set up rho and pkhash to point to the rho_pkhash buffer
+     * We're adding key material, set up rho and pkhash to point to the
+     * rho_pkhash buffer.  Zero the key hash when creating fresh keys.
      */
-    memset(key->rho_pkhash, 0, sizeof(key->rho_pkhash));
+    if (dup == 0)
+        memset(key->rho_pkhash, 0, sizeof(key->rho_pkhash));
     key->rho = key->rho_pkhash;
     key->pkhash = key->rho_pkhash + ML_KEM_RANDOM_BYTES;
     key->d = key->z = NULL;
@@ -1683,8 +1686,6 @@ ML_KEM_KEY *ossl_ml_kem_key_dup(const ML_KEM_KEY *key, int selection)
 {
     int ok = 0;
     ML_KEM_KEY *ret;
-    void *tmp_pub;
-    void *tmp_priv;

     if (key == NULL)
         return NULL;
@@ -1706,28 +1707,27 @@ ML_KEM_KEY *ossl_ml_kem_key_dup(const ML_KEM_KEY *key, int selection)
         selection = 0;
     else if (!ossl_ml_kem_have_prvkey(key))
         selection &= ~OSSL_KEYMGMT_SELECT_PRIVATE_KEY;
+    else if ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0)
+        selection &= ~OSSL_KEYMGMT_SELECT_PUBLIC_KEY;

     switch (selection & OSSL_KEYMGMT_SELECT_KEYPAIR) {
     case 0:
         ok = 1;
         break;
     case OSSL_KEYMGMT_SELECT_PUBLIC_KEY:
-        ok = add_storage(OPENSSL_memdup(key->t, key->vinfo->puballoc), NULL, 0, ret);
+        ok = add_storage(OPENSSL_memdup(key->t, key->vinfo->puballoc), NULL, 0, 1, ret);
         break;
     case OSSL_KEYMGMT_SELECT_PRIVATE_KEY:
-        tmp_pub = OPENSSL_memdup(key->t, key->vinfo->puballoc);
-        if (tmp_pub == NULL)
-            break;
-        tmp_priv = OPENSSL_secure_malloc(key->vinfo->prvalloc);
-        if (tmp_priv == NULL) {
-            OPENSSL_free(tmp_pub);
-            break;
+        /* Frees both and returns 0 if either is NULL */
+        ok = add_storage(OPENSSL_memdup(key->t, key->vinfo->puballoc),
+            OPENSSL_secure_malloc(key->vinfo->prvalloc), 1, 1, ret);
+        if (ok) {
+            memcpy(ret->s, key->s, key->vinfo->prvalloc);
+
+            /* Duplicated keys retain |d|, if available */
+            if (key->d != NULL)
+                ret->d = ret->z + ML_KEM_RANDOM_BYTES;
         }
-        if ((ok = add_storage(tmp_pub, tmp_priv, 1, ret)) != 0)
-            memcpy(tmp_priv, key->s, key->vinfo->prvalloc);
-        /* Duplicated keys retain |d|, if available */
-        if (key->d != NULL)
-            ret->d = ret->z + ML_KEM_RANDOM_BYTES;
         break;
     }

@@ -1840,7 +1840,7 @@ int ossl_ml_kem_parse_public_key(const uint8_t *in, size_t len, ML_KEM_KEY *key)
         || (mdctx = EVP_MD_CTX_new()) == NULL)
         return 0;

-    if (add_storage(OPENSSL_malloc(vinfo->puballoc), NULL, 0, key))
+    if (add_storage(OPENSSL_malloc(vinfo->puballoc), NULL, 0, 0, key))
         ret = parse_pubkey(in, mdctx, key);

     if (!ret)
@@ -1869,7 +1869,7 @@ int ossl_ml_kem_parse_private_key(const uint8_t *in, size_t len,
         return 0;

     if (add_storage(OPENSSL_malloc(vinfo->puballoc),
-            OPENSSL_secure_malloc(vinfo->prvalloc), 1, key))
+            OPENSSL_secure_malloc(vinfo->prvalloc), 1, 0, key))
         ret = parse_prvkey(in, mdctx, key);

     if (!ret)
@@ -1918,7 +1918,7 @@ int ossl_ml_kem_genkey(uint8_t *pubenc, size_t publen, ML_KEM_KEY *key)
     CONSTTIME_SECRET(seed, ML_KEM_SEED_BYTES);

     if (add_storage(OPENSSL_malloc(vinfo->puballoc),
-            OPENSSL_secure_malloc(vinfo->prvalloc), 1, key))
+            OPENSSL_secure_malloc(vinfo->prvalloc), 1, 0, key))
         ret = genkey(seed, mdctx, pubenc, key);
     OPENSSL_cleanse(seed, sizeof(seed));

diff --git a/test/endecode_test.c b/test/endecode_test.c
index 823ac06194..8afbee59ba 100644
--- a/test/endecode_test.c
+++ b/test/endecode_test.c
@@ -60,6 +60,7 @@ static OSSL_PROVIDER *keyprov = NULL;

 #ifndef OPENSSL_NO_EC
 static BN_CTX *bnctx = NULL;
+#ifndef OPENSSL_NO_EC_EXPLICIT_CURVES
 static OSSL_PARAM_BLD *bld_prime_nc = NULL;
 static OSSL_PARAM_BLD *bld_prime = NULL;
 static OSSL_PARAM *ec_explicit_prime_params_nc = NULL;
@@ -72,6 +73,7 @@ static OSSL_PARAM *ec_explicit_tri_params_nc = NULL;
 static OSSL_PARAM *ec_explicit_tri_params_explicit = NULL;
 #endif
 #endif
+#endif

 #ifndef OPENSSL_NO_KEYPARAMS
 static EVP_PKEY *make_template(const char *type, OSSL_PARAM *genparams)
@@ -912,21 +914,161 @@ static int test_public_via_MSBLOB(const char *type, EVP_PKEY *key)
         test_mem, check_public_MSBLOB, dump_der, 0);
 }

+/*
+ * Build a public-only EVP_PKEY of the same algorithm as |src| by
+ * round-tripping the public component through OSSL_PARAMs.
+ */
+static EVP_PKEY *make_public_only_copy(EVP_PKEY *src)
+{
+    OSSL_PARAM *params = NULL;
+    EVP_PKEY_CTX *cctx = NULL;
+    EVP_PKEY *pub = NULL;
+
+    if (!EVP_PKEY_todata(src, EVP_PKEY_PUBLIC_KEY, &params))
+        goto end;
+    if ((cctx = EVP_PKEY_CTX_new_from_pkey(NULL, src, NULL)) == NULL
+        || EVP_PKEY_fromdata_init(cctx) <= 0
+        || EVP_PKEY_fromdata(cctx, &pub, EVP_PKEY_PUBLIC_KEY, params) <= 0) {
+        EVP_PKEY_free(pub);
+        pub = NULL;
+    }
+end:
+    OSSL_PARAM_free(params);
+    EVP_PKEY_CTX_free(cctx);
+    return pub;
+}
+
+/*
+ * Build an "embryonic" EVP_PKEY of the same algorithm as |src|: just
+ * the keymgmt-bound type and (where applicable) domain parameters
+ * copied across, with no key material.  Mirrors the idiom used in
+ * test/ml_kem_evp_extra_test.c.
+ */
+static EVP_PKEY *make_embryonic_copy(EVP_PKEY *src)
+{
+    EVP_PKEY *embryo = EVP_PKEY_new();
+
+    if (embryo == NULL)
+        return NULL;
+    if (EVP_PKEY_copy_parameters(embryo, src) <= 0) {
+        EVP_PKEY_free(embryo);
+        return NULL;
+    }
+    return embryo;
+}
+
+/*
+ * Check that EVP_PKEY_dup() works for every supported provider-backed
+ * key type, and that the duplicate compares equal to the original.
+ *
+ * Exercised in three shapes:
+ *   1. The full keypair |key| (typically pub + priv).
+ *   2. A public-only key derived from |key|.
+ *   3. An "embryonic" key (algorithm + domain parameters only, no key
+ *      material) produced with EVP_PKEY_copy_parameters().
+ */
+static int test_dup(const char *type, EVP_PKEY *key)
+{
+    EVP_PKEY *dup = NULL;
+    EVP_PKEY *pub_only = NULL;
+    EVP_PKEY *embryo = NULL;
+    int ok = 0;
+
+    if (!TEST_ptr(key)) {
+        TEST_info("%s: no source key", type);
+        return 0;
+    }
+
+    /* 1. Dup the full keypair. */
+    if (!TEST_ptr(dup = EVP_PKEY_dup(key))) {
+        TEST_info("%s: EVP_PKEY_dup of keypair returned NULL", type);
+        goto end;
+    }
+    if (!TEST_int_eq(EVP_PKEY_eq(key, dup), 1)) {
+        TEST_info("%s: keypair dup does not compare equal to original", type);
+        goto end;
+    }
+    EVP_PKEY_free(dup);
+    dup = NULL;
+
+    /* 2. Dup a public-only copy of the same key. */
+    if (!TEST_ptr(pub_only = make_public_only_copy(key))) {
+        TEST_info("%s: could not derive a public-only key", type);
+        goto end;
+    }
+    if (!TEST_ptr(dup = EVP_PKEY_dup(pub_only))) {
+        TEST_info("%s: EVP_PKEY_dup of public-only key returned NULL", type);
+        goto end;
+    }
+    if (!TEST_int_eq(EVP_PKEY_eq(pub_only, dup), 1)) {
+        TEST_info("%s: public-only dup does not compare equal to original",
+            type);
+        goto end;
+    }
+    EVP_PKEY_free(dup);
+    dup = NULL;
+
+    /*
+     * 3. Dup an embryonic key (just algorithm + domain parameters, no
+     * key bits).  Not every keymgmt allows building such a key: RSA's
+     * dup op, for instance, deliberately refuses any selection without
+     * keypair bits ("do not allow creating empty keys by duplication"),
+     * which makes EVP_PKEY_copy_parameters() fail for RSA / RSA-PSS.
+     * Treat that as a graceful skip rather than a hard failure.
+     *
+     * Where an embryo can be built, the right comparator is
+     * EVP_PKEY_parameters_eq(): EVP_PKEY_eq() returns 0 for material-
+     * less keys because it requires public bits to match.  Algorithms
+     * without domain parameters may legitimately answer -2 ("nothing
+     * to compare").  We only reject 0 (definitively unequal) and -1
+     * (different keymgmts).
+     */
+    embryo = make_embryonic_copy(key);
+    if (embryo != NULL) {
+        if (!TEST_ptr(dup = EVP_PKEY_dup(embryo))) {
+            TEST_info("%s: EVP_PKEY_dup of embryonic key returned NULL",
+                type);
+            goto end;
+        }
+        {
+            int eq = EVP_PKEY_parameters_eq(embryo, dup);
+
+            if (!TEST_true(eq == 1 || eq == -2)) {
+                TEST_info("%s: embryonic dup parameters_eq %d (want 1 or -2)",
+                    type, eq);
+                goto end;
+            }
+        }
+    } else {
+        TEST_info("%s: skipping embryonic dup (no params-only key shape)",
+            type);
+    }
+
+    ok = 1;
+end:
+    EVP_PKEY_free(dup);
+    EVP_PKEY_free(pub_only);
+    EVP_PKEY_free(embryo);
+    return ok;
+}
+
 #define KEYS(KEYTYPE) \
     static EVP_PKEY *key_##KEYTYPE = NULL
 #define MAKE_KEYS(KEYTYPE, KEYTYPEstr, params) \
-    ok = ok                                    \
-        && TEST_ptr(key_##KEYTYPE = make_key(KEYTYPEstr, NULL, params))
+    ok &= TEST_ptr(key_##KEYTYPE = make_key(KEYTYPEstr, NULL, params))
 #define FREE_KEYS(KEYTYPE) \
     EVP_PKEY_free(key_##KEYTYPE);

 #define DOMAIN_KEYS(KEYTYPE)                    \
     static EVP_PKEY *template_##KEYTYPE = NULL; \
     static EVP_PKEY *key_##KEYTYPE = NULL
-#define MAKE_DOMAIN_KEYS(KEYTYPE, KEYTYPEstr, params)                       \
-    ok = ok                                                                 \
-        && TEST_ptr(template_##KEYTYPE = make_template(KEYTYPEstr, params)) \
-        && TEST_ptr(key_##KEYTYPE = make_key(KEYTYPEstr, template_##KEYTYPE, NULL))
+#define MAKE_DOMAIN_KEYS(KEYTYPE, KEYTYPEstr, params)                 \
+    do {                                                              \
+        ok &= TEST_ptr(template_##KEYTYPE = make_template(KEYTYPEstr, \
+                           params));                                  \
+        ok &= TEST_ptr(key_##KEYTYPE = make_key(KEYTYPEstr,           \
+                           template_##KEYTYPE, NULL));                \
+    } while (0)
 #define FREE_DOMAIN_KEYS(KEYTYPE)      \
     EVP_PKEY_free(template_##KEYTYPE); \
     EVP_PKEY_free(key_##KEYTYPE)
@@ -959,16 +1101,25 @@ static int test_public_via_MSBLOB(const char *type, EVP_PKEY *key)
     static int test_public_##KEYTYPE##_via_PEM(void)                      \
     {                                                                     \
         return test_public_via_PEM(KEYTYPEstr, key_##KEYTYPE, fips);      \
+    }                                                                     \
+    static int test_dup_##KEYTYPE(void)                                   \
+    {                                                                     \
+        return test_dup(KEYTYPEstr, key_##KEYTYPE);                       \
     }

-#define ADD_TEST_SUITE(KEYTYPE)                     \
-    ADD_TEST(test_unprotected_##KEYTYPE##_via_DER); \
-    ADD_TEST(test_unprotected_##KEYTYPE##_via_i2d); \
-    ADD_TEST(test_unprotected_##KEYTYPE##_via_PEM); \
-    ADD_TEST(test_protected_##KEYTYPE##_via_DER);   \
-    ADD_TEST(test_protected_##KEYTYPE##_via_PEM);   \
-    ADD_TEST(test_public_##KEYTYPE##_via_DER);      \
-    ADD_TEST(test_public_##KEYTYPE##_via_PEM)
+#define ADD_TEST_SUITE(KEYTYPE)                             \
+    do {                                                    \
+        if (key_##KEYTYPE != NULL) {                        \
+            ADD_TEST(test_unprotected_##KEYTYPE##_via_DER); \
+            ADD_TEST(test_unprotected_##KEYTYPE##_via_i2d); \
+            ADD_TEST(test_unprotected_##KEYTYPE##_via_PEM); \
+            ADD_TEST(test_protected_##KEYTYPE##_via_DER);   \
+            ADD_TEST(test_protected_##KEYTYPE##_via_PEM);   \
+            ADD_TEST(test_public_##KEYTYPE##_via_DER);      \
+            ADD_TEST(test_public_##KEYTYPE##_via_PEM);      \
+            ADD_TEST(test_dup_##KEYTYPE);                   \
+        }                                                   \
+    } while (0)

 #define IMPLEMENT_TEST_SUITE_PARAMS(KEYTYPE, KEYTYPEstr)       \
     static int test_params_##KEYTYPE##_via_DER(void)           \
@@ -980,9 +1131,13 @@ static int test_public_via_MSBLOB(const char *type, EVP_PKEY *key)
         return test_params_via_PEM(KEYTYPEstr, key_##KEYTYPE); \
     }

-#define ADD_TEST_SUITE_PARAMS(KEYTYPE)         \
-    ADD_TEST(test_params_##KEYTYPE##_via_DER); \
-    ADD_TEST(test_params_##KEYTYPE##_via_PEM)
+#define ADD_TEST_SUITE_PARAMS(KEYTYPE)                 \
+    do {                                               \
+        if (key_##KEYTYPE != NULL) {                   \
+            ADD_TEST(test_params_##KEYTYPE##_via_DER); \
+            ADD_TEST(test_params_##KEYTYPE##_via_PEM); \
+        }                                              \
+    } while (0)

 #define IMPLEMENT_TEST_SUITE_LEGACY(KEYTYPE, KEYTYPEstr)                   \
     static int test_unprotected_##KEYTYPE##_via_legacy_PEM(void)           \
@@ -994,9 +1149,13 @@ static int test_public_via_MSBLOB(const char *type, EVP_PKEY *key)
         return test_protected_via_legacy_PEM(KEYTYPEstr, key_##KEYTYPE);   \
     }

-#define ADD_TEST_SUITE_LEGACY(KEYTYPE)                     \
-    ADD_TEST(test_unprotected_##KEYTYPE##_via_legacy_PEM); \
-    ADD_TEST(test_protected_##KEYTYPE##_via_legacy_PEM)
+#define ADD_TEST_SUITE_LEGACY(KEYTYPE)                             \
+    do {                                                           \
+        if (key_##KEYTYPE != NULL) {                               \
+            ADD_TEST(test_unprotected_##KEYTYPE##_via_legacy_PEM); \
+            ADD_TEST(test_protected_##KEYTYPE##_via_legacy_PEM);   \
+        }                                                          \
+    } while (0)

 #define IMPLEMENT_TEST_SUITE_MSBLOB(KEYTYPE, KEYTYPEstr)               \
     static int test_unprotected_##KEYTYPE##_via_MSBLOB(void)           \
@@ -1008,25 +1167,35 @@ static int test_public_via_MSBLOB(const char *type, EVP_PKEY *key)
         return test_public_via_MSBLOB(KEYTYPEstr, key_##KEYTYPE);      \
     }

-#define ADD_TEST_SUITE_MSBLOB(KEYTYPE)                 \
-    ADD_TEST(test_unprotected_##KEYTYPE##_via_MSBLOB); \
-    ADD_TEST(test_public_##KEYTYPE##_via_MSBLOB)
+#define ADD_TEST_SUITE_MSBLOB(KEYTYPE)                         \
+    do {                                                       \
+        if (key_##KEYTYPE != NULL) {                           \
+            ADD_TEST(test_unprotected_##KEYTYPE##_via_MSBLOB); \
+            ADD_TEST(test_public_##KEYTYPE##_via_MSBLOB);      \
+        }                                                      \
+    } while (0)

 #define IMPLEMENT_TEST_SUITE_UNPROTECTED_PVK(KEYTYPE, KEYTYPEstr)   \
     static int test_unprotected_##KEYTYPE##_via_PVK(void)           \
     {                                                               \
         return test_unprotected_via_PVK(KEYTYPEstr, key_##KEYTYPE); \
     }
-#define ADD_TEST_SUITE_UNPROTECTED_PVK(KEYTYPE) \
-    ADD_TEST(test_unprotected_##KEYTYPE##_via_PVK)
+#define ADD_TEST_SUITE_UNPROTECTED_PVK(KEYTYPE)             \
+    do {                                                    \
+        if (key_##KEYTYPE != NULL)                          \
+            ADD_TEST(test_unprotected_##KEYTYPE##_via_PVK); \
+    } while (0)
 #if !defined(OPENSSL_NO_RC4) && !defined(OPENSSL_NO_PVKKDF)
 #define IMPLEMENT_TEST_SUITE_PROTECTED_PVK(KEYTYPE, KEYTYPEstr)   \
     static int test_protected_##KEYTYPE##_via_PVK(void)           \
     {                                                             \
         return test_protected_via_PVK(KEYTYPEstr, key_##KEYTYPE); \
     }
-#define ADD_TEST_SUITE_PROTECTED_PVK(KEYTYPE) \
-    ADD_TEST(test_protected_##KEYTYPE##_via_PVK)
+#define ADD_TEST_SUITE_PROTECTED_PVK(KEYTYPE)             \
+    do {                                                  \
+        if (key_##KEYTYPE != NULL)                        \
+            ADD_TEST(test_protected_##KEYTYPE##_via_PVK); \
+    } while (0)
 #endif

 #ifndef OPENSSL_NO_DH
@@ -1057,6 +1226,7 @@ DOMAIN_KEYS(EC);
 IMPLEMENT_TEST_SUITE(EC, "EC", 1)
 IMPLEMENT_TEST_SUITE_PARAMS(EC, "EC")
 IMPLEMENT_TEST_SUITE_LEGACY(EC, "EC")
+#ifndef OPENSSL_NO_EC_EXPLICIT_CURVES
 DOMAIN_KEYS(ECExplicitPrimeNamedCurve);
 IMPLEMENT_TEST_SUITE(ECExplicitPrimeNamedCurve, "EC", 1)
 IMPLEMENT_TEST_SUITE_LEGACY(ECExplicitPrimeNamedCurve, "EC")
@@ -1071,6 +1241,7 @@ DOMAIN_KEYS(ECExplicitTri2G);
 IMPLEMENT_TEST_SUITE(ECExplicitTri2G, "EC", 0)
 IMPLEMENT_TEST_SUITE_LEGACY(ECExplicitTri2G, "EC")
 #endif
+#endif /* OPENSSL_NO_EC_EXPLICIT_CURVES */
 #ifndef OPENSSL_NO_SM2
 KEYS(SM2);
 IMPLEMENT_TEST_SUITE(SM2, "SM2", 0)
@@ -1153,6 +1324,7 @@ IMPLEMENT_TEST_SUITE(ML_DSA_87, "ML-DSA-87", 1)
 #endif /*  OPENSSL_NO_ML_DSA */

 #ifndef OPENSSL_NO_EC
+#ifndef OPENSSL_NO_EC_EXPLICIT_CURVES
 /* Explicit parameters that match a named curve */
 static int do_create_ec_explicit_prime_params(OSSL_PARAM_BLD *bld,
     const unsigned char *gen,
@@ -1333,6 +1505,7 @@ static int create_ec_explicit_trinomial_params(OSSL_PARAM_BLD *bld)
     return do_create_ec_explicit_trinomial_params(bld, gen2, sizeof(gen2));
 }
 #endif /* OPENSSL_NO_EC2M */
+#endif /* OPENSSL_NO_EC_EXPLICIT_CURVES */

 /*
  * Test that multiple calls to OSSL_ENCODER_to_data() do not cause side effects
@@ -1472,8 +1645,10 @@ int setup_tests(void)
         return 0;

 #ifndef OPENSSL_NO_EC
-    if (!TEST_ptr(bnctx = BN_CTX_new_ex(testctx))
-        || !TEST_ptr(bld_prime_nc = OSSL_PARAM_BLD_new())
+    if (!TEST_ptr(bnctx = BN_CTX_new_ex(testctx)))
+        return 0;
+#ifndef OPENSSL_NO_EC_EXPLICIT_CURVES
+    if (!TEST_ptr(bld_prime_nc = OSSL_PARAM_BLD_new())
         || !TEST_ptr(bld_prime = OSSL_PARAM_BLD_new())
         || !create_ec_explicit_prime_params_namedcurve(bld_prime_nc)
         || !create_ec_explicit_prime_params(bld_prime)
@@ -1489,6 +1664,7 @@ int setup_tests(void)
 #endif
     )
         return 0;
+#endif /* OPENSSL_NO_EC_EXPLICIT_CURVES */
 #endif

     TEST_info("Generating keys...");
@@ -1505,12 +1681,14 @@ int setup_tests(void)
 #ifndef OPENSSL_NO_EC
     TEST_info("Generating EC keys...");
     MAKE_DOMAIN_KEYS(EC, "EC", EC_params);
+#ifndef OPENSSL_NO_EC_EXPLICIT_CURVES
     MAKE_DOMAIN_KEYS(ECExplicitPrimeNamedCurve, "EC", ec_explicit_prime_params_nc);
     MAKE_DOMAIN_KEYS(ECExplicitPrime2G, "EC", ec_explicit_prime_params_explicit);
 #ifndef OPENSSL_NO_EC2M
     MAKE_DOMAIN_KEYS(ECExplicitTriNamedCurve, "EC", ec_explicit_tri_params_nc);
     MAKE_DOMAIN_KEYS(ECExplicitTri2G, "EC", ec_explicit_tri_params_explicit);
 #endif
+#endif /* OPENSSL_NO_EC_EXPLICIT_CURVES */
 #ifndef OPENSSL_NO_SM2
     MAKE_KEYS(SM2, "SM2", NULL);
 #endif
@@ -1553,12 +1731,17 @@ int setup_tests(void)
 #endif /* OPENSSL_NO_SLH_DSA */

     TEST_info("Loading RSA key...");
-    ok = ok && TEST_ptr(key_RSA = load_pkey_pem(rsa_file, keyctx));
+    ok &= TEST_ptr(key_RSA = load_pkey_pem(rsa_file, keyctx));
     TEST_info("Loading RSA_PSS key...");
-    ok = ok && TEST_ptr(key_RSA_PSS = load_pkey_pem(rsa_pss_file, keyctx));
+    ok &= TEST_ptr(key_RSA_PSS = load_pkey_pem(rsa_pss_file, keyctx));
     TEST_info("Generating keys done");

-    if (ok) {
+    /*
+     * Register every test whose key was successfully generated.  The
+     * per-algorithm key_##KEYTYPE != NULL guard inside each
+     * ADD_TEST_SUITE* macro keeps us from referencing missing keys.
+     */
+    {
 #ifndef OPENSSL_NO_DH
         ADD_TEST_SUITE(DH);
         ADD_TEST_SUITE_PARAMS(DH);
@@ -1584,6 +1767,7 @@ int setup_tests(void)
         ADD_TEST_SUITE(EC);
         ADD_TEST_SUITE_PARAMS(EC);
         ADD_TEST_SUITE_LEGACY(EC);
+#ifndef OPENSSL_NO_EC_EXPLICIT_CURVES
         ADD_TEST_SUITE(ECExplicitPrimeNamedCurve);
         ADD_TEST_SUITE_LEGACY(ECExplicitPrimeNamedCurve);
         ADD_TEST_SUITE(ECExplicitPrime2G);
@@ -1594,6 +1778,7 @@ int setup_tests(void)
         ADD_TEST_SUITE(ECExplicitTri2G);
         ADD_TEST_SUITE_LEGACY(ECExplicitTri2G);
 #endif
+#endif /* OPENSSL_NO_EC_EXPLICIT_CURVES */
 #ifndef OPENSSL_NO_SM2
         if (!is_fips_3_0_0) {
             /* 3.0.0 FIPS provider imports explicit EC params and then fails. */
@@ -1657,12 +1842,13 @@ int setup_tests(void)
 #endif /* OPENSSL_NO_SLH_DSA */
     }

-    return 1;
+    return ok;
 }

 void cleanup_tests(void)
 {
 #ifndef OPENSSL_NO_EC
+#ifndef OPENSSL_NO_EC_EXPLICIT_CURVES
     OSSL_PARAM_free(ec_explicit_prime_params_nc);
     OSSL_PARAM_free(ec_explicit_prime_params_explicit);
     OSSL_PARAM_BLD_free(bld_prime_nc);
@@ -1673,6 +1859,7 @@ void cleanup_tests(void)
     OSSL_PARAM_BLD_free(bld_tri_nc);
     OSSL_PARAM_BLD_free(bld_tri);
 #endif
+#endif /* OPENSSL_NO_EC_EXPLICIT_CURVES */
     BN_CTX_free(bnctx);
 #endif /* OPENSSL_NO_EC */

@@ -1685,12 +1872,14 @@ void cleanup_tests(void)
 #endif
 #ifndef OPENSSL_NO_EC
     FREE_DOMAIN_KEYS(EC);
+#ifndef OPENSSL_NO_EC_EXPLICIT_CURVES
     FREE_DOMAIN_KEYS(ECExplicitPrimeNamedCurve);
     FREE_DOMAIN_KEYS(ECExplicitPrime2G);
 #ifndef OPENSSL_NO_EC2M
     FREE_DOMAIN_KEYS(ECExplicitTriNamedCurve);
     FREE_DOMAIN_KEYS(ECExplicitTri2G);
 #endif
+#endif /* OPENSSL_NO_EC_EXPLICIT_CURVES */
 #ifndef OPENSSL_NO_SM2
     FREE_KEYS(SM2);
 #endif