From: Stefan Eissing Date: Fri, 11 Oct 2024 11:09:51 +0000 (+0200) Subject: gnutls: use session cache for QUIC X-Git-Tag: curl-8_11_0~168 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=fe8399f066c0fe78018cbb567a0ebb52676bab24;p=thirdparty%2Fcurl.git gnutls: use session cache for QUIC Add session reuse for QUIC transfers using GnuTLS. This does not include support for TLS early data, yet. Fix check of early data support in common GnuTLS init code to not access the filter context, as the struct varies between TCP and QUIC connections. Closes #15265 --- diff --git a/lib/vquic/curl_ngtcp2.c b/lib/vquic/curl_ngtcp2.c index ef93d98af3..ef9635dcf7 100644 --- a/lib/vquic/curl_ngtcp2.c +++ b/lib/vquic/curl_ngtcp2.c @@ -2153,6 +2153,37 @@ static int quic_ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid) } #endif /* USE_OPENSSL */ +#ifdef USE_GNUTLS +static int quic_gtls_handshake_cb(gnutls_session_t session, unsigned int htype, + unsigned when, unsigned int incoming, + const gnutls_datum_t *msg) +{ + 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; + + (void)msg; + (void)incoming; + if(when) { /* after message has been processed */ + struct Curl_easy *data = CF_DATA_CURRENT(cf); + DEBUGASSERT(data); + if(data) { + CURL_TRC_CF(data, cf, "handshake: %s message type %d", + incoming ? "incoming" : "outgoing", htype); + } + switch(htype) { + case GNUTLS_HANDSHAKE_NEW_SESSION_TICKET: { + (void)Curl_gtls_update_session_id(cf, data, session, &ctx->peer, "h3"); + break; + } + default: + break; + } + } + return 0; +} +#endif /* USE_GNUTLS */ + static CURLcode tls_ctx_setup(struct Curl_cfilter *cf, struct Curl_easy *data, void *user_data) @@ -2186,6 +2217,10 @@ static CURLcode tls_ctx_setup(struct Curl_cfilter *cf, 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); + #elif defined(USE_WOLFSSL) if(ngtcp2_crypto_wolfssl_configure_client_context(ctx->wssl.ctx) != 0) { failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed"); diff --git a/lib/vquic/vquic-tls.c b/lib/vquic/vquic-tls.c index 882314659a..1caa83828a 100644 --- a/lib/vquic/vquic-tls.c +++ b/lib/vquic/vquic-tls.c @@ -240,7 +240,7 @@ CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx, #elif defined(USE_GNUTLS) (void)result; return Curl_gtls_ctx_init(&ctx->gtls, cf, data, peer, - (const unsigned char *)alpn, alpn_len, + (const unsigned char *)alpn, alpn_len, NULL, cb_setup, cb_user_data, ssl_user_data); #elif defined(USE_WOLFSSL) result = Curl_wssl_init_ctx(ctx, cf, data, cb_setup, cb_user_data); diff --git a/lib/vtls/gtls.c b/lib/vtls/gtls.c index 1b9bcd7a54..78cf1fdb61 100644 --- a/lib/vtls/gtls.c +++ b/lib/vtls/gtls.c @@ -720,49 +720,57 @@ static void gtls_sessionid_free(void *sessionid, size_t idsize) free(sessionid); } -static CURLcode gtls_update_session_id(struct Curl_cfilter *cf, - struct Curl_easy *data, - gnutls_session_t session) +CURLcode Curl_gtls_update_session_id(struct Curl_cfilter *cf, + struct Curl_easy *data, + gnutls_session_t session, + struct ssl_peer *peer, + const char *alpn) { struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - struct ssl_connect_data *connssl = cf->ctx; + void *connect_sessionid; + size_t connect_idsize = 0; CURLcode result = CURLE_OK; - if(ssl_config->primary.cache_session) { - /* we always unconditionally get the session id here, as even if we - already got it from the cache and asked to use it in the connection, it - might've been rejected and then a new one is in use now and we need to - detect that. */ - void *connect_sessionid; - size_t connect_idsize = 0; - - /* get the session ID data size */ - gnutls_session_get_data(session, NULL, &connect_idsize); - if(!connect_idsize) /* gnutls does this for some version combinations */ - return CURLE_OK; - - connect_sessionid = malloc(connect_idsize); /* get a buffer for it */ - if(!connect_sessionid) { - return CURLE_OUT_OF_MEMORY; - } - else { - /* extract session ID to the allocated buffer */ - gnutls_session_get_data(session, connect_sessionid, &connect_idsize); - - CURL_TRC_CF(data, cf, "get session id (len=%zu) and store in cache", - connect_idsize); - Curl_ssl_sessionid_lock(data); - /* store this session id, takes ownership */ - result = Curl_ssl_set_sessionid(cf, data, &connssl->peer, - connssl->alpn_negotiated, - connect_sessionid, connect_idsize, - gtls_sessionid_free); - Curl_ssl_sessionid_unlock(data); - } - } + if(!ssl_config->primary.cache_session) + return CURLE_OK; + + /* we always unconditionally get the session id here, as even if we + already got it from the cache and asked to use it in the connection, it + might've been rejected and then a new one is in use now and we need to + detect that. */ + + /* get the session ID data size */ + gnutls_session_get_data(session, NULL, &connect_idsize); + if(!connect_idsize) /* gnutls does this for some version combinations */ + return CURLE_OK; + + connect_sessionid = malloc(connect_idsize); /* get a buffer for it */ + if(!connect_sessionid) + return CURLE_OUT_OF_MEMORY; + + /* extract session ID to the allocated buffer */ + gnutls_session_get_data(session, connect_sessionid, &connect_idsize); + + CURL_TRC_CF(data, cf, "get session id (len=%zu, alpn=%s) and store in cache", + connect_idsize, alpn ? alpn : "-"); + Curl_ssl_sessionid_lock(data); + /* store this session id, takes ownership */ + result = Curl_ssl_set_sessionid(cf, data, peer, alpn, + connect_sessionid, connect_idsize, + gtls_sessionid_free); + Curl_ssl_sessionid_unlock(data); return result; } +static CURLcode cf_gtls_update_session_id(struct Curl_cfilter *cf, + struct Curl_easy *data, + gnutls_session_t session) +{ + struct ssl_connect_data *connssl = cf->ctx; + return Curl_gtls_update_session_id(cf, data, session, &connssl->peer, + connssl->alpn_negotiated); +} + static int gtls_handshake_cb(gnutls_session_t session, unsigned int htype, unsigned when, unsigned int incoming, const gnutls_datum_t *msg) @@ -778,7 +786,7 @@ static int gtls_handshake_cb(gnutls_session_t session, unsigned int htype, incoming ? "incoming" : "outgoing", htype); switch(htype) { case GNUTLS_HANDSHAKE_NEW_SESSION_TICKET: { - gtls_update_session_id(cf, data, session); + cf_gtls_update_session_id(cf, data, session); break; } default: @@ -1043,13 +1051,13 @@ CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx, struct Curl_easy *data, struct ssl_peer *peer, const unsigned char *alpn, size_t alpn_len, + struct ssl_connect_data *connssl, Curl_gtls_ctx_setup_cb *cb_setup, void *cb_user_data, void *ssl_user_data) { struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); - struct ssl_connect_data *connssl = cf->ctx; gnutls_datum_t gtls_alpns[5]; size_t gtls_alpns_count = 0; CURLcode result; @@ -1090,13 +1098,14 @@ CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx, if(rc < 0) infof(data, "SSL failed to set session ID"); else { - infof(data, "SSL reusing session ID (size=%zu)", ssl_idsize); + infof(data, "SSL reusing session ID (size=%zu, alpn=%s)", + ssl_idsize, session_alpn ? session_alpn : "-"); #ifdef DEBUGBUILD if((ssl_config->earlydata || !!getenv("CURL_USE_EARLYDATA")) && #else if(ssl_config->earlydata && #endif - !cf->conn->connect_only && + !cf->conn->connect_only && connssl && (gnutls_protocol_get_version(gctx->session) == GNUTLS_TLS1_3) && Curl_alpn_contains_proto(connssl->alpn, session_alpn)) { connssl->earlydata_max = @@ -1188,7 +1197,7 @@ gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) } result = Curl_gtls_ctx_init(&backend->gtls, cf, data, &connssl->peer, - proto.data, proto.len, NULL, NULL, cf); + proto.data, proto.len, connssl, NULL, NULL, cf); if(result) return result; @@ -1734,7 +1743,7 @@ static CURLcode gtls_verifyserver(struct Curl_cfilter *cf, /* Only on TLSv1.2 or lower do we have the session id now. For * TLSv1.3 we get it via a SESSION_TICKET message that arrives later. */ if(gnutls_protocol_get_version(session) < GNUTLS_TLS1_3) - result = gtls_update_session_id(cf, data, session); + result = cf_gtls_update_session_id(cf, data, session); out: return result; diff --git a/lib/vtls/gtls.h b/lib/vtls/gtls.h index b0ca55bfb7..4f9c540ed2 100644 --- a/lib/vtls/gtls.h +++ b/lib/vtls/gtls.h @@ -45,6 +45,7 @@ struct Curl_cfilter; struct ssl_primary_config; struct ssl_config_data; struct ssl_peer; +struct ssl_connect_data; struct gtls_shared_creds { gnutls_certificate_credentials_t creds; @@ -78,6 +79,7 @@ CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx, struct Curl_easy *data, struct ssl_peer *peer, const unsigned char *alpn, size_t alpn_len, + struct ssl_connect_data *connssl, Curl_gtls_ctx_setup_cb *cb_setup, void *cb_user_data, void *ssl_user_data); @@ -93,6 +95,13 @@ CURLcode Curl_gtls_verifyserver(struct Curl_easy *data, struct ssl_peer *peer, const char *pinned_key); +/* Extract TLS session and place in cache, if configured. */ +CURLcode Curl_gtls_update_session_id(struct Curl_cfilter *cf, + struct Curl_easy *data, + gnutls_session_t session, + struct ssl_peer *peer, + const char *alpn); + extern const struct Curl_ssl Curl_ssl_gnutls; #endif /* USE_GNUTLS */ diff --git a/tests/http/test_02_download.py b/tests/http/test_02_download.py index d40a9510cb..3ce88df583 100644 --- a/tests/http/test_02_download.py +++ b/tests/http/test_02_download.py @@ -625,10 +625,16 @@ class TestDownload: self.check_downloads(client, srcfile, count) # check that TLS earlydata worked as expected earlydata = {} + reused_session = False for line in r.trace_lines: m = re.match(r'^\[t-(\d+)] EarlyData: (\d+)', line) if m: earlydata[int(m.group(1))] = int(m.group(2)) + continue + m = re.match(r'\[1-1] \* SSL reusing session.*', line) + if m: + reused_session = True + assert reused_session, 'session was not reused for 2nd transfer' assert earlydata[0] == 0, f'{earlydata}' if proto == 'http/1.1': assert earlydata[1] == 69, f'{earlydata}'