Skip to content

Commit fe8399f

Browse files
icingbagder
authored andcommitted
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 curl#15265
1 parent 954177b commit fe8399f

File tree

5 files changed

+102
-43
lines changed

5 files changed

+102
-43
lines changed

lib/vquic/curl_ngtcp2.c

+35
Original file line numberDiff line numberDiff line change
@@ -2153,6 +2153,37 @@ static int quic_ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid)
21532153
}
21542154
#endif /* USE_OPENSSL */
21552155

2156+
#ifdef USE_GNUTLS
2157+
static int quic_gtls_handshake_cb(gnutls_session_t session, unsigned int htype,
2158+
unsigned when, unsigned int incoming,
2159+
const gnutls_datum_t *msg)
2160+
{
2161+
ngtcp2_crypto_conn_ref *conn_ref = gnutls_session_get_ptr(session);
2162+
struct Curl_cfilter *cf = conn_ref ? conn_ref->user_data : NULL;
2163+
struct cf_ngtcp2_ctx *ctx = cf->ctx;
2164+
2165+
(void)msg;
2166+
(void)incoming;
2167+
if(when) { /* after message has been processed */
2168+
struct Curl_easy *data = CF_DATA_CURRENT(cf);
2169+
DEBUGASSERT(data);
2170+
if(data) {
2171+
CURL_TRC_CF(data, cf, "handshake: %s message type %d",
2172+
incoming ? "incoming" : "outgoing", htype);
2173+
}
2174+
switch(htype) {
2175+
case GNUTLS_HANDSHAKE_NEW_SESSION_TICKET: {
2176+
(void)Curl_gtls_update_session_id(cf, data, session, &ctx->peer, "h3");
2177+
break;
2178+
}
2179+
default:
2180+
break;
2181+
}
2182+
}
2183+
return 0;
2184+
}
2185+
#endif /* USE_GNUTLS */
2186+
21562187
static CURLcode tls_ctx_setup(struct Curl_cfilter *cf,
21572188
struct Curl_easy *data,
21582189
void *user_data)
@@ -2186,6 +2217,10 @@ static CURLcode tls_ctx_setup(struct Curl_cfilter *cf,
21862217
failf(data, "ngtcp2_crypto_gnutls_configure_client_session failed");
21872218
return CURLE_FAILED_INIT;
21882219
}
2220+
gnutls_handshake_set_hook_function(ctx->gtls.session,
2221+
GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST,
2222+
quic_gtls_handshake_cb);
2223+
21892224
#elif defined(USE_WOLFSSL)
21902225
if(ngtcp2_crypto_wolfssl_configure_client_context(ctx->wssl.ctx) != 0) {
21912226
failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed");

lib/vquic/vquic-tls.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx,
240240
#elif defined(USE_GNUTLS)
241241
(void)result;
242242
return Curl_gtls_ctx_init(&ctx->gtls, cf, data, peer,
243-
(const unsigned char *)alpn, alpn_len,
243+
(const unsigned char *)alpn, alpn_len, NULL,
244244
cb_setup, cb_user_data, ssl_user_data);
245245
#elif defined(USE_WOLFSSL)
246246
result = Curl_wssl_init_ctx(ctx, cf, data, cb_setup, cb_user_data);

lib/vtls/gtls.c

+51-42
Original file line numberDiff line numberDiff line change
@@ -720,49 +720,57 @@ static void gtls_sessionid_free(void *sessionid, size_t idsize)
720720
free(sessionid);
721721
}
722722

