]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
gnutls: use session cache for QUIC
authorStefan Eissing <stefan@eissing.org>
Fri, 11 Oct 2024 11:09:51 +0000 (13:09 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Fri, 11 Oct 2024 21:37:47 +0000 (23:37 +0200)
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

lib/vquic/curl_ngtcp2.c
lib/vquic/vquic-tls.c
lib/vtls/gtls.c
lib/vtls/gtls.h
tests/http/test_02_download.py

index ef93d98af39445168718dd52ebb18d834e288d5c..ef9635dcf720160d02fc6e930d06a1a8c571fb42 100644 (file)
@@ -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");
index 882314659a1a7d18d74129d1d12eeb521d3d4090..1caa83828af133dfcab9281cec0dc322f5b862c6 100644 (file)
@@ -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);
index 1b9bcd7a5415d7f93e3d7aeff8a58fab15ddc873..78cf1fdb6119abc793e7f9ead7e9c5e0ec46f14e 100644 (file)
@@ -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;
index b0ca55bfb756bffa5231b6db394cebd7629b112e..4f9c540ed27aaa66a52ba7d83793ec372e5081f3 100644 (file)
@@ -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 */
index d40a9510cb5b4d76c18bbdcfbb4d85e32fcf3b52..3ce88df583f542719443513eac8597b36e5babf1 100644 (file)
@@ -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}'