Commit a8036eb1e8 for openssl.org

commit a8036eb1e8a9252444130ac23092b9bbd1809305
Author: Simo Sorce <simo@redhat.com>
Date:   Wed Dec 17 14:06:57 2025 -0500

    Refactor FIPS self-test dependencies and states

    Introduce `SELF_TEST_STATE_IMPLICIT` to handle recursive self-test calls
    when an algorithm is used by another algorithm's self-test (e.g., KDF
    using HMAC). This prevents unnecessarily running tests when they are
    effectively covered by a parent test.

    Refactor `SELF_TEST_kats` and `SELF_TEST_kats_execute` to unify
    execution logic, dependency resolution, and RNG setup. Remove the
    `deferred` flag from test definitions in favor of dynamic state
    evaluation. Explicitly add a dependency for AES-128-ECB on AES-256-GCM.

    Signed-off-by: Simo Sorce <simo@redhat.com>

    Reviewed-by: Dmitry Belyavskiy <beldmit@gmail.com>
    (Merged from https://github.com/openssl/openssl/pull/29222)

diff --git a/providers/fips/fipsprov.c b/providers/fips/fipsprov.c
index b759bd9249..1a50d45926 100644
--- a/providers/fips/fipsprov.c
+++ b/providers/fips/fipsprov.c
@@ -1304,48 +1304,10 @@ void OSSL_INDICATOR_get_callback(OSSL_LIB_CTX *libctx,
     }
 }

-static int FIPS_kat_deferred_execute(OSSL_SELF_TEST *st, OSSL_LIB_CTX *libctx,
-    self_test_id_t id)
-{
-    /*
-     * Dependency chains may cause a test to be referenced multiple times
-     * immediately return if any state is present
-     */
-    if (st_all_tests[id].state != SELF_TEST_STATE_INIT)
-        return 1;
-
-    /* Mark test as in progress */
-    st_all_tests[id].state = SELF_TEST_STATE_IN_PROGRESS;
-
-    /* check if there are dependent tests to run */
-    if (st_all_tests[id].depends_on) {
-        for (int i = 0; st_all_tests[id].depends_on[i] != ST_ID_MAX; i++) {
-            self_test_id_t dep_id = st_all_tests[id].depends_on[i];
-
-            FIPS_kat_deferred_execute(st, libctx, dep_id);
-            switch (st_all_tests[dep_id].state) {
-            case SELF_TEST_STATE_PASSED:
-            case SELF_TEST_STATE_IN_PROGRESS:
-                continue;
-            default:
-                return 0;
-            }
-        }
-    }
-
-    /* may have already been run as a dependency, recheck before executing */
-    if (st_all_tests[id].state == SELF_TEST_STATE_IN_PROGRESS)
-        if (!SELF_TEST_kats_single(st, libctx, id))
-            return 0;
-
-    return 1;
-}
-
 static int FIPS_kat_deferred(OSSL_LIB_CTX *libctx, self_test_id_t id)
 {
     FIPS_GLOBAL *fgbl = ossl_lib_ctx_get_data(libctx,
         OSSL_LIB_CTX_FIPS_PROV_INDEX);
-    int *rt = NULL;
     int ret = 0;

     if (fgbl == NULL) {
@@ -1357,14 +1319,15 @@ static int FIPS_kat_deferred(OSSL_LIB_CTX *libctx, self_test_id_t id)
      * before we do anything, make sure a local test is not already in
      * progress or we'll deadlock
      */
-    rt = CRYPTO_THREAD_get_local_ex(CRYPTO_THREAD_LOCAL_FIPS_DEFERRED_KEY,
-        libctx);
-    if (rt) {
+    if (CRYPTO_THREAD_get_local_ex(CRYPTO_THREAD_LOCAL_FIPS_DEFERRED_KEY,
+            libctx)
+        != NULL) {
         /*
          * record this test as invoked by the original test, for marking
          * it later as also satisfied
          */
-        rt[id] = 1;
+        if (st_all_tests[id].state == SELF_TEST_STATE_INIT)
+            st_all_tests[id].state = SELF_TEST_STATE_IMPLICIT;
         /*
          * A self test is in progress for this thread so we let this
          * thread continue and perform the test while all other
@@ -1390,17 +1353,14 @@ static int FIPS_kat_deferred(OSSL_LIB_CTX *libctx, self_test_id_t id)
             ret = 1;
             goto done;
         default:
-            /* should not happen, something is broken */
+            /* something is broken */
             ret = 0;
             goto done;
         }

-        if ((rt = OPENSSL_calloc(ST_ID_MAX, sizeof(int))) == NULL)
-            goto done;
-
         /* mark that we are executing a test on the local thread */
         if (!CRYPTO_THREAD_set_local_ex(CRYPTO_THREAD_LOCAL_FIPS_DEFERRED_KEY,
-                libctx, rt))
+                libctx, (void *)0xC001))
             goto done;

         unset_key = true;
@@ -1411,25 +1371,13 @@ static int FIPS_kat_deferred(OSSL_LIB_CTX *libctx, self_test_id_t id)
         if ((st = OSSL_SELF_TEST_new(cb, cb_arg)) == NULL)
             goto done;

-        /* Handles dependencies via recursion */
-        if (!(ret = FIPS_kat_deferred_execute(st, libctx, id)))
-            goto done;
-
-        /*
-         * now mark as passed all the algorithms that have been executed by
-         * this test and that we tracked as RECORDED_TESTS
-         */
-        if (st_all_tests[id].state == SELF_TEST_STATE_PASSED)
-            for (int i = 0; i < ST_ID_MAX; i++)
-                if (rt[i])
-                    st_all_tests[i].state = SELF_TEST_STATE_PASSED;
+        ret = SELF_TEST_kats_execute(st, libctx, id, 1);

     done:
         OSSL_SELF_TEST_free(st);
         if (unset_key)
             CRYPTO_THREAD_set_local_ex(CRYPTO_THREAD_LOCAL_FIPS_DEFERRED_KEY,
                 libctx, NULL);
-        OPENSSL_free(rt);
         CRYPTO_THREAD_unlock(fgbl->deferred_lock);
     }
     return ret;
