From: Stefan Eissing Date: Tue, 9 Jan 2024 12:21:02 +0000 (+0100) Subject: vquic: extract TLS setup into own source X-Git-Tag: curl-8_6_0~97 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=5d044ad9480a9f556f4b6a252d7533b1ba7fe57e;p=thirdparty%2Fcurl.git 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 --- diff --git a/lib/Makefile.inc b/lib/Makefile.inc index e568ef9535..d937e1ed0c 100644 --- a/lib/Makefile.inc +++ b/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 \ diff --git a/lib/cf-socket.c b/lib/cf-socket.c index 2e985a77f3..cfb98386d9 100644 --- a/lib/cf-socket.c +++ b/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]", diff --git a/lib/vquic/curl_ngtcp2.c b/lib/vquic/curl_ngtcp2.c index e391e4f421..a26b3e429d 100644 --- a/lib/vquic/curl_ngtcp2.c +++ b/lib/vquic/curl_ngtcp2.c @@ -41,7 +41,6 @@ #include "vtls/gtls.h" #elif defined(USE_WOLFSSL) #include -#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: { diff --git a/lib/vquic/curl_quiche.c b/lib/vquic/curl_quiche.c index ed0bde3670..fcb0eb8f84 100644 --- a/lib/vquic/curl_quiche.c +++ b/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: { diff --git a/lib/vquic/vquic-tls.c b/lib/vquic/vquic-tls.c new file mode 100644 index 0000000000..e9f16773b9 --- /dev/null +++ b/lib/vquic/vquic-tls.c @@ -0,0 +1,603 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 +#include "vtls/openssl.h" +#elif defined(USE_GNUTLS) +#include +#include +#include +#include +#include +#include "vtls/gtls.h" +#elif defined(USE_WOLFSSL) +#include +#include +#include +#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) */ diff --git a/lib/vquic/vquic-tls.h b/lib/vquic/vquic-tls.h new file mode 100644 index 0000000000..9c0dfd8d52 --- /dev/null +++ b/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, , 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 */ diff --git a/lib/vquic/vquic.c b/lib/vquic/vquic.c index 41fefceac3..7da4641468 100644 --- a/lib/vquic/vquic.c +++ b/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; } diff --git a/lib/vquic/vquic_int.h b/lib/vquic/vquic_int.h index a820f39aec..c218a949c7 100644 --- a/lib/vquic/vquic_int.h +++ b/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); diff --git a/tests/http/test_01_basic.py b/tests/http/test_01_basic.py index 7c1792d374..3902b75b7c 100644 --- a/tests/http/test_01_basic.py +++ b/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]}' + +