Commit 37809a1e52 for openssl.org

commit 37809a1e52ab74e867f69430c3837968888ad270
Author: Jakub Zelenka <jakub.zelenka@openssl.foundation>
Date:   Tue Sep 1 11:48:32 2026 +0200

    store: add a fake store provider for API tests

    Introduce a fake store provider with two loader schemes: "fake" with
    a plain open() entry point and delete support, and "fake-ex" with an
    open_ex() entry point and no delete support.  The loader emits NAME
    objects so it needs no decoders or other providers, and it records
    the ctx params it receives so tests can assert what actually reached
    the loader.

    Use it to cover previously unexercised parts of the OSSL_STORE API:
    the legacy open function, open failure handling, all four search
    criteria with their accessors, the post process callback, various
    error conditions and deleting through a loader with and without
    delete support.

    Assisted-by: Claude:claude-fable-5
    Reviewed-by: Richard Levitte <levitte@openssl.org>
    Reviewed-by: Daniel Kubec <kubec@openssl.foundation>
    Merge-date: Wed Sep 16 08:46:32 2026
    Merged-from: https://github.com/openssl/openssl/pull/32624

diff --git a/test/build.info b/test/build.info
index 74469ae9e2..e1b972bc55 100644
--- a/test/build.info
+++ b/test/build.info
@@ -309,7 +309,7 @@ IF[{- !$disabled{tests} -}]
     DEPEND[acvp_test]=../libcrypto libtestutil.a
   ENDIF

-  SOURCE[ossl_store_test]=ossl_store_test.c
+  SOURCE[ossl_store_test]=ossl_store_test.c fake_storeprov.c
   INCLUDE[ossl_store_test]=../include ../apps/include
   DEPEND[ossl_store_test]=../libcrypto libtestutil.a

