From: Stefan Eissing Date: Tue, 22 Oct 2024 12:13:00 +0000 (+0200) Subject: quic: use the session cache with wolfSSL as well X-Git-Tag: curl-8_11_0~68 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8cb2d5f48a50b28266163e8432adcc3fc791966a;p=thirdparty%2Fcurl.git quic: use the session cache with wolfSSL as well Use session cache for QUIC when built with quictls or wolfSSL. Add test_017_10 for verifying QUIC TLS session reuse when built with quictls, gnutls or wolfssl. Closes #15358 --- diff --git a/lib/vquic/curl_ngtcp2.c b/lib/vquic/curl_ngtcp2.c index bf8381e6dd..4a3c004591 100644 --- a/lib/vquic/curl_ngtcp2.c +++ b/lib/vquic/curl_ngtcp2.c @@ -41,6 +41,7 @@ #include "vtls/gtls.h" #elif defined(USE_WOLFSSL) #include +#include "vtls/wolfssl.h" #endif #include "urldata.h" @@ -2160,11 +2161,11 @@ static int quic_gtls_handshake_cb(gnutls_session_t session, unsigned int htype, { ngtcp2_crypto_conn_ref *conn_ref = gnutls_session_get_ptr(session); struct Curl_cfilter *cf = conn_ref ? conn_ref->user_data : NULL; - struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct cf_ngtcp2_ctx *ctx = cf ? cf->ctx : NULL; (void)msg; (void)incoming; - if(when) { /* after message has been processed */ + if(when && cf && ctx) { /* after message has been processed */ struct Curl_easy *data = CF_DATA_CURRENT(cf); DEBUGASSERT(data); if(data) { @@ -2184,12 +2185,32 @@ static int quic_gtls_handshake_cb(gnutls_session_t session, unsigned int htype, } #endif /* USE_GNUTLS */ +#ifdef USE_WOLFSSL +static int wssl_quic_new_session_cb(WOLFSSL *ssl, WOLFSSL_SESSION *session) +{ + ngtcp2_crypto_conn_ref *conn_ref = wolfSSL_get_app_data(ssl); + struct Curl_cfilter *cf = conn_ref ? conn_ref->user_data : NULL; + + DEBUGASSERT(cf != NULL); + if(cf && session) { + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + DEBUGASSERT(data); + if(data && ctx) { + (void)wssl_cache_session(cf, data, &ctx->peer, session); + } + } + return 0; +} +#endif /* USE_WOLFSSL */ + static CURLcode tls_ctx_setup(struct Curl_cfilter *cf, struct Curl_easy *data, void *user_data) { struct curl_tls_ctx *ctx = user_data; - (void)cf; + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + #ifdef USE_OPENSSL #if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) if(ngtcp2_crypto_boringssl_configure_client_context(ctx->ossl.ssl_ctx) @@ -2203,29 +2224,37 @@ static CURLcode tls_ctx_setup(struct Curl_cfilter *cf, return CURLE_FAILED_INIT; } #endif /* !OPENSSL_IS_BORINGSSL && !OPENSSL_IS_AWSLC */ - /* Enable the session cache because it is a prerequisite for the - * "new session" callback. Use the "external storage" mode to prevent - * OpenSSL from creating an internal session cache. - */ - SSL_CTX_set_session_cache_mode(ctx->ossl.ssl_ctx, - SSL_SESS_CACHE_CLIENT | - SSL_SESS_CACHE_NO_INTERNAL); - SSL_CTX_sess_set_new_cb(ctx->ossl.ssl_ctx, quic_ossl_new_session_cb); + if(ssl_config->primary.cache_session) { + /* Enable the session cache because it is a prerequisite for the + * "new session" callback. Use the "external storage" mode to prevent + * OpenSSL from creating an internal session cache. + */ + SSL_CTX_set_session_cache_mode(ctx->ossl.ssl_ctx, + SSL_SESS_CACHE_CLIENT | + SSL_SESS_CACHE_NO_INTERNAL); + SSL_CTX_sess_set_new_cb(ctx->ossl.ssl_ctx, quic_ossl_new_session_cb); + } #elif defined(USE_GNUTLS) if(ngtcp2_crypto_gnutls_configure_client_session(ctx->gtls.session) != 0) { failf(data, "ngtcp2_crypto_gnutls_configure_client_session failed"); return CURLE_FAILED_INIT; } - gnutls_handshake_set_hook_function(ctx->gtls.session, - GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, - quic_gtls_handshake_cb); + if(ssl_config->primary.cache_session) { + gnutls_handshake_set_hook_function(ctx->gtls.session, + GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, + quic_gtls_handshake_cb); + } #elif defined(USE_WOLFSSL) if(ngtcp2_crypto_wolfssl_configure_client_context(ctx->wssl.ctx) != 0) { failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed"); return CURLE_FAILED_INIT; } + if(ssl_config->primary.cache_session) { + /* Register to get notified when a new session is received */ + wolfSSL_CTX_sess_set_new_cb(ctx->wssl.ctx, wssl_quic_new_session_cb); + } #endif return CURLE_OK; } @@ -2305,8 +2334,10 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.ossl.ssl); #elif defined(USE_GNUTLS) ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.gtls.session); -#else +#elif defined(USE_WOLFSSL) ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.wssl.handle); +#else + #error "ngtcp2 TLS backend not defined" #endif ngtcp2_ccerr_default(&ctx->last_error); diff --git a/lib/vquic/vquic-tls.c b/lib/vquic/vquic-tls.c index 1caa83828a..5afe23ee22 100644 --- a/lib/vquic/vquic-tls.c +++ b/lib/vquic/vquic-tls.c @@ -195,12 +195,14 @@ out: /** SSL callbacks ***/ static CURLcode Curl_wssl_init_ssl(struct curl_tls_ctx *ctx, + struct Curl_cfilter *cf, struct Curl_easy *data, struct ssl_peer *peer, const char *alpn, size_t alpn_len, void *user_data) { - (void)data; + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + DEBUGASSERT(!ctx->wssl.handle); DEBUGASSERT(ctx->wssl.ctx); ctx->wssl.handle = wolfSSL_new(ctx->wssl.ctx); @@ -218,6 +220,10 @@ static CURLcode Curl_wssl_init_ssl(struct curl_tls_ctx *ctx, peer->sni, (unsigned short)strlen(peer->sni)); } + if(ssl_config->primary.cache_session) { + (void)wssl_setup_session(cf, data, &ctx->wssl, peer); + } + return CURLE_OK; } #endif /* defined(USE_WOLFSSL) */ @@ -247,7 +253,8 @@ CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx, if(result) return result; - return Curl_wssl_init_ssl(ctx, data, peer, alpn, alpn_len, ssl_user_data); + return Curl_wssl_init_ssl(ctx, cf, data, peer, alpn, alpn_len, + ssl_user_data); #else #error "no TLS lib in used, should not happen" return CURLE_FAILED_INIT; diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index bb84cb85ad..f0c5622496 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -3974,7 +3974,7 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, #endif octx->reused_session = FALSE; - if(ssl_config->primary.cache_session && transport == TRNSPRT_TCP) { + if(ssl_config->primary.cache_session) { Curl_ssl_sessionid_lock(data); if(!Curl_ssl_getsessionid(cf, data, peer, (void **)&der_sessionid, &der_sessionid_size, NULL)) { diff --git a/lib/vtls/wolfssl.c b/lib/vtls/wolfssl.c index c77b18afc7..c636e4788e 100644 --- a/lib/vtls/wolfssl.c +++ b/lib/vtls/wolfssl.c @@ -370,13 +370,60 @@ static void wolfssl_bio_cf_free_methods(void) #endif /* !USE_BIO_CHAIN */ -static void wolfssl_session_free(void *sessionid, size_t idsize) +static void wolfssl_session_free(void *sdata, size_t slen) { - (void)idsize; - wolfSSL_SESSION_free(sessionid); + (void)slen; + free(sdata); } -static int wolfssl_new_session_cb(WOLFSSL *ssl, WOLFSSL_SESSION *session) +CURLcode wssl_cache_session(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + WOLFSSL_SESSION *session) +{ + CURLcode result = CURLE_OK; + unsigned char *sdata = NULL; + unsigned int slen; + + if(!session) + goto out; + + slen = wolfSSL_i2d_SSL_SESSION(session, NULL); + if(slen <= 0) { + CURL_TRC_CF(data, cf, "fail to assess session length: %u", slen); + result = CURLE_FAILED_INIT; + goto out; + } + sdata = calloc(1, slen); + if(!sdata) { + failf(data, "unable to allocate session buffer of %u bytes", slen); + result = CURLE_OUT_OF_MEMORY; + goto out; + } + slen = wolfSSL_i2d_SSL_SESSION(session, &sdata); + if(slen <= 0) { + CURL_TRC_CF(data, cf, "fail to serialize session: %u", slen); + result = CURLE_FAILED_INIT; + goto out; + } + + Curl_ssl_sessionid_lock(data); + result = Curl_ssl_set_sessionid(cf, data, peer, NULL, + sdata, slen, wolfssl_session_free); + Curl_ssl_sessionid_unlock(data); + if(result) + failf(data, "failed to add new ssl session to cache (%d)", result); + else { + CURL_TRC_CF(data, cf, "added new session to cache"); + sdata = NULL; + } + +out: + free(sdata); + return 0; +} + +static int wssl_vtls_new_session_cb(WOLFSSL *ssl, WOLFSSL_SESSION *session) { struct Curl_cfilter *cf; @@ -388,18 +435,44 @@ static int wolfssl_new_session_cb(WOLFSSL *ssl, WOLFSSL_SESSION *session) DEBUGASSERT(connssl); DEBUGASSERT(data); if(connssl && data) { - CURLcode result; - Curl_ssl_sessionid_lock(data); - result = Curl_ssl_set_sessionid(cf, data, &connssl->peer, NULL, - session, 0, wolfssl_session_free); - Curl_ssl_sessionid_unlock(data); - if(result) - failf(data, "failed to add new ssl session to cache (%d)", result); + (void)wssl_cache_session(cf, data, &connssl->peer, session); + } + } + return 0; +} + +CURLcode wssl_setup_session(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct wolfssl_ctx *wss, + struct ssl_peer *peer) +{ + void *psdata; + const unsigned char *sdata = NULL; + size_t slen = 0; + CURLcode result = CURLE_OK; + + Curl_ssl_sessionid_lock(data); + if(!Curl_ssl_getsessionid(cf, data, peer, &psdata, &slen, NULL)) { + WOLFSSL_SESSION *session; + sdata = psdata; + session = wolfSSL_d2i_SSL_SESSION(NULL, &sdata, (long)slen); + if(session) { + int ret = wolfSSL_set_session(wss->handle, session); + if(ret != WOLFSSL_SUCCESS) { + Curl_ssl_delsessionid(data, psdata); + infof(data, "previous session not accepted (%d), " + "removing from cache", ret); + } else - CURL_TRC_CF(data, cf, "added new session to cache"); + infof(data, "SSL reusing session ID"); + wolfSSL_SESSION_free(session); + } + else { + failf(data, "could not decode previous session"); } } - return 1; + Curl_ssl_sessionid_unlock(data); + return result; } static CURLcode populate_x509_store(struct Curl_cfilter *cf, @@ -1087,24 +1160,11 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) /* Check if there is a cached ID we can/should use here! */ if(ssl_config->primary.cache_session) { - void *ssl_sessionid = NULL; - + /* Set session from cache if there is one */ + (void)wssl_setup_session(cf, data, backend, &connssl->peer); /* Register to get notified when a new session is received */ wolfSSL_set_app_data(backend->handle, cf); - wolfSSL_CTX_sess_set_new_cb(backend->ctx, wolfssl_new_session_cb); - - Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(cf, data, &connssl->peer, - &ssl_sessionid, NULL, NULL)) { - /* we got a session id, use it! */ - if(!SSL_set_session(backend->handle, ssl_sessionid)) { - Curl_ssl_delsessionid(data, ssl_sessionid); - infof(data, "cannot use session ID, going on without"); - } - else - infof(data, "SSL reusing session ID"); - } - Curl_ssl_sessionid_unlock(data); + wolfSSL_CTX_sess_set_new_cb(backend->ctx, wssl_vtls_new_session_cb); } #ifdef USE_ECH diff --git a/lib/vtls/wolfssl.h b/lib/vtls/wolfssl.h index 318d8b4ab3..e39fa88adc 100644 --- a/lib/vtls/wolfssl.h +++ b/lib/vtls/wolfssl.h @@ -48,5 +48,16 @@ CURLcode Curl_wssl_setup_x509_store(struct Curl_cfilter *cf, struct Curl_easy *data, struct wolfssl_ctx *wssl); +CURLcode wssl_setup_session(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct wolfssl_ctx *wss, + struct ssl_peer *peer); + +CURLcode wssl_cache_session(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + WOLFSSL_SESSION *session); + + #endif /* USE_WOLFSSL */ #endif /* HEADER_CURL_WOLFSSL_H */ diff --git a/tests/http/test_02_download.py b/tests/http/test_02_download.py index 149919625e..5bcbb8c439 100644 --- a/tests/http/test_02_download.py +++ b/tests/http/test_02_download.py @@ -168,8 +168,6 @@ class TestDownload: @pytest.mark.parametrize("proto", ['http/1.1']) def test_02_07b_download_reuse(self, env: Env, httpd, nghttpx, repeat, proto): - if env.curl_uses_lib('wolfssl'): - pytest.skip("wolfssl session reuse borked") count = 6 curl = CurlClient(env=env) urln = f'https://{env.authority_for(env.domain1, proto)}/data.json?[0-{count-1}]' diff --git a/tests/http/test_17_ssl_use.py b/tests/http/test_17_ssl_use.py index eeccbd0af4..197a5b4f92 100644 --- a/tests/http/test_17_ssl_use.py +++ b/tests/http/test_17_ssl_use.py @@ -27,9 +27,10 @@ import json import logging import os +import re import pytest -from testenv import Env, CurlClient +from testenv import Env, CurlClient, LocalClient log = logging.getLogger(__name__) @@ -38,7 +39,8 @@ log = logging.getLogger(__name__) class TestSSLUse: @pytest.fixture(autouse=True, scope='class') - def _class_scope(self, env, nghttpx): + def _class_scope(self, env, httpd, nghttpx): + env.make_data_file(indir=httpd.docs_dir, fname="data-10k", fsize=10*1024) if env.have_h3(): nghttpx.start_if_needed() @@ -118,8 +120,6 @@ class TestSSLUse: def test_17_04_double_dot(self, env: Env, proto): if proto == 'h3' and not env.have_h3(): pytest.skip("h3 not supported") - if proto == 'h3' and env.curl_uses_lib('wolfssl'): - pytest.skip("wolfSSL HTTP/3 peer verification does not properly check") curl = CurlClient(env=env) domain = f'{env.domain1}..' url = f'https://{env.authority_for(domain, proto)}/curltest/sslinfo' @@ -313,3 +313,31 @@ class TestSSLUse: assert r.json['SSL_PROTOCOL'] == tls_proto, r.dump_logs() else: assert r.exit_code != 0, f'extra_args={extra_args}\n{r.dump_logs()}' + + def test_17_10_h3_session_reuse(self, env: Env, httpd, nghttpx): + if not env.have_h3(): + pytest.skip("h3 not supported") + if not env.curl_uses_lib('quictls') and \ + not env.curl_uses_lib('gnutls') and \ + not env.curl_uses_lib('wolfssl'): + pytest.skip("QUIC session reuse not implemented") + count = 2 + docname = 'data-10k' + url = f'https://localhost:{env.https_port}/{docname}' + client = LocalClient(name='hx-download', env=env) + if not client.exists(): + pytest.skip(f'example client not built: {client.name}') + r = client.run(args=[ + '-n', f'{count}', + '-f', # forbid reuse of connections + '-r', f'{env.domain1}:{env.port_for("h3")}:127.0.0.1', + '-V', 'h3', url + ]) + r.check_exit_code(0) + # check that TLS session was reused as expected + reused_session = False + for line in r.trace_lines: + m = re.match(r'\[1-1] \* SSL reusing session.*', line) + if m: + reused_session = True + assert reused_session, f'{r}\n{r.dump_logs()}'