723-
static CURLcode gtls_update_session_id(struct Curl_cfilter *cf,
724-
struct Curl_easy *data,
725-
gnutls_session_t session)
723+
CURLcode Curl_gtls_update_session_id(struct Curl_cfilter *cf,
724+
struct Curl_easy *data,
725+
gnutls_session_t session,
726+
struct ssl_peer *peer,
727+
const char *alpn)
726728
{
727729
struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
728-
struct ssl_connect_data *connssl = cf->ctx;
730+
void *connect_sessionid;
731+
size_t connect_idsize = 0;
729732
CURLcode result = CURLE_OK;
730733

731-
if(ssl_config->primary.cache_session) {
732-
/* we always unconditionally get the session id here, as even if we
733-
already got it from the cache and asked to use it in the connection, it
734-
might've been rejected and then a new one is in use now and we need to
735-
detect that. */
736-
void *connect_sessionid;
737-
size_t connect_idsize = 0;
738-
739-
/* get the session ID data size */
740-
gnutls_session_get_data(session, NULL, &connect_idsize);
741-
if(!connect_idsize) /* gnutls does this for some version combinations */
742-
return CURLE_OK;
743-
744-
connect_sessionid = malloc(connect_idsize); /* get a buffer for it */
745-
if(!connect_sessionid) {
746-
return CURLE_OUT_OF_MEMORY;
747-
}
748-
else {
749-
/* extract session ID to the allocated buffer */
750-
gnutls_session_get_data(session, connect_sessionid, &connect_idsize);
751-
752-
CURL_TRC_CF(data, cf, "get session id (len=%zu) and store in cache",
753-
connect_idsize);
754-
Curl_ssl_sessionid_lock(data);
755-
/* store this session id, takes ownership */
756-
result = Curl_ssl_set_sessionid(cf, data, &connssl->peer,
757-
connssl->alpn_negotiated,
758-
connect_sessionid, connect_idsize,
759-
gtls_sessionid_free);
760-
Curl_ssl_sessionid_unlock(data);
761-
}
762-
}
734+
if(!ssl_config->primary.cache_session)
735+
return CURLE_OK;
736+
737+
/* we always unconditionally get the session id here, as even if we
738+
already got it from the cache and asked to use it in the connection, it
739+
might've been rejected and then a new one is in use now and we need to
740+
detect that. */
741+
742+
/* get the session ID data size */
743+
gnutls_session_get_data(session, NULL, &connect_idsize);
744+
if(!connect_idsize) /* gnutls does this for some version combinations */
745+
return CURLE_OK;
746+
747+
connect_sessionid = malloc(connect_idsize); /* get a buffer for it */
748+
if(!connect_sessionid)
749+
return CURLE_OUT_OF_MEMORY;
750+
751+
/* extract session ID to the allocated buffer */
752+
gnutls_session_get_data(session, connect_sessionid, &connect_idsize);
753+
754+
CURL_TRC_CF(data, cf, "get session id (len=%zu, alpn=%s) and store in cache",
755+
connect_idsize, alpn ? alpn : "-");
756+
Curl_ssl_sessionid_lock(data);
757+
/* store this session id, takes ownership */
758+
result = Curl_ssl_set_sessionid(cf, data, peer, alpn,
759+
connect_sessionid, connect_idsize,
760+
gtls_sessionid_free);
761+
Curl_ssl_sessionid_unlock(data);
763762
return result;
764763
}
765764

