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; + if(!ssl_config->primary.cache_session) + return CURLE_OK; - /* 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; + /* 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. */ - 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); + /* 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; - 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); - } - } + 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}'