diff --git a/test/fake_storeprov.c b/test/fake_storeprov.c
new file mode 100644
index 0000000000..e0cfbbdb34
--- /dev/null
+++ b/test/fake_storeprov.c
@@ -0,0 +1,276 @@
+/*
+ * Copyright 2026 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
+ * https://www.openssl.org/source/license.html
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <openssl/core.h>
+#include <openssl/core_dispatch.h>
+#include <openssl/core_names.h>
+#include <openssl/core_object.h>
+#include <openssl/crypto.h>
+#include <openssl/params.h>
+#include <openssl/store.h>
+#include "fake_storeprov.h"
+
+/*
+ * A minimal store loader that emits OSSL_STORE_INFO_NAME objects, so that
+ * no decoders or other providers are needed to exercise the OSSL_STORE API.
+ * The URI part after the scheme selects the loader behaviour, see the
+ * FAKE_STORE_CMD_* macros.
+ */
+
+struct fake_store_ctx_st {
+    int remaining;
+    int fail_params;
+    int expected_type;
+};
+
+static unsigned int seen_params = 0;
+static char last_deleted[256] = "";
+
+unsigned int fake_store_get_seen_params(void)
+{
+    return seen_params;
+}
+
+void fake_store_clear_state(void)
+{
+    seen_params = 0;
+    last_deleted[0] = '\0';
+}
+
+const char *fake_store_get0_last_deleted(void)
+{
+    return last_deleted;
+}
+
+static OSSL_FUNC_store_open_fn fake_store_open;
+static OSSL_FUNC_store_open_ex_fn fake_store_open_ex;
+static OSSL_FUNC_store_attach_fn fake_store_attach;
+static OSSL_FUNC_store_settable_ctx_params_fn fake_store_settable_ctx_params;
+static OSSL_FUNC_store_set_ctx_params_fn fake_store_set_ctx_params;
+static OSSL_FUNC_store_load_fn fake_store_load;
+static OSSL_FUNC_store_eof_fn fake_store_eof;
+static OSSL_FUNC_store_close_fn fake_store_close;
+static OSSL_FUNC_store_delete_fn fake_store_delete;
+
+static void *fake_store_open(void *provctx, const char *uri)
+{
+    struct fake_store_ctx_st *ctx;
+    const char *cmd;
+
+    if ((cmd = strchr(uri, ':')) == NULL)
+        return NULL;
+    cmd++;
+
+    if (strcmp(cmd, FAKE_STORE_CMD_OPEN_FAIL) == 0)
+        return NULL;
+
+    if ((ctx = OPENSSL_zalloc(sizeof(*ctx))) == NULL)
+        return NULL;
+    ctx->remaining = strcmp(cmd, FAKE_STORE_CMD_TWO_NAMES) == 0 ? 2 : 1;
+    ctx->fail_params = strcmp(cmd, FAKE_STORE_CMD_PARAMS_FAIL) == 0;
+    return ctx;
+}
+
+static void *fake_store_open_ex(void *provctx, const char *uri,
+    const OSSL_PARAM params[],
+    OSSL_PASSPHRASE_CALLBACK *pw_cb, void *pw_cbarg)
+{
+    struct fake_store_ctx_st *ctx = fake_store_open(provctx, uri);
+
+    if (ctx != NULL && params != NULL
+        && !fake_store_set_ctx_params(ctx, params)) {
+        OPENSSL_free(ctx);
+        return NULL;
+    }
+    return ctx;
+}
+
+static void *fake_store_attach(void *provctx, OSSL_CORE_BIO *in)
+{
+    struct fake_store_ctx_st *ctx = OPENSSL_zalloc(sizeof(*ctx));
+
+    if (ctx != NULL)
+        ctx->remaining = 1;
+    return ctx;
+}
+
+static const OSSL_PARAM fake_store_settable_params_list[] = {
+    OSSL_PARAM_int(OSSL_STORE_PARAM_EXPECT, NULL),
+    OSSL_PARAM_octet_string(OSSL_STORE_PARAM_SUBJECT, NULL, 0),
+    OSSL_PARAM_octet_string(OSSL_STORE_PARAM_ISSUER, NULL, 0),
+    OSSL_PARAM_BN(OSSL_STORE_PARAM_SERIAL, NULL, 0),
+    OSSL_PARAM_utf8_string(OSSL_STORE_PARAM_DIGEST, NULL, 0),
+    OSSL_PARAM_octet_string(OSSL_STORE_PARAM_FINGERPRINT, NULL, 0),
+    OSSL_PARAM_utf8_string(OSSL_STORE_PARAM_ALIAS, NULL, 0),
+    OSSL_PARAM_utf8_string(OSSL_STORE_PARAM_PROPERTIES, NULL, 0),
+    OSSL_PARAM_utf8_string(OSSL_STORE_PARAM_INPUT_TYPE, NULL, 0),
+    OSSL_PARAM_END
+};
+
+static const OSSL_PARAM *fake_store_settable_ctx_params(void *provctx)
+{
+    return fake_store_settable_params_list;
+}
+
+static int fake_store_set_ctx_params(void *loaderctx, const OSSL_PARAM params[])
+{
+    struct fake_store_ctx_st *ctx = loaderctx;
+    const OSSL_PARAM *p;
+
+    if (ctx->fail_params)
+        return 0;
+
+    if ((p = OSSL_PARAM_locate_const(params, OSSL_STORE_PARAM_EXPECT)) != NULL) {
+        if (!OSSL_PARAM_get_int(p, &ctx->expected_type))
+            return 0;
+        seen_params |= FAKE_STORE_SEEN_EXPECT;
+    }
+    if (OSSL_PARAM_locate_const(params, OSSL_STORE_PARAM_SUBJECT) != NULL)
+        seen_params |= FAKE_STORE_SEEN_SUBJECT;
+    if (OSSL_PARAM_locate_const(params, OSSL_STORE_PARAM_ISSUER) != NULL)
+        seen_params |= FAKE_STORE_SEEN_ISSUER;
+    if (OSSL_PARAM_locate_const(params, OSSL_STORE_PARAM_SERIAL) != NULL)
+        seen_params |= FAKE_STORE_SEEN_SERIAL;
+    if (OSSL_PARAM_locate_const(params, OSSL_STORE_PARAM_DIGEST) != NULL)
+        seen_params |= FAKE_STORE_SEEN_DIGEST;
+    if (OSSL_PARAM_locate_const(params, OSSL_STORE_PARAM_FINGERPRINT) != NULL)
+        seen_params |= FAKE_STORE_SEEN_FINGERPRINT;
+    if (OSSL_PARAM_locate_const(params, OSSL_STORE_PARAM_ALIAS) != NULL)
+        seen_params |= FAKE_STORE_SEEN_ALIAS;
+    if (OSSL_PARAM_locate_const(params, OSSL_STORE_PARAM_PROPERTIES) != NULL)
+        seen_params |= FAKE_STORE_SEEN_PROPERTIES;
+    return 1;
+}
+
+static int fake_store_load(void *loaderctx,
+    OSSL_CALLBACK *object_cb, void *object_cbarg,
+    OSSL_PASSPHRASE_CALLBACK *pw_cb, void *pw_cbarg)
+{
+    struct fake_store_ctx_st *ctx = loaderctx;
+    OSSL_PARAM params[4];
+    int object_type = OSSL_OBJECT_NAME;
+    char name[16], desc[16];
+
+    if (ctx->remaining <= 0)
+        return 0;
+    snprintf(name, sizeof(name), "name%d", ctx->remaining);
+    snprintf(desc, sizeof(desc), "desc%d", ctx->remaining);
+    ctx->remaining--;
+
+    params[0] = OSSL_PARAM_construct_int(OSSL_OBJECT_PARAM_TYPE, &object_type);
+    params[1] = OSSL_PARAM_construct_utf8_string(OSSL_OBJECT_PARAM_DATA,
+        name, 0);
+    params[2] = OSSL_PARAM_construct_utf8_string(OSSL_OBJECT_PARAM_DESC,
+        desc, 0);
+    params[3] = OSSL_PARAM_construct_end();
+
+    return object_cb(params, object_cbarg);
+}
+
+static int fake_store_eof(void *loaderctx)
+{
+    struct fake_store_ctx_st *ctx = loaderctx;
+
+    return ctx->remaining <= 0;
+}
+
+static int fake_store_close(void *loaderctx)
+{
+    OPENSSL_free(loaderctx);
+    return 1;
+}
+
+static int fake_store_delete(void *provctx, const char *uri,
+    const OSSL_PARAM params[],
+    OSSL_PASSPHRASE_CALLBACK *pw_cb, void *pw_cbarg)
+{
+    OPENSSL_strlcpy(last_deleted, uri, sizeof(last_deleted));
+    return 1;
+}
+
+static const OSSL_DISPATCH fake_store_funcs[] = {
+    { OSSL_FUNC_STORE_OPEN, (void (*)(void))fake_store_open },
+    { OSSL_FUNC_STORE_ATTACH, (void (*)(void))fake_store_attach },
+    { OSSL_FUNC_STORE_SETTABLE_CTX_PARAMS,
+        (void (*)(void))fake_store_settable_ctx_params },
+    { OSSL_FUNC_STORE_SET_CTX_PARAMS,
+        (void (*)(void))fake_store_set_ctx_params },
+    { OSSL_FUNC_STORE_LOAD, (void (*)(void))fake_store_load },
+    { OSSL_FUNC_STORE_EOF, (void (*)(void))fake_store_eof },
+    { OSSL_FUNC_STORE_CLOSE, (void (*)(void))fake_store_close },
+    { OSSL_FUNC_STORE_DELETE, (void (*)(void))fake_store_delete },
+    OSSL_DISPATCH_END
+};
+
+/* open() is required for a complete loader but open_ex() takes precedence */
+static const OSSL_DISPATCH fake_store_open_ex_funcs[] = {
+    { OSSL_FUNC_STORE_OPEN_EX, (void (*)(void))fake_store_open_ex },
+    { OSSL_FUNC_STORE_OPEN, (void (*)(void))fake_store_open },
+    { OSSL_FUNC_STORE_SETTABLE_CTX_PARAMS,
+        (void (*)(void))fake_store_settable_ctx_params },
+    { OSSL_FUNC_STORE_SET_CTX_PARAMS,
+        (void (*)(void))fake_store_set_ctx_params },
+    { OSSL_FUNC_STORE_LOAD, (void (*)(void))fake_store_load },
+    { OSSL_FUNC_STORE_EOF, (void (*)(void))fake_store_eof },
+    { OSSL_FUNC_STORE_CLOSE, (void (*)(void))fake_store_close },
+    OSSL_DISPATCH_END
+};
+
+static const OSSL_ALGORITHM fake_store_store_algs[] = {
+    { FAKE_STORE_SCHEME, FAKE_STORE_FETCH_PROPS, fake_store_funcs,
+        "Fake store loader" },
+    { FAKE_STORE_SCHEME_OPEN_EX, FAKE_STORE_FETCH_PROPS,
+        fake_store_open_ex_funcs, "Fake store loader with open_ex" },
+    { NULL, NULL, NULL, NULL }
+};
+
+static const OSSL_ALGORITHM *fake_store_query(void *provctx, int operation_id,
+    int *no_cache)
+{
+    *no_cache = 0;
+    if (operation_id == OSSL_OP_STORE)
+        return fake_store_store_algs;
+    return NULL;
+}
+
+/* Functions we provide to the core */
+static const OSSL_DISPATCH fake_store_method[] = {
+    { OSSL_FUNC_PROVIDER_TEARDOWN, (void (*)(void))OSSL_LIB_CTX_free },
+    { OSSL_FUNC_PROVIDER_QUERY_OPERATION, (void (*)(void))fake_store_query },
+    OSSL_DISPATCH_END
+};
+
+static int fake_store_provider_init(const OSSL_CORE_HANDLE *handle,
+    const OSSL_DISPATCH *in,
+    const OSSL_DISPATCH **out, void **provctx)
+{
+    if ((*provctx = OSSL_LIB_CTX_new()) == NULL)
+        return 0;
+    *out = fake_store_method;
+    return 1;
+}
+
+OSSL_PROVIDER *fake_store_start(OSSL_LIB_CTX *libctx)
+{
+    OSSL_PROVIDER *p;
+
+    if (!OSSL_PROVIDER_add_builtin(libctx, FAKE_STORE_PROV_NAME,
+            fake_store_provider_init)
+        || (p = OSSL_PROVIDER_try_load(libctx, FAKE_STORE_PROV_NAME, 1)) == NULL)
+        return NULL;
+
+    return p;
+}
+
+void fake_store_finish(OSSL_PROVIDER *p)
+{
+    OSSL_PROVIDER_unload(p);
+}
diff --git a/test/fake_storeprov.h b/test/fake_storeprov.h
new file mode 100644
index 0000000000..ab5bdbeae0
--- /dev/null
+++ b/test/fake_storeprov.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2026 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
+ * https://www.openssl.org/source/license.html
+ */
+
+#include <openssl/core_dispatch.h>
+#include <openssl/provider.h>
+
+/* Fake store provider implementation */
+OSSL_PROVIDER *fake_store_start(OSSL_LIB_CTX *libctx);
+void fake_store_finish(OSSL_PROVIDER *p);
+
+#define FAKE_STORE_PROV_NAME "fake-store"
+#define FAKE_STORE_FETCH_PROPS "provider=fake-store"
+
+/* Scheme with a plain open() entry point and delete support */
+#define FAKE_STORE_SCHEME "fake"
+/* Scheme with an open_ex() entry point and no delete support */
+#define FAKE_STORE_SCHEME_OPEN_EX "fake-ex"
+
+/* URI commands recognised after the scheme, e.g. "fake:two-names" */
+#define FAKE_STORE_CMD_ONE_NAME "one-name"
+#define FAKE_STORE_CMD_TWO_NAMES "two-names"
+#define FAKE_STORE_CMD_OPEN_FAIL "open-fail"
+#define FAKE_STORE_CMD_PARAMS_FAIL "params-fail"
+
+/* Bitmask of ctx params the fake loader has observed */
+#define FAKE_STORE_SEEN_EXPECT (1u << 0)
+#define FAKE_STORE_SEEN_SUBJECT (1u << 1)
+#define FAKE_STORE_SEEN_ISSUER (1u << 2)
+#define FAKE_STORE_SEEN_SERIAL (1u << 3)
+#define FAKE_STORE_SEEN_DIGEST (1u << 4)
+#define FAKE_STORE_SEEN_FINGERPRINT (1u << 5)
+#define FAKE_STORE_SEEN_ALIAS (1u << 6)
+#define FAKE_STORE_SEEN_PROPERTIES (1u << 7)
+
+unsigned int fake_store_get_seen_params(void);
+void fake_store_clear_state(void);
+const char *fake_store_get0_last_deleted(void);
diff --git a/test/ossl_store_test.c b/test/ossl_store_test.c
index 62a3cccd9c..5453fe454c 100644
--- a/test/ossl_store_test.c
+++ b/test/ossl_store_test.c
@@ -13,7 +13,12 @@
 #include <openssl/ui.h>
 #include <openssl/core_names.h>
 #include <openssl/params.h>
