Browse Source

vquic: extract TLS setup into own source

- separate ngtcp2 specific parts out
- provide callback during init to allow ngtcp2 to apply its defaults

Closes #12678
Stefan Eissing 4 months ago
parent
commit
5d044ad948
9 changed files with 839 additions and 724 deletions
  1. 4 2
      lib/Makefile.inc
  2. 11 31
      lib/cf-socket.c
  3. 48 503
      lib/vquic/curl_ngtcp2.c
  4. 51 186
      lib/vquic/curl_quiche.c
  5. 603 0
      lib/vquic/vquic-tls.c
  6. 98 0
      lib/vquic/vquic-tls.h
  7. 6 1
      lib/vquic/vquic.c
  8. 3 1
      lib/vquic/vquic_int.h
  9. 15 0
      tests/http/test_01_basic.py

+ 4 - 2
lib/Makefile.inc

@@ -79,14 +79,16 @@ LIB_VQUIC_CFILES = \
   vquic/curl_msh3.c   \
   vquic/curl_ngtcp2.c   \
   vquic/curl_quiche.c   \
-  vquic/vquic.c
+  vquic/vquic.c \
+  vquic/vquic-tls.c
 
 LIB_VQUIC_HFILES = \
   vquic/curl_msh3.h   \
   vquic/curl_ngtcp2.h   \
   vquic/curl_quiche.h   \
   vquic/vquic.h    \
-  vquic/vquic_int.h
+  vquic/vquic_int.h \
+  vquic/vquic-tls.h
 
 LIB_VSSH_CFILES =  \
   vssh/libssh.c    \

+ 11 - 31
lib/cf-socket.c

@@ -788,6 +788,7 @@ struct cf_socket_ctx {
 #endif
   BIT(got_first_byte);               /* if first byte was received */
   BIT(accepted);                     /* socket was accepted, not connected */
+  BIT(sock_connected);               /* socket is "connected", e.g. in UDP */
   BIT(active);
   BIT(buffer_recv);
 };
@@ -1053,7 +1054,7 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf,
 
   /* set socket non-blocking */
   (void)curlx_nonblock(ctx->sock, TRUE);