@@ -1485,11 +1433,15 @@ int ossl_deferred_self_test(OSSL_LIB_CTX *libctx, self_test_id_t id)
         return 1;

     /*
-     * During the initial selftest we do not try to self-test individual
-     * algorithms, or we end up in loops.
+     * During the initial selftest a call into this function means
+     * a higher level algorithm test is exercising a lower one.
+     * Immediately mark it and return.
      */
-    if (ossl_fips_self_testing())
+    if (ossl_fips_self_testing()) {
+        if (st_all_tests[id].state == SELF_TEST_STATE_INIT)
+            st_all_tests[id].state = SELF_TEST_STATE_IMPLICIT;
         return 1;
+    }

     /*
      * NOTE: that the order in which we check the 'state' here is not important,
diff --git a/providers/fips/self_test.c b/providers/fips/self_test.c
index 84e65bcf4a..2630129795 100644
--- a/providers/fips/self_test.c
+++ b/providers/fips/self_test.c
@@ -199,7 +199,7 @@ static int verify_integrity(OSSL_CORE_BIO *bio, OSSL_FUNC_BIO_read_ex_fn read_ex
     EVP_MAC_CTX *ctx = NULL;
     OSSL_PARAM params[2], *p = params;

-    if (!SELF_TEST_kats_single(ev, libctx, ST_ID_MAC_HMAC))
+    if (!SELF_TEST_kats_execute(ev, libctx, ST_ID_MAC_HMAC, 0))
         goto err;

     OSSL_SELF_TEST_onbegin(ev, event_type, OSSL_SELF_TEST_DESC_INTEGRITY_HMAC);
@@ -330,7 +330,7 @@ int SELF_TEST_post(SELF_TEST_POST_PARAMS *st, int on_demand_test)
         for (int i = 0; i < ST_ID_MAX; i++)
             st_all_tests[i].state = SELF_TEST_STATE_INIT;

-    if (!SELF_TEST_kats(ev, st->libctx, on_demand_test)) {
+    if (on_demand_test && !SELF_TEST_kats(ev, st->libctx)) {
         ERR_raise(ERR_LIB_PROV, PROV_R_SELF_TEST_KAT_FAILURE);
         goto end;
     }
diff --git a/providers/fips/self_test.h b/providers/fips/self_test.h
index a2c14beaa2..f9b6a2531a 100644
--- a/providers/fips/self_test.h
+++ b/providers/fips/self_test.h
@@ -32,8 +32,9 @@ typedef struct self_test_post_params_st {
 } SELF_TEST_POST_PARAMS;

 int SELF_TEST_post(SELF_TEST_POST_PARAMS *st, int on_demand_test);
-int SELF_TEST_kats(OSSL_SELF_TEST *st, OSSL_LIB_CTX *libctx, int do_deferred);
-int SELF_TEST_kats_single(OSSL_SELF_TEST *st, OSSL_LIB_CTX *libctx, int id);
+int SELF_TEST_kats_execute(OSSL_SELF_TEST *st, OSSL_LIB_CTX *libctx,
+    self_test_id_t id, int switch_rand);
+int SELF_TEST_kats(OSSL_SELF_TEST *st, OSSL_LIB_CTX *libctx);

 void SELF_TEST_disable_conditional_error_state(void);

@@ -57,11 +58,9 @@ enum st_test_state {
     SELF_TEST_STATE_IN_PROGRESS,
     SELF_TEST_STATE_PASSED,
     SELF_TEST_STATE_FAILED,
+    SELF_TEST_STATE_IMPLICIT,
 };

-#define SELF_TEST_ONLOAD 0
-#define SELF_TEST_DEFERRED 1
-
 /* used to store raw parameters for keys and algorithms */
 typedef struct st_kat_param_st {
     const char *name; /* an OSSL_PARAM name */
@@ -154,7 +153,6 @@ typedef struct self_test_st {
     const char *algorithm;
     const char *desc;
     enum st_test_category category;
-    int deferred;
     enum st_test_state state;
     ST_BUFFER pt;
     ST_BUFFER expected; /* Set to NULL if this value changes */
diff --git a/providers/fips/self_test_data.c b/providers/fips/self_test_data.c
index 71af012099..59758a7617 100644
--- a/providers/fips/self_test_data.c
+++ b/providers/fips/self_test_data.c
@@ -116,6 +116,11 @@ static const unsigned char aes_128_ecb_ct[] = {
     0x4e, 0xaa, 0x6f, 0xb4, 0xdb, 0xf7, 0x84, 0x65
 };

+static const self_test_id_t aes_ecb_depends_on[] = {
+    ST_ID_CIPHER_AES_256_GCM,
+    ST_ID_MAX
+};
+
 #ifndef OPENSSL_NO_DES
 /*
  * TDES-ECB test data from
@@ -3331,7 +3336,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "SHA1",
         OSSL_SELF_TEST_DESC_MD_SHA1,
         SELF_TEST_KAT_DIGEST,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         ITM_BUF_STR(sha1_pt),
         ITM_BUF(sha1_digest),
@@ -3341,7 +3345,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "SHA256",
         OSSL_SELF_TEST_DESC_MD_SHA2,
         SELF_TEST_KAT_DIGEST,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         ITM_BUF_STR(sha256_pt),
         ITM_BUF(sha256_digest),
@@ -3351,7 +3354,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "SHA512",
         OSSL_SELF_TEST_DESC_MD_SHA2,
         SELF_TEST_KAT_DIGEST,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         ITM_BUF_STR(sha512_pt),
         ITM_BUF(sha512_digest),
@@ -3361,7 +3363,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "SHA3-256",
         OSSL_SELF_TEST_DESC_MD_SHA3,
         SELF_TEST_KAT_DIGEST,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         ITM_BUF(sha3_256_pt),
         ITM_BUF(sha3_256_digest),
@@ -3371,7 +3372,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "AES-256-GCM",
         OSSL_SELF_TEST_DESC_CIPHER_AES_GCM,
         SELF_TEST_KAT_CIPHER,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         ITM_BUF(aes_256_gcm_pt),
         ITM_BUF(aes_256_gcm_ct),
@@ -3387,7 +3387,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "AES-128-ECB",
         OSSL_SELF_TEST_DESC_CIPHER_AES_ECB,
         SELF_TEST_KAT_CIPHER,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         ITM_BUF(aes_128_ecb_pt),
         ITM_BUF(aes_128_ecb_ct),
@@ -3395,6 +3394,7 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
             CIPHER_MODE_DECRYPT,
             ITM_BUF(aes_128_ecb_key),
         },
+        .depends_on = aes_ecb_depends_on,
     },
 #ifndef OPENSSL_NO_DES
     {
@@ -3402,7 +3402,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "DES-EDE3-ECB",
         OSSL_SELF_TEST_DESC_CIPHER_TDES,
         SELF_TEST_KAT_CIPHER,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         ITM_BUF(tdes_pt),
         ITM_BUF(tdes_ct),
@@ -3417,7 +3416,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "RSA-SHA256",
         OSSL_SELF_TEST_DESC_SIGN_RSA,
         SELF_TEST_KAT_SIGNATURE,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         ITM_BUF_STR(rsa_sig_msg),
         ITM_BUF(rsa_expected_sig),
@@ -3436,7 +3434,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "ECDSA-SHA256",
         OSSL_SELF_TEST_DESC_SIGN_ECDSA,
         SELF_TEST_KAT_SIGNATURE,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         ITM_BUF_STR(rsa_sig_msg),
         ITM_BUF(ecdsa_prime_expected_sig),
@@ -3456,7 +3453,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "ECDSA-SHA256",
         OSSL_SELF_TEST_DESC_SIGN_DetECDSA,
         SELF_TEST_KAT_SIGNATURE,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         ITM_BUF_STR(rsa_sig_msg),
         ITM_BUF(ecdsa_prime_expected_detsig),
@@ -3474,7 +3470,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "ECDSA-SHA256",
         OSSL_SELF_TEST_DESC_SIGN_ECDSA,
         SELF_TEST_KAT_SIGNATURE,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         ITM_BUF_STR(rsa_sig_msg),
         ITM_BUF(ecdsa_bin_expected_sig),
@@ -3494,7 +3489,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "ED448",
         OSSL_SELF_TEST_DESC_SIGN_EDDSA,
         SELF_TEST_KAT_SIGNATURE,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         ITM_BUF(ecx_sig_msg),
         ITM_BUF(ed448_expected_sig),
@@ -3509,7 +3503,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "ED25519",
         OSSL_SELF_TEST_DESC_SIGN_EDDSA,
         SELF_TEST_KAT_SIGNATURE,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         ITM_BUF(ecx_sig_msg),
         ITM_BUF(ed25519_expected_sig),
@@ -3527,7 +3520,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "DSA-SHA256",
         OSSL_SELF_TEST_DESC_SIGN_DSA,
         SELF_TEST_KAT_SIGNATURE,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         ITM_BUF_STR(rsa_sig_msg),
         ITM_BUF(dsa_expected_sig),
@@ -3547,7 +3539,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "ML-DSA-65",
         OSSL_SELF_TEST_DESC_SIGN_ML_DSA,
         SELF_TEST_KAT_SIGNATURE,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         ITM_BUF(ml_dsa_65_msg),
         ITM_BUF(ml_dsa_65_sig),
@@ -3576,7 +3567,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "SLH-DSA-SHA2-128f",
         OSSL_SELF_TEST_DESC_SIGN_SLH_DSA,
         SELF_TEST_KAT_SIGNATURE,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         ITM_BUF(slh_dsa_sha2_sig_msg),
         ITM_BUF(slh_dsa_sha2_128f_sig_digest),
@@ -3593,7 +3583,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "SLH-DSA-SHAKE-128f",
         OSSL_SELF_TEST_DESC_SIGN_SLH_DSA,
         SELF_TEST_KAT_SIGNATURE,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         ITM_BUF(slh_dsa_shake_sig_msg),
         ITM_BUF(slh_dsa_shake_128f_sig_digest),
@@ -3621,7 +3610,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "LMS",
         OSSL_SELF_TEST_DESC_SIGN_LMS,
         SELF_TEST_KAT_SIGNATURE,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         ITM_BUF(sha256_192_msg),
         ITM_BUF(sha256_192_sig),
@@ -3637,7 +3625,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         OSSL_KDF_NAME_TLS1_3_KDF,
         OSSL_SELF_TEST_DESC_KDF_TLS13_EXTRACT,
         SELF_TEST_KAT_KDF,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         .expected = ITM_BUF(tls13_kdf_early_secret),
         .u.kdf = {
@@ -3650,7 +3637,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         OSSL_KDF_NAME_TLS1_3_KDF,
         OSSL_SELF_TEST_DESC_KDF_TLS13_EXPAND,
         SELF_TEST_KAT_KDF,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         .expected = ITM_BUF(tls13_kdf_client_early_traffic_secret),
         .u.kdf = {
@@ -3663,7 +3649,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         OSSL_KDF_NAME_TLS1_PRF,
         OSSL_SELF_TEST_DESC_KDF_TLS12_PRF,
         SELF_TEST_KAT_KDF,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         .expected = ITM_BUF(tls12prf_expected),
         .u.kdf = {
@@ -3675,7 +3660,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         OSSL_KDF_NAME_PBKDF2,
         OSSL_SELF_TEST_DESC_KDF_PBKDF2,
         SELF_TEST_KAT_KDF,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         .expected = ITM_BUF(pbkdf2_expected),
         .u.kdf = {
@@ -3688,7 +3672,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         OSSL_KDF_NAME_KBKDF,
         OSSL_SELF_TEST_DESC_KDF_KBKDF,
         SELF_TEST_KAT_KDF,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         .expected = ITM_BUF(kbkdf_expected),
         .u.kdf = {
@@ -3701,7 +3684,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         OSSL_KDF_NAME_KBKDF,
         OSSL_SELF_TEST_DESC_KDF_KBKDF_KMAC,
         SELF_TEST_KAT_KDF,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         .expected = ITM_BUF(kbkdf_kmac_expected),
         .u.kdf = {
@@ -3715,7 +3697,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         OSSL_KDF_NAME_HKDF,
         OSSL_SELF_TEST_DESC_KDF_HKDF,
         SELF_TEST_KAT_KDF,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         .expected = ITM_BUF(hkdf_expected),
         .u.kdf = {
@@ -3729,7 +3710,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         OSSL_KDF_NAME_SNMPKDF,
         OSSL_SELF_TEST_DESC_KDF_SNMPKDF,
         SELF_TEST_KAT_KDF,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         .expected = ITM_BUF(snmpkdf_expected),
         .u.kdf = {
@@ -3743,7 +3723,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         OSSL_KDF_NAME_SRTPKDF,
         OSSL_SELF_TEST_DESC_KDF_SRTPKDF,
         SELF_TEST_KAT_KDF,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         .expected = ITM_BUF(srtpkdf_expected),
         .u.kdf = {
@@ -3757,7 +3736,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         OSSL_KDF_NAME_SSKDF,
         OSSL_SELF_TEST_DESC_KDF_SSKDF,
         SELF_TEST_KAT_KDF,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         .expected = ITM_BUF(sskdf_expected),
         .u.kdf = { sskdf_params },
@@ -3769,7 +3747,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         OSSL_KDF_NAME_X963KDF,
         OSSL_SELF_TEST_DESC_KDF_X963KDF,
         SELF_TEST_KAT_KDF,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         .expected = ITM_BUF(x963kdf_expected),
         .u.kdf = { x963kdf_params },
@@ -3781,7 +3758,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         OSSL_KDF_NAME_X942KDF_ASN1,
         OSSL_SELF_TEST_DESC_KDF_X942KDF,
         SELF_TEST_KAT_KDF,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         .expected = ITM_BUF(x942kdf_expected),
         .u.kdf = {
@@ -3794,7 +3770,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "HASH-DRBG",
         OSSL_SELF_TEST_DESC_DRBG_HASH,
         SELF_TEST_DRBG,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         .expected = ITM_BUF(drbg_hash_sha256_pr_expected),
         .u.drbg = {
@@ -3814,7 +3789,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "CTR-DRBG",
         OSSL_SELF_TEST_DESC_DRBG_CTR,
         SELF_TEST_DRBG,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         .expected = ITM_BUF(drbg_ctr_aes128_pr_df_expected),
         .u.drbg = {
@@ -3834,7 +3808,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "HMAC-DRBG",
         OSSL_SELF_TEST_DESC_DRBG_HMAC,
         SELF_TEST_DRBG,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         .expected = ITM_BUF(drbg_hmac_sha2_pr_expected),
         .u.drbg = {
@@ -3855,7 +3828,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "DH",
         OSSL_SELF_TEST_DESC_KA_DH,
         SELF_TEST_KAT_KAS,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         .expected = ITM_BUF(dh_secret_expected),
         .u.kas = {
@@ -3871,7 +3843,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "EC",
         OSSL_SELF_TEST_DESC_KA_ECDH,
         SELF_TEST_KAT_KAS,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         .expected = ITM_BUF(ecdh_secret_expected),
         .u.kas = {
@@ -3891,7 +3862,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "ML-KEM-512",
         OSSL_SELF_TEST_DESC_KEYGEN_ML_KEM,
         SELF_TEST_KAT_ASYM_KEYGEN,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         .u.akgen = {
             ml_kem_keygen_params,
@@ -3905,7 +3875,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "ML-DSA-65",
         OSSL_SELF_TEST_DESC_KEYGEN_ML_DSA,
         SELF_TEST_KAT_ASYM_KEYGEN,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         .u.akgen = {
             ml_dsa_keygen_params,
@@ -3919,7 +3888,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "SLH-DSA-SHA2-128f",
         OSSL_SELF_TEST_DESC_KEYGEN_SLH_DSA,
         SELF_TEST_KAT_ASYM_KEYGEN,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         .u.akgen = {
             slh_dsa_sha2_128f_keygen_init_params,
@@ -3933,7 +3901,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "ML-KEM-512",
         OSSL_SELF_TEST_DESC_KEM,
         SELF_TEST_KAT_KEM,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         .u.kem = {
             ml_kem_key,
@@ -3949,7 +3916,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "RSA",
         OSSL_SELF_TEST_DESC_ASYM_RSA_ENC,
         SELF_TEST_KAT_ASYM_CIPHER,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         ITM_BUF(rsa_asym_plaintext_encrypt),
         ITM_BUF(rsa_asym_expected_encrypt),
@@ -3965,7 +3931,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "RSA",
         OSSL_SELF_TEST_DESC_ASYM_RSA_DEC,
         SELF_TEST_KAT_ASYM_CIPHER,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         ITM_BUF(rsa_asym_expected_encrypt),
         ITM_BUF(rsa_asym_plaintext_encrypt),
@@ -3981,7 +3946,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "RSA",
         OSSL_SELF_TEST_DESC_ASYM_RSA_DEC,
         SELF_TEST_KAT_ASYM_CIPHER,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         ITM_BUF(rsa_asym_expected_encrypt),
         ITM_BUF(rsa_asym_plaintext_encrypt),
@@ -3997,7 +3961,6 @@ ST_DEFINITION st_all_tests[ST_ID_MAX] = {
         "HMAC",
         OSSL_SELF_TEST_DESC_INTEGRITY_HMAC,
         SELF_TEST_KAT_MAC,
-        SELF_TEST_DEFERRED,
         SELF_TEST_STATE_INIT,
         ITM_BUF(hmac_kat_pt),
         ITM_BUF(hmac_kat_expected),
diff --git a/providers/fips/self_test_kats.c b/providers/fips/self_test_kats.c
index 20743d144f..fd6363a315 100644
--- a/providers/fips/self_test_kats.c
+++ b/providers/fips/self_test_kats.c
@@ -1086,121 +1086,16 @@ static int setup_main_random(OSSL_LIB_CTX *libctx)
     return 1;
 err:
     EVP_RAND_CTX_free(main_rand);
-    return 0;
-}
-
-/*
- * Run the algorithm KAT's.
- * Return 1 is successful, otherwise return 0.
- * This runs all the tests regardless of if any fail.
- * when do_deferred is 1 also run deferred tests, they are normally skipped
- */
-int SELF_TEST_kats(OSSL_SELF_TEST *st, OSSL_LIB_CTX *libctx, int do_deferred)
-{
-    EVP_RAND_CTX *saved_rand = ossl_rand_get0_private_noncreating(libctx);
-    int i, ret = 1;
-
-    if (saved_rand != NULL && !EVP_RAND_CTX_up_ref(saved_rand))
-        return 0;
-    if (!setup_main_random(libctx)
-        || !RAND_set0_private(libctx, main_rand)) {
-        /* Decrement saved_rand reference counter */
-        EVP_RAND_CTX_free(saved_rand);
-        EVP_RAND_CTX_free(main_rand);
-        /* Ensure this global variable does not reference freed memory */
-        main_rand = NULL;
-        return 0;
-    }
-
-    for (i = 0; i < ST_ID_MAX; i++) {
-        int res;
-
-        if (st_all_tests[i].id != i) {
-            ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_CONFIG_DATA);
-            return 0;
-        }
-
-        if (!do_deferred && (st_all_tests[i].deferred == SELF_TEST_DEFERRED) && (st_all_tests[i].state != SELF_TEST_STATE_PASSED))
-            continue;
-
-        switch (st_all_tests[i].category) {
-        case SELF_TEST_KAT_DIGEST:
-            res = self_test_digest(&st_all_tests[i], st, libctx);
-            break;
-        case SELF_TEST_KAT_CIPHER:
-            res = self_test_cipher(&st_all_tests[i], st, libctx);
-            break;
-        case SELF_TEST_KAT_SIGNATURE:
-            res = self_test_digest_sign(&st_all_tests[i], st, libctx);
-            break;
-        case SELF_TEST_KAT_KDF:
-            res = self_test_kdf(&st_all_tests[i], st, libctx);
-            break;
-        case SELF_TEST_DRBG:
-            res = self_test_drbg(&st_all_tests[i], st, libctx);
-            break;
-        case SELF_TEST_KAT_KAS:
-            res = self_test_ka(&st_all_tests[i], st, libctx);
-            break;
-        case SELF_TEST_KAT_ASYM_KEYGEN:
-            res = self_test_asym_keygen(&st_all_tests[i], st, libctx);
-            break;
-        case SELF_TEST_KAT_KEM:
-            res = self_test_kem(&st_all_tests[i], st, libctx);
-            break;
-        case SELF_TEST_KAT_ASYM_CIPHER:
-            res = self_test_asym_cipher(&st_all_tests[i], st, libctx);
-            break;
-        case SELF_TEST_KAT_MAC:
-            res = self_test_mac(&st_all_tests[i], st, libctx);
-            break;
-        default:
-            res = 0;
-            break;
-        }
-        if (res)
-            st_all_tests[i].state = SELF_TEST_STATE_PASSED;
-        else
-            ret = 0;
-    }
-
-    RAND_set0_private(libctx, saved_rand);
-    /* The above call will cause main_rand to be freed */
+    /* Ensure this global variable does not reference freed memory */
     main_rand = NULL;
-    return ret;
+    return 0;
 }

-/*
- * Run a single algorithm KAT.
- * This is similar to SELF_TEST_kats() but only runs the test for a single
- * algorithm.
- * Return 1 is successful, otherwise return 0. If no test is found for the
- * algorithm it also returns 0.
- * This runs all the tests for the given algorithm regardless of if any fail.
- *
- * NOTE: currently tests that require the TEST RNG will not work, as we can't
- * replace the working DRBG with the TEST DRB after initialization.
- */
-int SELF_TEST_kats_single(OSSL_SELF_TEST *st, OSSL_LIB_CTX *libctx, int id)
+static int SELF_TEST_kats_single(OSSL_SELF_TEST *st, OSSL_LIB_CTX *libctx,
+    self_test_id_t id)
 {
-    EVP_RAND_CTX *saved_rand = ossl_rand_get0_private_noncreating(libctx);
     int ret;

-    if (id >= ST_ID_MAX || st_all_tests[id].id != id) {
-        ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_CONFIG_DATA);
-        return 0;
-    }
-
-    if (saved_rand != NULL && !EVP_RAND_CTX_up_ref(saved_rand))
-        return 0;
-    if (!setup_main_random(libctx)
-        || !RAND_set0_private(libctx, main_rand)) {
-        /* Decrement saved_rand reference counter */
-        EVP_RAND_CTX_free(saved_rand);
-        EVP_RAND_CTX_free(main_rand);
-        return 0;
-    }
-
     switch (st_all_tests[id].category) {
     case SELF_TEST_KAT_DIGEST:
         ret = self_test_digest(&st_all_tests[id], st, libctx);
@@ -1238,10 +1133,156 @@ int SELF_TEST_kats_single(OSSL_SELF_TEST *st, OSSL_LIB_CTX *libctx, int id)
     }
     if (ret)
         st_all_tests[id].state = SELF_TEST_STATE_PASSED;
-    else
+    else {
         st_all_tests[id].state = SELF_TEST_STATE_FAILED;
+        ERR_raise(ERR_LIB_PROV, PROV_R_SELF_TEST_KAT_FAILURE);
+    }
+
+    return ret;
+}
+
+static int SELF_TEST_kat_deps(OSSL_SELF_TEST *st, OSSL_LIB_CTX *libctx,
+    ST_DEFINITION *test)
+{
+    if (test->depends_on == NULL)
+        return 0;
+
+    for (int i = 0; test->depends_on[i] != ST_ID_MAX; i++)
+        if (!SELF_TEST_kats_execute(st, libctx, test->depends_on[i], 0))
+            return 0;
+
+    return 1;
+}
+
+/*
+ * Run a single algorithm KAT, and its dependencies.
+ * Return 1 if successful, otherwise return 0.
+ */
+int SELF_TEST_kats_execute(OSSL_SELF_TEST *st, OSSL_LIB_CTX *libctx,
+    self_test_id_t id, int switch_rand)
+{
+    EVP_RAND_CTX *saved_rand = NULL;
+    int ret;
+
+    if (id >= ST_ID_MAX || st_all_tests[id].id != id) {
+        ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_CONFIG_DATA);
+        return 0;
+    }
+
+    /*
+     * Dependency chains may cause a test to be referenced multiple times,
+     * immediately return if not in initial state.
+     */
+    switch (st_all_tests[id].state) {
+    case SELF_TEST_STATE_INIT:
+        break;
+    case SELF_TEST_STATE_FAILED:
+        return 0;
+    case SELF_TEST_STATE_IN_PROGRESS:
+    case SELF_TEST_STATE_PASSED:
+    case SELF_TEST_STATE_IMPLICIT:
+        return 1;
+    }
+
+    if (switch_rand) {
+        saved_rand = ossl_rand_get0_private_noncreating(libctx);
+        if (saved_rand != NULL && !EVP_RAND_CTX_up_ref(saved_rand))
+            return 0;
+        if (!setup_main_random(libctx)
+            || !RAND_set0_private(libctx, main_rand)) {
+            /* Decrement saved_rand reference counter */
+            EVP_RAND_CTX_free(saved_rand);
+            EVP_RAND_CTX_free(main_rand);
+            /* Ensure this global variable does not reference freed memory */
+            main_rand = NULL;
+            return 0;
+        }
+    }
+
+    /* Mark test as in progress */
+    st_all_tests[id].state = SELF_TEST_STATE_IN_PROGRESS;
+
+    /* check if there are dependent tests to run */
+    if (st_all_tests[id].depends_on) {
+        if (!SELF_TEST_kat_deps(st, libctx, &st_all_tests[id])) {
+            ret = 0;
+            goto done;
+        }
+    }
+
+    /* may have already been run through dependency chains */
+    switch (st_all_tests[id].state) {
+    case SELF_TEST_STATE_IN_PROGRESS:
+        ret = SELF_TEST_kats_single(st, libctx, id);
+        break;
+    case SELF_TEST_STATE_PASSED:
+        ret = 1;
+        break;
+    default:
+        /* ensure all states are set to failed if we get here */
+        st_all_tests[id].state = SELF_TEST_STATE_FAILED;
+        ret = 0;
+    }
+
+    /*
+     * if an implicit algorithm has explicit dependencies we want to
+     * ensure they are all executed as well otherwise we could not
+     * mark it as passed.
+     */
+    if (st_all_tests[id].state == SELF_TEST_STATE_PASSED)
+        for (int i = 0; i < ST_ID_MAX; i++)
+            if (st_all_tests[i].state == SELF_TEST_STATE_IMPLICIT
+                && st_all_tests[i].depends_on != NULL)
+                if (!(ret = SELF_TEST_kat_deps(st, libctx, &st_all_tests[i])))
+                    break;
+
+done:
+    /*
+     * now mark (pass or fail) all the algorithm tests that have been marked
+     * by this test implicitly tested.
+     */
+    for (int i = 0; i < ST_ID_MAX; i++)
+        if (st_all_tests[i].state == SELF_TEST_STATE_IMPLICIT)
+            st_all_tests[i].state = st_all_tests[id].state;
+
+    if (switch_rand) {
+        RAND_set0_private(libctx, saved_rand);
+        /* The above call will cause main_rand to be freed */
+        main_rand = NULL;
+    }
+    return ret;
+}
+
+/*
+ * Run the algorithm KAT's.
+ * Return 1 is successful, otherwise return 0.
+ * This runs all the tests regardless of if any fail, but it will not forcibly
+ * run tests that have been implicitly satisfied.
+ */
+int SELF_TEST_kats(OSSL_SELF_TEST *st, OSSL_LIB_CTX *libctx)
+{
+    EVP_RAND_CTX *saved_rand = ossl_rand_get0_private_noncreating(libctx);
+    int i, ret = 1;
+
+    if (saved_rand != NULL && !EVP_RAND_CTX_up_ref(saved_rand))
+        return 0;
+    if (!setup_main_random(libctx)
+        || !RAND_set0_private(libctx, main_rand)) {
+        /* Decrement saved_rand reference counter */
+        EVP_RAND_CTX_free(saved_rand);
+        EVP_RAND_CTX_free(main_rand);
+        /* Ensure this global variable does not reference freed memory */
+        main_rand = NULL;
+        return 0;
+    }
+
+    for (i = 0; i < ST_ID_MAX; i++)
+        if (!SELF_TEST_kats_execute(st, libctx, i, 0))
+            ret = 0;

     RAND_set0_private(libctx, saved_rand);
+    /* The above call will cause main_rand to be freed */
+    main_rand = NULL;
     return ret;
 }