765+
static CURLcode cf_gtls_update_session_id(struct Curl_cfilter *cf,
766+
struct Curl_easy *data,
767+
gnutls_session_t session)
768+
{
769+
struct ssl_connect_data *connssl = cf->ctx;
770+
return Curl_gtls_update_session_id(cf, data, session, &connssl->peer,
771+
connssl->alpn_negotiated);
772+
}
773+
766774
static int gtls_handshake_cb(gnutls_session_t session, unsigned int htype,
767775
unsigned when, unsigned int incoming,
768776
const gnutls_datum_t *msg)
@@ -778,7 +786,7 @@ static int gtls_handshake_cb(gnutls_session_t session, unsigned int htype,
778786
incoming ? "incoming" : "outgoing", htype);
779787
switch(htype) {
780788
case GNUTLS_HANDSHAKE_NEW_SESSION_TICKET: {
781-
gtls_update_session_id(cf, data, session);
789+
cf_gtls_update_session_id(cf, data, session);
782790
break;
783791
}
784792
default:
@@ -1043,13 +1051,13 @@ CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx,
10431051
struct Curl_easy *data,
10441052
struct ssl_peer *peer,
10451053
const unsigned char *alpn, size_t alpn_len,
1054+
struct ssl_connect_data *connssl,
10461055
Curl_gtls_ctx_setup_cb *cb_setup,
10471056
void *cb_user_data,
10481057
void *ssl_user_data)
10491058
{
10501059
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
10511060
struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
1052-
struct ssl_connect_data *connssl = cf->ctx;
10531061
gnutls_datum_t gtls_alpns[5];
10541062
size_t gtls_alpns_count = 0;
10551063
CURLcode result;
@@ -1090,13 +1098,14 @@ CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx,
10901098
if(rc < 0)
10911099
infof(data, "SSL failed to set session ID");
10921100
else {
1093-
infof(data, "SSL reusing session ID (size=%zu)", ssl_idsize);
1101+
infof(data, "SSL reusing session ID (size=%zu, alpn=%s)",
1102+
ssl_idsize, session_alpn ? session_alpn : "-");
10941103
#ifdef DEBUGBUILD
10951104
if((ssl_config->earlydata || !!getenv("CURL_USE_EARLYDATA")) &&
10961105
#else
10971106
if(ssl_config->earlydata &&
10981107
#endif
1099-
!cf->conn->connect_only &&
1108+
!cf->conn->connect_only && connssl &&
11001109
(gnutls_protocol_get_version(gctx->session) == GNUTLS_TLS1_3) &&
11011110
Curl_alpn_contains_proto(connssl->alpn, session_alpn)) {
11021111
connssl->earlydata_max =
@@ -1188,7 +1197,7 @@ gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
11881197
}
11891198

11901199
result = Curl_gtls_ctx_init(&backend->gtls, cf, data, &connssl->peer,
1191-
proto.data, proto.len, NULL, NULL, cf);
1200+
proto.data, proto.len, connssl, NULL, NULL, cf);
11921201
if(result)
11931202
return result;
11941203

@@ -1734,7 +1743,7 @@ static CURLcode gtls_verifyserver(struct Curl_cfilter *cf,
17341743
/* Only on TLSv1.2 or lower do we have the session id now. For
17351744
* TLSv1.3 we get it via a SESSION_TICKET message that arrives later. */
17361745
if(gnutls_protocol_get_version(session) < GNUTLS_TLS1_3)
1737-
result = gtls_update_session_id(cf, data, session);
1746+
result = cf_gtls_update_session_id(cf, data, session);
17381747

17391748
out:
17401749
return result;

lib/vtls/gtls.h

+9
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ struct Curl_cfilter;
4545
struct ssl_primary_config;
4646
struct ssl_config_data;
4747
struct ssl_peer;
48+
struct ssl_connect_data;
4849

4950
struct gtls_shared_creds {
5051
gnutls_certificate_credentials_t creds;
@@ -78,6 +79,7 @@ CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx,
7879
struct Curl_easy *data,
7980
struct ssl_peer *peer,
8081
const unsigned char *alpn, size_t alpn_len,
82+
struct ssl_connect_data *connssl,
8183
Curl_gtls_ctx_setup_cb *cb_setup,
8284
void *cb_user_data,
8385
void *ssl_user_data);
@@ -93,6 +95,13 @@ CURLcode Curl_gtls_verifyserver(struct Curl_easy *data,
9395
struct ssl_peer *peer,
9496
const char *pinned_key);
9597

98+
/* Extract TLS session and place in cache, if configured. */
99+
CURLcode Curl_gtls_update_session_id(struct Curl_cfilter *cf,
100+
struct Curl_easy *data,
101+
gnutls_session_t session,
102+
struct ssl_peer *peer,
103+
const char *alpn);
104+
96105
extern const struct Curl_ssl Curl_ssl_gnutls;
97106

98107
#endif /* USE_GNUTLS */

tests/http/test_02_download.py

+6
Original file line numberDiff line numberDiff line change
@@ -625,10 +625,16 @@ def test_02_32_earlydata(self, env: Env, httpd, nghttpx, proto):
625625
self.check_downloads(client, srcfile, count)
626626
# check that TLS earlydata worked as expected
627627
earlydata = {}
628+
reused_session = False
628629
for line in r.trace_lines:
629630
m = re.match(r'^\[t-(\d+)] EarlyData: (\d+)', line)
630631
if m:
631632
earlydata[int(m.group(1))] = int(m.group(2))
633+
continue
634+
m = re.match(r'\[1-1] \* SSL reusing session.*', line)
635+
if m:
636+
reused_session = True
637+
assert reused_session, 'session was not reused for 2nd transfer'
632638
assert earlydata[0] == 0, f'{earlydata}'
633639
if proto == 'http/1.1':
634640
assert earlydata[1] == 69, f'{earlydata}'

0 commit comments

Comments
 (0)