-
+  ctx->sock_connected = (ctx->addr.socktype != SOCK_DGRAM);
 out:
   if(result) {
     if(ctx->sock != CURL_SOCKET_BAD) {
@@ -1241,11 +1242,14 @@ static void cf_socket_adjust_pollset(struct Curl_cfilter *cf,
   struct cf_socket_ctx *ctx = cf->ctx;
 
   if(ctx->sock != CURL_SOCKET_BAD) {
-    if(!cf->connected)
+    if(!cf->connected) {
       Curl_pollset_set_out_only(data, ps, ctx->sock);
-    else if(!ctx->active)
+      CURL_TRC_CF(data, cf, "adjust_pollset(!connected) -> %d socks", ps->num);
+    }
+    else if(!ctx->active) {
       Curl_pollset_add_in(data, ps, ctx->sock);
-    CURL_TRC_CF(data, cf, "adjust_pollset -> %d socks", ps->num);
+      CURL_TRC_CF(data, cf, "adjust_pollset(!active) -> %d socks", ps->num);
+    }
   }
 }
 
@@ -1428,36 +1432,11 @@ out:
 static void conn_set_primary_ip(struct Curl_cfilter *cf,
                                 struct Curl_easy *data)
 {
-#ifdef HAVE_GETPEERNAME
   struct cf_socket_ctx *ctx = cf->ctx;
-  if(!(data->conn->handler->protocol & CURLPROTO_TFTP)) {
-    /* TFTP does not connect the endpoint: getpeername() failed with errno
-       107: Transport endpoint is not connected */
 
-    char buffer[STRERROR_LEN];
-    struct Curl_sockaddr_storage ssrem;
-    curl_socklen_t plen;
-    int port;
-
-    plen = sizeof(ssrem);
-    memset(&ssrem, 0, plen);
-    if(getpeername(ctx->sock, (struct sockaddr*) &ssrem, &plen)) {
-      int error = SOCKERRNO;
-      failf(data, "getpeername() failed with errno %d: %s",
-            error, Curl_strerror(error, buffer, sizeof(buffer)));
-      return;
-    }
-    if(!Curl_addr2string((struct sockaddr*)&ssrem, plen,
-                         cf->conn->primary_ip, &port)) {
-      failf(data, "ssrem inet_ntop() failed with errno %d: %s",
-            errno, Curl_strerror(errno, buffer, sizeof(buffer)));
-      return;
-    }
-  }
-#else
-  cf->conn->primary_ip[0] = 0;
   (void)data;
-#endif
+  DEBUGASSERT(sizeof(ctx->r_ip) == sizeof(cf->conn->primary_ip));
+  memcpy(cf->conn->primary_ip, ctx->r_ip, sizeof(cf->conn->primary_ip));
 }
 
 static void cf_socket_active(struct Curl_cfilter *cf, struct Curl_easy *data)
@@ -1651,6 +1630,7 @@ static CURLcode cf_udp_setup_quic(struct Curl_cfilter *cf,
   if(-1 == rc) {
     return socket_connect_result(data, ctx->r_ip, SOCKERRNO);
   }
+  ctx->sock_connected = TRUE;
   set_local_ip(cf, data);
   CURL_TRC_CF(data, cf, "%s socket %" CURL_FORMAT_SOCKET_T
               " connected: [%s:%d] -> [%s:%d]",

+ 48 - 503
lib/vquic/curl_ngtcp2.c

@@ -41,7 +41,6 @@
 #include "vtls/gtls.h"
 #elif defined(USE_WOLFSSL)
 #include <ngtcp2/ngtcp2_crypto_wolfssl.h>
-#include "vtls/wolfssl.h"
 #endif
 
 #include "urldata.h"
@@ -61,6 +60,7 @@
 #include "inet_pton.h"
 #include "vquic.h"
 #include "vquic_int.h"
+#include "vquic-tls.h"
 #include "vtls/keylog.h"
 #include "vtls/vtls.h"
 #include "curl_ngtcp2.h"
@@ -73,9 +73,6 @@
 #include "memdebug.h"
 
 
-#define H3_ALPN_H3_29 "\x5h3-29"
-#define H3_ALPN_H3 "\x2h3"
-
 #define QUIC_MAX_STREAMS (256*1024)
 #define QUIC_MAX_DATA (1*1024*1024)
 #define QUIC_HANDSHAKE_TIMEOUT (10*NGTCP2_SECONDS)
@@ -101,25 +98,6 @@
           (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE)
 
 
-#ifdef USE_OPENSSL
-#define QUIC_CIPHERS                                                          \
-  "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_"               \
-  "POLY1305_SHA256:TLS_AES_128_CCM_SHA256"
-#define QUIC_GROUPS "P-256:X25519:P-384:P-521"
-#elif defined(USE_GNUTLS)
-#define QUIC_PRIORITY \
-  "NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:" \
-  "+CHACHA20-POLY1305:+AES-128-CCM:-GROUP-ALL:+GROUP-SECP256R1:" \
-  "+GROUP-X25519:+GROUP-SECP384R1:+GROUP-SECP521R1:" \
-  "%DISABLE_TLS13_COMPAT_MODE"
-#elif defined(USE_WOLFSSL)
-#define QUIC_CIPHERS                                                          \
-  "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_"               \
-  "POLY1305_SHA256:TLS_AES_128_CCM_SHA256"
-#define QUIC_GROUPS "P-256:P-384:P-521"
-#endif
-
-
 /*
  * Store ngtcp2 version info in this buffer.
  */
@@ -134,6 +112,7 @@ void Curl_ngtcp2_ver(char *p, size_t len)
 struct cf_ngtcp2_ctx {
   struct cf_quic_ctx q;
   struct ssl_peer peer;
+  struct quic_tls_ctx tls;
   ngtcp2_path connected_path;
   ngtcp2_conn *qconn;
   ngtcp2_cid dcid;
@@ -143,30 +122,16 @@ struct cf_ngtcp2_ctx {
   ngtcp2_transport_params transport_params;
   ngtcp2_ccerr last_error;
   ngtcp2_crypto_conn_ref conn_ref;
-#ifdef USE_OPENSSL
-  SSL_CTX *sslctx;
-  SSL *ssl;
-#elif defined(USE_GNUTLS)
-  struct gtls_instance *gtls;
-#elif defined(USE_WOLFSSL)
-  WOLFSSL_CTX *sslctx;
-  WOLFSSL *ssl;
-#endif
   struct cf_call_data call_data;
   nghttp3_conn *h3conn;
   nghttp3_settings h3settings;
   struct curltime started_at;        /* time the current attempt started */
   struct curltime handshake_at;      /* time connect handshake finished */
-  struct curltime first_byte_at;     /* when first byte was recvd */
   struct curltime reconnect_at;      /* time the next attempt should start */
   struct bufc_pool stream_bufcp;     /* chunk pool for streams */
   size_t max_stream_window;          /* max flow window for one stream */
   uint64_t max_idle_ms;              /* max idle time for QUIC connection */
   int qlogfd;
-  BIT(got_first_byte);               /* if first byte was received */
-#ifdef USE_OPENSSL
-  BIT(x509_store_setup);             /* if x509 store has been set up */
-#endif
 };
 
 /* How to access `call_data` from a cf_ngtcp2 filter */
@@ -413,388 +378,8 @@ static void quic_settings(struct cf_ngtcp2_ctx *ctx,
   }
 }
 
-#ifdef USE_OPENSSL
-static void keylog_callback(const SSL *ssl, const char *line)
-{
-  (void)ssl;
-  Curl_tls_keylog_write_line(line);
-}
-#elif defined(USE_GNUTLS)
-static int keylog_callback(gnutls_session_t session, const char *label,
-                    const gnutls_datum_t *secret)
-{
-  gnutls_datum_t crandom;
-  gnutls_datum_t srandom;
-
-  gnutls_session_get_random(session, &crandom, &srandom);
-  if(crandom.size != 32) {
-    return -1;
-  }
-
-  Curl_tls_keylog_write(label, crandom.data, secret->data, secret->size);
-  return 0;
-}
-#elif defined(USE_WOLFSSL)
-#if defined(HAVE_SECRET_CALLBACK)
-static void keylog_callback(const WOLFSSL *ssl, const char *line)
-{
-  (void)ssl;
-  Curl_tls_keylog_write_line(line);
-}
-#endif
-#endif
-
 static int init_ngh3_conn(struct Curl_cfilter *cf);
 
-#ifdef USE_OPENSSL
-static CURLcode quic_ssl_ctx(SSL_CTX **pssl_ctx,
-                             struct Curl_cfilter *cf, struct Curl_easy *data)
-{
-  struct cf_ngtcp2_ctx *ctx = cf->ctx;
-  struct ssl_primary_config *conn_config;
-  CURLcode result = CURLE_FAILED_INIT;
-
-  SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method());
-  if(!ssl_ctx) {
-    result = CURLE_OUT_OF_MEMORY;
-    goto out;
-  }
-  conn_config = Curl_ssl_cf_get_primary_config(cf);
-  if(!conn_config) {
-    result = CURLE_FAILED_INIT;
-    goto out;
-  }
-
-#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)
-  if(ngtcp2_crypto_boringssl_configure_client_context(ssl_ctx) != 0) {
-    failf(data, "ngtcp2_crypto_boringssl_configure_client_context failed");
-    goto out;
-  }
-#else
-  if(ngtcp2_crypto_quictls_configure_client_context(ssl_ctx) != 0) {
-    failf(data, "ngtcp2_crypto_quictls_configure_client_context failed");
-    goto out;
-  }
-#endif
-
-  SSL_CTX_set_default_verify_paths(ssl_ctx);
-
-  {
-    const char *curves = conn_config->curves ?
-      conn_config->curves : QUIC_GROUPS;
-    if(!SSL_CTX_set1_curves_list(ssl_ctx, curves)) {
-      failf(data, "failed setting curves list for QUIC: '%s'", curves);
-      return CURLE_SSL_CIPHER;
-    }
-  }
-
-#ifndef OPENSSL_IS_BORINGSSL
-  {
-    const char *ciphers13 = conn_config->cipher_list13 ?
-      conn_config->cipher_list13 : QUIC_CIPHERS;
-    if(SSL_CTX_set_ciphersuites(ssl_ctx, ciphers13) != 1) {
-      failf(data, "failed setting QUIC cipher suite: %s", ciphers13);
-      return CURLE_SSL_CIPHER;
-    }
-    infof(data, "QUIC cipher selection: %s", ciphers13);
-  }
-#endif
-
-  /* Open the file if a TLS or QUIC backend has not done this before. */
-  Curl_tls_keylog_open();
-  if(Curl_tls_keylog_enabled()) {
-    SSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback);
-  }
-
-  /* OpenSSL always tries to verify the peer, this only says whether it should
-   * fail to connect if the verification fails, or if it should continue
-   * anyway. In the latter case the result of the verification is checked with
-   * SSL_get_verify_result() below. */
-  SSL_CTX_set_verify(ssl_ctx, conn_config->verifypeer ?
-                     SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL);
-
-  /* give application a chance to interfere with SSL set up. */
-  if(data->set.ssl.fsslctx) {
-    /* When a user callback is installed to modify the SSL_CTX,
-     * we need to do the full initialization before calling it.
-     * See: #11800 */
-    if(!ctx->x509_store_setup) {
-      result = Curl_ssl_setup_x509_store(cf, data, ssl_ctx);
-      if(result)
-        goto out;
-      ctx->x509_store_setup = TRUE;
-    }
-    Curl_set_in_callback(data, true);
-    result = (*data->set.ssl.fsslctx)(data, ssl_ctx,
-                                      data->set.ssl.fsslctxp);
-    Curl_set_in_callback(data, false);
-    if(result) {
-      failf(data, "error signaled by ssl ctx callback");
-      goto out;
-    }
-  }
-  result = CURLE_OK;
-
-out:
-  *pssl_ctx = result? NULL : ssl_ctx;
-  if(result && ssl_ctx)
-    SSL_CTX_free(ssl_ctx);
-  return result;
-}
-
-static CURLcode quic_set_client_cert(struct Curl_cfilter *cf,
-                                     struct Curl_easy *data)
-{
-  struct cf_ngtcp2_ctx *ctx = cf->ctx;
-  SSL_CTX *ssl_ctx = ctx->sslctx;
-  const struct ssl_config_data *ssl_config;
-
-  ssl_config = Curl_ssl_cf_get_config(cf, data);
-  DEBUGASSERT(ssl_config);
-
-  if(ssl_config->primary.clientcert || ssl_config->primary.cert_blob
-     || ssl_config->cert_type) {
-    return Curl_ossl_set_client_cert(
-        data, ssl_ctx, ssl_config->primary.clientcert,
-        ssl_config->primary.cert_blob, ssl_config->cert_type,
-        ssl_config->key, ssl_config->key_blob,
-        ssl_config->key_type, ssl_config->key_passwd);
-  }
-
-  return CURLE_OK;
-}
-
-/** SSL callbacks ***/
-
-static CURLcode quic_init_ssl(struct Curl_cfilter *cf,
-                              struct Curl_easy *data)
-{
-  struct cf_ngtcp2_ctx *ctx = cf->ctx;
-  const uint8_t *alpn = NULL;
-  size_t alpnlen = 0;
-
-  DEBUGASSERT(!ctx->ssl);
-  ctx->ssl = SSL_new(ctx->sslctx);
-
-  SSL_set_app_data(ctx->ssl, &ctx->conn_ref);
-  SSL_set_connect_state(ctx->ssl);
-  SSL_set_quic_use_legacy_codepoint(ctx->ssl, 0);
-
-  alpn = (const uint8_t *)H3_ALPN_H3 H3_ALPN_H3_29;
-  alpnlen = sizeof(H3_ALPN_H3) - 1 + sizeof(H3_ALPN_H3_29) - 1;
-  if(alpn)
-    SSL_set_alpn_protos(ctx->ssl, alpn, (int)alpnlen);
-
-  /* set SNI */
-  if(ctx->peer.sni) {
-    if(!SSL_set_tlsext_host_name(ctx->ssl, ctx->peer.sni)) {
-      failf(data, "Failed set SNI");
-      SSL_free(ctx->ssl);
-      ctx->ssl = NULL;
-      return CURLE_QUIC_CONNECT_ERROR;
-    }
-  }
-  return CURLE_OK;
-}
-#elif defined(USE_GNUTLS)
-static CURLcode quic_init_ssl(struct Curl_cfilter *cf,
-                              struct Curl_easy *data)
-{
-  struct cf_ngtcp2_ctx *ctx = cf->ctx;
-  struct ssl_primary_config *conn_config;
-  CURLcode result;
-  gnutls_datum_t alpn[2];
-  /* this will need some attention when HTTPS proxy over QUIC get fixed */
-  long * const pverifyresult = &data->set.ssl.certverifyresult;
-  int rc;
-
-  conn_config = Curl_ssl_cf_get_primary_config(cf);
-  if(!conn_config)
-    return CURLE_FAILED_INIT;
-
-  DEBUGASSERT(ctx->gtls == NULL);
-  ctx->gtls = calloc(1, sizeof(*(ctx->gtls)));
-  if(!ctx->gtls)
-    return CURLE_OUT_OF_MEMORY;
-
-  result = gtls_client_init(data, conn_config, &data->set.ssl,
-                            &ctx->peer, ctx->gtls, pverifyresult);
-  if(result)
-    return result;
-
-  gnutls_session_set_ptr(ctx->gtls->session, &ctx->conn_ref);
-
-  if(ngtcp2_crypto_gnutls_configure_client_session(ctx->gtls->session) != 0) {
-    CURL_TRC_CF(data, cf,
-                "ngtcp2_crypto_gnutls_configure_client_session failed\n");
-    return CURLE_QUIC_CONNECT_ERROR;
-  }
-
-  rc = gnutls_priority_set_direct(ctx->gtls->session, QUIC_PRIORITY, NULL);
-  if(rc < 0) {
-    CURL_TRC_CF(data, cf, "gnutls_priority_set_direct failed: %s\n",
-                gnutls_strerror(rc));
-    return CURLE_QUIC_CONNECT_ERROR;
-  }
-
-  /* Open the file if a TLS or QUIC backend has not done this before. */
-  Curl_tls_keylog_open();
-  if(Curl_tls_keylog_enabled()) {
-    gnutls_session_set_keylog_function(ctx->gtls->session, keylog_callback);
-  }
-
-  /* strip the first byte (the length) from NGHTTP3_ALPN_H3 */
-  alpn[0].data = (unsigned char *)H3_ALPN_H3 + 1;
-  alpn[0].size = sizeof(H3_ALPN_H3) - 2;
-  alpn[1].data = (unsigned char *)H3_ALPN_H3_29 + 1;
-  alpn[1].size = sizeof(H3_ALPN_H3_29) - 2;
-
-  gnutls_alpn_set_protocols(ctx->gtls->session,
-                            alpn, 2, GNUTLS_ALPN_MANDATORY);
-  return CURLE_OK;
-}
-#elif defined(USE_WOLFSSL)
-
-static CURLcode quic_ssl_ctx(WOLFSSL_CTX **pssl_ctx,
-                             struct Curl_cfilter *cf, struct Curl_easy *data)
-{
-  CURLcode result = CURLE_FAILED_INIT;
-  struct ssl_primary_config *conn_config;
-  WOLFSSL_CTX *ssl_ctx = NULL;
-
-  conn_config = Curl_ssl_cf_get_primary_config(cf);
-  if(!conn_config) {
-    result = CURLE_FAILED_INIT;
-    goto out;
-  }
-
-  ssl_ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method());
-  if(!ssl_ctx) {
-    result = CURLE_OUT_OF_MEMORY;
-    goto out;
-  }
-
-  if(ngtcp2_crypto_wolfssl_configure_client_context(ssl_ctx) != 0) {
-    failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed");
-    result = CURLE_FAILED_INIT;
-    goto out;
-  }
-
-  wolfSSL_CTX_set_default_verify_paths(ssl_ctx);
-
-  if(wolfSSL_CTX_set_cipher_list(ssl_ctx, conn_config->cipher_list13 ?
-                                 conn_config->cipher_list13 :
-                                 QUIC_CIPHERS) != 1) {
-    char error_buffer[256];
-    ERR_error_string_n(ERR_get_error(), error_buffer, sizeof(error_buffer));
-    failf(data, "wolfSSL failed to set ciphers: %s", error_buffer);
-    goto out;
-  }
-
-  if(wolfSSL_CTX_set1_groups_list(ssl_ctx, conn_config->curves ?
-                                  conn_config->curves :
-                                  (char *)QUIC_GROUPS) != 1) {
-    failf(data, "wolfSSL failed to set curves");
-    goto out;
-  }
-
-  /* Open the file if a TLS or QUIC backend has not done this before. */
-  Curl_tls_keylog_open();
-  if(Curl_tls_keylog_enabled()) {
-#if defined(HAVE_SECRET_CALLBACK)
-    wolfSSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback);
-#else
-    failf(data, "wolfSSL was built without keylog callback");
-    goto out;
-#endif
-  }
-
-  if(conn_config->verifypeer) {
-    const char * const ssl_cafile = conn_config->CAfile;
-    const char * const ssl_capath = conn_config->CApath;
-
-    wolfSSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
-    if(ssl_cafile || ssl_capath) {
-      /* tell wolfSSL where to find CA certificates that are used to verify
-         the server's certificate. */
-      int rc =
-        wolfSSL_CTX_load_verify_locations_ex(ssl_ctx, ssl_cafile, ssl_capath,
-                                             WOLFSSL_LOAD_FLAG_IGNORE_ERR);
-      if(SSL_SUCCESS != rc) {
-        /* Fail if we insist on successfully verifying the server. */
-        failf(data, "error setting certificate verify locations:"
-              "  CAfile: %s CApath: %s",
-              ssl_cafile ? ssl_cafile : "none",
-              ssl_capath ? ssl_capath : "none");
-        goto out;
-      }
-      infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none");
-      infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none");
-    }
-#ifdef CURL_CA_FALLBACK
-    else {
-      /* verifying the peer without any CA certificates won't work so
-         use wolfssl's built-in default as fallback */
-      wolfSSL_CTX_set_default_verify_paths(ssl_ctx);
-    }
-#endif
-  }
-  else {
-    wolfSSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL);
-  }
-
-  /* give application a chance to interfere with SSL set up. */
-  if(data->set.ssl.fsslctx) {
-    Curl_set_in_callback(data, true);
-    result = (*data->set.ssl.fsslctx)(data, ssl_ctx,
-                                      data->set.ssl.fsslctxp);
-    Curl_set_in_callback(data, false);
-    if(result) {
-      failf(data, "error signaled by ssl ctx callback");
-      goto out;
-    }
-  }
-  result = CURLE_OK;
-
-out:
-  *pssl_ctx = result? NULL : ssl_ctx;
-  if(result && ssl_ctx)
-    SSL_CTX_free(ssl_ctx);
-  return result;
-}
-
-/** SSL callbacks ***/
-
-static CURLcode quic_init_ssl(struct Curl_cfilter *cf,
-                              struct Curl_easy *data)
-{
-  struct cf_ngtcp2_ctx *ctx = cf->ctx;
-  const uint8_t *alpn = NULL;
-  size_t alpnlen = 0;
-  /* this will need some attention when HTTPS proxy over QUIC get fixed */
-  const char * const hostname = cf->conn->host.name;
-
-  (void)data;
-  DEBUGASSERT(!ctx->ssl);
-  ctx->ssl = wolfSSL_new(ctx->sslctx);
-
-  wolfSSL_set_app_data(ctx->ssl, &ctx->conn_ref);
-  wolfSSL_set_connect_state(ctx->ssl);
-  wolfSSL_set_quic_use_legacy_codepoint(ctx->ssl, 0);
-
-  alpn = (const uint8_t *)H3_ALPN_H3 H3_ALPN_H3_29;
-  alpnlen = sizeof(H3_ALPN_H3) - 1 + sizeof(H3_ALPN_H3_29) - 1;
-  if(alpn)
-    wolfSSL_set_alpn_protos(ctx->ssl, alpn, (int)alpnlen);
-
-  /* set SNI */
-  wolfSSL_UseSNI(ctx->ssl, WOLFSSL_SNI_HOST_NAME,
-                 hostname, (unsigned short)strlen(hostname));
-
-  return CURLE_OK;
-}
-#endif /* defined(USE_WOLFSSL) */
-
 static int cb_handshake_completed(ngtcp2_conn *tconn, void *user_data)
 {
   (void)user_data;
@@ -1950,49 +1535,12 @@ static CURLcode qng_verify_peer(struct Curl_cfilter *cf,
                                 struct Curl_easy *data)
 {
   struct cf_ngtcp2_ctx *ctx = cf->ctx;
-  struct ssl_primary_config *conn_config;
-  CURLcode result = CURLE_OK;
-
-  conn_config = Curl_ssl_cf_get_primary_config(cf);
-  if(!conn_config)
-    return CURLE_FAILED_INIT;
 
   cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
   cf->conn->httpversion = 30;
   cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX;
 
-  if(conn_config->verifyhost) {
-#ifdef USE_OPENSSL
-    X509 *server_cert;
-    server_cert = SSL_get1_peer_certificate(ctx->ssl);
-    if(!server_cert) {
-      return CURLE_PEER_FAILED_VERIFICATION;
-    }
-    result = Curl_ossl_verifyhost(data, cf->conn, &ctx->peer, server_cert);
-    X509_free(server_cert);
-    if(result)
-      return result;
-#elif defined(USE_GNUTLS)
-    result = Curl_gtls_verifyserver(data, ctx->gtls->session,
-                                    conn_config, &data->set.ssl, &ctx->peer,
-                                    data->set.str[STRING_SSL_PINNEDPUBLICKEY]);
-    if(result)
-      return result;
-#elif defined(USE_WOLFSSL)
-    if(!ctx->peer.sni ||
-       wolfSSL_check_domain_name(ctx->ssl, ctx->peer.sni) == SSL_FAILURE)
-      return CURLE_PEER_FAILED_VERIFICATION;
-#endif
-    infof(data, "Verified certificate just fine");
-  }
-  else
-    infof(data, "Skipped certificate verification");
-#ifdef USE_OPENSSL
-  if(data->set.ssl.certinfo)
-    /* asked to gather certificate info */
-    (void)Curl_ossl_certchain(data, ctx->ssl);
-#endif
-  return result;
+  return Curl_vquic_tls_verify_peer(&ctx->tls, cf, data, &ctx->peer);
 }
 
 static CURLcode recv_pkt(const unsigned char *pkt, size_t pktlen,
@@ -2056,14 +1604,9 @@ static CURLcode cf_progress_ingress(struct Curl_cfilter *cf,
     pktx_update_time(pktx, cf);
   }
 
-#ifdef USE_OPENSSL
-  if(!ctx->x509_store_setup) {
-    result = Curl_ssl_setup_x509_store(cf, data, ctx->sslctx);
-    if(result)
-      return result;
-    ctx->x509_store_setup = TRUE;
-  }
-#endif
+  result = Curl_vquic_tls_before_recv(&ctx->tls, cf, data);
+  if(result)
+    return result;
 
   for(i = 0; i < pkts_max; i += pkts_chunk) {
     pktx->pkt_count = 0;
@@ -2388,25 +1931,7 @@ static void cf_ngtcp2_ctx_clear(struct cf_ngtcp2_ctx *ctx)
   if(ctx->qlogfd != -1) {
     close(ctx->qlogfd);
   }
-#ifdef USE_OPENSSL
-  if(ctx->ssl)
-    SSL_free(ctx->ssl);
-  if(ctx->sslctx)
-    SSL_CTX_free(ctx->sslctx);
-#elif defined(USE_GNUTLS)
-  if(ctx->gtls) {
-    if(ctx->gtls->cred)
-      gnutls_certificate_free_credentials(ctx->gtls->cred);
-    if(ctx->gtls->session)
-      gnutls_deinit(ctx->gtls->session);
-    free(ctx->gtls);
-  }
-#elif defined(USE_WOLFSSL)
-  if(ctx->ssl)
-    wolfSSL_free(ctx->ssl);
-  if(ctx->sslctx)
-    wolfSSL_CTX_free(ctx->sslctx);
-#endif
+  Curl_vquic_tls_cleanup(&ctx->tls);
   vquic_ctx_free(&ctx->q);
   if(ctx->h3conn)
     nghttp3_conn_del(ctx->h3conn);
@@ -2465,6 +1990,37 @@ static void cf_ngtcp2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
   (void)save;
 }
 
+static CURLcode tls_ctx_setup(struct quic_tls_ctx *ctx,
+                              struct Curl_cfilter *cf,
+                              struct Curl_easy *data)
+{
+  (void)cf;
+#ifdef USE_OPENSSL
+#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)
+  if(ngtcp2_crypto_boringssl_configure_client_context(ctx->ssl_ctx) != 0) {
+    failf(data, "ngtcp2_crypto_boringssl_configure_client_context failed");
+    return CURLE_FAILED_INIT;
+  }
+#else
+  if(ngtcp2_crypto_quictls_configure_client_context(ctx->ssl_ctx) != 0) {
+    failf(data, "ngtcp2_crypto_quictls_configure_client_context failed");
+    return CURLE_FAILED_INIT;
+  }
+#endif /* !OPENSSL_IS_BORINGSSL && !OPENSSL_IS_AWSLC */
+#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;
+  }
+#elif defined(USE_WOLFSSL)
+  if(ngtcp2_crypto_wolfssl_configure_client_context(ctx->ssl_ctx) != 0) {
+    failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed");
+    return CURLE_FAILED_INIT;
+  }
+#endif
+  return CURLE_OK;
+}
+
 /*
  * Might be called twice for happy eyeballs.
  */
@@ -2489,21 +2045,10 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf,
   if(result)
     return result;
 
-#ifdef USE_OPENSSL
-  result = quic_ssl_ctx(&ctx->sslctx, cf, data);
-  if(result)
-    return result;
-
-  result = quic_set_client_cert(cf, data);
-  if(result)
-    return result;
-#elif defined(USE_WOLFSSL)
-  result = quic_ssl_ctx(&ctx->sslctx, cf, data);
-  if(result)
-    return result;
-#endif
-
-  result = quic_init_ssl(cf, data);
+#define H3_ALPN "\x2h3\x5h3-29"
+  result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer,
+                               H3_ALPN, sizeof(H3_ALPN) - 1,
+                               tls_ctx_setup, &ctx->conn_ref);
   if(result)
     return result;
 
