summaryrefslogtreecommitdiff
path: root/libs/libcurl/src/vquic/ngtcp2.c
diff options
context:
space:
mode:
Diffstat (limited to 'libs/libcurl/src/vquic/ngtcp2.c')
-rw-r--r--libs/libcurl/src/vquic/ngtcp2.c239
1 files changed, 207 insertions, 32 deletions
diff --git a/libs/libcurl/src/vquic/ngtcp2.c b/libs/libcurl/src/vquic/ngtcp2.c
index ca9c388345..097cca44b0 100644
--- a/libs/libcurl/src/vquic/ngtcp2.c
+++ b/libs/libcurl/src/vquic/ngtcp2.c
@@ -38,6 +38,9 @@
#elif defined(USE_GNUTLS)
#include <ngtcp2/ngtcp2_crypto_gnutls.h>
#include "vtls/gtls.h"
+#elif defined(USE_WOLFSSL)
+#include <ngtcp2/ngtcp2_crypto_wolfssl.h>
+#include "vtls/wolfssl.h"
#endif
#include "urldata.h"
#include "sendf.h"
@@ -101,6 +104,11 @@ struct h3out {
"+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
/* ngtcp2 default congestion controller does not perform pacing. Limit
@@ -113,7 +121,7 @@ static CURLcode ng_process_ingress(struct Curl_easy *data,
static CURLcode ng_flush_egress(struct Curl_easy *data, int sockfd,
struct quicsocket *qs);
static int cb_h3_acked_stream_data(nghttp3_conn *conn, int64_t stream_id,
- size_t datalen, void *user_data,
+ uint64_t datalen, void *user_data,
void *stream_user_data);
static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref)
@@ -202,6 +210,14 @@ static int keylog_callback(gnutls_session_t session, const char *label,
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 quicsocket *qs);
@@ -395,7 +411,105 @@ static int quic_init_ssl(struct quicsocket *qs)
gnutls_server_name_set(qs->ssl, GNUTLS_NAME_DNS, hostname, strlen(hostname));
return 0;
}
+#elif defined(USE_WOLFSSL)
+
+static WOLFSSL_CTX *quic_ssl_ctx(struct Curl_easy *data)
+{
+ struct connectdata *conn = data->conn;
+ WOLFSSL_CTX *ssl_ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method());
+
+ if(ngtcp2_crypto_wolfssl_configure_client_context(ssl_ctx) != 0) {
+ failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed");
+ return NULL;
+ }
+
+ wolfSSL_CTX_set_default_verify_paths(ssl_ctx);
+
+ if(wolfSSL_CTX_set_cipher_list(ssl_ctx, QUIC_CIPHERS) != 1) {
+ char error_buffer[256];
+ ERR_error_string_n(ERR_get_error(), error_buffer, sizeof(error_buffer));
+ failf(data, "SSL_CTX_set_ciphersuites: %s", error_buffer);
+ return NULL;
+ }
+
+ if(wolfSSL_CTX_set1_groups_list(ssl_ctx, (char *)QUIC_GROUPS) != 1) {
+ failf(data, "SSL_CTX_set1_groups_list failed");
+ return NULL;
+ }
+
+ /* 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");
+ return NULL;
#endif
+ }
+
+ if(conn->ssl_config.verifypeer) {
+ const char * const ssl_cafile = conn->ssl_config.CAfile;
+ const char * const ssl_capath = conn->ssl_config.CApath;
+
+ if(ssl_cafile || ssl_capath) {
+ wolfSSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
+ /* tell wolfSSL where to find CA certificates that are used to verify
+ the server's certificate. */
+ if(!wolfSSL_CTX_load_verify_locations(ssl_ctx, 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 NULL;
+ }
+ 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);
+ }
+
+ return ssl_ctx;
+}
+
+/** SSL callbacks ***/
+
+static int quic_init_ssl(struct quicsocket *qs)
+{
+ 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 = qs->conn->host.name;
+
+ DEBUGASSERT(!qs->ssl);
+ qs->ssl = SSL_new(qs->sslctx);
+
+ wolfSSL_set_app_data(qs->ssl, &qs->conn_ref);
+ wolfSSL_set_connect_state(qs->ssl);
+ wolfSSL_set_quic_use_legacy_codepoint(qs->ssl, 0);
+
+ alpn = (const uint8_t *)H3_ALPN_H3_29 H3_ALPN_H3;
+ alpnlen = sizeof(H3_ALPN_H3_29) - 1 + sizeof(H3_ALPN_H3) - 1;
+ if(alpn)
+ wolfSSL_set_alpn_protos(qs->ssl, alpn, (int)alpnlen);
+
+ /* set SNI */
+ wolfSSL_UseSNI(qs->ssl, WOLFSSL_SNI_HOST_NAME,
+ hostname, (unsigned short)strlen(hostname));
+
+ return 0;
+}
+#endif /* defined(USE_WOLFSSL) */
static int cb_handshake_completed(ngtcp2_conn *tconn, void *user_data)
{
@@ -646,6 +760,7 @@ static ngtcp2_callbacks ng_callbacks = {
NULL, /* version_negotiation */
cb_recv_rx_key,
NULL, /* recv_tx_key */
+ NULL, /* early_data_rejected */
};
/*
@@ -691,6 +806,10 @@ CURLcode Curl_quic_connect(struct Curl_easy *data,
result = quic_set_client_cert(data, qs);
if(result)
return result;
+#elif defined(USE_WOLFSSL)
+ qs->sslctx = quic_ssl_ctx(data);
+ if(!qs->sslctx)
+ return CURLE_QUIC_CONNECT_ERROR;
#endif
if(quic_init_ssl(qs))
@@ -818,6 +937,8 @@ static void qs_disconnect(struct quicsocket *qs)
SSL_free(qs->ssl);
#elif defined(USE_GNUTLS)
gnutls_deinit(qs->ssl);
+#elif defined(USE_WOLFSSL)
+ wolfSSL_free(qs->ssl);
#endif
qs->ssl = NULL;
#ifdef USE_GNUTLS
@@ -831,6 +952,8 @@ static void qs_disconnect(struct quicsocket *qs)
ngtcp2_conn_del(qs->qconn);
#ifdef USE_OPENSSL
SSL_CTX_free(qs->sslctx);
+#elif defined(USE_WOLFSSL)
+ wolfSSL_CTX_free(qs->sslctx);
#endif
}
@@ -899,6 +1022,7 @@ static int cb_h3_stream_close(nghttp3_conn *conn, int64_t stream_id,
H3BUGF(infof(data, "cb_h3_stream_close CALLED"));
stream->closed = TRUE;
+ stream->error3 = app_error_code;
Curl_expire(data, 0, EXPIRE_QUIC);
/* make sure that ngh3_stream_recv is called again to complete the transfer
even if there are no more packets to be received from the server. */
@@ -1010,6 +1134,10 @@ static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id,
return -1;
}
}
+
+ if(stream->status_code / 100 != 1) {
+ stream->bodystarted = TRUE;
+ }
return 0;
}
@@ -1032,9 +1160,10 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id,
if(token == NGHTTP3_QPACK_TOKEN__STATUS) {
char line[14]; /* status line is always 13 characters long */
size_t ncopy;
- int status = decode_status_code(h3val.base, h3val.len);
- DEBUGASSERT(status != -1);
- ncopy = msnprintf(line, sizeof(line), "HTTP/3 %03d \r\n", status);
+ stream->status_code = decode_status_code(h3val.base, h3val.len);
+ DEBUGASSERT(stream->status_code != -1);
+ ncopy = msnprintf(line, sizeof(line), "HTTP/3 %03d \r\n",
+ stream->status_code);
result = write_data(stream, line, ncopy);
if(result) {
return -1;
@@ -1064,16 +1193,36 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id,
return 0;
}
-static int cb_h3_send_stop_sending(nghttp3_conn *conn, int64_t stream_id,
- uint64_t app_error_code,
- void *user_data,
- void *stream_user_data)
+static int cb_h3_stop_sending(nghttp3_conn *conn, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data)
{
+ struct quicsocket *qs = user_data;
+ int rv;
+ (void)conn;
+ (void)stream_user_data;
+
+ rv = ngtcp2_conn_shutdown_stream_read(qs->qconn, stream_id, app_error_code);
+ if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ return 0;
+}
+
+static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t stream_id,
+ uint64_t app_error_code, void *user_data,
+ void *stream_user_data) {
+ struct quicsocket *qs = user_data;
+ int rv;
(void)conn;
- (void)stream_id;
- (void)app_error_code;
- (void)user_data;
(void)stream_user_data;
+
+ rv = ngtcp2_conn_shutdown_stream_write(qs->qconn, stream_id, app_error_code);
+ if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
return 0;
}
@@ -1088,9 +1237,9 @@ static nghttp3_callbacks ngh3_callbacks = {
NULL, /* begin_trailers */
cb_h3_recv_header,
NULL, /* end_trailers */
- cb_h3_send_stop_sending,
+ cb_h3_stop_sending,
NULL, /* end_stream */
- NULL, /* reset_stream */
+ cb_h3_reset_stream,
NULL /* shutdown */
};
@@ -1226,6 +1375,24 @@ static ssize_t ngh3_stream_recv(struct Curl_easy *data,
}
if(stream->closed) {
+ if(stream->error3 != NGHTTP3_H3_NO_ERROR) {
+ failf(data,
+ "HTTP/3 stream %" PRId64 " was not closed cleanly: (err %" PRIu64
+ ")",
+ stream->stream3_id, stream->error3);
+ *curlcode = CURLE_HTTP3;
+ return -1;
+ }
+
+ if(!stream->bodystarted) {
+ failf(data,
+ "HTTP/3 stream %" PRId64 " was closed cleanly, but before getting"
+ " all response header fields, treated as error",
+ stream->stream3_id);
+ *curlcode = CURLE_HTTP3;
+ return -1;
+ }
+
*curlcode = CURLE_OK;
return 0;
}
@@ -1237,7 +1404,7 @@ static ssize_t ngh3_stream_recv(struct Curl_easy *data,
/* this amount of data has now been acked on this stream */
static int cb_h3_acked_stream_data(nghttp3_conn *conn, int64_t stream_id,
- size_t datalen, void *user_data,
+ uint64_t datalen, void *user_data,
void *stream_user_data)
{
struct Curl_easy *data = stream_user_data;
@@ -1375,6 +1542,7 @@ static CURLcode http_request(struct Curl_easy *data, const void *mem,
nva[i].namelen = hreq->header[i].namelen;
nva[i].value = (unsigned char *)hreq->header[i].value;
nva[i].valuelen = hreq->header[i].valuelen;
+ nva[i].flags = NGHTTP3_NV_FLAG_NONE;
}
}
@@ -1443,6 +1611,11 @@ static ssize_t ngh3_stream_send(struct Curl_easy *data,
curl_socket_t sockfd = conn->sock[sockindex];
struct HTTP *stream = data->req.p.http;
+ if(stream->closed) {
+ *curlcode = CURLE_HTTP3;
+ return -1;
+ }
+
if(!stream->h3req) {
CURLcode result = http_request(data, mem, len);
if(result) {
@@ -1519,12 +1692,23 @@ static CURLcode ng_has_connected(struct Curl_easy *data,
if(result)
return result;
infof(data, "Verified certificate just fine");
-#else
+#elif defined(USE_GNUTLS)
result = Curl_gtls_verifyserver(data, conn, conn->quic->ssl, FIRSTSOCKET);
+#elif defined(USE_WOLFSSL)
+ char *snihost = Curl_ssl_snihost(data, SSL_HOST_NAME(), NULL);
+ if(!snihost ||
+ (wolfSSL_check_domain_name(conn->quic->ssl, snihost) == SSL_FAILURE))
+ return CURLE_PEER_FAILED_VERIFICATION;
+ infof(data, "Verified certificate just fine");
#endif
}
else
infof(data, "Skipped certificate verification");
+#ifdef USE_OPENSSL
+ if(data->set.ssl.certinfo)
+ /* asked to gather certificate info */
+ (void)Curl_ossl_certchain(data, conn->quic->ssl);
+#endif
return result;
}
@@ -1650,15 +1834,17 @@ static CURLcode do_sendmsg(size_t *psent, struct Curl_easy *data, int sockfd,
size_t pktlen, size_t gsolen)
{
#ifdef HAVE_SENDMSG
- struct iovec msg_iov = {(void *)pkt, pktlen};
+ struct iovec msg_iov;
struct msghdr msg = {0};
- uint8_t msg_ctrl[32];
ssize_t sent;
#if defined(__linux__) && defined(UDP_SEGMENT)
+ uint8_t msg_ctrl[32];
struct cmsghdr *cm;
#endif
*psent = 0;
+ msg_iov.iov_base = (uint8_t *)pkt;
+ msg_iov.iov_len = pktlen;
msg.msg_iov = &msg_iov;
msg.msg_iovlen = 1;
@@ -1803,9 +1989,9 @@ static CURLcode ng_flush_egress(struct Curl_easy *data,
ngtcp2_ssize outlen;
uint8_t *outpos = qs->pktbuf;
size_t max_udp_payload_size =
- ngtcp2_conn_get_max_udp_payload_size(qs->qconn);
+ ngtcp2_conn_get_max_tx_udp_payload_size(qs->qconn);
size_t path_max_udp_payload_size =
- ngtcp2_conn_get_path_max_udp_payload_size(qs->qconn);
+ ngtcp2_conn_get_path_max_tx_udp_payload_size(qs->qconn);
size_t max_pktcnt =
CURLMIN(MAX_PKT_BURST, qs->pktbuflen / max_udp_payload_size);
size_t pktcnt = 0;
@@ -1889,22 +2075,11 @@ static CURLcode ng_flush_egress(struct Curl_easy *data,
switch(outlen) {
case NGTCP2_ERR_STREAM_DATA_BLOCKED:
assert(ndatalen == -1);
- rv = nghttp3_conn_block_stream(qs->h3conn, stream_id);
- if(rv) {
- failf(data, "nghttp3_conn_block_stream returned error: %s\n",
- nghttp3_strerror(rv));
- return CURLE_SEND_ERROR;
- }
+ nghttp3_conn_block_stream(qs->h3conn, stream_id);
continue;
case NGTCP2_ERR_STREAM_SHUT_WR:
assert(ndatalen == -1);
- rv = nghttp3_conn_shutdown_stream_write(qs->h3conn, stream_id);
- if(rv) {
- failf(data,
- "nghttp3_conn_shutdown_stream_write returned error: %s\n",
- nghttp3_strerror(rv));
- return CURLE_SEND_ERROR;
- }
+ nghttp3_conn_shutdown_stream_write(qs->h3conn, stream_id);
continue;
case NGTCP2_ERR_WRITE_MORE:
assert(ndatalen >= 0);