Author: dsahlberg
Date: Tue Jul 1 08:52:03 2025
New Revision: 1926894
URL: http://svn.apache.org/viewvc?rev=1926894&view=rev
Log:
On the PR-8 branch:
Applied all commits from the PR to allow for easier review and testing.
Full commit message with comments will follow when merging to trunk.
Modified:
serf/branches/PR-8/CMakeLists.txt
serf/branches/PR-8/SConstruct
serf/branches/PR-8/buckets/ssl_buckets.c
serf/branches/PR-8/serf_bucket_types.h
serf/branches/PR-8/test/serf_get.c
serf/branches/PR-8/test/test_ssl.c
Modified: serf/branches/PR-8/CMakeLists.txt
URL:
http://svn.apache.org/viewvc/serf/branches/PR-8/CMakeLists.txt?rev=1926894&r1=1926893&r2=1926894&view=diff
==============================================================================
--- serf/branches/PR-8/CMakeLists.txt (original)
+++ serf/branches/PR-8/CMakeLists.txt Tue Jul 1 08:52:03 2025
@@ -305,6 +305,11 @@ CheckNotFunction("X509_STORE_CTX_get0_ch
CheckNotFunction("ASN1_STRING_get0_data" "NULL"
"SERF_NO_SSL_ASN1_STRING_GET0_DATA"
"openssl/asn1.h" "${OPENSSL_INCLUDE_DIR}"
${OPENSSL_LIBRARIES} ${SERF_STANDARD_LIBRARIES})
+CheckFunction("OSSL_STORE_open_ex"
+ "NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL"
+ "SERF_HAVE_OSSL_STORE_OPEN_EX" "openssl/store.h"
+ "${OPENSSL_INCLUDE_DIR}" ${OPENSSL_LIBRARIES}
+ ${SERF_STANDARD_LIBRARIES})
CheckFunction("CRYPTO_set_locking_callback" "NULL"
"SERF_HAVE_SSL_LOCKING_CALLBACKS"
"openssl/crypto.h" "${OPENSSL_INCLUDE_DIR}"
${OPENSSL_LIBRARIES} ${SERF_STANDARD_LIBRARIES})
Modified: serf/branches/PR-8/SConstruct
URL:
http://svn.apache.org/viewvc/serf/branches/PR-8/SConstruct?rev=1926894&r1=1926893&r2=1926894&view=diff
==============================================================================
--- serf/branches/PR-8/SConstruct (original)
+++ serf/branches/PR-8/SConstruct Tue Jul 1 08:52:03 2025
@@ -612,6 +612,9 @@ if conf.CheckFunc('OpenSSL_version_num',
env.Append(CPPDEFINES=['SERF_HAVE_OPENSSL_VERSION_NUM'])
if conf.CheckFunc('SSL_set_alpn_protos', ssl_includes, 'C', 'NULL, NULL, 0'):
env.Append(CPPDEFINES=['SERF_HAVE_OPENSSL_ALPN'])
+if conf.CheckFunc('OSSL_STORE_open_ex', ssl_includes, 'C',
+ 'NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL'):
+ env.Append(CPPDEFINES=['SERF_HAVE_OSSL_STORE_OPEN_EX'])
if conf.CheckType('OSSL_HANDSHAKE_STATE', ssl_includes):
env.Append(CPPDEFINES=['SERF_HAVE_OSSL_HANDSHAKE_STATE'])
env = conf.Finish()
Modified: serf/branches/PR-8/buckets/ssl_buckets.c
URL:
http://svn.apache.org/viewvc/serf/branches/PR-8/buckets/ssl_buckets.c?rev=1926894&r1=1926893&r2=1926894&view=diff
==============================================================================
--- serf/branches/PR-8/buckets/ssl_buckets.c (original)
+++ serf/branches/PR-8/buckets/ssl_buckets.c Tue Jul 1 08:52:03 2025
@@ -44,6 +44,15 @@
#ifndef OPENSSL_NO_OCSP /* requires openssl 0.9.7 or later */
#include <openssl/ocsp.h>
#endif
+#if defined(SERF_HAVE_OSSL_STORE_OPEN_EX)
+#include <openssl/store.h>
+#include <openssl/evp.h>
+#include <openssl/safestack.h>
+#include <openssl/ui.h>
+#ifndef sk_EVP_PKEY_new_null
+DEFINE_STACK_OF(EVP_PKEY)
+#endif
+#endif
#ifndef APR_ARRAY_PUSH
#define APR_ARRAY_PUSH(ary,type) (*((type *)apr_array_push(ary)))
@@ -117,6 +126,8 @@
*
*/
+static int ssl_x509_ex_data_idx = -1;
+
typedef struct bucket_list {
serf_bucket_t *bucket;
struct bucket_list *next;
@@ -177,12 +188,20 @@ struct serf_ssl_context_t {
apr_pool_t *cert_pw_cache_pool;
const char *cert_pw_success;
+ /* Cert uri callbacks */
+ serf_ssl_need_cert_uri_t cert_uri_callback;
+ void *cert_uri_userdata;
+ apr_pool_t *cert_uri_cache_pool;
+ const char *cert_uri_success;
+
/* Server cert callbacks */
serf_ssl_need_server_cert_t server_cert_callback;
serf_ssl_server_cert_chain_cb_t server_cert_chain_callback;
void *server_cert_userdata;
const char *cert_path;
+ const char *cert_uri;
+ const char *cert_pw;
X509 *cached_cert;
EVP_PKEY *cached_cert_pw;
@@ -1502,6 +1521,12 @@ static apr_status_t do_init_libraries(vo
OpenSSL_add_all_algorithms();
#endif
+#if defined(SERF_HAVE_OSSL_STORE_OPEN_EX)
+ if (ssl_x509_ex_data_idx < 0) {
+ ssl_x509_ex_data_idx = X509_get_ex_new_index(0, NULL, NULL, NULL,
NULL);
+ }
+#endif
+
#if APR_HAS_THREADS && defined(SERF_HAVE_SSL_LOCKING_CALLBACKS)
numlocks = CRYPTO_num_locks();
apr_pool_create(&ssl_pool, NULL);
@@ -1533,10 +1558,50 @@ static apr_status_t init_ssl_libraries(v
return serf__init_once(&init_ctx, do_init_libraries, NULL);
}
+#if defined(SERF_HAVE_OSSL_STORE_OPEN_EX)
+
+static int ssl_pass_cb(UI *ui, UI_STRING *uis)
+{
+ serf_ssl_context_t *ctx = UI_get0_user_data(ui);
+
+ const char *password;
+ apr_status_t status;
+
+ if (ctx->cert_pw_success) {
+ status = APR_SUCCESS;
+ password = ctx->cert_pw_success;
+ ctx->cert_pw_success = NULL;
+ }
+ else if (ctx->cert_pw_callback) {
+ status = ctx->cert_pw_callback(ctx->cert_pw_userdata,
+ ctx->cert_uri,
+ &password);
+ }
+ else {
+ return 0;
+ }
+
+ UI_set_result(ui, uis, password);
+
+ ctx->cert_pw = apr_pstrdup(ctx->pool, password);
+
+ return 1;
+}
+
+#endif
+
static int ssl_need_client_cert(SSL *ssl, X509 **cert, EVP_PKEY **pkey)
{
serf_ssl_context_t *ctx = SSL_get_app_data(ssl);
+#if defined(SERF_HAVE_OSSL_STORE_OPEN_EX)
+ STACK_OF(X509) *leaves;
+ STACK_OF(X509) *intermediates;
+ STACK_OF(EVP_PKEY) *keys;
+ X509_STORE *requests;
+ UI_METHOD *ui_method;
+#endif
apr_status_t status;
+ int retrying_success = 0;
serf__log(LOGLVL_DEBUG, LOGCOMP_SSL, __FILE__, ctx->config,
"Server requests a client certificate.\n");
@@ -1547,6 +1612,212 @@ static int ssl_need_client_cert(SSL *ssl
return 1;
}
+#if defined(SERF_HAVE_OSSL_STORE_OPEN_EX)
+
+ /* until further notice */
+ *cert = NULL;
+ *pkey = NULL;
+
+ leaves = sk_X509_new_null();
+ intermediates = sk_X509_new_null();
+ keys = sk_EVP_PKEY_new_null();
+ requests = X509_STORE_new();
+
+ ui_method = UI_create_method("passphrase");
+ UI_method_set_reader(ui_method, ssl_pass_cb);
+
+ while (ctx->cert_uri_callback) {
+ const char *cert_uri = NULL;
+ OSSL_STORE_CTX *store = NULL;
+ OSSL_STORE_INFO *info;
+ X509 *c;
+ STACK_OF(X509_NAME) *requested;
+ int type;
+
+ retrying_success = 0;
+
+ if (ctx->cert_uri_success) {
+ status = APR_SUCCESS;
+ cert_uri = ctx->cert_uri_success;
+ ctx->cert_uri_success = NULL;
+ retrying_success = 1;
+ } else {
+ status = ctx->cert_uri_callback(ctx->cert_uri_userdata, &cert_uri);
+ }
+
+ if (status || !cert_uri) {
+ break;
+ }
+
+ ctx->cert_uri = cert_uri;
+
+ /* server side request some certs? this list may be empty */
+ requested = SSL_get_client_CA_list(ssl);
+
+ store = OSSL_STORE_open_ex(cert_uri, NULL, NULL, ui_method, ctx, NULL,
+ NULL, NULL);
+ if (!store) {
+ int err = ERR_get_error();
+ serf__log(LOGLVL_ERROR, LOGCOMP_SSL, __FILE__, ctx->config,
+ "OpenSSL store error (%s): %d %d\n", cert_uri,
+ ERR_GET_LIB(err), ERR_GET_REASON(err));
+ break;
+ }
+
+ /* walk the store, what are we working with */
+
+ while (!OSSL_STORE_eof(store)) {
+ info = OSSL_STORE_load(store);
+
+ if (!info) {
+ break;
+ }
+
+ type = OSSL_STORE_INFO_get_type(info);
+ if (type == OSSL_STORE_INFO_CERT) {
+ X509 *c = OSSL_STORE_INFO_get1_CERT(info);
+
+ int n, i;
+
+ int is_ca = X509_check_ca(c);
+
+ /* split into leaves and intermediate certs */
+ if (is_ca) {
+ sk_X509_push(intermediates, c);
+ }
+ else {
+ sk_X509_push(leaves, c);
+ }
+
+ /* any cert with an issuer matching our requested CAs is also
+ * added to the requests list, except for leaf certs which are
+ * marked as requested with a flag so we can skip the chain
+ * check later. */
+ n = sk_X509_NAME_num(requested);
+ for (i = 0; i < n; ++i) {
+ X509_NAME *name = sk_X509_NAME_value(requested, i);
+ if (X509_NAME_cmp(name, X509_get_issuer_name(c)) == 0) {
+ if (is_ca) {
+ X509_STORE_add_cert(requests, c);
+ }
+ else {
+ X509_set_ex_data(c, ssl_x509_ex_data_idx,
+ (void *)1);
+ }
+ }
+ }
+
+ } else if (type == OSSL_STORE_INFO_PKEY) {
+ EVP_PKEY *k = OSSL_STORE_INFO_get1_PKEY(info);
+
+ sk_EVP_PKEY_push(keys, k);
+ }
+
+ OSSL_STORE_INFO_free(info);
+ }
+
+ /* FIXME: openssl error checking goes here */
+
+ OSSL_STORE_close(store);
+
+ /* walk the leaf certificates, choose the best one */
+
+ while ((c = sk_X509_pop(leaves))) {
+
+ EVP_PKEY *k = NULL;
+ int i, n, found = 0;
+
+ /* no key, skip */
+ n = sk_EVP_PKEY_num(keys);
+ for (i = 0; i < n; ++i) {
+ k = sk_EVP_PKEY_value(keys, i);
+ if (X509_check_private_key(c, k)) {
+ found = 1;
+ break;
+ }
+ }
+ if (!found) {
+ continue;
+ }
+
+ /* CAs requested? if so, skip non matches, if not, accept all */
+ if (sk_X509_NAME_num(requested) &&
+ !X509_get_ex_data(c, ssl_x509_ex_data_idx)) {
+ STACK_OF(X509) *chain;
+
+ chain = X509_build_chain(c, intermediates, requests, 0, NULL,
+ NULL);
+
+ if (!chain) {
+ continue;
+ }
+
+ sk_X509_pop_free(chain, X509_free);
+ }
+
+ /* no best candidate yet? we're in first place */
+ if (!*cert) {
+ EVP_PKEY_up_ref(k);
+ *cert = c; /* don't dup, we're returning this */
+ *pkey = k;
+ continue;
+ }
+
+ /* were we issued after the previous best? */
+ if (ASN1_TIME_compare(X509_get0_notBefore(*cert),
+ X509_get0_notBefore(c)) < 0) {
+ X509_free(*cert);
+ EVP_PKEY_free(*pkey);
+ EVP_PKEY_up_ref(k);
+ *cert = c; /* don't dup, we're returning this */
+ *pkey = k;
+ continue;
+ }
+
+ X509_free(c);
+ }
+
+ break;
+ }
+
+ sk_X509_pop_free(leaves, X509_free);
+ sk_X509_pop_free(intermediates, X509_free);
+ sk_EVP_PKEY_pop_free(keys, EVP_PKEY_free);
+ X509_STORE_free(requests);
+ UI_destroy_method(ui_method);
+
+ /* we settled on a cert and key, cache it for later */
+
+ if (*cert && *pkey) {
+
+ ctx->cached_cert = *cert;
+ ctx->cached_cert_pw = *pkey;
+ if (!retrying_success && ctx->cert_cache_pool) {
+ const char *c;
+
+ c = apr_pstrdup(ctx->cert_cache_pool, ctx->cert_uri);
+
+ apr_pool_userdata_setn(c, "serf:ssl:cert",
+ apr_pool_cleanup_null,
+ ctx->cert_cache_pool);
+ }
+
+ if (!retrying_success && ctx->cert_pw_cache_pool && ctx->cert_pw) {
+ const char *pw;
+
+ pw = apr_pstrdup(ctx->cert_pw_cache_pool,
+ ctx->cert_pw);
+
+ apr_pool_userdata_setn(pw, "serf:ssl:certpw",
+ apr_pool_cleanup_null,
+ ctx->cert_pw_cache_pool);
+ }
+
+ return 1;
+ }
+
+#endif
+
while (ctx->cert_callback) {
const char *cert_path;
apr_file_t *cert_file;
@@ -1554,7 +1825,7 @@ static int ssl_need_client_cert(SSL *ssl
BIO_METHOD *biom;
PKCS12 *p12;
int i;
- int retrying_success = 0;
+ retrying_success = 0;
if (ctx->cert_file_success) {
status = APR_SUCCESS;
@@ -1704,6 +1975,22 @@ void serf_ssl_client_cert_password_set(
}
+void serf_ssl_cert_uri_set(
+ serf_ssl_context_t *context,
+ serf_ssl_need_cert_uri_t callback,
+ void *data,
+ void *cache_pool)
+{
+ context->cert_uri_callback = callback;
+ context->cert_uri_userdata = data;
+ context->cert_cache_pool = cache_pool;
+ if (context->cert_cache_pool) {
+ apr_pool_userdata_get((void**)&context->cert_uri_success,
+ "serf:ssl:certuri", cache_pool);
+ }
+}
+
+
void serf_ssl_server_cert_callback_set(
serf_ssl_context_t *context,
serf_ssl_need_server_cert_t callback,
@@ -1780,6 +2067,7 @@ static serf_ssl_context_t *ssl_init_cont
ssl_ctx->cert_callback = NULL;
ssl_ctx->cert_pw_callback = NULL;
+ ssl_ctx->cert_uri_callback = NULL;
ssl_ctx->server_cert_callback = NULL;
ssl_ctx->server_cert_chain_callback = NULL;
Modified: serf/branches/PR-8/serf_bucket_types.h
URL:
http://svn.apache.org/viewvc/serf/branches/PR-8/serf_bucket_types.h?rev=1926894&r1=1926893&r2=1926894&view=diff
==============================================================================
--- serf/branches/PR-8/serf_bucket_types.h (original)
+++ serf/branches/PR-8/serf_bucket_types.h Tue Jul 1 08:52:03 2025
@@ -600,6 +600,10 @@ typedef apr_status_t (*serf_ssl_need_cer
const char *cert_path,
const char **password);
+typedef apr_status_t (*serf_ssl_need_cert_uri_t)(
+ void *data,
+ const char **cert_uri);
+
/**
* Callback type for server certificate status info and OCSP responses.
* Note that CERT can be NULL in case its called from the OCSP callback.
@@ -616,18 +620,53 @@ typedef apr_status_t (*serf_ssl_server_c
const serf_ssl_certificate_t * const * certs,
apr_size_t certs_len);
+/**
+ * Set a callback to provide a filesystem path to a PKCS12 file.
+ *
+ * This has been replaced by serf_ssl_cert_uri_set(). On Unix
+ * platforms the same path from serf_ssl_client_cert_provider_set()
+ * can be passed to serf_ssl_cert_uri_set(). On Windows the drive
+ * letter will be interpreted by serf_ssl_cert_uri_set() as a scheme,
+ * so the same path will not work, and will need to be escaped as
+ * a file URL instead.
+ */
void serf_ssl_client_cert_provider_set(
serf_ssl_context_t *context,
serf_ssl_need_client_cert_t callback,
void *data,
void *cache_pool);
+/**
+ * Set a callback to provide the password corresponding to the URL of
+ * the client certificate store.
+ *
+ * If the serf_ssl_client_cert_provider_set callback is set, this
+ * password will also be used to decode the PKCS12 file.
+ */
void serf_ssl_client_cert_password_set(
serf_ssl_context_t *context,
serf_ssl_need_cert_password_t callback,
void *data,
void *cache_pool);
+/**
+ * Set a callback to provide the URL of the client certificate store.
+ *
+ * In the absence of a scheme the default scheme is file:, and the file
+ * can point to PKCS12, PEM or other supported certificates and keys.
+ *
+ * With the correct OpenSSL provider configured, URLs can be provided
+ * for pkcs11, tpm2, and other certificate stores.
+ *
+ * On Windows, file paths must be escaped as file: URLs to prevent the
+ * drive letter being intepreted as a scheme.
+ */
+void serf_ssl_cert_uri_set(
+ serf_ssl_context_t *context,
+ serf_ssl_need_cert_uri_t callback,
+ void *data,
+ void *cache_pool);
+
/**
* Set a callback to override the default SSL server certificate validation
* algorithm.
Modified: serf/branches/PR-8/test/serf_get.c
URL:
http://svn.apache.org/viewvc/serf/branches/PR-8/test/serf_get.c?rev=1926894&r1=1926893&r2=1926894&view=diff
==============================================================================
--- serf/branches/PR-8/test/serf_get.c (original)
+++ serf/branches/PR-8/test/serf_get.c Tue Jul 1 08:52:03 2025
@@ -41,6 +41,7 @@ typedef struct app_baton_t {
int use_h2direct;
const char *pem_path;
const char *pem_pwd;
+ const char *cert_uri;
serf_bucket_alloc_t *bkt_alloc;
serf_context_t *serf_ctx;
} app_baton_t;
@@ -80,7 +81,8 @@ static apr_status_t client_cert_pw_cb(vo
{
app_baton_t *ctx = data;
- if (strcmp(cert_path, ctx->pem_path) == 0)
+ if ((ctx->cert_uri && !strcmp(cert_path, ctx->cert_uri)) ||
+ (ctx->pem_path && !strcmp(cert_path, ctx->pem_path)))
{
*password = ctx->pem_pwd;
return APR_SUCCESS;
@@ -89,6 +91,15 @@ static apr_status_t client_cert_pw_cb(vo
return APR_EGENERAL;
}
+static apr_status_t client_cert_uri_cb(void *data, const char **cert_uri)
+{
+ app_baton_t *ctx = data;
+
+ *cert_uri = ctx->cert_uri;
+
+ return APR_SUCCESS;
+}
+
static void print_ssl_cert_errors(int failures)
{
if (failures) {
@@ -235,6 +246,13 @@ static apr_status_t conn_setup(apr_socke
pool);
}
+ if (ctx->cert_uri) {
+ serf_ssl_cert_uri_set(conn_ctx->ssl_ctx,
+ client_cert_uri_cb,
+ ctx,
+ pool);
+ }
+
if (ctx->pem_pwd) {
serf_ssl_client_cert_password_set(conn_ctx->ssl_ctx,
client_cert_pw_cb,
@@ -494,6 +512,7 @@ credentials_callback(char **username,
#define CERTPWD 257
#define HTTP2FLAG 258
#define H2DIRECT 259
+#define CERTURI 260
static const apr_getopt_option_t options[] =
{
@@ -510,6 +529,7 @@ static const apr_getopt_option_t options
{NULL, 'f', 1, "<file> Use the <file> as the request body"},
{NULL, 'p', 1, "<hostname:port> Use the <host:port> as proxy server"},
{"cert", CERTFILE, 1, "<file> Use SSL client certificate <file>"},
+ {"certuri", CERTURI, 1, "<uri> Use SSL client certificate <uri>"},
{"certpwd", CERTPWD, 1, "<password> Password for the SSL client
certificate"},
{NULL, 'r', 1, "<header:value> Use <header:value> as request header"},
{"debug", 'd', 0, "Enable debugging"},
@@ -564,7 +584,7 @@ int main(int argc, const char **argv)
int print_headers, debug, negotiate_http2, use_h2direct;
const char *username = NULL;
const char *password = "";
- const char *pem_path = NULL, *pem_pwd = NULL;
+ const char *pem_path = NULL, *pem_pwd = NULL, *cert_uri = NULL;
apr_getopt_t *opt;
int opt_c;
const char *opt_arg;
@@ -677,6 +697,9 @@ int main(int argc, const char **argv)
case CERTFILE:
pem_path = opt_arg;
break;
+ case CERTURI:
+ cert_uri = opt_arg;
+ break;
case CERTPWD:
pem_pwd = opt_arg;
break;
@@ -731,6 +754,7 @@ int main(int argc, const char **argv)
app_ctx.hostname = url.hostname;
app_ctx.pem_path = pem_path;
app_ctx.pem_pwd = pem_pwd;
+ app_ctx.cert_uri = cert_uri;
context = serf_context_create(pool);
app_ctx.serf_ctx = context;
Modified: serf/branches/PR-8/test/test_ssl.c
URL:
http://svn.apache.org/viewvc/serf/branches/PR-8/test/test_ssl.c?rev=1926894&r1=1926893&r2=1926894&view=diff
==============================================================================
--- serf/branches/PR-8/test/test_ssl.c (original)
+++ serf/branches/PR-8/test/test_ssl.c Tue Jul 1 08:52:03 2025
@@ -1173,6 +1173,76 @@ static void test_ssl_client_certificate(
EndVerify
}
+static apr_status_t
+client_cert_uri_conn_setup(apr_socket_t *skt,
+ serf_bucket_t **input_bkt,
+ serf_bucket_t **output_bkt,
+ void *setup_baton,
+ apr_pool_t *pool)
+{
+ test_baton_t *tb = setup_baton;
+ apr_status_t status;
+
+ status = https_set_root_ca_conn_setup(skt, input_bkt, output_bkt,
+ setup_baton, pool);
+ if (status)
+ return status;
+
+ serf_ssl_cert_uri_set(tb->ssl_context,
+ client_cert_cb,
+ tb,
+ pool);
+
+ serf_ssl_client_cert_password_set(tb->ssl_context,
+ client_cert_pw_cb,
+ tb,
+ pool);
+
+ return APR_SUCCESS;
+}
+
+static void test_ssl_client_certificate_uri(CuTest *tc)
+{
+#if defined(SERF_HAVE_OSSL_STORE_OPEN_EX)
+ test_baton_t *tb = tc->testBaton;
+ handler_baton_t handler_ctx[1];
+ const int num_requests = sizeof(handler_ctx)/sizeof(handler_ctx[0]);
+ apr_status_t status;
+
+
+ /* Set up a test context and a https server */
+ /* The SSL server uses the complete certificate chain to validate the
client
+ certificate. */
+ setup_test_mock_https_server(tb, server_key,
+ all_server_certs,
+ test_clientcert_optional);
+ status = setup_test_client_https_context(tb,
+ client_cert_uri_conn_setup,
+ NULL, /* No server cert callback
*/
+ tb->pool);
+ CuAssertIntEquals(tc, APR_SUCCESS, status);
+
+ Given(tb->mh)
+ ConnectionSetup(ClientCertificateIsValid,
+ ClientCertificateCNEqualTo("Serf Client"))
+
+ GETRequest(URLEqualTo("/"), ChunkedBodyEqualTo("1"),
+ HeaderEqualTo("Host", tb->serv_host))
+ Respond(WithCode(200), WithChunkedBody(""))
+ EndGiven
+
+ create_new_request(tb, &handler_ctx[0], "GET", "/", 1);
+
+ status = run_client_and_mock_servers_loops(tb, num_requests, handler_ctx,
+ tb->pool);
+ CuAssertTrue(tc, tb->result_flags & TEST_RESULT_CLIENT_CERTCB_CALLED);
+ CuAssertTrue(tc, tb->result_flags & TEST_RESULT_CLIENT_CERTPWCB_CALLED);
+ Verify(tb->mh)
+ CuAssert(tc, ErrorMessage, VerifyConnectionSetupOk);
+ EndVerify
+#endif
+}
+
/* Validate that the expired certificate is reported as failure in the
callback. */
static void test_ssl_expired_server_cert(CuTest *tc)
@@ -2752,6 +2822,7 @@ CuSuite *test_ssl(void)
SUITE_ADD_TEST(suite, test_ssl_large_response);
SUITE_ADD_TEST(suite, test_ssl_large_request);
SUITE_ADD_TEST(suite, test_ssl_client_certificate);
+ SUITE_ADD_TEST(suite, test_ssl_client_certificate_uri);
SUITE_ADD_TEST(suite, test_ssl_expired_server_cert);
SUITE_ADD_TEST(suite, test_ssl_future_server_cert);
SUITE_ADD_TEST(suite, test_ssl_revoked_server_cert);