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
This commit is contained in:
Stefan Eissing 2024-01-09 13:21:02 +01:00 committed by Daniel Stenberg
parent 98543fc2cf
commit 5d044ad948
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
9 changed files with 839 additions and 724 deletions

View File

@ -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 \

View File

@ -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]",

View File

@ -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: {

View File

@ -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
lib/vquic/vquic-tls.c Normal file
View File

@ -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
lib/vquic/vquic-tls.h Normal file
View File

@ -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 */

View File

@ -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;
}

View File

@ -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);

View File

@ -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]}'