@@ -2550,9 +2095,9 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf,
     return CURLE_QUIC_CONNECT_ERROR;
 
 #ifdef USE_GNUTLS
-  ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->gtls->session);
+  ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.gtls->session);
 #else
-  ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->ssl);
+  ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.ssl);
 #endif
 
   ngtcp2_ccerr_default(&ctx->last_error);
@@ -2683,8 +2228,8 @@ static CURLcode cf_ngtcp2_query(struct Curl_cfilter *cf,
     return CURLE_OK;
   }
   case CF_QUERY_CONNECT_REPLY_MS:
-    if(ctx->got_first_byte) {
-      timediff_t ms = Curl_timediff(ctx->first_byte_at, ctx->started_at);
+    if(ctx->q.got_first_byte) {
+      timediff_t ms = Curl_timediff(ctx->q.first_byte_at, ctx->started_at);
       *pres1 = (ms < INT_MAX)? (int)ms : INT_MAX;
     }
     else
@@ -2692,8 +2237,8 @@ static CURLcode cf_ngtcp2_query(struct Curl_cfilter *cf,
     return CURLE_OK;
   case CF_QUERY_TIMER_CONNECT: {
     struct curltime *when = pres2;
-    if(ctx->got_first_byte)
-      *when = ctx->first_byte_at;
+    if(ctx->q.got_first_byte)
+      *when = ctx->q.first_byte_at;
     return CURLE_OK;
   }
   case CF_QUERY_TIMER_APPCONNECT: {

+ 51 - 186
lib/vquic/curl_quiche.c

@@ -43,6 +43,7 @@
 #include "http1.h"
 #include "vquic.h"
 #include "vquic_int.h"
+#include "vquic-tls.h"
 #include "curl_quiche.h"
 #include "transfer.h"
 #include "inet_pton.h"
@@ -84,31 +85,22 @@ void Curl_quiche_ver(char *p, size_t len)
   (void)msnprintf(p, len, "quiche/%s", quiche_version());
 }
 
-static void keylog_callback(const SSL *ssl, const char *line)
-{
-  (void)ssl;
-  Curl_tls_keylog_write_line(line);
-}
-
 struct cf_quiche_ctx {
   struct cf_quic_ctx q;
   struct ssl_peer peer;
+  struct quic_tls_ctx tls;
   quiche_conn *qconn;
   quiche_config *cfg;
   quiche_h3_conn *h3c;
   quiche_h3_config *h3config;
   uint8_t scid[QUICHE_MAX_CONN_ID_LEN];
-  SSL_CTX *sslctx;
-  SSL *ssl;
   struct curltime started_at;        /* time the current attempt started */
   struct curltime handshake_at;      /* time connect handshake finished */
-  struct curltime first_byte_at;     /* when first byte was recvd */
   struct curltime reconnect_at;      /* time the next attempt should start */
   struct bufc_pool stream_bufcp;     /* chunk pool for streams */
   curl_off_t data_recvd;
   uint64_t max_idle_ms;              /* max idle time for QUIC conn */
   BIT(goaway);                       /* got GOAWAY from server */
-  BIT(got_first_byte);               /* if first byte was received */
   BIT(x509_store_setup);             /* if x509 store has been set up */
 };
 
@@ -123,121 +115,25 @@ static void quiche_debug_log(const char *line, void *argp)
 static void cf_quiche_ctx_clear(struct cf_quiche_ctx *ctx)
 {
   if(ctx) {
-    vquic_ctx_free(&ctx->q);
-    if(ctx->qconn)
-      quiche_conn_free(ctx->qconn);
-    if(ctx->h3config)
-      quiche_h3_config_free(ctx->h3config);
     if(ctx->h3c)
       quiche_h3_conn_free(ctx->h3c);
+    if(ctx->h3config)
+      quiche_h3_config_free(ctx->h3config);
+    if(ctx->qconn)
+      quiche_conn_free(ctx->qconn);
     if(ctx->cfg)
       quiche_config_free(ctx->cfg);
-    Curl_bufcp_free(&ctx->stream_bufcp);
+    /* quiche just freed ctx->tls.ssl */
+    ctx->tls.ssl = NULL;
+    Curl_vquic_tls_cleanup(&ctx->tls);
     Curl_ssl_peer_cleanup(&ctx->peer);
+    vquic_ctx_free(&ctx->q);
+    Curl_bufcp_free(&ctx->stream_bufcp);
 
     memset(ctx, 0, sizeof(*ctx));
   }
 }
 
-static CURLcode quic_x509_store_setup(struct Curl_cfilter *cf,
-                                      struct Curl_easy *data)
-{
-  struct cf_quiche_ctx *ctx = cf->ctx;
-  struct ssl_primary_config *conn_config;
-
-  conn_config = Curl_ssl_cf_get_primary_config(cf);
-  if(!conn_config)
-    return CURLE_FAILED_INIT;
-
-  if(!ctx->x509_store_setup) {
-    if(conn_config->verifypeer) {
-      const char * const ssl_cafile = conn_config->CAfile;
-      const char * const ssl_capath = conn_config->CApath;
-      if(ssl_cafile || ssl_capath) {
-        SSL_CTX_set_verify(ctx->sslctx, SSL_VERIFY_PEER, NULL);
-        /* tell OpenSSL where to find CA certificates that are used to verify
-           the server's certificate. */
-        if(!SSL_CTX_load_verify_locations(ctx->sslctx, ssl_cafile,
-                                          ssl_capath)) {
-          /* Fail if we insist on successfully verifying the server. */
-          failf(data, "error setting certificate verify locations:"
-                "  CAfile: %s CApath: %s",
-                ssl_cafile ? ssl_cafile : "none",
-                ssl_capath ? ssl_capath : "none");
-          return CURLE_SSL_CACERT_BADFILE;
-        }
-        infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none");
-        infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none");
-      }
-#ifdef CURL_CA_FALLBACK
-      else {
-        /* verifying the peer without any CA certificates won't work so
-           use openssl's built-in default as fallback */
-        SSL_CTX_set_default_verify_paths(ctx->sslctx);
-      }
-#endif
-    }
-    ctx->x509_store_setup = TRUE;
-  }
-  return CURLE_OK;
-}
-
-static CURLcode quic_ssl_setup(struct Curl_cfilter *cf, struct Curl_easy *data)
-{
-  struct cf_quiche_ctx *ctx = cf->ctx;
-  struct ssl_primary_config *conn_config;
-  CURLcode result;
-
-  conn_config = Curl_ssl_cf_get_primary_config(cf);
-  if(!conn_config)
-    return CURLE_FAILED_INIT;
-
-  result = Curl_ssl_peer_init(&ctx->peer, cf);
-  if(result)
-    return result;
-
-  DEBUGASSERT(!ctx->sslctx);
-  ctx->sslctx = SSL_CTX_new(TLS_method());
-  if(!ctx->sslctx)
-    return CURLE_OUT_OF_MEMORY;
-
-  SSL_CTX_set_alpn_protos(ctx->sslctx,
-                          (const uint8_t *)QUICHE_H3_APPLICATION_PROTOCOL,
-                          sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1);
-
-  SSL_CTX_set_default_verify_paths(ctx->sslctx);
-
-  /* Open the file if a TLS or QUIC backend has not done this before. */
-  Curl_tls_keylog_open();
-  if(Curl_tls_keylog_enabled()) {
-    SSL_CTX_set_keylog_callback(ctx->sslctx, keylog_callback);
-  }
-
-  if(conn_config->curves &&
-     !SSL_CTX_set1_curves_list(ctx->sslctx, conn_config->curves)) {
-    failf(data, "failed setting curves list for QUIC: '%s'",
-          conn_config->curves);
-    return CURLE_SSL_CIPHER;
-  }
-
-  ctx->ssl = SSL_new(ctx->sslctx);
-  if(!ctx->ssl)
-    return CURLE_QUIC_CONNECT_ERROR;
-
-  SSL_set_app_data(ctx->ssl, cf);
-
-  if(ctx->peer.sni) {
-    if(!SSL_set_tlsext_host_name(ctx->ssl, ctx->peer.sni)) {
-      failf(data, "Failed set SNI");
-      SSL_free(ctx->ssl);
-      ctx->ssl = NULL;
-      return CURLE_QUIC_CONNECT_ERROR;
-    }
-  }
-
-  return CURLE_OK;
-}
-
 /**
  * All about the H3 internals of a stream
  */
@@ -668,7 +564,7 @@ static CURLcode recv_pkt(const unsigned char *pkt, size_t pktlen,
       return CURLE_OK;
     }
     else if(QUICHE_ERR_TLS_FAIL == nread) {
-      long verify_ok = SSL_get_verify_result(ctx->ssl);
+      long verify_ok = SSL_get_verify_result(ctx->tls.ssl);
       if(verify_ok != X509_V_OK) {
         failf(r->data, "SSL certificate problem: %s",
               X509_verify_cert_error_string(verify_ok));
@@ -696,7 +592,7 @@ static CURLcode cf_process_ingress(struct Curl_cfilter *cf,
   CURLcode result;
 
   DEBUGASSERT(ctx->qconn);
-  result = quic_x509_store_setup(cf, data);
+  result = Curl_vquic_tls_before_recv(&ctx->tls, cf, data);
   if(result)
     return result;
 
@@ -1277,66 +1173,6 @@ static CURLcode cf_quiche_data_event(struct Curl_cfilter *cf,
   return result;
 }
 
-static CURLcode cf_verify_peer(struct Curl_cfilter *cf,
-                               struct Curl_easy *data)
-{
-  struct cf_quiche_ctx *ctx = cf->ctx;
-  struct ssl_primary_config *conn_config;
-  CURLcode result = CURLE_OK;
-
-  conn_config = Curl_ssl_cf_get_primary_config(cf);
-  if(!conn_config)
-    return CURLE_FAILED_INIT;
-
-  cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
-  cf->conn->httpversion = 30;
-  cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX;
-
-  if(conn_config->verifyhost) {
-    X509 *server_cert;
-    server_cert = SSL_get_peer_certificate(ctx->ssl);
-    if(!server_cert) {
-      result = CURLE_PEER_FAILED_VERIFICATION;
-      goto out;
-    }
-    result = Curl_ossl_verifyhost(data, cf->conn, &ctx->peer, server_cert);
-    X509_free(server_cert);
-    if(result)
-      goto out;
-  }
-  else
-    CURL_TRC_CF(data, cf, "Skipped certificate verification");
-
-  ctx->h3config = quiche_h3_config_new();
-  if(!ctx->h3config) {
-    result = CURLE_OUT_OF_MEMORY;
-    goto out;
-  }
-
-  /* Create a new HTTP/3 connection on the QUIC connection. */
-  ctx->h3c = quiche_h3_conn_new_with_transport(ctx->qconn, ctx->h3config);
-  if(!ctx->h3c) {
-    result = CURLE_OUT_OF_MEMORY;
-    goto out;
-  }
-  if(data->set.ssl.certinfo)
-    /* asked to gather certificate info */
-    (void)Curl_ossl_certchain(data, ctx->ssl);
-
-out:
-  if(result) {
-    if(ctx->h3config) {
-      quiche_h3_config_free(ctx->h3config);
-      ctx->h3config = NULL;
-    }
-    if(ctx->h3c) {
-      quiche_h3_conn_free(ctx->h3c);
-      ctx->h3c = NULL;
-    }
-  }
-  return result;
-}
-
 static CURLcode cf_connect_start(struct Curl_cfilter *cf,
                                  struct Curl_easy *data)
 {
@@ -1364,6 +1200,10 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf,
   if(result)
     return result;
 
+  result = Curl_ssl_peer_init(&ctx->peer, cf);
+  if(result)
+    return result;
+
   ctx->cfg = quiche_config_new(QUICHE_PROTOCOL_VERSION);
   if(!ctx->cfg) {
     failf(data, "can't create quiche config");
@@ -1392,9 +1232,10 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf,
                                        sizeof(QUICHE_H3_APPLICATION_PROTOCOL)
                                        - 1);
 
-  DEBUGASSERT(!ctx->ssl);
-  DEBUGASSERT(!ctx->sslctx);
-  result = quic_ssl_setup(cf, data);
+  result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer,
+                               QUICHE_H3_APPLICATION_PROTOCOL,
+                               sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1,
+                               NULL, cf);
   if(result)
     return result;
 
@@ -1415,7 +1256,7 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf,
                                       (struct sockaddr *)&ctx->q.local_addr,
                                       ctx->q.local_addrlen,
                                       &sockaddr->sa_addr, sockaddr->addrlen,
-                                      ctx->cfg, ctx->ssl, false);
+                                      ctx->cfg, ctx->tls.ssl, false);
   if(!ctx->qconn) {
     failf(data, "can't create quiche connection");
     return CURLE_OUT_OF_MEMORY;
@@ -1454,6 +1295,18 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf,
   return CURLE_OK;
 }
 
+static CURLcode cf_quiche_verify_peer(struct Curl_cfilter *cf,
+                                      struct Curl_easy *data)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+
+  cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
+  cf->conn->httpversion = 30;
+  cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX;
+
+  return Curl_vquic_tls_verify_peer(&ctx->tls, cf, data, &ctx->peer);
+}
+
 static CURLcode cf_quiche_connect(struct Curl_cfilter *cf,
                                   struct Curl_easy *data,
                                   bool blocking, bool *done)
@@ -1505,9 +1358,21 @@ static CURLcode cf_quiche_connect(struct Curl_cfilter *cf,
     ctx->handshake_at = ctx->q.last_op;
     CURL_TRC_CF(data, cf, "handshake complete after %dms",
                 (int)Curl_timediff(ctx->handshake_at, ctx->started_at));
-    result = cf_verify_peer(cf, data);
+    result = cf_quiche_verify_peer(cf, data);
     if(!result) {
       CURL_TRC_CF(data, cf, "peer verified");
+      ctx->h3config = quiche_h3_config_new();
+      if(!ctx->h3config) {
+        result = CURLE_OUT_OF_MEMORY;
+        goto out;
+      }
+
+      /* Create a new HTTP/3 connection on the QUIC connection. */
+      ctx->h3c = quiche_h3_conn_new_with_transport(ctx->qconn, ctx->h3config);
+      if(!ctx->h3c) {
+        result = CURLE_OUT_OF_MEMORY;
+        goto out;
+      }
       cf->connected = TRUE;
       cf->conn->alpn = CURL_HTTP_VERSION_3;
       *done = TRUE;
@@ -1580,8 +1445,8 @@ static CURLcode cf_quiche_query(struct Curl_cfilter *cf,
     return CURLE_OK;
   }
   case CF_QUERY_CONNECT_REPLY_MS:
-    if(ctx->got_first_byte) {
-      timediff_t ms = Curl_timediff(ctx->first_byte_at, ctx->started_at);
+    if(ctx->q.got_first_byte) {
+      timediff_t ms = Curl_timediff(ctx->q.first_byte_at, ctx->started_at);
       *pres1 = (ms < INT_MAX)? (int)ms : INT_MAX;
     }
     else
@@ -1589,8 +1454,8 @@ static CURLcode cf_quiche_query(struct Curl_cfilter *cf,
     return CURLE_OK;
   case CF_QUERY_TIMER_CONNECT: {
     struct curltime *when = pres2;
-    if(ctx->got_first_byte)
-      *when = ctx->first_byte_at;
+    if(ctx->q.got_first_byte)
+      *when = ctx->q.first_byte_at;
     return CURLE_OK;
   }
   case CF_QUERY_TIMER_APPCONNECT: {

+ 603 - 0
lib/vquic/vquic-tls.c

@@ -0,0 +1,603 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#if defined(ENABLE_QUIC) && \
+  (defined(USE_OPENSSL) || defined(USE_GNUTLS) || defined(USE_WOLFSSL))
+
+#ifdef USE_OPENSSL
+#include <openssl/err.h>
+#include "vtls/openssl.h"
+#elif defined(USE_GNUTLS)
+#include <gnutls/abstract.h>
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+#include <gnutls/crypto.h>
+#include <nettle/sha2.h>
+#include "vtls/gtls.h"
+#elif defined(USE_WOLFSSL)
+#include <wolfssl/options.h>
+#include <wolfssl/ssl.h>
+#include <wolfssl/quic.h>
+#include "vtls/wolfssl.h"
+#endif
+
+#include "urldata.h"
+#include "curl_trc.h"
+#include "cfilters.h"
+#include "multiif.h"
+#include "vtls/keylog.h"
+#include "vtls/vtls.h"
+#include "vquic-tls.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+#ifndef ARRAYSIZE
+#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0]))
+#endif
+
+#ifdef USE_OPENSSL
+#define QUIC_CIPHERS                                                          \
+  "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_"               \
+  "POLY1305_SHA256:TLS_AES_128_CCM_SHA256"
+#define QUIC_GROUPS "P-256:X25519:P-384:P-521"
+#elif defined(USE_GNUTLS)
+#define QUIC_PRIORITY \
+  "NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:" \
+  "+CHACHA20-POLY1305:+AES-128-CCM:-GROUP-ALL:+GROUP-SECP256R1:" \
+  "+GROUP-X25519:+GROUP-SECP384R1:+GROUP-SECP521R1:" \
+  "%DISABLE_TLS13_COMPAT_MODE"
+#elif defined(USE_WOLFSSL)
+#define QUIC_CIPHERS                                                          \
+  "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_"               \
+  "POLY1305_SHA256:TLS_AES_128_CCM_SHA256"
+#define QUIC_GROUPS "P-256:P-384:P-521"
+#endif
+
+
+#ifdef USE_OPENSSL
+
+static void keylog_callback(const SSL *ssl, const char *line)
+{
+  (void)ssl;
+  Curl_tls_keylog_write_line(line);
+}
+
+static CURLcode curl_ossl_init_ctx(struct quic_tls_ctx *ctx,
+                                   struct Curl_cfilter *cf,
+                                   struct Curl_easy *data,
+                                   Curl_vquic_tls_ctx_setup *ctx_setup)
+{
+  struct ssl_primary_config *conn_config;
+  CURLcode result = CURLE_FAILED_INIT;
+
+  DEBUGASSERT(!ctx->ssl_ctx);
+  ctx->ssl_ctx = SSL_CTX_new(TLS_method());
+  if(!ctx->ssl_ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+  conn_config = Curl_ssl_cf_get_primary_config(cf);
+  if(!conn_config) {
+    result = CURLE_FAILED_INIT;
+    goto out;
+  }
+
+  if(ctx_setup) {
+    result = ctx_setup(ctx, cf, data);
+    if(result)
+      goto out;
+  }
+
+  SSL_CTX_set_default_verify_paths(ctx->ssl_ctx);
+
+  {
+    const char *curves = conn_config->curves ?
+      conn_config->curves : QUIC_GROUPS;
+    if(!SSL_CTX_set1_curves_list(ctx->ssl_ctx, curves)) {
+      failf(data, "failed setting curves list for QUIC: '%s'", curves);
+      return CURLE_SSL_CIPHER;
+    }
+  }
+
+#ifndef OPENSSL_IS_BORINGSSL
+  {
+    const char *ciphers13 = conn_config->cipher_list13 ?
+      conn_config->cipher_list13 : QUIC_CIPHERS;
+    if(SSL_CTX_set_ciphersuites(ctx->ssl_ctx, ciphers13) != 1) {
+      failf(data, "failed setting QUIC cipher suite: %s", ciphers13);
+      return CURLE_SSL_CIPHER;
+    }
+    infof(data, "QUIC cipher selection: %s", ciphers13);
+  }
+#endif
+
+  /* Open the file if a TLS or QUIC backend has not done this before. */
+  Curl_tls_keylog_open();
+  if(Curl_tls_keylog_enabled()) {
+    SSL_CTX_set_keylog_callback(ctx->ssl_ctx, keylog_callback);
+  }
+
+  /* OpenSSL always tries to verify the peer, this only says whether it should
+   * fail to connect if the verification fails, or if it should continue
+   * anyway. In the latter case the result of the verification is checked with
+   * SSL_get_verify_result() below. */
+  SSL_CTX_set_verify(ctx->ssl_ctx, conn_config->verifypeer ?
+                     SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL);
+
+  /* give application a chance to interfere with SSL set up. */
+  if(data->set.ssl.fsslctx) {
+    /* When a user callback is installed to modify the SSL_CTX,
+     * we need to do the full initialization before calling it.
+     * See: #11800 */
+    if(!ctx->x509_store_setup) {
+      result = Curl_ssl_setup_x509_store(cf, data, ctx->ssl_ctx);
+      if(result)
+        goto out;
+      ctx->x509_store_setup = TRUE;
+    }
+    Curl_set_in_callback(data, true);
+    result = (*data->set.ssl.fsslctx)(data, ctx->ssl_ctx,
+                                      data->set.ssl.fsslctxp);
+    Curl_set_in_callback(data, false);
+    if(result) {
+      failf(data, "error signaled by ssl ctx callback");
+      goto out;
+    }
+  }
+  result = CURLE_OK;
+
+out:
+  if(result && ctx->ssl_ctx) {
+    SSL_CTX_free(ctx->ssl_ctx);
+    ctx->ssl_ctx = NULL;
+  }
+  return result;
+}
+
+static CURLcode curl_ossl_set_client_cert(struct quic_tls_ctx *ctx,
+                                     struct Curl_cfilter *cf,
+                                     struct Curl_easy *data)
+{
+  SSL_CTX *ssl_ctx = ctx->ssl_ctx;
+  const struct ssl_config_data *ssl_config;
+
+  ssl_config = Curl_ssl_cf_get_config(cf, data);
+  DEBUGASSERT(ssl_config);
+
+  if(ssl_config->primary.clientcert ||
+     ssl_config->primary.cert_blob ||
+     ssl_config->cert_type) {
+    return Curl_ossl_set_client_cert(
+        data, ssl_ctx, ssl_config->primary.clientcert,
+        ssl_config->primary.cert_blob, ssl_config->cert_type,
+        ssl_config->key, ssl_config->key_blob,
+        ssl_config->key_type, ssl_config->key_passwd);
+  }
+
+  return CURLE_OK;
+}
+
+/** SSL callbacks ***/
+
+static CURLcode curl_ossl_init_ssl(struct quic_tls_ctx *ctx,
+                                   struct Curl_easy *data,
+                                   struct ssl_peer *peer,
+                                   const char *alpn, size_t alpn_len,
+                                   void *user_data)
+{
+  DEBUGASSERT(!ctx->ssl);
+  ctx->ssl = SSL_new(ctx->ssl_ctx);
+
+  SSL_set_app_data(ctx->ssl, user_data);
+  SSL_set_connect_state(ctx->ssl);
+  SSL_set_quic_use_legacy_codepoint(ctx->ssl, 0);
+
+  if(alpn)
+    SSL_set_alpn_protos(ctx->ssl, (const uint8_t *)alpn, (int)alpn_len);
+
+  if(peer->sni) {
+    if(!SSL_set_tlsext_host_name(ctx->ssl, peer->sni)) {
+      failf(data, "Failed set SNI");
+      SSL_free(ctx->ssl);
+      ctx->ssl = NULL;
+      return CURLE_QUIC_CONNECT_ERROR;
+    }
+  }
+  return CURLE_OK;
+}
+
+#elif defined(USE_GNUTLS)
+static int keylog_callback(gnutls_session_t session, const char *label,
+                    const gnutls_datum_t *secret)
+{
+  gnutls_datum_t crandom;
+  gnutls_datum_t srandom;
+
+  gnutls_session_get_random(session, &crandom, &srandom);
+  if(crandom.size != 32) {
+    return -1;
+  }
+
+  Curl_tls_keylog_write(label, crandom.data, secret->data, secret->size);
+  return 0;
+}
+
+static CURLcode curl_gtls_init_ctx(struct quic_tls_ctx *ctx,
+                                   struct Curl_cfilter *cf,
+                                   struct Curl_easy *data,
+                                   struct ssl_peer *peer,
+                                   const char *alpn, size_t alpn_len,
+                                   Curl_vquic_tls_ctx_setup *ctx_setup,
+                                   void *user_data)
+{
+  struct ssl_primary_config *conn_config;
+  CURLcode result;
+  gnutls_datum_t alpns[5];
+  /* this will need some attention when HTTPS proxy over QUIC get fixed */
+  long * const pverifyresult = &data->set.ssl.certverifyresult;
+  int rc;
+
+  conn_config = Curl_ssl_cf_get_primary_config(cf);
+  if(!conn_config)
+    return CURLE_FAILED_INIT;
+
+  DEBUGASSERT(ctx->gtls == NULL);
+  ctx->gtls = calloc(1, sizeof(*(ctx->gtls)));
+  if(!ctx->gtls)
+    return CURLE_OUT_OF_MEMORY;
+
+  result = gtls_client_init(data, conn_config, &data->set.ssl,
+                            peer, ctx->gtls, pverifyresult);
+  if(result)
+    return result;
+
+  gnutls_session_set_ptr(ctx->gtls->session, user_data);
+
+  if(ctx_setup) {
+    result = ctx_setup(ctx, cf, data);
+    if(result)
+      return result;
+  }
+
+  rc = gnutls_priority_set_direct(ctx->gtls->session, QUIC_PRIORITY, NULL);
+  if(rc < 0) {
+    CURL_TRC_CF(data, cf, "gnutls_priority_set_direct failed: %s\n",
+                gnutls_strerror(rc));
+    return CURLE_QUIC_CONNECT_ERROR;
+  }
+
+  /* Open the file if a TLS or QUIC backend has not done this before. */
+  Curl_tls_keylog_open();
+  if(Curl_tls_keylog_enabled()) {
+    gnutls_session_set_keylog_function(ctx->gtls->session, keylog_callback);
+  }
+
+  /* convert the ALPN string from our arguments to a list of strings
+   * that gnutls wants and will convert internally back to this very
+   * string for sending to the server. nice. */
+  if(alpn) {
+    size_t i, alen = alpn_len;
+    unsigned char *s = (unsigned char *)alpn;
+    unsigned char slen;
+    for(i = 0; (i < ARRAYSIZE(alpns)) && alen; ++i) {
+      slen = s[0];
+      if(slen >= alen)
+        return CURLE_FAILED_INIT;
+      alpns[i].data = s + 1;
+      alpns[i].size = slen;
+      s += slen + 1;
+      alen -= (size_t)slen + 1;
+    }
+    if(alen) /* not all alpn chars used, wrong format or too many */
+        return CURLE_FAILED_INIT;
+    if(i) {
+      gnutls_alpn_set_protocols(ctx->gtls->session,
+                                alpns, (unsigned int)i,
+                                GNUTLS_ALPN_MANDATORY);
+    }
+  }
+
+  return CURLE_OK;
+}
+#elif defined(USE_WOLFSSL)
+
+#if defined(HAVE_SECRET_CALLBACK)
+static void keylog_callback(const WOLFSSL *ssl, const char *line)
+{
+  (void)ssl;
+  Curl_tls_keylog_write_line(line);
+}
+#endif
+
+static CURLcode curl_wssl_init_ctx(struct quic_tls_ctx *ctx,
+                                   struct Curl_cfilter *cf,
+                                   struct Curl_easy *data,
+                                   Curl_vquic_tls_ctx_setup *ctx_setup)
+{
+  struct ssl_primary_config *conn_config;
+  CURLcode result = CURLE_FAILED_INIT;
+
+  conn_config = Curl_ssl_cf_get_primary_config(cf);
+  if(!conn_config) {
+    result = CURLE_FAILED_INIT;
+    goto out;
+  }
+
+  ctx->ssl_ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method());
+  if(!ctx->ssl_ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+
+  if(ctx_setup) {
+    result = ctx_setup(ctx, cf, data);
+    if(result)
+      goto out;
+  }
+
+  wolfSSL_CTX_set_default_verify_paths(ctx->ssl_ctx);
+
+  if(wolfSSL_CTX_set_cipher_list(ctx->ssl_ctx, conn_config->cipher_list13 ?
+                                 conn_config->cipher_list13 :
+                                 QUIC_CIPHERS) != 1) {
+    char error_buffer[256];
+    ERR_error_string_n(ERR_get_error(), error_buffer, sizeof(error_buffer));
+    failf(data, "wolfSSL failed to set ciphers: %s", error_buffer);
+    goto out;
+  }
+
+  if(wolfSSL_CTX_set1_groups_list(ctx->ssl_ctx, conn_config->curves ?
+                                  conn_config->curves :
+                                  (char *)QUIC_GROUPS) != 1) {
+    failf(data, "wolfSSL failed to set curves");
+    goto out;
+  }
+
+  /* Open the file if a TLS or QUIC backend has not done this before. */
+  Curl_tls_keylog_open();
+  if(Curl_tls_keylog_enabled()) {
+#if defined(HAVE_SECRET_CALLBACK)
+    wolfSSL_CTX_set_keylog_callback(ctx->ssl_ctx, keylog_callback);
+#else
+    failf(data, "wolfSSL was built without keylog callback");
+    goto out;
+#endif
+  }
+
+  if(conn_config->verifypeer) {
+    const char * const ssl_cafile = conn_config->CAfile;
+    const char * const ssl_capath = conn_config->CApath;
+
+    wolfSSL_CTX_set_verify(ctx->ssl_ctx, SSL_VERIFY_PEER, NULL);
+    if(ssl_cafile || ssl_capath) {
+      /* tell wolfSSL where to find CA certificates that are used to verify
+         the server's certificate. */
+      int rc =
+        wolfSSL_CTX_load_verify_locations_ex(ctx->ssl_ctx, ssl_cafile,
+                                             ssl_capath,
+                                             WOLFSSL_LOAD_FLAG_IGNORE_ERR);
+      if(SSL_SUCCESS != rc) {
+        /* Fail if we insist on successfully verifying the server. */
+        failf(data, "error setting certificate verify locations:"
+              "  CAfile: %s CApath: %s",
+              ssl_cafile ? ssl_cafile : "none",
+              ssl_capath ? ssl_capath : "none");
+        goto out;
+      }
+      infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none");
+      infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none");
+    }
+#ifdef CURL_CA_FALLBACK
+    else {
+      /* verifying the peer without any CA certificates won't work so
+         use wolfssl's built-in default as fallback */
+      wolfSSL_CTX_set_default_verify_paths(ctx->ssl_ctx);
+    }
+#endif
+  }
+  else {
+    wolfSSL_CTX_set_verify(ctx->ssl_ctx, SSL_VERIFY_NONE, NULL);
+  }
+
+  /* give application a chance to interfere with SSL set up. */
+  if(data->set.ssl.fsslctx) {
+    Curl_set_in_callback(data, true);
+    result = (*data->set.ssl.fsslctx)(data, ctx->ssl_ctx,
+                                      data->set.ssl.fsslctxp);
+    Curl_set_in_callback(data, false);
+    if(result) {
+      failf(data, "error signaled by ssl ctx callback");
+      goto out;
+    }
+  }
+  result = CURLE_OK;
+
+out:
+  if(result && ctx->ssl_ctx) {
+    SSL_CTX_free(ctx->ssl_ctx);
+    ctx->ssl_ctx = NULL;
+  }
+  return result;
+}
+
+/** SSL callbacks ***/
+
+static CURLcode curl_wssl_init_ssl(struct quic_tls_ctx *ctx,
+                                   struct Curl_easy *data,
+                                   struct ssl_peer *peer,
+                                   const char *alpn, size_t alpn_len,
+                                   void *user_data)
+{
+  (void)data;
+  DEBUGASSERT(!ctx->ssl);
+  DEBUGASSERT(ctx->ssl_ctx);
+  ctx->ssl = wolfSSL_new(ctx->ssl_ctx);
+
+  wolfSSL_set_app_data(ctx->ssl, user_data);
+  wolfSSL_set_connect_state(ctx->ssl);
+  wolfSSL_set_quic_use_legacy_codepoint(ctx->ssl, 0);
+
+  if(alpn)
+    wolfSSL_set_alpn_protos(ctx->ssl, (const unsigned char *)alpn,
+                            (int)alpn_len);
+
+  if(peer->sni) {
+    wolfSSL_UseSNI(ctx->ssl, WOLFSSL_SNI_HOST_NAME,
+                   peer->sni, (unsigned short)strlen(peer->sni));
+  }
+
+  return CURLE_OK;
+}
+#endif /* defined(USE_WOLFSSL) */
+
+CURLcode Curl_vquic_tls_init(struct quic_tls_ctx *ctx,
+                             struct Curl_cfilter *cf,
+                             struct Curl_easy *data,
+                             struct ssl_peer *peer,
+                             const char *alpn, size_t alpn_len,
+                             Curl_vquic_tls_ctx_setup *ctx_setup,
+                             void *user_data)
+{
+  CURLcode result;
+
+#ifdef USE_OPENSSL
+  result = curl_ossl_init_ctx(ctx, cf, data, ctx_setup);
+  if(result)
+    return result;
+
+  result = curl_ossl_set_client_cert(ctx, cf, data);
+  if(result)
+    return result;
+
+  return curl_ossl_init_ssl(ctx, data, peer, alpn, alpn_len, user_data);
+#elif defined(USE_GNUTLS)
+  (void)result;
+  return curl_gtls_init_ctx(ctx, cf, data, peer, alpn, alpn_len,
+                            ctx_setup, user_data);
+#elif defined(USE_WOLFSSL)
+  result = curl_wssl_init_ctx(ctx, cf, data, ctx_setup);
+  if(result)
+    return result;
+
+  return curl_wssl_init_ssl(ctx, data, peer, alpn, alpn_len, user_data);
+#else
+#error "no TLS lib in used, should not happen"
+  return CURLE_FAILED_INIT;
+#endif
+}
+
+void Curl_vquic_tls_cleanup(struct quic_tls_ctx *ctx)
+{
+#ifdef USE_OPENSSL
+  if(ctx->ssl)
+    SSL_free(ctx->ssl);
+  if(ctx->ssl_ctx)
+    SSL_CTX_free(ctx->ssl_ctx);
+#elif defined(USE_GNUTLS)
+  if(ctx->gtls) {
+    if(ctx->gtls->cred)
+      gnutls_certificate_free_credentials(ctx->gtls->cred);
+    if(ctx->gtls->session)
+      gnutls_deinit(ctx->gtls->session);
+    free(ctx->gtls);
+  }
+#elif defined(USE_WOLFSSL)
+  if(ctx->ssl)
+    wolfSSL_free(ctx->ssl);
+  if(ctx->ssl_ctx)
+    wolfSSL_CTX_free(ctx->ssl_ctx);
+#endif
+  memset(ctx, 0, sizeof(*ctx));
+}
+
+CURLcode Curl_vquic_tls_before_recv(struct quic_tls_ctx *ctx,
+                                    struct Curl_cfilter *cf,
+                                    struct Curl_easy *data)
+{
+#ifdef USE_OPENSSL
+  if(!ctx->x509_store_setup) {
+    CURLcode result = Curl_ssl_setup_x509_store(cf, data, ctx->ssl_ctx);
+    if(result)
+      return result;
+    ctx->x509_store_setup = TRUE;
+  }
+#else
+  (void)ctx; (void)cf; (void)data;
+#endif
+  return CURLE_OK;
+}
+
+CURLcode Curl_vquic_tls_verify_peer(struct quic_tls_ctx *ctx,
+                                    struct Curl_cfilter *cf,
+                                    struct Curl_easy *data,
+                                    struct ssl_peer *peer)
+{
+  struct ssl_primary_config *conn_config;
+  CURLcode result = CURLE_OK;
+
+  conn_config = Curl_ssl_cf_get_primary_config(cf);
+  if(!conn_config)
+    return CURLE_FAILED_INIT;
+
+  if(conn_config->verifyhost) {
+#ifdef USE_OPENSSL
+    X509 *server_cert;
+    server_cert = SSL_get1_peer_certificate(ctx->ssl);
+    if(!server_cert) {
+      return CURLE_PEER_FAILED_VERIFICATION;
+    }
+    result = Curl_ossl_verifyhost(data, cf->conn, peer, server_cert);
+    X509_free(server_cert);
+    if(result)
+      return result;
+#elif defined(USE_GNUTLS)
+    result = Curl_gtls_verifyserver(data, ctx->gtls->session,
+                                    conn_config, &data->set.ssl, peer,
+                                    data->set.str[STRING_SSL_PINNEDPUBLICKEY]);
+    if(result)
+      return result;
+#elif defined(USE_WOLFSSL)
+    if(!peer->sni ||
+       wolfSSL_check_domain_name(ctx->ssl, peer->sni) == SSL_FAILURE)
+      return CURLE_PEER_FAILED_VERIFICATION;
+#endif
+    infof(data, "Verified certificate just fine");
+  }
+  else
+    infof(data, "Skipped certificate verification");
+#ifdef USE_OPENSSL
+  if(data->set.ssl.certinfo)
+    /* asked to gather certificate info */
+    (void)Curl_ossl_certchain(data, ctx->ssl);
+#endif
+  return result;
+}
+
+
+#endif /* !ENABLE_QUIC && (USE_OPENSSL || USE_GNUTLS || USE_WOLFSSL) */

+ 98 - 0
lib/vquic/vquic-tls.h

@@ -0,0 +1,98 @@
+#ifndef HEADER_CURL_VQUIC_TLS_H
+#define HEADER_CURL_VQUIC_TLS_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+#include "bufq.h"
+
+#if defined(ENABLE_QUIC) && \
+  (defined(USE_OPENSSL) || defined(USE_GNUTLS) || defined(USE_WOLFSSL))
+
+struct quic_tls_ctx {
+#ifdef USE_OPENSSL
+  SSL_CTX *ssl_ctx;
+  SSL *ssl;
+#elif defined(USE_GNUTLS)
+  struct gtls_instance *gtls;
+#elif defined(USE_WOLFSSL)
+  WOLFSSL_CTX *ssl_ctx;
+  WOLFSSL *ssl;
+#endif
+  BIT(x509_store_setup);             /* if x509 store has been set up */
+};
+
+/**
+ * Callback passed to `Curl_vquic_tls_init()` that can
+ * do early initializations on the not otherwise configured TLS
+ * instances created. This varies by TLS backend:
+ * - openssl/wolfssl: SSL_CTX* has just been created
+ * - gnutls: gtls_client_init() has run
+ */
+typedef CURLcode Curl_vquic_tls_ctx_setup(struct quic_tls_ctx *ctx,
+                                          struct Curl_cfilter *cf,
+                                          struct Curl_easy *data);
+
+/**
+ * Initialize the QUIC TLS instances based of the SSL configurations
+ * for the connection filter, transfer and peer.
+ * @param ctx         the TLS context to initialize
+ * @param cf          the connection filter involved
+ * @param data        the transfer involved
+ * @param peer        the peer that will be connected to
+ * @param alpn        the ALPN string in protocol format ((len+bytes+)+),
+ *                    may be NULL
+ * @param alpn_len    the overall number of bytes in `alpn`
+ * @param ctx_setup   optional callback for very early TLS config
+ * @param user_data   optional pointer to set in TLS application context
+ */
+CURLcode Curl_vquic_tls_init(struct quic_tls_ctx *ctx,
+                             struct Curl_cfilter *cf,
+                             struct Curl_easy *data,
+                             struct ssl_peer *peer,
+                             const char *alpn, size_t alpn_len,
+                             Curl_vquic_tls_ctx_setup *ctx_setup,
+                             void *user_data);
+
+/**
+ * Cleanup all data that has been initialized.
+ */
+void Curl_vquic_tls_cleanup(struct quic_tls_ctx *ctx);
+
+CURLcode Curl_vquic_tls_before_recv(struct quic_tls_ctx *ctx,
+                                    struct Curl_cfilter *cf,
+                                    struct Curl_easy *data);
+
+/**
+ * After the QUIC basic handshake has been, verify that the peer
+ * (and its certificate) fulfill our requirements.
+ */
+CURLcode Curl_vquic_tls_verify_peer(struct quic_tls_ctx *ctx,
+                                    struct Curl_cfilter *cf,
+                                    struct Curl_easy *data,
+                                    struct ssl_peer *peer);
+
+#endif /* !ENABLE_QUIC && (USE_OPENSSL || USE_GNUTLS || USE_WOLFSSL) */
+
+#endif /* HEADER_CURL_VQUIC_TLS_H */

+ 6 - 1
lib/vquic/vquic.c

@@ -543,8 +543,13 @@ CURLcode vquic_recv_packets(struct Curl_cfilter *cf,
 #else
   result = recvfrom_packets(cf, data, qctx, max_pkts, recv_cb, userp);
 #endif
-  if(!result)
+  if(!result) {
+    if(!qctx->got_first_byte) {
+      qctx->got_first_byte = TRUE;
+      qctx->first_byte_at = qctx->last_op;
+    }
     qctx->last_io = qctx->last_op;
+  }
   return result;
 }
 

+ 3 - 1
lib/vquic/vquic_int.h

@@ -40,6 +40,7 @@ struct cf_quic_ctx {
   socklen_t local_addrlen; /* length of local address */
 
   struct bufq sendbuf; /* buffer for sending one or more packets */
+  struct curltime first_byte_at;     /* when first byte was recvd */
   struct curltime last_op; /* last (attempted) send/recv operation */
   struct curltime last_io; /* last successful socket IO */
   size_t gsolen; /* length of individual packets in send buf */
@@ -48,7 +49,8 @@ struct cf_quic_ctx {
 #ifdef DEBUGBUILD
   int wblock_percent; /* percent of writes doing EAGAIN */
 #endif
-  bool no_gso; /* do not use gso on sending */
+  BIT(got_first_byte); /* if first byte was received */
+  BIT(no_gso); /* do not use gso on sending */
 };
 
 CURLcode vquic_ctx_init(struct cf_quic_ctx *qctx);

+ 15 - 0
tests/http/test_01_basic.py

@@ -84,3 +84,18 @@ class TestBasic:
         r = curl.http_get(url=url, extra_args=['--http3-only'])
         r.check_response(http_status=200, protocol='HTTP/3')
         assert r.json['server'] == env.domain1
+
+    # simple download, check connect/handshake timings
+    @pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason=f"curl without SSL")
+    @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
+    def test_01_06_timings(self, env: Env, httpd, nghttpx, repeat, proto):
+        if proto == 'h3' and not env.have_h3():
+            pytest.skip("h3 not supported")
+        curl = CurlClient(env=env)
+        url = f'https://{env.authority_for(env.domain1, proto)}/data.json'
+        r = curl.http_download(urls=[url], alpn_proto=proto, with_stats=True)
+        r.check_stats(http_status=200, count=1)
+        assert r.stats[0]['time_connect'] > 0, f'{r.stats[0]}'
+        assert r.stats[0]['time_appconnect'] > 0, f'{r.stats[0]}'
+
+