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
This commit is contained in:
Stefan Eissing 2024-10-22 14:13:00 +02:00 committed by Daniel Stenberg
parent b34b757c2e
commit 8cb2d5f48a
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
7 changed files with 189 additions and 54 deletions

View File

@ -41,6 +41,7 @@
#include "vtls/gtls.h"
#elif defined(USE_WOLFSSL)
#include <ngtcp2/ngtcp2_crypto_wolfssl.h>
#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);

View File

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

View File

@ -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)) {

View File

@ -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);
else
CURL_TRC_CF(data, cf, "added new session to cache");
(void)wssl_cache_session(cf, data, &connssl->peer, session);
}
}
return 1;
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
infof(data, "SSL reusing session ID");
wolfSSL_SESSION_free(session);
}
else {
failf(data, "could not decode previous session");
}
}
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

View File

@ -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 */

View File

@ -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}]'

View File

@ -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()}'