Author: minfrin Date: Sat Jul 5 09:47:32 2025 New Revision: 1926972 URL: http://svn.apache.org/viewvc?rev=1926972&view=rev Log: Add support for SSL error handling
- Allow the registration of an optional callback using serf_ssl_error_cb_set(). - If the callback is registered, return a fixed string describing the error as created by the underlying crypto library. - Handle the error when a PKCS12 file cannot be opened, remove an infinite loop. - Client side SSL certificate errors now cause the client side to abort the connection. Previously no certificate was silently sent, and the error was access denied from the server. Example: [minfrin@rocky9 subversion]$ svn info https://svn.example.com/svn/example/core/ svn: E170013: Unable to connect to a repository at URL 'https://svn.example.com/svn/example/core' svn: E120170: TLS: error:0308010C:digital envelope routines::unsupported svn: E120170: TLS: could not parse PKCS12: /home/minfrin/.my-cert.p12 Modified: serf/trunk/buckets/ssl_buckets.c serf/trunk/serf_bucket_types.h Modified: serf/trunk/buckets/ssl_buckets.c URL: http://svn.apache.org/viewvc/serf/trunk/buckets/ssl_buckets.c?rev=1926972&r1=1926971&r2=1926972&view=diff ============================================================================== --- serf/trunk/buckets/ssl_buckets.c (original) +++ serf/trunk/buckets/ssl_buckets.c Sat Jul 5 09:47:32 2025 @@ -206,6 +206,10 @@ struct serf_ssl_context_t { X509 *cached_cert; EVP_PKEY *cached_cert_pw; + /* Error callback */ + serf_ssl_error_cb_t error_callback; + void *error_baton; + apr_status_t pending_err; /* Status of a fatal error, returned on subsequent encrypt or decrypt @@ -353,10 +357,17 @@ detect_renegotiate(const SSL *s, int whe static void log_ssl_error(serf_ssl_context_t *ctx) { - unsigned long e = ERR_get_error(); - serf__log(LOGLVL_ERROR, LOGCOMP_SSL, __FILE__, ctx->config, - "SSL Error: %s\n", ERR_error_string(e, NULL)); + unsigned long err; + + while ((err = ERR_get_error())) { + + if (err && ctx->error_callback) { + char ebuf[256]; + ERR_error_string_n(err, ebuf, sizeof(ebuf)); + ctx->error_callback(ctx->error_baton, ctx->fatal_err, ebuf); + } + } } static void bio_set_data(BIO *bio, void *data) @@ -1075,15 +1086,6 @@ static apr_status_t status_from_ssl_erro status = ctx->pending_err; ctx->pending_err = APR_SUCCESS; } else { - /*unsigned long l = ERR_peek_error(); - int lib = ERR_GET_LIB(l); - int reason = ERR_GET_REASON(l);*/ - - /* ### Detect more specific errors? - When lib is ERR_LIB_SSL, then reason is one of the - many SSL_R_XXXX reasons in ssl.h - */ - if (SSL_in_init(ctx->ssl)) ctx->fatal_err = SERF_ERROR_SSL_SETUP_FAILED; else @@ -1093,6 +1095,15 @@ static apr_status_t status_from_ssl_erro log_ssl_error(ctx); } break; + + case SSL_ERROR_WANT_X509_LOOKUP: + /* The ssl_need_client_cert() function returned -1 because an + * error occurred inside that function. The error has already + * been handled, just return the fatal error. + */ + status = ctx->fatal_err = SERF_ERROR_SSL_CERT_FAILED; + break; + default: status = ctx->fatal_err = SERF_ERROR_SSL_COMM_FAILED; log_ssl_error(ctx); @@ -1592,6 +1603,7 @@ static int ssl_pass_cb(UI *ui, UI_STRING static int ssl_need_client_cert(SSL *ssl, X509 **cert, EVP_PKEY **pkey) { serf_ssl_context_t *ctx = SSL_get_app_data(ssl); + unsigned long err = 0; #if defined(SERF_HAVE_OSSL_STORE_OPEN_EX) STACK_OF(X509) *leaves; STACK_OF(X509) *intermediates; @@ -1656,10 +1668,14 @@ static int ssl_need_client_cert(SSL *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)); + + if (ctx->error_callback) { + char ebuf[1024]; + ctx->fatal_err = SERF_ERROR_SSL_CERT_FAILED; + apr_snprintf(ebuf, sizeof(ebuf), "could not open URI: %s", cert_uri); + ctx->error_callback(ctx->error_baton, ctx->fatal_err, ebuf); + } + break; } @@ -1669,6 +1685,14 @@ static int ssl_need_client_cert(SSL *ssl info = OSSL_STORE_load(store); if (!info) { + + if (ctx->error_callback) { + char ebuf[1024]; + ctx->fatal_err = SERF_ERROR_SSL_CERT_FAILED; + apr_snprintf(ebuf, sizeof(ebuf), "could not read URI: %s", cert_uri); + ctx->error_callback(ctx->error_baton, ctx->fatal_err, ebuf); + } + break; } @@ -1715,10 +1739,12 @@ static int ssl_need_client_cert(SSL *ssl OSSL_STORE_INFO_free(info); } - /* FIXME: openssl error checking goes here */ - OSSL_STORE_close(store); + if (ERR_peek_error()) { + break; + } + /* walk the leaf certificates, choose the best one */ while ((c = sk_X509_pop(leaves))) { @@ -1785,6 +1811,12 @@ static int ssl_need_client_cert(SSL *ssl X509_STORE_free(requests); UI_destroy_method(ui_method); + if (ERR_peek_error()) { + log_ssl_error(ctx); + + return -1; + } + /* we settled on a cert and key, cache it for later */ if (*cert && *pkey) { @@ -1843,9 +1875,15 @@ static int ssl_need_client_cert(SSL *ssl status = apr_file_open(&cert_file, cert_path, APR_READ, APR_OS_DEFAULT, ctx->pool); - /* TODO: this will hang indefintely when the file can't be found. */ if (status) { - continue; + if (ctx->error_callback) { + char ebuf[1024]; + apr_snprintf(ebuf, sizeof(ebuf), "could not open PKCS12: %s", cert_path); + ctx->error_callback(ctx->error_baton, ctx->fatal_err, ebuf); + apr_strerror(status, ebuf, sizeof(ebuf)); + ctx->error_callback(ctx->error_baton, ctx->fatal_err, ebuf); + } + return -1; } biom = bio_meth_file_new(); @@ -1876,7 +1914,7 @@ static int ssl_need_client_cert(SSL *ssl return 1; } else { - int err = ERR_get_error(); + err = ERR_get_error(); ERR_clear_error(); if (ERR_GET_LIB(err) == ERR_LIB_PKCS12 && ERR_GET_REASON(err) == PKCS12_R_MAC_VERIFY_FAILURE) { @@ -1922,18 +1960,46 @@ static int ssl_need_client_cert(SSL *ssl } return 1; } + else { + + if (ctx->error_callback) { + char ebuf[1024]; + ctx->fatal_err = SERF_ERROR_SSL_CERT_FAILED; + apr_snprintf(ebuf, sizeof(ebuf), "could not parse PKCS12: %s", cert_path); + ctx->error_callback(ctx->error_baton, ctx->fatal_err, ebuf); + } + + log_ssl_error(ctx); + return -1; + } } } PKCS12_free(p12); bio_meth_free(biom); - return 0; + + if (ctx->error_callback) { + char ebuf[1024]; + ctx->fatal_err = SERF_ERROR_SSL_CERT_FAILED; + apr_snprintf(ebuf, sizeof(ebuf), "PKCS12 needs a password: %s", cert_path); + ctx->error_callback(ctx->error_baton, ctx->fatal_err, ebuf); + } + + log_ssl_error(ctx); + return -1; } else { - serf__log(LOGLVL_ERROR, LOGCOMP_SSL, __FILE__, ctx->config, - "OpenSSL cert error: %d %d\n", ERR_GET_LIB(err), - ERR_GET_REASON(err)); PKCS12_free(p12); bio_meth_free(biom); + + if (ctx->error_callback) { + char ebuf[1024]; + ctx->fatal_err = SERF_ERROR_SSL_CERT_FAILED; + apr_snprintf(ebuf, sizeof(ebuf), "could not parse PKCS12: %s", cert_path); + ctx->error_callback(ctx->error_baton, ctx->fatal_err, ebuf); + } + + log_ssl_error(ctx); + return -1; } } } @@ -2010,6 +2076,15 @@ void serf_ssl_server_cert_chain_callback context->server_cert_userdata = data; } +void serf_ssl_error_cb_set( + serf_ssl_context_t *context, + serf_ssl_error_cb_t callback, + void *baton) +{ + context->error_callback = callback; + context->error_baton = baton; +} + static int ssl_new_session(SSL *ssl, SSL_SESSION *session) { serf_ssl_context_t *ctx = SSL_get_app_data(ssl); @@ -2075,6 +2150,9 @@ static serf_ssl_context_t *ssl_init_cont ssl_ctx->protocol_callback = NULL; ssl_ctx->protocol_userdata = NULL; + ssl_ctx->error_callback = NULL; + ssl_ctx->error_baton = NULL; + SSL_CTX_set_verify(ssl_ctx->ctx, SSL_VERIFY_PEER, validate_server_certificate); SSL_CTX_set_options(ssl_ctx->ctx, SSL_OP_ALL); @@ -2386,8 +2464,9 @@ apr_status_t serf_ssl_add_crl_from_file( result = X509_STORE_add_crl(store, crl); if (!result) { + ssl_ctx->fatal_err = status = SERF_ERROR_SSL_CERT_FAILED; log_ssl_error(ssl_ctx); - return SERF_ERROR_SSL_CERT_FAILED; + return status; } /* TODO: free crl when closing ssl session */ Modified: serf/trunk/serf_bucket_types.h URL: http://svn.apache.org/viewvc/serf/trunk/serf_bucket_types.h?rev=1926972&r1=1926971&r2=1926972&view=diff ============================================================================== --- serf/trunk/serf_bucket_types.h (original) +++ serf/trunk/serf_bucket_types.h Sat Jul 5 09:47:32 2025 @@ -687,6 +687,33 @@ void serf_ssl_server_cert_chain_callback void *data); /** + * Callback type for detailed TLS error strings. This callback will be fired + * every time the underlying crypto library encounters an error. The message + * lasts only as long as the callback, if the caller wants to set aside the + * message for later use, a copy must be made. + * + * It is possible that for a given error multiple strings will be returned + * in multiple callbacks. The caller may choose to handle all strings, or + * may choose to ignore all strings but the last most detailed one. + */ +typedef apr_status_t (*serf_ssl_error_cb_t)( + void *baton, + apr_status_t status, + const char *message); + +/** + * Set a callback to return any detailed certificate error from the underlying + * cryptographic library. + * + * The callback is associated with the context, however the choice of baton + * will depend on the needs of the caller. + */ +void serf_ssl_error_cb_set( + serf_ssl_context_t *context, + serf_ssl_error_cb_t callback, + void *baton); + +/** * Use the default root CA certificates as included with the OpenSSL library. */ apr_status_t serf_ssl_use_default_certificates(