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);