+#include <openssl/provider.h>
+#include <openssl/x509.h>
+#include <openssl/evp.h>
+#include <openssl/bio.h>
 #include "testutil.h"
+#include "fake_storeprov.h"

 #ifndef PATH_MAX
 #if defined(_WIN32) && defined(_MAX_PATH)
@@ -38,10 +43,13 @@ static const char *infile = NULL;
 static const char *sm2file = NULL;
 static const char *datadir = NULL;

+static OSSL_LIB_CTX *fake_libctx = NULL;
+static OSSL_PROVIDER *fake_prov = NULL;
+
 static int test_store_open(void)
 {
     int ret = 0;
-    OSSL_STORE_CTX *sctx = NULL;
+    OSSL_STORE_CTX *sctx = NULL, *sctx2 = NULL;
     OSSL_STORE_SEARCH *search = NULL;
     UI_METHOD *ui_method = NULL;
     char *input = test_mk_file_path(inputdir, infile);
@@ -51,11 +59,17 @@ static int test_store_open(void)
         && TEST_ptr(ui_method = UI_create_method("DummyUI"))
         && TEST_ptr(sctx = OSSL_STORE_open_ex(input, NULL, NULL, ui_method,
                         NULL, NULL, NULL, NULL))
+        && TEST_true(OSSL_STORE_supports_search(sctx, OSSL_STORE_SEARCH_BY_NAME))
+        && TEST_false(OSSL_STORE_supports_search(sctx,
+            OSSL_STORE_SEARCH_BY_KEY_FINGERPRINT))
         && TEST_false(OSSL_STORE_find(sctx, NULL))
-        && TEST_true(OSSL_STORE_find(sctx, search));
+        && TEST_true(OSSL_STORE_find(sctx, search))
+        && TEST_ptr(sctx2 = OSSL_STORE_open(input, ui_method, NULL,
+                        NULL, NULL));
     UI_destroy_method(ui_method);
     OSSL_STORE_SEARCH_free(search);
     OSSL_STORE_close(sctx);
+    OSSL_STORE_close(sctx2);
     OPENSSL_free(input);
     return ret;
 }
