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:
parent
b34b757c2e
commit
8cb2d5f48a
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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}]'
|
||||
|
||||
@ -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()}'
|
||||
|
||||
Loading…
Reference in New Issue
Block a user