Author: brane
Date: Sat Jul  5 07:03:43 2025
New Revision: 1926970

URL: http://svn.apache.org/viewvc?rev=1926970&view=rev
Log:
On the user-defined-authn branch: sync with trunk r1926949.

Modified:
    serf/branches/user-defined-authn/   (props changed)
    serf/branches/user-defined-authn/CMakeLists.txt
    serf/branches/user-defined-authn/README
    serf/branches/user-defined-authn/SConstruct
    serf/branches/user-defined-authn/buckets/deflate_buckets.c
    serf/branches/user-defined-authn/buckets/hpack_buckets.c
    serf/branches/user-defined-authn/buckets/ssl_buckets.c
    serf/branches/user-defined-authn/serf_bucket_types.h
    serf/branches/user-defined-authn/test/serf_get.c
    serf/branches/user-defined-authn/test/test_ssl.c

Propchange: serf/branches/user-defined-authn/
------------------------------------------------------------------------------
  Merged /serf/trunk:r1926939-1926969
  Merged /serf/branches/PR-8:r1926893-1926949

Modified: serf/branches/user-defined-authn/CMakeLists.txt
URL: 
http://svn.apache.org/viewvc/serf/branches/user-defined-authn/CMakeLists.txt?rev=1926970&r1=1926969&r2=1926970&view=diff
==============================================================================
--- serf/branches/user-defined-authn/CMakeLists.txt (original)
+++ serf/branches/user-defined-authn/CMakeLists.txt Sat Jul  5 07:03:43 2025
@@ -76,6 +76,7 @@ endif()
 if(NOT CMAKE_BUILD_TYPE)
   if(DEBUG)
     set(CMAKE_BUILD_TYPE Debug CACHE STRING "Default to debug build." FORCE)
+    set(SERF_MAINTAINER_MODE TRUE)
   else()
     set(CMAKE_BUILD_TYPE Release CACHE STRING "Default to release build." 
FORCE)
   endif()
@@ -307,6 +308,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/user-defined-authn/README
URL: 
http://svn.apache.org/viewvc/serf/branches/user-defined-authn/README?rev=1926970&r1=1926969&r2=1926970&view=diff
==============================================================================
--- serf/branches/user-defined-authn/README (original)
+++ serf/branches/user-defined-authn/README Sat Jul  5 07:03:43 2025
@@ -223,16 +223,15 @@ or, with a multi-config generator:
 
     $ ctest -C Release
 
-This is equivalent to
+This is equivalent to:
 
     $ cmake --build out --target test
 
-or
+or:
 
     $ cmake --build out --config Release --target test
 
-(or, on Windows using the Visual Studio generator, which always has to be
- special and different for no discernible benefit:
+or, on Windows using the Visual Studio generator:
 
     $ cmake --build out --config Release --target run_tests
 )

Modified: serf/branches/user-defined-authn/SConstruct
URL: 
http://svn.apache.org/viewvc/serf/branches/user-defined-authn/SConstruct?rev=1926970&r1=1926969&r2=1926970&view=diff
==============================================================================
--- serf/branches/user-defined-authn/SConstruct (original)
+++ serf/branches/user-defined-authn/SConstruct Sat Jul  5 07:03:43 2025
@@ -59,6 +59,9 @@ except TypeError:
   custom_tests['CheckFunc'] = build.scons_extras.CheckFunc
   print('warning: replaced Conftest.CheckFunc() for SCons version < 4.7.')
 
