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
#include "vtls/gtls.h"
#elif defined(USE_WOLFSSL)
#include <ngtcp2/ngtcp2_crypto_wolfssl.h>
+#include "vtls/wolfssl.h"
#endif
#include "urldata.h"
{
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) {
}
#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)
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;
}
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);
/** 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);
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) */
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;
#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)) {
#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;
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,
/* 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
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 */
@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}]'
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__)
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()
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'
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()}'