@@ -310,6 +324,346 @@ static int test_store_delete_null_uri(void)
     return TEST_int_eq(OSSL_STORE_delete(NULL, NULL, NULL, NULL, NULL, NULL), 0);
 }

+static int test_fake_store_names(void)
+{
+    OSSL_STORE_CTX *sctx = NULL;
+    OSSL_STORE_INFO *info = NULL;
+    char *name = NULL, *desc = NULL;
+    int ret = 0;
+
+    /* The authority prefix (//) invalidates the implicit 'file' scheme */
+    if (!TEST_ptr(sctx = OSSL_STORE_open_ex("fake://" FAKE_STORE_CMD_ONE_NAME,
+                      fake_libctx, FAKE_STORE_FETCH_PROPS,
+                      NULL, NULL, NULL, NULL, NULL))
+        || !TEST_true(OSSL_STORE_expect(sctx, OSSL_STORE_INFO_NAME))
+        || !TEST_ptr(info = OSSL_STORE_load(sctx)))
+        goto err;
+
+    if (!TEST_int_eq(OSSL_STORE_INFO_get_type(info), OSSL_STORE_INFO_NAME)
+        || !TEST_str_eq(OSSL_STORE_INFO_get0_NAME(info), "name1")
+        || !TEST_str_eq(OSSL_STORE_INFO_get0_NAME_description(info), "desc1")
+        || !TEST_ptr(name = OSSL_STORE_INFO_get1_NAME(info))
+        || !TEST_str_eq(name, "name1")
+        || !TEST_ptr(desc = OSSL_STORE_INFO_get1_NAME_description(info))
+        || !TEST_str_eq(desc, "desc1")
+        || !TEST_ptr(OSSL_STORE_INFO_get0_data(OSSL_STORE_INFO_NAME, info))
+        || !TEST_ptr_null(OSSL_STORE_INFO_get0_data(OSSL_STORE_INFO_CERT,
+            info)))
+        goto err;
+
+    /* Wrong-type accessors must fail cleanly */
+    if (!TEST_ptr_null(OSSL_STORE_INFO_get0_PARAMS(info))
+        || !TEST_ptr_null(OSSL_STORE_INFO_get1_PARAMS(info))
+        || !TEST_ptr_null(OSSL_STORE_INFO_get0_PUBKEY(info))
+        || !TEST_ptr_null(OSSL_STORE_INFO_get1_PUBKEY(info))
+        || !TEST_ptr_null(OSSL_STORE_INFO_get0_PKEY(info))
+        || !TEST_ptr_null(OSSL_STORE_INFO_get1_PKEY(info))
+        || !TEST_ptr_null(OSSL_STORE_INFO_get0_CERT(info))
+        || !TEST_ptr_null(OSSL_STORE_INFO_get1_CERT(info))
+        || !TEST_ptr_null(OSSL_STORE_INFO_get0_CRL(info))
+        || !TEST_ptr_null(OSSL_STORE_INFO_get1_CRL(info))
+        || !TEST_ptr_null(OSSL_STORE_INFO_get0_SKEY(info))
+        || !TEST_ptr_null(OSSL_STORE_INFO_get1_SKEY(info)))
+        goto err;
+
+    if (!TEST_true(OSSL_STORE_eof(sctx))
+        || !TEST_ptr_null(OSSL_STORE_load(sctx))
+        || !TEST_false(OSSL_STORE_error(sctx)))
+        goto err;
+
+    ret = 1;
+err:
+    OPENSSL_free(name);
+    OPENSSL_free(desc);
+    OSSL_STORE_INFO_free(info);
+    OSSL_STORE_close(sctx);
+    return ret;
+}
+
+static int test_fake_store_open_ex(void)
+{
+    OSSL_STORE_CTX *sctx = NULL;
+    OSSL_STORE_INFO *info = NULL;
+    OSSL_PARAM params[2];
+    int expect = OSSL_STORE_INFO_NAME;
+    int ret = 0;
+
+    params[0] = OSSL_PARAM_construct_int(OSSL_STORE_PARAM_EXPECT, &expect);
+    params[1] = OSSL_PARAM_construct_end();
+
+    fake_store_clear_state();
+    if (!TEST_ptr(sctx = OSSL_STORE_open_ex(
+                      FAKE_STORE_SCHEME_OPEN_EX ":" FAKE_STORE_CMD_ONE_NAME,
+                      fake_libctx, FAKE_STORE_FETCH_PROPS, NULL, NULL,
+                      params, NULL, NULL))
+        || !TEST_true(fake_store_get_seen_params() & FAKE_STORE_SEEN_EXPECT)
+        || !TEST_ptr(info = OSSL_STORE_load(sctx))
+        || !TEST_str_eq(OSSL_STORE_INFO_get0_NAME(info), "name1"))
+        goto err;
+
+    /* Both open_ex() failure modes must result in a failed open */
+    if (!TEST_ptr_null(OSSL_STORE_open_ex(
+            FAKE_STORE_SCHEME_OPEN_EX ":" FAKE_STORE_CMD_OPEN_FAIL,
+            fake_libctx, FAKE_STORE_FETCH_PROPS, NULL, NULL,
+            params, NULL, NULL))
+        || !TEST_ptr_null(OSSL_STORE_open_ex(
+            FAKE_STORE_SCHEME_OPEN_EX ":" FAKE_STORE_CMD_PARAMS_FAIL,
+            fake_libctx, FAKE_STORE_FETCH_PROPS, NULL, NULL,
+            params, NULL, NULL)))
+        goto err;
+
+    ret = 1;
+err:
+    OSSL_STORE_INFO_free(info);
+    OSSL_STORE_close(sctx);
+    return ret;
+}
+
+static int test_fake_store_params(void)
+{
+    OSSL_STORE_CTX *sctx = NULL;
+    OSSL_PARAM params[2];
+    char propq[] = FAKE_STORE_FETCH_PROPS;
+    int expect = OSSL_STORE_INFO_NAME;
+    int ret = 0;
+
+    /* Properties in params take precedence over the propq argument */
+    params[0] = OSSL_PARAM_construct_utf8_string(OSSL_STORE_PARAM_PROPERTIES,
+        propq, 0);
+    params[1] = OSSL_PARAM_construct_end();
+    fake_store_clear_state();
+    if (!TEST_ptr(sctx = OSSL_STORE_open_ex(
+                      FAKE_STORE_SCHEME ":" FAKE_STORE_CMD_ONE_NAME,
+                      fake_libctx, FAKE_STORE_FETCH_PROPS, NULL, NULL,
+                      params, NULL, NULL))
+        || !TEST_true(fake_store_get_seen_params()
+            & FAKE_STORE_SEEN_PROPERTIES))
+        goto err;
+    OSSL_STORE_close(sctx);
+    sctx = NULL;
+
+    /* A set_ctx_params failure at open time must make the open fail */
+    params[0] = OSSL_PARAM_construct_int(OSSL_STORE_PARAM_EXPECT, &expect);
+    if (!TEST_ptr_null(OSSL_STORE_open_ex(
+            FAKE_STORE_SCHEME ":" FAKE_STORE_CMD_PARAMS_FAIL,
+            fake_libctx, FAKE_STORE_FETCH_PROPS, NULL, NULL,
+            params, NULL, NULL)))
+        goto err;
+
+    ret = 1;
+err:
+    OSSL_STORE_close(sctx);
+    return ret;
+}
+
+static int test_fake_store_find(void)
+{
+    OSSL_STORE_CTX *sctx = NULL;
+    OSSL_STORE_SEARCH *search = NULL;
+    X509_NAME *nm = NULL;
+    ASN1_INTEGER *serial = NULL;
+    unsigned char fingerprint[32] = { 0 };
+    const unsigned char *bytes = NULL;
+    size_t len = 0;
+    int ret = 0;
+
+    if (!TEST_ptr(sctx = OSSL_STORE_open_ex(
+                      FAKE_STORE_SCHEME ":" FAKE_STORE_CMD_ONE_NAME,
+                      fake_libctx, FAKE_STORE_FETCH_PROPS,
+                      NULL, NULL, NULL, NULL, NULL)))
+        goto err;
+
+    if (!TEST_true(OSSL_STORE_supports_search(sctx, OSSL_STORE_SEARCH_BY_NAME))
+        || !TEST_true(OSSL_STORE_supports_search(sctx,
+            OSSL_STORE_SEARCH_BY_ISSUER_SERIAL))
+        || !TEST_true(OSSL_STORE_supports_search(sctx,
+            OSSL_STORE_SEARCH_BY_KEY_FINGERPRINT))
+        || !TEST_true(OSSL_STORE_supports_search(sctx,
+            OSSL_STORE_SEARCH_BY_ALIAS)))
+        goto err;
+
+    if (!TEST_ptr(nm = X509_NAME_new())
+        || !TEST_true(X509_NAME_add_entry_by_txt(nm, "CN", MBSTRING_ASC,
+            (const unsigned char *)"fake", -1, -1, 0))
+        || !TEST_ptr(serial = ASN1_INTEGER_new())
+        || !TEST_true(ASN1_INTEGER_set(serial, 42)))
+        goto err;
+
+    fake_store_clear_state();
+    if (!TEST_ptr(search = OSSL_STORE_SEARCH_by_name(nm))
+        || !TEST_int_eq(OSSL_STORE_SEARCH_get_type(search),
+            OSSL_STORE_SEARCH_BY_NAME)
+        || !TEST_ptr_eq(OSSL_STORE_SEARCH_get0_name(search), nm)
+        || !TEST_true(OSSL_STORE_find(sctx, search))
+        || !TEST_true(fake_store_get_seen_params() & FAKE_STORE_SEEN_SUBJECT))
+        goto err;
+    OSSL_STORE_SEARCH_free(search);
+    search = NULL;
+
+    fake_store_clear_state();
+    if (!TEST_ptr(search = OSSL_STORE_SEARCH_by_issuer_serial(nm, serial))
+        || !TEST_int_eq(OSSL_STORE_SEARCH_get_type(search),
+            OSSL_STORE_SEARCH_BY_ISSUER_SERIAL)
+        || !TEST_ptr_eq(OSSL_STORE_SEARCH_get0_serial(search), serial)
+        || !TEST_true(OSSL_STORE_find(sctx, search))
+        || !TEST_true(fake_store_get_seen_params() & FAKE_STORE_SEEN_ISSUER)
+        || !TEST_true(fake_store_get_seen_params() & FAKE_STORE_SEEN_SERIAL))
+        goto err;
+    OSSL_STORE_SEARCH_free(search);
+    search = NULL;
+
+    fake_store_clear_state();
+    if (!TEST_ptr(search = OSSL_STORE_SEARCH_by_key_fingerprint(EVP_sha256(),
+                      fingerprint, sizeof(fingerprint)))
+        || !TEST_int_eq(OSSL_STORE_SEARCH_get_type(search),
+            OSSL_STORE_SEARCH_BY_KEY_FINGERPRINT)
+        || !TEST_ptr_eq(OSSL_STORE_SEARCH_get0_digest(search), EVP_sha256())
+        || !TEST_ptr(bytes = OSSL_STORE_SEARCH_get0_bytes(search, &len))
+        || !TEST_ptr_eq(bytes, fingerprint)
+        || !TEST_size_t_eq(len, sizeof(fingerprint))
+        || !TEST_true(OSSL_STORE_find(sctx, search))
+        || !TEST_true(fake_store_get_seen_params() & FAKE_STORE_SEEN_DIGEST)
+        || !TEST_true(fake_store_get_seen_params()
+            & FAKE_STORE_SEEN_FINGERPRINT))
+        goto err;
+    OSSL_STORE_SEARCH_free(search);
+    search = NULL;
+
+    fake_store_clear_state();
+    if (!TEST_ptr(search = OSSL_STORE_SEARCH_by_alias("myalias"))
+        || !TEST_int_eq(OSSL_STORE_SEARCH_get_type(search),
+            OSSL_STORE_SEARCH_BY_ALIAS)
+        || !TEST_str_eq(OSSL_STORE_SEARCH_get0_string(search), "myalias")
+        || !TEST_true(OSSL_STORE_find(sctx, search))
+        || !TEST_true(fake_store_get_seen_params() & FAKE_STORE_SEEN_ALIAS))
+        goto err;
+
+    ret = 1;
+err:
+    OSSL_STORE_SEARCH_free(search);
+    X509_NAME_free(nm);
+    ASN1_INTEGER_free(serial);
+    OSSL_STORE_close(sctx);
+    return ret;
+}
+
+static int test_fake_store_loading_started(void)
+{
+    OSSL_STORE_CTX *sctx = NULL;
+    OSSL_STORE_INFO *info = NULL;
+    OSSL_STORE_SEARCH *search = NULL;
+    int ret = 0;
+
+    if (!TEST_ptr(sctx = OSSL_STORE_open_ex(
+                      FAKE_STORE_SCHEME ":" FAKE_STORE_CMD_TWO_NAMES,
+                      fake_libctx, FAKE_STORE_FETCH_PROPS,
+                      NULL, NULL, NULL, NULL, NULL))
+        || !TEST_false(OSSL_STORE_expect(NULL, OSSL_STORE_INFO_CERT))
+        || !TEST_false(OSSL_STORE_expect(sctx, -1))
+        || !TEST_false(OSSL_STORE_expect(sctx, OSSL_STORE_INFO_SKEY + 1))
+        || !TEST_ptr(search = OSSL_STORE_SEARCH_by_alias("myalias")))
+        goto err;
+
+    /* Once loading has started, expect() and find() must fail */
+    if (!TEST_ptr(info = OSSL_STORE_load(sctx))
+        || !TEST_false(OSSL_STORE_expect(sctx, OSSL_STORE_INFO_NAME))
+        || !TEST_false(OSSL_STORE_find(sctx, search)))
+        goto err;
+
+    ret = 1;
+err:
+    OSSL_STORE_SEARCH_free(search);
+    OSSL_STORE_INFO_free(info);
+    OSSL_STORE_close(sctx);
+    return ret;
+}
+
+static OSSL_STORE_INFO *filter_name2(OSSL_STORE_INFO *info, void *arg)
+{
+    int *calls = arg;
+
+    (*calls)++;
+    if (strcmp(OSSL_STORE_INFO_get0_NAME(info), "name2") == 0) {
+        OSSL_STORE_INFO_free(info);
+        return NULL;
+    }
+    return info;
+}
+
+static int test_fake_store_post_process(void)
+{
+    OSSL_STORE_CTX *sctx = NULL;
+    OSSL_STORE_INFO *info = NULL;
+    int calls = 0;
+    int ret = 0;
+
+    if (!TEST_ptr(sctx = OSSL_STORE_open_ex(
+                      FAKE_STORE_SCHEME ":" FAKE_STORE_CMD_TWO_NAMES,
+                      fake_libctx, FAKE_STORE_FETCH_PROPS, NULL, NULL, NULL,
+                      filter_name2, &calls))
+        || !TEST_ptr(info = OSSL_STORE_load(sctx))
+        || !TEST_str_eq(OSSL_STORE_INFO_get0_NAME(info), "name1")
+        || !TEST_int_eq(calls, 2)
+        || !TEST_true(OSSL_STORE_eof(sctx)))
+        goto err;
+
+    ret = 1;
+err:
+    OSSL_STORE_INFO_free(info);
+    OSSL_STORE_close(sctx);
+    return ret;
+}
+
+static int test_fake_store_attach(void)
+{
+    OSSL_STORE_CTX *sctx = NULL;
+    OSSL_STORE_INFO *info = NULL;
+    BIO *bio = NULL;
+    OSSL_PARAM params[2];
+    int expect = OSSL_STORE_INFO_NAME;
+    int ret = 0;
+
+    params[0] = OSSL_PARAM_construct_int(OSSL_STORE_PARAM_EXPECT, &expect);
+    params[1] = OSSL_PARAM_construct_end();
+
+    fake_store_clear_state();
+    if (!TEST_ptr(bio = BIO_new(BIO_s_mem()))
+        || !TEST_ptr(sctx = OSSL_STORE_attach(bio, FAKE_STORE_SCHEME,
+                         fake_libctx, FAKE_STORE_FETCH_PROPS,
+                         NULL, NULL, params, NULL, NULL))
+        || !TEST_true(fake_store_get_seen_params() & FAKE_STORE_SEEN_EXPECT)
+        || !TEST_true(fake_store_get_seen_params()
+            & FAKE_STORE_SEEN_PROPERTIES)
+        || !TEST_ptr(info = OSSL_STORE_load(sctx))
+        || !TEST_str_eq(OSSL_STORE_INFO_get0_NAME(info), "name1"))
+        goto err;
+
+    ret = 1;
+err:
+    OSSL_STORE_INFO_free(info);
+    OSSL_STORE_close(sctx);
+    BIO_free(bio);
+    return ret;
+}
+
+static int test_fake_store_delete(void)
+{
+    fake_store_clear_state();
+    if (!TEST_int_eq(OSSL_STORE_delete(FAKE_STORE_SCHEME ":object",
+                         fake_libctx, FAKE_STORE_FETCH_PROPS,
+                         NULL, NULL, NULL),
+            1)
+        || !TEST_str_eq(fake_store_get0_last_deleted(),
+            FAKE_STORE_SCHEME ":object"))
+        return 0;
+
+    /* The fake-ex loader has no delete support */
+    return TEST_int_eq(OSSL_STORE_delete(FAKE_STORE_SCHEME_OPEN_EX ":object",
+                           fake_libctx, FAKE_STORE_FETCH_PROPS,
+                           NULL, NULL, NULL),
+        0);
+}
+
 const OPTIONS *test_get_options(void)
 {
     static const OPTIONS test_options[] = {
@@ -358,6 +712,10 @@ int setup_tests(void)
         return 0;
     }

+    if (!TEST_ptr(fake_libctx = OSSL_LIB_CTX_new())
+        || !TEST_ptr(fake_prov = fake_store_start(fake_libctx)))
+        return 0;
+
     if (infile != NULL)
         ADD_TEST(test_store_open);
 #ifndef OPENSSL_NO_WINSTORE
@@ -370,5 +728,20 @@ int setup_tests(void)
     ADD_ALL_TESTS(test_store_get_params, 3);
     if (sm2file != NULL)
         ADD_TEST(test_store_attach_unregistered_scheme);
+    ADD_TEST(test_fake_store_names);
+    ADD_TEST(test_fake_store_open_ex);
+    ADD_TEST(test_fake_store_params);
+    ADD_TEST(test_fake_store_find);
+    ADD_TEST(test_fake_store_loading_started);
+    ADD_TEST(test_fake_store_post_process);
+    ADD_TEST(test_fake_store_attach);
+    ADD_TEST(test_fake_store_delete);
     return 1;
 }
+
+void cleanup_tests(void)
+{
+    if (fake_prov != NULL)
+        fake_store_finish(fake_prov);
+    OSSL_LIB_CTX_free(fake_libctx);
+}