+# By default, we silence all warnings when compiling MockHTTP.
+# Set this to True to show them instead.
+SHOW_MOCKHTTP_WARNINGS = False
 
 HEADER_FILES = ['serf.h',
                 'serf_bucket_types.h',
@@ -364,6 +367,13 @@ if sys.platform != 'win32':
     # env.Append(CCFLAGS=['-g'])
     env.SerfAppendIf(['CFLAGS', 'CCFLAGS'], r'-g\S*', CCFLAGS=['-g'])
     env.Append(CPPDEFINES=['DEBUG', '_DEBUG'])
+    env.Append(CCFLAGS=['-Wimplicit-function-declaration',
+                        '-Wmissing-variable-declarations',
+                        '-Wunreachable-code',
+                        '-Wshorten-64-to-32',
+                        '-Wno-system-headers',
+                        '-Wextra-tokens',
+                        '-Wnewline-eof'])
   else:
     # env.Append(CCFLAGS=['-O2'])
     env.SerfAppendIf(['CFLAGS', 'CCFLAGS'], r'-O\S*', CCFLAGS=['-O2'])
@@ -418,6 +428,9 @@ if export_filter is not None:
   env.GenExports(target=export_filter, source=HEADER_FILES)
   env.Depends(lib_shared, export_filter)
 
+# We do not want or need OpenSSL's compatibility macros.
+env.Append(CPPDEFINES=['OPENSSL_NO_DEPRECATED'])
+
 # Define OPENSSL_NO_STDIO to prevent using _fp() API.
 env.Append(CPPDEFINES=['OPENSSL_NO_STDIO'])
 
@@ -582,7 +595,7 @@ else:
     env.Append(LIBPATH=['$OPENSSL/lib'])
 
   if brotli:
-    brotli_libs = '-lbrotlicommon -lbrotlienc'
+    brotli_libs = '-lbrotlicommon -lbrotlidec'
     env.Append(CPPPATH=['$BROTLI/include'],
                LIBPATH=['$BROTLI/lib'])
   else:
@@ -623,6 +636,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()
@@ -724,24 +740,45 @@ env.Alias('install', ['install-lib', 'in
 ### make move to a separate scons file in the test/ subdir?
 
 tenv = env.Clone()
+tenv.Append(CPPDEFINES=['MOCKHTTP_OPENSSL'])
+
+# Build the MockHTTP static library. MockHTTP needs C99 and OpenSSL's
+# deprecated APIs. Also silence all warnings from MockHTTP.
+mockenv = tenv.Clone()
+mockenv.Replace(CFLAGS = [f.replace('-std=c89', '-std=c99')
+                          for f in mockenv['CFLAGS']])
+mockenv.Replace(CCFLAGS = list(
+  filter(lambda f: (SHOW_MOCKHTTP_WARNINGS
+                    # NOTE: SCons flags are sometimes tuples, not strings.
+                    #       In those cases, the first element is the flag
+                    #       and the rest ar the flag's value(s).
+                    or (# GCC-like warning flags
+                        not re.match(r'^\s*-W[a-z][a-z-]+',
+                                     f if type(f) == type('') else f[0])
+                        # MSVC-like warning flags
+                        and not re.match(r'^\s*/(W|w[de])\d+',
+                                         f if type(f) == type('') else f[0]))),
+         mockenv['CCFLAGS'])
+))
+if not SHOW_MOCKHTTP_WARNINGS:
+  mockenv.Append(CCFLAGS = ['-w' if sys.platform != 'win32' else '/w'])
+mockenv.Replace(CPPDEFINES = list(filter(lambda d: d != 
'OPENSSL_NO_DEPRECATED',
+                                         mockenv['CPPDEFINES'])))
+
+mockhttpinc = mockenv.StaticLibrary('mockhttpinc',
+                                    ['test/MockHTTPinC/MockHTTP.c',
+                                     'test/MockHTTPinC/MockHTTP_server.c'])
 
 # Check if long-running tests should be enabled
 if tenv.get('ENABLE_SLOW_TESTS', None):
     tenv.Append(CPPDEFINES=['SERF_TEST_DEFLATE_4GBPLUS_BUCKETS'])
 
-# MockHTTP requires C99 standard, so use it for the test suite.
-cflags = tenv['CFLAGS']
-tenv.Replace(CFLAGS = [f.replace('-std=c89', '-std=c99') for f in cflags])
-
-tenv.Append(CPPDEFINES=['MOCKHTTP_OPENSSL'])
-
 TEST_PROGRAMS = [ 'serf_get', 'serf_response', 'serf_request', 'serf_spider',
                   'serf_httpd',
                   'test_all', 'serf_bwtp' ]
-if sys.platform == 'win32':
-  TEST_EXES = [ os.path.join('test', '%s.exe' % (prog)) for prog in 
TEST_PROGRAMS ]
-else:
-  TEST_EXES = [ os.path.join('test', '%s' % (prog)) for prog in TEST_PROGRAMS ]
+
+_exe = '.exe' if sys.platform == 'win32' else ''
+TEST_EXES = [os.path.join('test', '%s%s' % (prog, _exe)) for prog in 
TEST_PROGRAMS]
 
 check_script = env.File('build/check.py').rstr()
 test_dir = env.File('test/test_all.c').rfile().get_dir()
@@ -769,18 +806,18 @@ testall_files = [
         'test/mock_buckets.c',
         'test/mock_sock_buckets.c',
         'test/test_ssl.c',
-        'test/MockHTTPinC/MockHTTP.c',
-        'test/MockHTTPinC/MockHTTP_server.c',
         ]
 
 # We link the programs explicitly against the static libraries, to allow
 # access to private functions
+mocklib = mockhttpinc[0].rfile().abspath
+serflib = lib_static[0].rfile().abspath
 for proggie in TEST_EXES:
   if 'test_all' in proggie:
-    tenv.Program(proggie, testall_files + [LIBNAME + env['LIBSUFFIX']])
+    tenv.Program(proggie, testall_files + [mocklib, serflib])
   else:
-    tenv.Program(target = proggie, source = [proggie.replace('.exe','') + '.c',
-                                             LIBNAME + env['LIBSUFFIX']])
+    tenv.Program(target=proggie,
+                 source=[proggie.replace('.exe','') + '.c', serflib])
 
 
 # HANDLE CLEANING

Modified: serf/branches/user-defined-authn/buckets/deflate_buckets.c
URL: 
http://svn.apache.org/viewvc/serf/branches/user-defined-authn/buckets/deflate_buckets.c?rev=1926970&r1=1926969&r2=1926970&view=diff
==============================================================================
--- serf/branches/user-defined-authn/buckets/deflate_buckets.c (original)
+++ serf/branches/user-defined-authn/buckets/deflate_buckets.c Sat Jul  5 
07:03:43 2025
@@ -204,7 +204,7 @@ static void serf_deflate_destroy_and_dat
 
     if ((ctx->state > STATE_INIT && ctx->state <= STATE_FINISH)
         || (ctx->state > STATE_COMPRESS_INIT
-            && ctx->state < STATE_COMPRESS_FINISH))
+            && ctx->state <= STATE_COMPRESS_FINISH))
     {
         if (ctx->memLevel >= 0)
             deflateEnd(&ctx->zstream);

Modified: serf/branches/user-defined-authn/buckets/hpack_buckets.c
URL: 
http://svn.apache.org/viewvc/serf/branches/user-defined-authn/buckets/hpack_buckets.c?rev=1926970&r1=1926969&r2=1926970&view=diff
==============================================================================
--- serf/branches/user-defined-authn/buckets/hpack_buckets.c (original)
+++ serf/branches/user-defined-authn/buckets/hpack_buckets.c Sat Jul  5 
07:03:43 2025
@@ -1396,9 +1396,8 @@ handle_read_entry_and_clear(serf_hpack_d
     serf_hpack_table_t *tbl = ctx->tbl;
     const char *keep_key = NULL;
     const char *keep_val = NULL;
-    apr_status_t status;
-    char own_key;
-    char own_val;
+    bool own_key;
+    bool own_val;
 
     serf__log(LOGLVL_INFO, SERF_LOGCOMP_PROTOCOL, __FILE__, ctx->config,
               "Parsed from HPACK: %.*s: %.*s\n",
@@ -1476,9 +1475,11 @@ handle_read_entry_and_clear(serf_hpack_d
 
     if (ctx->reuse_item)
     {
-        status = hpack_table_get(ctx->reuse_item, tbl,
-                                 &keep_key, NULL,
-                                 &keep_val, NULL);
+        /* hpack_table_get() does not modify its output arguments if
+           it returns an error, so we ignore the return value here. */
+        hpack_table_get(ctx->reuse_item, tbl,
+                        &keep_key, NULL,
+                        &keep_val, NULL);
     }
 
     own_key = (ctx->key && ctx->key != keep_key);

Modified: serf/branches/user-defined-authn/buckets/ssl_buckets.c
URL: 
http://svn.apache.org/viewvc/serf/branches/user-defined-authn/buckets/ssl_buckets.c?rev=1926970&r1=1926969&r2=1926970&view=diff
==============================================================================
--- serf/branches/user-defined-authn/buckets/ssl_buckets.c (original)
+++ serf/branches/user-defined-authn/buckets/ssl_buckets.c Sat Jul  5 07:03:43 
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,49 @@ 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;
+
+    if (ctx->cert_pw_success) {
+        password = ctx->cert_pw_success;
+        ctx->cert_pw_success = NULL;
+    }
+    else if (ctx->cert_pw_callback) {
+        if (APR_SUCCESS != ctx->cert_pw_callback(ctx->cert_pw_userdata,
+                                                 ctx->cert_uri,
+                                                 &password))
+            return 0;
+    }
+    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 +1611,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 +1824,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 +1974,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 +2066,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/user-defined-authn/serf_bucket_types.h
URL: 
http://svn.apache.org/viewvc/serf/branches/user-defined-authn/serf_bucket_types.h?rev=1926970&r1=1926969&r2=1926970&view=diff
==============================================================================
--- serf/branches/user-defined-authn/serf_bucket_types.h (original)
+++ serf/branches/user-defined-authn/serf_bucket_types.h Sat Jul  5 07:03:43 
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/user-defined-authn/test/serf_get.c
URL: 
http://svn.apache.org/viewvc/serf/branches/user-defined-authn/test/serf_get.c?rev=1926970&r1=1926969&r2=1926970&view=diff
==============================================================================
--- serf/branches/user-defined-authn/test/serf_get.c (original)
+++ serf/branches/user-defined-authn/test/serf_get.c Sat Jul  5 07:03:43 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/user-defined-authn/test/test_ssl.c
URL: 
http://svn.apache.org/viewvc/serf/branches/user-defined-authn/test/test_ssl.c?rev=1926970&r1=1926969&r2=1926970&view=diff
==============================================================================
--- serf/branches/user-defined-authn/test/test_ssl.c (original)
+++ serf/branches/user-defined-authn/test/test_ssl.c Sat Jul  5 07:03:43 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);


Reply via email to