Commit 0c2a196af8 for openssl.org
commit 0c2a196af8ab9c12da27826fa6024ca1b22f6db1
Author: Daniel Kubec <kubec@openssl.org>
Date: Mon Nov 24 02:25:08 2025 +0100
CONF: Add support for configurations per OSSL_LIB_CTX
Add support for configurations per OSSL_LIB_CTX and fix cross-context overrides.
Fixes #19248
Fixes #19243
Co-authored-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Nikola Pajkovsky <nikolap@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/29145)
diff --git a/crypto/conf/conf_mod.c b/crypto/conf/conf_mod.c
index 631eb3fec0..3559293f27 100644
--- a/crypto/conf/conf_mod.c
+++ b/crypto/conf/conf_mod.c
@@ -15,6 +15,7 @@
#include <stdio.h>
#include <ctype.h>
#include <openssl/crypto.h>
+#include <openssl/conf.h>
#include "internal/conf.h"
#include <openssl/conf_api.h>
#include "internal/dso.h"
@@ -30,39 +31,6 @@ DEFINE_STACK_OF(CONF_IMODULE)
#define DSO_mod_init_name "OPENSSL_init"
#define DSO_mod_finish_name "OPENSSL_finish"
-/*
- * This structure contains a data about supported modules. entries in this
- * table correspond to either dynamic or static modules.
- */
-
-struct conf_module_st {
- /* DSO of this module or NULL if static */
- DSO *dso;
- /* Name of the module */
- char *name;
- /* Init function */
- conf_init_func *init;
- /* Finish function */
- conf_finish_func *finish;
- /* Number of successfully initialized modules */
- int links;
- void *usr_data;
-};
-
-/*
- * This structure contains information about modules that have been
- * successfully initialized. There may be more than one entry for a given
- * module.
- */
-
-struct conf_imodule_st {
- CONF_MODULE *pmod;
- char *name;
- char *value;
- unsigned long flags;
- void *usr_data;
-};
-
static CRYPTO_ONCE init_module_list_lock = CRYPTO_ONCE_STATIC_INIT;
static CRYPTO_RCU_LOCK *module_list_lock = NULL;
static STACK_OF(CONF_MODULE) *supported_modules = NULL; /* protected by lock */
@@ -447,6 +415,7 @@ static int module_init(CONF_MODULE *pmod, const char *name, const char *value,
imod->name = OPENSSL_strdup(name);
imod->value = OPENSSL_strdup(value);
imod->usr_data = NULL;
+ imod->libctx = NULL;
if (!imod->name || !imod->value)
goto memerr;
diff --git a/crypto/conf/conf_ssl.c b/crypto/conf/conf_ssl.c
index 53ebc98c8b..bc1706aca5 100644
--- a/crypto/conf/conf_ssl.c
+++ b/crypto/conf/conf_ssl.c
@@ -11,51 +11,41 @@
#include <string.h>
#include <openssl/conf.h>
#include <openssl/err.h>
-#include "internal/sslconf.h"
#include "conf_local.h"
+#include "internal/sslconf.h"
+#include "internal/core.h"
+#include "internal/cryptlib.h"
-/*
- * SSL library configuration module placeholder. We load it here but defer
- * all decisions about its contents to libssl.
- */
-
-struct ssl_conf_name_st {
- /* Name of this set of commands */
- char *name;
- /* List of commands */
- SSL_CONF_CMD *cmds;
- /* Number of commands */
- size_t cmd_count;
-};
+typedef struct ssl_module_st SSL_MODULE;
-struct ssl_conf_cmd_st {
- /* Command */
- char *cmd;
- /* Argument */
- char *arg;
+struct ssl_module_st {
+ struct ssl_conf_name_st *names;
+ size_t names_count;
};
-static struct ssl_conf_name_st *ssl_names;
-static size_t ssl_names_count;
-
static void ssl_module_free(CONF_IMODULE *md)
{
- size_t i, j;
- if (ssl_names == NULL)
+ SSL_MODULE *ssl = CONF_imodule_get_usr_data(md);
+
+ if (ssl == NULL)
return;
- for (i = 0; i < ssl_names_count; i++) {
- struct ssl_conf_name_st *tname = ssl_names + i;
+
+ CONF_imodule_set_usr_data(md, NULL);
+ ossl_lib_ctx_detach_ssl_conf_imodule(NULL, md);
+
+ for (size_t i = 0; i < ssl->names_count; i++) {
+ struct ssl_conf_name_st *tname = ssl->names + i;
OPENSSL_free(tname->name);
- for (j = 0; j < tname->cmd_count; j++) {
+ for (size_t j = 0; j < tname->cmd_count; j++) {
OPENSSL_free(tname->cmds[j].cmd);
OPENSSL_free(tname->cmds[j].arg);
}
OPENSSL_free(tname->cmds);
}
- OPENSSL_free(ssl_names);
- ssl_names = NULL;
- ssl_names_count = 0;
+
+ OPENSSL_free(ssl->names);
+ OPENSSL_free(ssl);
}
static int ssl_module_init(CONF_IMODULE *md, const CONF *cnf)
@@ -64,6 +54,8 @@ static int ssl_module_init(CONF_IMODULE *md, const CONF *cnf)
int rv = 0;
const char *ssl_conf_section;
STACK_OF(CONF_VALUE) *cmd_lists;
+ OSSL_LIB_CTX *libctx;
+ SSL_MODULE *ssl = NULL;
ssl_conf_section = CONF_imodule_get_value(md);
cmd_lists = NCONF_get_section(cnf, ssl_conf_section);
@@ -78,12 +70,21 @@ static int ssl_module_init(CONF_IMODULE *md, const CONF *cnf)
}
cnt = sk_CONF_VALUE_num(cmd_lists);
ssl_module_free(md);
- ssl_names = OPENSSL_calloc(cnt, sizeof(*ssl_names));
- if (ssl_names == NULL)
+
+ ssl = OPENSSL_zalloc(sizeof(*ssl));
+ if (ssl == NULL)
goto err;
- ssl_names_count = cnt;
- for (i = 0; i < ssl_names_count; i++) {
- struct ssl_conf_name_st *ssl_name = ssl_names + i;
+ CONF_imodule_set_usr_data(md, ssl);
+
+ ssl->names = OPENSSL_calloc(cnt, sizeof(*ssl->names));
+ libctx = ossl_lib_ctx_get_concrete(cnf->libctx);
+ if (libctx == NULL || ssl->names == NULL)
+ goto err;
+
+ ossl_lib_ctx_attach_ssl_conf_imodule(libctx, md);
+ ssl->names_count = cnt;
+ for (i = 0; i < ssl->names_count; i++) {
+ struct ssl_conf_name_st *ssl_name = ssl->names + i;
CONF_VALUE *sect = sk_CONF_VALUE_value(cmd_lists, (int)i);
STACK_OF(CONF_VALUE) *cmds = NCONF_get_section(cnf, sect->value);
@@ -135,11 +136,16 @@ static int ssl_module_init(CONF_IMODULE *md, const CONF *cnf)
* conf_ssl_name_find. Also stores the name of the set of commands in |*name|
* and the number of commands in the set in |*cnt|.
*/
-const SSL_CONF_CMD *conf_ssl_get(size_t idx, const char **name, size_t *cnt)
+const SSL_CONF_CMD *conf_ssl_get(CONF_IMODULE *md, size_t idx, const char **name, size_t *cnt)
{
- *name = ssl_names[idx].name;
- *cnt = ssl_names[idx].cmd_count;
- return ssl_names[idx].cmds;
+ SSL_MODULE *ssl = md != NULL ? CONF_imodule_get_usr_data(md): NULL;
+
+ if (ssl == NULL || ssl->names == NULL)
+ return NULL;
+
+ *name = ssl->names[idx].name;
+ *cnt = ssl->names[idx].cmd_count;
+ return ssl->names[idx].cmds;
}
/*
@@ -147,14 +153,16 @@ const SSL_CONF_CMD *conf_ssl_get(size_t idx, const char **name, size_t *cnt)
* index for the command set in |*idx|.
* Returns 1 on success or 0 on failure.
*/
-int conf_ssl_name_find(const char *name, size_t *idx)
+int conf_ssl_name_find(CONF_IMODULE *md, const char *name, size_t *idx)
{
- size_t i;
+ SSL_MODULE *ssl = md != NULL ? CONF_imodule_get_usr_data(md): NULL;
const struct ssl_conf_name_st *nm;
+ size_t i;
- if (name == NULL)
+ if (ssl == NULL || ssl->names == NULL)
return 0;
- for (i = 0, nm = ssl_names; i < ssl_names_count; i++, nm++) {
+
+ for (i = 0, nm = ssl->names; i < ssl->names_count; i++, nm++) {
if (strcmp(nm->name, name) == 0) {
*idx = i;
return 1;
diff --git a/crypto/context.c b/crypto/context.c
index 80a87562cd..80c29a2161 100644
--- a/crypto/context.c
+++ b/crypto/context.c
@@ -16,14 +16,14 @@
#include "internal/core.h"
#include "internal/bio.h"
#include "internal/provider.h"
-#include "internal/threads_common.h"
+#include "internal/conf.h"
#include "crypto/decoder.h"
#include "crypto/context.h"
struct ossl_lib_ctx_st {
CRYPTO_RWLOCK *lock;
OSSL_EX_DATA_GLOBAL global;
-
+ CONF_IMODULE *ssl_imod;
void *property_string_data;
void *evp_method_store;
void *provider_store;
@@ -85,6 +85,34 @@ int ossl_lib_ctx_is_child(OSSL_LIB_CTX *ctx)
return ctx->ischild;
}
+int ossl_lib_ctx_attach_ssl_conf_imodule(OSSL_LIB_CTX *ctx, CONF_IMODULE *md)
+{
+ if (ctx == NULL || md == NULL || ctx->ssl_imod != NULL)
+ return 0;
+
+ ctx->ssl_imod = md;
+ md->libctx = ctx;
+ return 1;
+}
+
+int ossl_lib_ctx_detach_ssl_conf_imodule(OSSL_LIB_CTX *ctx, CONF_IMODULE *md)
+{
+ if (ctx != NULL && md != NULL)
+ return 0;
+
+ if (ctx != NULL && ctx->ssl_imod) {
+ ctx->ssl_imod->libctx = NULL;
+ ctx->ssl_imod = NULL;
+ }
+
+ if (md != NULL && md->libctx) {
+ md->libctx->ssl_imod = NULL;
+ md->libctx = NULL;
+ }
+
+ return 1;
+}
+
static void context_deinit_objs(OSSL_LIB_CTX *ctx);
static int context_init(OSSL_LIB_CTX *ctx)
@@ -362,6 +390,8 @@ static int context_deinit(OSSL_LIB_CTX *ctx)
if (ctx == NULL)
return 1;
+ ossl_lib_ctx_detach_ssl_conf_imodule(ctx, NULL);
+
ossl_ctx_thread_stop(ctx);
context_deinit_objs(ctx);
@@ -612,6 +642,9 @@ void *ossl_lib_ctx_get_data(OSSL_LIB_CTX *ctx, int index)
case OSSL_LIB_CTX_COMP_METHODS:
return (void *)&ctx->comp_methods;
+ case OSSL_LIB_CTX_SSL_CONF_IMODULE:
+ return (void *)ctx->ssl_imod;
+
default:
return NULL;
}
diff --git a/include/internal/conf.h b/include/internal/conf.h
index 8c6c29cd2c..54794dc620 100644
--- a/include/internal/conf.h
+++ b/include/internal/conf.h
@@ -11,6 +11,9 @@
# define OSSL_INTERNAL_CONF_H
# pragma once
+# include "internal/dso.h"
+# include "internal/thread_once.h"
+
# include <openssl/conf.h>
# define DEFAULT_CONF_MFLAGS \
@@ -24,6 +27,40 @@ struct ossl_init_settings_st {
unsigned long flags;
};
+/*
+ * This structure contains a data about supported modules. entries in this
+ * table correspond to either dynamic or static modules.
+ */
+
+struct conf_module_st {
+ /* DSO of this module or NULL if static */
+ DSO *dso;
+ /* Name of the module */
+ char *name;
+ /* Init function */
+ conf_init_func *init;
+ /* Finish function */
+ conf_finish_func *finish;
+ /* Number of successfully initialized modules */
+ int links;
+ void *usr_data;
+};
+
+/*
+ * This structure contains information about modules that have been
+ * successfully initialized. There may be more than one entry for a given
+ * module.
+ */
+
+struct conf_imodule_st {
+ CONF_MODULE *pmod;
+ OSSL_LIB_CTX *libctx;
+ char *name;
+ char *value;
+ unsigned long flags;
+ void *usr_data;
+};
+
int ossl_config_int(const OPENSSL_INIT_SETTINGS *);
void ossl_no_config_int(void);
void ossl_config_modules_free(void);
diff --git a/include/internal/core.h b/include/internal/core.h
index 03adb66bd3..1b559c22d1 100644
--- a/include/internal/core.h
+++ b/include/internal/core.h
@@ -11,6 +11,8 @@
# define OSSL_INTERNAL_CORE_H
# pragma once
+# include <openssl/conf.h>
+
/*
* namespaces:
*
@@ -68,4 +70,7 @@ __owur int ossl_lib_ctx_write_lock(OSSL_LIB_CTX *ctx);
__owur int ossl_lib_ctx_read_lock(OSSL_LIB_CTX *ctx);
int ossl_lib_ctx_unlock(OSSL_LIB_CTX *ctx);
int ossl_lib_ctx_is_child(OSSL_LIB_CTX *ctx);
+int ossl_lib_ctx_attach_ssl_conf_imodule(OSSL_LIB_CTX *ctx, CONF_IMODULE *md);
+int ossl_lib_ctx_detach_ssl_conf_imodule(OSSL_LIB_CTX *ctx, CONF_IMODULE *md);
+
#endif
diff --git a/include/internal/cryptlib.h b/include/internal/cryptlib.h
index 11c1023b19..378375ba14 100644
--- a/include/internal/cryptlib.h
+++ b/include/internal/cryptlib.h
@@ -121,6 +121,7 @@ typedef struct ossl_ex_data_global_st {
# define OSSL_LIB_CTX_COMP_METHODS 21
# define OSSL_LIB_CTX_INDICATOR_CB_INDEX 22
# define OSSL_LIB_CTX_MAX_INDEXES 22
+# define OSSL_LIB_CTX_SSL_CONF_IMODULE 23
OSSL_LIB_CTX *ossl_lib_ctx_get_concrete(OSSL_LIB_CTX *ctx);
int ossl_lib_ctx_is_default(OSSL_LIB_CTX *ctx);
diff --git a/include/internal/sslconf.h b/include/internal/sslconf.h
index fd7f7e3331..56176845ea 100644
--- a/include/internal/sslconf.h
+++ b/include/internal/sslconf.h
@@ -13,9 +13,30 @@
typedef struct ssl_conf_cmd_st SSL_CONF_CMD;
-const SSL_CONF_CMD *conf_ssl_get(size_t idx, const char **name, size_t *cnt);
-int conf_ssl_name_find(const char *name, size_t *idx);
-void conf_ssl_get_cmd(const SSL_CONF_CMD *cmd, size_t idx, char **cmdstr,
- char **arg);
+/*
+ * SSL library configuration module placeholder. We load it here but defer
+ * all decisions about its contents to libssl.
+ */
+
+struct ssl_conf_name_st {
+ /* Name of this set of commands */
+ char *name;
+ /* List of commands */
+ SSL_CONF_CMD *cmds;
+ /* Number of commands */
+ size_t cmd_count;
+};
+
+struct ssl_conf_cmd_st {
+ /* Command */
+ char *cmd;
+ /* Argument */
+ char *arg;
+};
+
+const SSL_CONF_CMD *conf_ssl_get(CONF_IMODULE *m, size_t idx, const char **name,
+ size_t *cnt);
+int conf_ssl_name_find(CONF_IMODULE *m, const char *name, size_t *idx);
+void conf_ssl_get_cmd(const SSL_CONF_CMD *c, size_t idx, char **cmd, char **arg);
#endif
diff --git a/ssl/ssl_mcnf.c b/ssl/ssl_mcnf.c
index 06a23344b2..7bb69d90b8 100644
--- a/ssl/ssl_mcnf.c
+++ b/ssl/ssl_mcnf.c
@@ -10,8 +10,10 @@
#include <stdio.h>
#include <openssl/conf.h>
#include <openssl/ssl.h>
+#include <openssl/trace.h>
#include "ssl_local.h"
#include "internal/sslconf.h"
+#include "internal/cryptlib.h"
/* SSL library configuration module. */
@@ -20,6 +22,17 @@ void SSL_add_ssl_module(void)
/* Do nothing. This will be added automatically by libcrypto */
}
+static CONF_IMODULE *ssl_do_lookup_module(OSSL_LIB_CTX *libctx)
+{
+ CONF_IMODULE *m = OSSL_LIB_CTX_get_data(libctx, OSSL_LIB_CTX_SSL_CONF_IMODULE);
+
+ if (m != NULL)
+ return m;
+
+ libctx = OSSL_LIB_CTX_get0_global_default();
+ return OSSL_LIB_CTX_get_data(libctx, OSSL_LIB_CTX_SSL_CONF_IMODULE);
+}
+
static int ssl_do_config(SSL *s, SSL_CTX *ctx, const char *name, int system)
{
SSL_CONF_CTX *cctx = NULL;
@@ -29,32 +42,37 @@ static int ssl_do_config(SSL *s, SSL_CTX *ctx, const char *name, int system)
unsigned int conf_diagnostics = 0;
const SSL_METHOD *meth;
const SSL_CONF_CMD *cmds;
- OSSL_LIB_CTX *prev_libctx = NULL;
- OSSL_LIB_CTX *libctx = NULL;
+ OSSL_LIB_CTX *libctx = NULL, *prev_libctx = NULL;
+ CONF_IMODULE *imod = NULL;
if (s == NULL && ctx == NULL) {
ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER);
goto err;
}
-
if (name == NULL && system)
name = "system_default";
- if (!conf_ssl_name_find(name, &idx)) {
+
+ libctx = s != NULL ? s->ctx->libctx: ctx->libctx;
+ imod = ssl_do_lookup_module(libctx);
+ if (!conf_ssl_name_find(imod, name, &idx)) {
if (!system)
ERR_raise_data(ERR_LIB_SSL, SSL_R_INVALID_CONFIGURATION_NAME,
"name=%s", name);
goto err;
}
- cmds = conf_ssl_get(idx, &name, &cmd_count);
+
+ cmds = conf_ssl_get(imod, idx, &name, &cmd_count);
+ flags = SSL_CONF_FLAG_FILE;
+ if (!system)
+ flags |= SSL_CONF_FLAG_CERTIFICATE | SSL_CONF_FLAG_REQUIRE_PRIVATE;
+
cctx = SSL_CONF_CTX_new();
if (cctx == NULL) {
/* this is a fatal error, always report */
system = 0;
goto err;
}
- flags = SSL_CONF_FLAG_FILE;
- if (!system)
- flags |= SSL_CONF_FLAG_CERTIFICATE | SSL_CONF_FLAG_REQUIRE_PRIVATE;
+
if (s != NULL) {
meth = s->method;
SSL_CONF_CTX_set_ssl(cctx, s);
@@ -64,6 +82,7 @@ static int ssl_do_config(SSL *s, SSL_CTX *ctx, const char *name, int system)
SSL_CONF_CTX_set_ssl_ctx(cctx, ctx);
libctx = ctx->libctx;
}
+
conf_diagnostics = OSSL_LIB_CTX_get_conf_diagnostics(libctx);
if (conf_diagnostics)
flags |= SSL_CONF_FLAG_SHOW_ERRORS;
diff --git a/test/build.info b/test/build.info
index 0fcb2c80fd..1392bb1a48 100644
--- a/test/build.info
+++ b/test/build.info
@@ -66,7 +66,7 @@ IF[{- !$disabled{tests} -}]
context_internal_test aesgcmtest params_test evp_pkey_dparams_test \
keymgmt_internal_test hexstr_test provider_status_test defltfips_test \
bio_readbuffer_test user_property_test pkcs7_test upcallstest \
- provfetchtest prov_config_test rand_test \
+ provfetchtest prov_config_test libctx_config_test rand_test \
ca_internals_test bio_tfo_test membio_test bio_dgram_test list_test \
fips_version_test x509_test hpke_test pairwise_fail_test \
nodefltctxtest evp_xof_test x509_load_cert_file_test bio_meth_test \
@@ -253,6 +253,10 @@ IF[{- !$disabled{tests} -}]
INCLUDE[prov_config_test]=../include ../apps/include
DEPEND[prov_config_test]=../libcrypto libtestutil.a
+ SOURCE[libctx_config_test]=libctx_config_test.c
+ INCLUDE[libctx_config_test]=../include ../apps/include
+ DEPEND[libctx_config_test]=../libcrypto ../libssl libtestutil.a
+
SOURCE[evp_pkey_provided_test]=evp_pkey_provided_test.c
INCLUDE[evp_pkey_provided_test]=../include ../apps/include
DEPEND[evp_pkey_provided_test]=../libcrypto.a libtestutil.a
@@ -1176,6 +1180,7 @@ IF[{- !$disabled{tests} -}]
IF[{- $disabled{module} || !$target{dso_scheme} -}]
DEFINE[provider_test]=NO_PROVIDER_MODULE
DEFINE[prov_config_test]=NO_PROVIDER_MODULE
+ DEFINE[libctx_config_test]=NO_PROVIDER_MODULE
DEFINE[provider_internal_test]=NO_PROVIDER_MODULE
ENDIF
DEPEND[]=provider_internal_test.cnf
diff --git a/test/libctx_config_test.c b/test/libctx_config_test.c
new file mode 100644
index 0000000000..ad390c7273
--- /dev/null
+++ b/test/libctx_config_test.c
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2025 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 <sys/stat.h>
+#include <openssl/conf.h>
+#include <openssl/ssl.h>
+#include "testutil.h"
+
+static char *cfg1 = NULL;
+static char *cfg2 = NULL;
+
+static const struct cfg_proto_version {
+ char **file;
+ long minver, maxver;
+} cfg_proto_version[] = {
+ { &cfg1, TLS1_1_VERSION, TLS1_1_VERSION },
+ { &cfg2, TLS1_2_VERSION, TLS1_2_VERSION }
+};
+
+static int test_config_libctx_global(int idx)
+{
+ const struct cfg_proto_version *cpv = &cfg_proto_version[idx];
+ SSL_CTX *ctx = NULL;
+ int test;
+
+ CONF_modules_load_file(*cpv->file, NULL, 0);
+
+ test = TEST_ptr(ctx = SSL_CTX_new(TLS_method()))
+ && TEST_int_eq(SSL_CTX_get_min_proto_version(ctx), cpv->minver)
+ && TEST_int_eq(SSL_CTX_get_max_proto_version(ctx), cpv->maxver);
+
+ CONF_modules_unload(0);
+ SSL_CTX_free(ctx);
+ return test;
+}
+
+static int test_config_libctx_local(void)
+{
+ OSSL_LIB_CTX *lib1 = NULL, *lib2 = NULL;
+ SSL_CTX *ctx1 = NULL, *ctx2 = NULL;
+ int test;
+
+ test = TEST_ptr(lib1 = OSSL_LIB_CTX_new())
+ && TEST_ptr(lib2 = OSSL_LIB_CTX_new())
+ && TEST_int_eq(OSSL_LIB_CTX_load_config(lib1, cfg1), 1)
+ && TEST_int_eq(OSSL_LIB_CTX_load_config(lib2, cfg2), 1)
+ && TEST_ptr(ctx1 = SSL_CTX_new_ex(lib1, NULL, TLS_server_method()))
+ && TEST_ptr(ctx2 = SSL_CTX_new_ex(lib2, NULL, TLS_server_method()))
+ && TEST_int_eq(SSL_CTX_get_min_proto_version(ctx1), TLS1_1_VERSION)
+ && TEST_int_eq(SSL_CTX_get_min_proto_version(ctx2), TLS1_2_VERSION)
+ && TEST_int_eq(SSL_CTX_get_max_proto_version(ctx1), TLS1_1_VERSION)
+ && TEST_int_eq(SSL_CTX_get_max_proto_version(ctx2), TLS1_2_VERSION);
+
+ SSL_CTX_free(ctx1);
+ SSL_CTX_free(ctx2);
+ OSSL_LIB_CTX_free(lib1);
+ OSSL_LIB_CTX_free(lib2);
+ return test;
+}
+
+OPT_TEST_DECLARE_USAGE("configfile\n")
+
+int setup_tests(void)
+{
+ int test;
+
+ test = TEST_true(test_skip_common_options())
+ && TEST_ptr(cfg1 = test_get_argument(0))
+ && TEST_ptr(cfg2 = test_get_argument(1));
+
+ ADD_TEST(test_config_libctx_local);
+ ADD_ALL_TESTS(test_config_libctx_global, OSSL_NELEM(cfg_proto_version));
+
+ return test;
+}
diff --git a/test/recipes/30-test_libctx_config.t b/test/recipes/30-test_libctx_config.t
new file mode 100644
index 0000000000..4c19112be7
--- /dev/null
+++ b/test/recipes/30-test_libctx_config.t
@@ -0,0 +1,25 @@
+#! /usr/bin/env perl
+# Copyright 2025 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
+
+
+use OpenSSL::Test::Simple;
+use OpenSSL::Test qw/:DEFAULT srctop_file srctop_dir bldtop_dir/;
+use OpenSSL::Test::Utils;
+
+BEGIN {
+setup("test_prov_config");
+}
+
+use lib srctop_dir('Configurations');
+use lib bldtop_dir('.');
+
+plan tests => 1;
+
+ok(run(test(["libctx_config_test", srctop_file("test", "tls-max-v11.cnf"),
+ srctop_file("test", "tls-max-v12.cnf")])),
+ "running libctx_config_test default.cnf");
diff --git a/test/tls-max-v11.cnf b/test/tls-max-v11.cnf
new file mode 100644
index 0000000000..ab764c19d9
--- /dev/null
+++ b/test/tls-max-v11.cnf
@@ -0,0 +1,14 @@
+openssl_conf = openssl_init
+
+# Comment out the next line to ignore configuration errors
+config_diagnostics = 1
+
+[openssl_init]
+ssl_conf = ssl_configuration
+
+[ssl_configuration]
+system_default = tls_system_default
+
+[tls_system_default]
+MinProtocol = TLSv1.1
+MaxProtocol = TLSv1.1
diff --git a/test/tls-max-v12.cnf b/test/tls-max-v12.cnf
new file mode 100644
index 0000000000..6b07369d13
--- /dev/null
+++ b/test/tls-max-v12.cnf
@@ -0,0 +1,14 @@
+openssl_conf = openssl_init
+
+# Comment out the next line to ignore configuration errors
+config_diagnostics = 1
+
+[openssl_init]
+ssl_conf = ssl_configuration
+
+[ssl_configuration]
+system_default = tls_system_default
+
+[tls_system_default]
+MinProtocol = TLSv1.2
+MaxProtocol = TLSv1.2