mirror of
https://github.com/moparisthebest/curl
synced 2024-12-24 17:18:48 -05:00
3af0e76d1e
USe configure --with-ngtcp2 or --with-quiche Using either option will enable a HTTP3 build. Co-authored-by: Alessandro Ghedini <alessandro@ghedini.me> Closes #3500
1030 lines
29 KiB
C
1030 lines
29 KiB
C
/***************************************************************************
|
|
* _ _ ____ _
|
|
* Project ___| | | | _ \| |
|
|
* / __| | | | |_) | |
|
|
* | (__| |_| | _ <| |___
|
|
* \___|\___/|_| \_\_____|
|
|
*
|
|
* Copyright (C) 1998 - 2019, 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.haxx.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.
|
|
*
|
|
***************************************************************************/
|
|
|
|
#include "curl_setup.h"
|
|
|
|
#ifdef USE_NGTCP2
|
|
#include <ngtcp2/ngtcp2.h>
|
|
#include <openssl/err.h>
|
|
#include "urldata.h"
|
|
#include "sendf.h"
|
|
#include "strdup.h"
|
|
#include "rand.h"
|
|
#include "ngtcp2.h"
|
|
#include "ngtcp2-crypto.h"
|
|
|
|
/* The last 3 #include files should be in this order */
|
|
#include "curl_printf.h"
|
|
#include "curl_memory.h"
|
|
#include "memdebug.h"
|
|
|
|
#define QUIC_MAX_STREAMS (256*1024)
|
|
#define QUIC_MAX_DATA (1*1024*1024)
|
|
#define QUIC_IDLE_TIMEOUT 60 /* seconds? */
|
|
#define QUIC_CIPHERS "TLS13-AES-128-GCM-SHA256:" \
|
|
"TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256"
|
|
#define QUIC_GROUPS "P-256:X25519:P-384:P-521"
|
|
|
|
static void quic_printf(void *user_data, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
(void)user_data; /* TODO, use this to do infof() instead long-term */
|
|
va_start(ap, fmt);
|
|
vfprintf(stderr, fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
static int setup_initial_crypto_context(struct connectdata *conn)
|
|
{
|
|
int rv;
|
|
uint8_t initial_secret[32];
|
|
uint8_t secret[32];
|
|
const ngtcp2_cid *dcid;
|
|
uint8_t key[16];
|
|
ssize_t keylen;
|
|
uint8_t iv[16];
|
|
ssize_t ivlen;
|
|
uint8_t hp[16];
|
|
ssize_t hplen;
|
|
|
|
dcid = ngtcp2_conn_get_dcid(conn->quic.conn);
|
|
rv = Curl_qc_derive_initial_secret(initial_secret, sizeof(initial_secret),
|
|
dcid, (uint8_t *)NGTCP2_INITIAL_SALT,
|
|
strlen(NGTCP2_INITIAL_SALT));
|
|
if(rv) {
|
|
return -1;
|
|
}
|
|
|
|
Curl_qc_prf_sha256(&conn->quic.hs_crypto_ctx);
|
|
Curl_qc_aead_aes_128_gcm(&conn->quic.hs_crypto_ctx);
|
|
|
|
rv = Curl_qc_derive_client_initial_secret(secret, sizeof(secret),
|
|
initial_secret,
|
|
sizeof(initial_secret));
|
|
if(rv) {
|
|
return -1;
|
|
}
|
|
|
|
keylen = Curl_qc_derive_packet_protection_key(key, sizeof(key),
|
|
secret, sizeof(secret),
|
|
&conn->quic.hs_crypto_ctx);
|
|
if(keylen < 0) {
|
|
return -1;
|
|
}
|
|
|
|
ivlen = Curl_qc_derive_packet_protection_iv(iv, sizeof(iv),
|
|
secret, sizeof(secret),
|
|
&conn->quic.hs_crypto_ctx);
|
|
if(ivlen < 0) {
|
|
return -1;
|
|
}
|
|
|
|
hplen = Curl_qc_derive_header_protection_key(hp, sizeof(hp),
|
|
secret, sizeof(secret),
|
|
&conn->quic.hs_crypto_ctx);
|
|
if(hplen < 0) {
|
|
return -1;
|
|
}
|
|
|
|
ngtcp2_conn_install_initial_tx_keys(conn->quic.conn, key, keylen, iv, ivlen,
|
|
hp, hplen);
|
|
|
|
rv = Curl_qc_derive_server_initial_secret(secret, sizeof(secret),
|
|
initial_secret,
|
|
sizeof(initial_secret));
|
|
if(rv) {
|
|
return -1;
|
|
}
|
|
|
|
keylen = Curl_qc_derive_packet_protection_key(key, sizeof(key),
|
|
secret, sizeof(secret),
|
|
&conn->quic.hs_crypto_ctx);
|
|
if(keylen < 0) {
|
|
return -1;
|
|
}
|
|
|
|
ivlen = Curl_qc_derive_packet_protection_iv(iv, sizeof(iv),
|
|
secret, sizeof(secret),
|
|
&conn->quic.hs_crypto_ctx);
|
|
if(ivlen < 0) {
|
|
return -1;
|
|
}
|
|
|
|
hplen = Curl_qc_derive_header_protection_key(hp, sizeof(hp),
|
|
secret, sizeof(secret),
|
|
&conn->quic.hs_crypto_ctx);
|
|
if(hplen < 0) {
|
|
return -1;
|
|
}
|
|
|
|
ngtcp2_conn_install_initial_rx_keys(conn->quic.conn,
|
|
key, keylen, iv, ivlen, hp, hplen);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void quic_settings(ngtcp2_settings *s)
|
|
{
|
|
s->log_printf = quic_printf;
|
|
s->initial_ts = 0;
|
|
s->max_stream_data_bidi_local = QUIC_MAX_STREAMS;
|
|
s->max_stream_data_bidi_remote = QUIC_MAX_STREAMS;
|
|
s->max_stream_data_uni = QUIC_MAX_STREAMS;
|
|
s->max_data = QUIC_MAX_DATA;
|
|
s->max_streams_bidi = 1;
|
|
s->max_streams_uni = 1;
|
|
s->idle_timeout = QUIC_IDLE_TIMEOUT;
|
|
s->max_packet_size = NGTCP2_MAX_PKT_SIZE;
|
|
s->ack_delay_exponent = NGTCP2_DEFAULT_ACK_DELAY_EXPONENT;
|
|
s->max_ack_delay = NGTCP2_DEFAULT_MAX_ACK_DELAY;
|
|
}
|
|
|
|
/* SSL extension functions */
|
|
static int transport_params_add_cb(SSL *ssl, unsigned int ext_type,
|
|
unsigned int content,
|
|
const unsigned char **out,
|
|
size_t *outlen, X509 *x,
|
|
size_t chainidx, int *al, void *add_arg)
|
|
{
|
|
int rv;
|
|
struct connectdata *conn = (struct connectdata *)SSL_get_app_data(ssl);
|
|
ngtcp2_transport_params params;
|
|
uint8_t buf[64];
|
|
ssize_t nwrite;
|
|
(void)ext_type;
|
|
(void)content;
|
|
(void)x;
|
|
(void)chainidx;
|
|
(void)add_arg;
|
|
|
|
rv = ngtcp2_conn_get_local_transport_params(
|
|
conn->quic.conn, ¶ms, NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO);
|
|
if(rv) {
|
|
*al = SSL_AD_INTERNAL_ERROR;
|
|
return -1;
|
|
}
|
|
|
|
nwrite = ngtcp2_encode_transport_params(
|
|
buf, sizeof(buf), NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO, ¶ms);
|
|
if(nwrite < 0) {
|
|
fprintf(stderr, "ngtcp2_encode_transport_params: %s\n",
|
|
ngtcp2_strerror((int)nwrite));
|
|
*al = SSL_AD_INTERNAL_ERROR;
|
|
return -1;
|
|
}
|
|
|
|
*out = Curl_memdup(buf, nwrite);
|
|
*outlen = nwrite;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void transport_params_free_cb(SSL *ssl, unsigned int ext_type,
|
|
unsigned int context,
|
|
const unsigned char *out,
|
|
void *add_arg)
|
|
{
|
|
(void)ssl;
|
|
(void)ext_type;
|
|
(void)context;
|
|
(void)add_arg;
|
|
free((char *)out);
|
|
}
|
|
|
|
static int transport_params_parse_cb(SSL *ssl, unsigned int ext_type,
|
|
unsigned int context,
|
|
const unsigned char *in,
|
|
size_t inlen, X509 *x, size_t chainidx,
|
|
int *al, void *parse_arg)
|
|
{
|
|
struct connectdata *conn = (struct connectdata *)SSL_get_app_data(ssl);
|
|
int rv;
|
|
ngtcp2_transport_params params;
|
|
(void)ext_type;
|
|
(void)context;
|
|
(void)x;
|
|
(void)chainidx;
|
|
(void)parse_arg;
|
|
|
|
rv = ngtcp2_decode_transport_params(
|
|
¶ms, NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS, in, inlen);
|
|
if(rv) {
|
|
fprintf(stderr, "ngtcp2_decode_transport_params: %s\n",
|
|
ngtcp2_strerror(rv));
|
|
*al = SSL_AD_ILLEGAL_PARAMETER;
|
|
return -1;
|
|
}
|
|
|
|
rv = ngtcp2_conn_set_remote_transport_params(
|
|
conn->quic.conn, NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS,
|
|
¶ms);
|
|
if(rv) {
|
|
*al = SSL_AD_ILLEGAL_PARAMETER;
|
|
return -1;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static SSL_CTX *quic_ssl_ctx(struct Curl_easy *data)
|
|
{
|
|
SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method());
|
|
|
|
SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION);
|
|
SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION);
|
|
|
|
/* This makes OpenSSL client not send CCS after an initial ClientHello. */
|
|
SSL_CTX_clear_options(ssl_ctx, SSL_OP_ENABLE_MIDDLEBOX_COMPAT);
|
|
|
|
SSL_CTX_set_default_verify_paths(ssl_ctx);
|
|
|
|
if(SSL_CTX_set_cipher_list(ssl_ctx, QUIC_CIPHERS) != 1) {
|
|
failf(data, "SSL_CTX_set_cipher_list: %s",
|
|
ERR_error_string(ERR_get_error(), NULL));
|
|
return NULL;
|
|
}
|
|
|
|
if(SSL_CTX_set1_groups_list(ssl_ctx, QUIC_GROUPS) != 1) {
|
|
failf(data, "SSL_CTX_set1_groups_list failed");
|
|
return NULL;
|
|
}
|
|
|
|
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_QUIC_HACK);
|
|
|
|
if(SSL_CTX_add_custom_ext(ssl_ctx,
|
|
NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS,
|
|
SSL_EXT_CLIENT_HELLO |
|
|
SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS,
|
|
transport_params_add_cb,
|
|
transport_params_free_cb, NULL,
|
|
transport_params_parse_cb, NULL) != 1) {
|
|
failf(data, "SSL_CTX_add_custom_ext(NGTCP2_TLSEXT_QUIC_TRANSPORT_"
|
|
"PARAMETERS) failed: %s\n",
|
|
ERR_error_string(ERR_get_error(), NULL));
|
|
return NULL;
|
|
}
|
|
|
|
return ssl_ctx;
|
|
}
|
|
|
|
/** SSL callbacks ***/
|
|
|
|
static void set_tls_alert(struct connectdata *conn,
|
|
uint8_t alert)
|
|
{
|
|
struct quicsocket *qs = &conn->quic;
|
|
qs->tls_alert = alert;
|
|
}
|
|
|
|
static int ssl_on_key(struct connectdata *conn,
|
|
int name, const uint8_t *secret, size_t secretlen)
|
|
{
|
|
int rv;
|
|
uint8_t hp[64];
|
|
ssize_t hplen;
|
|
uint8_t key[64];
|
|
ssize_t keylen;
|
|
uint8_t iv[64];
|
|
ssize_t ivlen;
|
|
struct Context *crypto_ctx = &conn->quic.crypto_ctx;
|
|
|
|
switch(name) {
|
|
case SSL_KEY_CLIENT_EARLY_TRAFFIC:
|
|
case SSL_KEY_CLIENT_HANDSHAKE_TRAFFIC:
|
|
case SSL_KEY_CLIENT_APPLICATION_TRAFFIC:
|
|
case SSL_KEY_SERVER_HANDSHAKE_TRAFFIC:
|
|
case SSL_KEY_SERVER_APPLICATION_TRAFFIC:
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
/* TODO We don't have to call this everytime we get key generated. */
|
|
rv = Curl_qc_negotiated_prf(crypto_ctx, conn->quic.ssl);
|
|
if(rv != 0) {
|
|
return -1;
|
|
}
|
|
rv = Curl_qc_negotiated_aead(crypto_ctx, conn->quic.ssl);
|
|
if(rv != 0) {
|
|
return -1;
|
|
}
|
|
|
|
keylen = Curl_qc_derive_packet_protection_key(key, sizeof(key),
|
|
secret, sizeof(secret),
|
|
crypto_ctx);
|
|
if(keylen < 0) {
|
|
return -1;
|
|
}
|
|
|
|
ivlen = Curl_qc_derive_packet_protection_iv(iv, sizeof(iv),
|
|
secret, sizeof(secret),
|
|
crypto_ctx);
|
|
if(ivlen < 0) {
|
|
return -1;
|
|
}
|
|
|
|
hplen =
|
|
Curl_qc_derive_header_protection_key(hp, sizeof(hp),
|
|
secret, secretlen, crypto_ctx);
|
|
if(hplen < 0)
|
|
return -1;
|
|
|
|
/* TODO Just call this once. */
|
|
ngtcp2_conn_set_aead_overhead(conn->quic.conn,
|
|
Curl_qc_aead_max_overhead(crypto_ctx));
|
|
|
|
switch(name) {
|
|
case SSL_KEY_CLIENT_EARLY_TRAFFIC:
|
|
ngtcp2_conn_install_early_keys(conn->quic.conn, key, keylen, iv, ivlen,
|
|
hp, hplen);
|
|
break;
|
|
case SSL_KEY_CLIENT_HANDSHAKE_TRAFFIC:
|
|
ngtcp2_conn_install_handshake_tx_keys(conn->quic.conn, key, keylen,
|
|
iv, ivlen, hp, hplen);
|
|
break;
|
|
case SSL_KEY_CLIENT_APPLICATION_TRAFFIC:
|
|
ngtcp2_conn_install_tx_keys(conn->quic.conn, key, keylen, iv, ivlen,
|
|
hp, hplen);
|
|
break;
|
|
case SSL_KEY_SERVER_HANDSHAKE_TRAFFIC:
|
|
ngtcp2_conn_install_handshake_rx_keys(conn->quic.conn, key, keylen,
|
|
iv, ivlen,
|
|
hp, hplen);
|
|
break;
|
|
case SSL_KEY_SERVER_APPLICATION_TRAFFIC:
|
|
ngtcp2_conn_install_rx_keys(conn->quic.conn, key, keylen, iv, ivlen,
|
|
hp, hplen);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void ssl_msg_cb(int write_p, int version, int content_type,
|
|
const void *buf, size_t len, SSL *ssl, void *user_data)
|
|
{
|
|
int rv;
|
|
struct connectdata *conn = (struct connectdata *)user_data;
|
|
uint8_t *msg = (uint8_t *)buf;
|
|
(void)version;
|
|
(void)ssl;
|
|
|
|
if(!write_p)
|
|
return;
|
|
|
|
switch(content_type) {
|
|
case SSL3_RT_HANDSHAKE:
|
|
break;
|
|
case SSL3_RT_ALERT:
|
|
assert(len == 2);
|
|
if(msg[0] != 2 /* FATAL */) {
|
|
return;
|
|
}
|
|
set_tls_alert(conn, msg[1]);
|
|
return;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
rv = ngtcp2_conn_submit_crypto_data(conn->quic.conn, buf, len);
|
|
if(rv) {
|
|
fprintf(stderr, "write_client_handshake failed\n");
|
|
}
|
|
assert(0 == rv);
|
|
}
|
|
|
|
static int ssl_key_cb(SSL *ssl, int name,
|
|
const unsigned char *secret,
|
|
size_t secretlen,
|
|
void *arg)
|
|
{
|
|
struct connectdata *conn = (struct connectdata *)arg;
|
|
(void)ssl;
|
|
|
|
if(ssl_on_key(conn, name, secret, secretlen) != 0)
|
|
return 0;
|
|
|
|
/* log_secret(ssl, name, secret, secretlen); */
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int read_server_handshake(struct connectdata *conn,
|
|
char *buf, int buflen)
|
|
{
|
|
struct quic_handshake *hs = &conn->quic.handshake;
|
|
int avail = (int)(hs->len - hs->nread);
|
|
int n = CURLMIN(buflen, avail);
|
|
memcpy(buf, &hs->buf[hs->nread], n);
|
|
infof(conn->data, "read %d bytes of handshake data\n", n);
|
|
hs->nread += n;
|
|
return n;
|
|
}
|
|
|
|
static void write_server_handshake(struct connectdata *conn,
|
|
const uint8_t *ptr, size_t datalen)
|
|
{
|
|
char *p;
|
|
struct quic_handshake *hs = &conn->quic.handshake;
|
|
size_t alloclen = datalen + hs->alloclen;
|
|
infof(conn->data, "store %zd bytes of handshake data\n", datalen);
|
|
if(alloclen > hs->alloclen) {
|
|
alloclen *= 2;
|
|
p = realloc(conn->quic.handshake.buf, alloclen);
|
|
if(!p)
|
|
return; /* BAAAAAD */
|
|
hs->buf = p;
|
|
hs->alloclen = alloclen;
|
|
}
|
|
memcpy(&hs->buf[hs->len], ptr, datalen);
|
|
hs->len += datalen;
|
|
}
|
|
|
|
/** BIO functions ***/
|
|
|
|
static int bio_write(BIO *b, const char *buf, int len)
|
|
{
|
|
(void)b;
|
|
(void)buf;
|
|
(void)len;
|
|
assert(0);
|
|
return -1;
|
|
}
|
|
|
|
static int bio_read(BIO *b, char *buf, int len)
|
|
{
|
|
struct connectdata *conn;
|
|
BIO_clear_retry_flags(b);
|
|
|
|
conn = (struct connectdata *)BIO_get_data(b);
|
|
|
|
len = read_server_handshake(conn, buf, len);
|
|
if(len == 0) {
|
|
BIO_set_retry_read(b);
|
|
return -1;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
static int bio_puts(BIO *b, const char *str)
|
|
{
|
|
return bio_write(b, str, (int)strlen(str));
|
|
}
|
|
|
|
static int bio_gets(BIO *b, char *buf, int len)
|
|
{
|
|
(void)b;
|
|
(void)buf;
|
|
(void)len;
|
|
return -1;
|
|
}
|
|
|
|
static long bio_ctrl(BIO *b, int cmd, long num, void *ptr)
|
|
{
|
|
(void)b;
|
|
(void)cmd;
|
|
(void)num;
|
|
(void)ptr;
|
|
switch(cmd) {
|
|
case BIO_CTRL_FLUSH:
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bio_create(BIO *b)
|
|
{
|
|
BIO_set_init(b, 1);
|
|
return 1;
|
|
}
|
|
|
|
static int bio_destroy(BIO *b)
|
|
{
|
|
if(!b)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static BIO_METHOD *create_bio_method(void)
|
|
{
|
|
BIO_METHOD *meth = BIO_meth_new(BIO_TYPE_FD, "bio");
|
|
BIO_meth_set_write(meth, bio_write);
|
|
BIO_meth_set_read(meth, bio_read);
|
|
BIO_meth_set_puts(meth, bio_puts);
|
|
BIO_meth_set_gets(meth, bio_gets);
|
|
BIO_meth_set_ctrl(meth, bio_ctrl);
|
|
BIO_meth_set_create(meth, bio_create);
|
|
BIO_meth_set_destroy(meth, bio_destroy);
|
|
return meth;
|
|
}
|
|
|
|
|
|
static int quic_init_ssl(struct connectdata *conn)
|
|
{
|
|
struct quicsocket *qs = &conn->quic;
|
|
BIO *bio;
|
|
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 = conn->host.name;
|
|
|
|
if(qs->ssl)
|
|
SSL_free(qs->ssl);
|
|
|
|
qs->ssl = SSL_new(qs->sslctx);
|
|
bio = BIO_new(create_bio_method());
|
|
/* supposedly this can fail too? */
|
|
|
|
BIO_set_data(bio, conn);
|
|
SSL_set_bio(qs->ssl, bio, bio);
|
|
SSL_set_app_data(qs->ssl, conn);
|
|
SSL_set_connect_state(qs->ssl);
|
|
SSL_set_msg_callback(qs->ssl, ssl_msg_cb);
|
|
SSL_set_msg_callback_arg(qs->ssl, conn);
|
|
SSL_set_key_callback(qs->ssl, ssl_key_cb, conn);
|
|
|
|
switch(qs->version) {
|
|
#ifdef NGTCP2_PROTO_VER_D17
|
|
case NGTCP2_PROTO_VER_D17:
|
|
alpn = (const uint8_t *)NGTCP2_ALPN_D17;
|
|
alpnlen = strlen(NGTCP2_ALPN_D17);
|
|
break;
|
|
#endif
|
|
}
|
|
if(alpn)
|
|
SSL_set_alpn_protos(qs->ssl, alpn, (int)alpnlen);
|
|
|
|
/* set SNI */
|
|
SSL_set_tlsext_host_name(qs->ssl, hostname);
|
|
return 0;
|
|
}
|
|
|
|
static int quic_tls_handshake(struct connectdata *conn,
|
|
bool resumption,
|
|
bool initial)
|
|
{
|
|
int rv;
|
|
struct quicsocket *qs = &conn->quic;
|
|
ERR_clear_error();
|
|
|
|
/* Note that SSL_SESSION_get_max_early_data() and
|
|
SSL_get_max_early_data() return completely different value. */
|
|
if(initial && resumption &&
|
|
SSL_SESSION_get_max_early_data(SSL_get_session(qs->ssl))) {
|
|
size_t nwrite;
|
|
/* OpenSSL returns error if SSL_write_early_data is called when resumption
|
|
is not attempted. Sending empty string is a trick to just early_data
|
|
extension. */
|
|
rv = SSL_write_early_data(qs->ssl, "", 0, &nwrite);
|
|
if(rv == 0) {
|
|
int err = SSL_get_error(qs->ssl, rv);
|
|
switch(err) {
|
|
case SSL_ERROR_SSL:
|
|
fprintf(stderr, "TLS handshake error: %s\n",
|
|
ERR_error_string(ERR_get_error(), NULL));
|
|
return -1;
|
|
default:
|
|
fprintf(stderr, "TLS handshake error: %d\n", err);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
rv = SSL_do_handshake(qs->ssl);
|
|
if(rv <= 0) {
|
|
int err = SSL_get_error(qs->ssl, rv);
|
|
switch(err) {
|
|
case SSL_ERROR_WANT_READ:
|
|
case SSL_ERROR_WANT_WRITE:
|
|
return 0;
|
|
case SSL_ERROR_SSL:
|
|
fprintf(stderr, "TLS handshake error: %s\n",
|
|
ERR_error_string(ERR_get_error(), NULL));
|
|
return -1;
|
|
default:
|
|
fprintf(stderr, "TLS handshake error: %d\n", err);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* SSL_get_early_data_status works after handshake completes. */
|
|
if(resumption &&
|
|
SSL_get_early_data_status(qs->ssl) != SSL_EARLY_DATA_ACCEPTED) {
|
|
fprintf(stderr, "Early data was rejected by server\n");
|
|
ngtcp2_conn_early_data_rejected(conn->quic.conn);
|
|
}
|
|
|
|
ngtcp2_conn_handshake_completed(conn->quic.conn);
|
|
return 0;
|
|
}
|
|
|
|
static int cb_initial(ngtcp2_conn *quic, void *user_data)
|
|
{
|
|
struct connectdata *conn = (struct connectdata *)user_data;
|
|
(void)quic;
|
|
if(quic_tls_handshake(conn, false, true) != 0)
|
|
return NGTCP2_ERR_CALLBACK_FAILURE;
|
|
return 0;
|
|
}
|
|
|
|
static int quic_read_tls(struct connectdata *conn)
|
|
{
|
|
uint8_t buf[4096];
|
|
size_t nread;
|
|
|
|
ERR_clear_error();
|
|
for(;;) {
|
|
int err;
|
|
int rv = SSL_read_ex(conn->quic.ssl, buf, sizeof(buf), &nread);
|
|
if(rv == 1) {
|
|
infof(conn->data, "Read %zd bytes from TLS crypto stream",
|
|
nread);
|
|
continue;
|
|
}
|
|
err = SSL_get_error(conn->quic.ssl, 0);
|
|
switch(err) {
|
|
case SSL_ERROR_WANT_READ:
|
|
case SSL_ERROR_WANT_WRITE:
|
|
return 0;
|
|
case SSL_ERROR_SSL:
|
|
case SSL_ERROR_ZERO_RETURN:
|
|
infof(conn->data, "TLS read error: %s\n",
|
|
ERR_error_string(ERR_get_error(), NULL));
|
|
return NGTCP2_ERR_CRYPTO;
|
|
default:
|
|
infof(conn->data, "TLS read error: %d\n", err);
|
|
return NGTCP2_ERR_CRYPTO;
|
|
}
|
|
}
|
|
/* NEVER-REACHED */
|
|
}
|
|
|
|
static int
|
|
cb_recv_crypto_data(ngtcp2_conn *tconn, ngtcp2_crypto_level crypto_level,
|
|
uint64_t offset,
|
|
const uint8_t *data, size_t datalen,
|
|
void *user_data)
|
|
{
|
|
struct connectdata *conn = (struct connectdata *)user_data;
|
|
(void)offset;
|
|
(void)crypto_level;
|
|
|
|
write_server_handshake(conn, data, datalen);
|
|
|
|
if(!ngtcp2_conn_get_handshake_completed(tconn) &&
|
|
quic_tls_handshake(conn, false, false)) {
|
|
return NGTCP2_ERR_CRYPTO;
|
|
}
|
|
|
|
/* SSL_do_handshake() might not consume all data (e.g.,
|
|
NewSessionTicket). */
|
|
return quic_read_tls(conn);
|
|
}
|
|
|
|
static int cb_handshake_completed(ngtcp2_conn *tconn, void *user_data)
|
|
{
|
|
struct connectdata *conn = (struct connectdata *)user_data;
|
|
(void)tconn;
|
|
infof(conn->data, "QUIC handshake is completed\n");
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t cb_in_encrypt(ngtcp2_conn *tconn,
|
|
uint8_t *dest, size_t destlen,
|
|
const uint8_t *plaintext,
|
|
size_t plaintextlen,
|
|
const uint8_t *key, size_t keylen,
|
|
const uint8_t *nonce, size_t noncelen,
|
|
const uint8_t *ad, size_t adlen,
|
|
void *user_data)
|
|
{
|
|
struct connectdata *conn = (struct connectdata *)user_data;
|
|
ssize_t nwrite = Curl_qc_encrypt(dest, destlen, plaintext, plaintextlen,
|
|
&conn->quic.hs_crypto_ctx,
|
|
key, keylen, nonce, noncelen, ad, adlen);
|
|
if(nwrite < 0) {
|
|
return NGTCP2_ERR_CALLBACK_FAILURE;
|
|
}
|
|
(void)tconn;
|
|
|
|
return nwrite;
|
|
}
|
|
|
|
static ssize_t cb_in_decrypt(ngtcp2_conn *tconn,
|
|
uint8_t *dest, size_t destlen,
|
|
const uint8_t *ciphertext, size_t ciphertextlen,
|
|
const uint8_t *key, size_t keylen,
|
|
const uint8_t *nonce, size_t noncelen,
|
|
const uint8_t *ad, size_t adlen,
|
|
void *user_data)
|
|
{
|
|
struct connectdata *conn = (struct connectdata *)user_data;
|
|
(void)tconn;
|
|
return Curl_qc_decrypt(dest, destlen, ciphertext, ciphertextlen,
|
|
&conn->quic.hs_crypto_ctx, key, keylen,
|
|
nonce, noncelen, ad, adlen);
|
|
}
|
|
|
|
|
|
static ssize_t cb_encrypt_data(ngtcp2_conn *tconn,
|
|
uint8_t *dest, size_t destlen,
|
|
const uint8_t *plaintext, size_t plaintextlen,
|
|
const uint8_t *key, size_t keylen,
|
|
const uint8_t *nonce, size_t noncelen,
|
|
const uint8_t *ad, size_t adlen,
|
|
void *user_data)
|
|
{
|
|
struct connectdata *conn = (struct connectdata *)user_data;
|
|
ssize_t rc;
|
|
(void)tconn;
|
|
rc = Curl_qc_encrypt(dest, destlen, plaintext, plaintextlen,
|
|
&conn->quic.crypto_ctx,
|
|
key, keylen, nonce, noncelen, ad, adlen);
|
|
if(rc < 0)
|
|
return NGTCP2_ERR_CALLBACK_FAILURE;
|
|
return rc;
|
|
}
|
|
|
|
static ssize_t
|
|
cb_decrypt_data(ngtcp2_conn *tconn,
|
|
uint8_t *dest, size_t destlen,
|
|
const uint8_t *ciphertext, size_t ciphertextlen,
|
|
const uint8_t *key, size_t keylen,
|
|
const uint8_t *nonce, size_t noncelen,
|
|
const uint8_t *ad, size_t adlen,
|
|
void *user_data)
|
|
{
|
|
struct connectdata *conn = (struct connectdata *)user_data;
|
|
ssize_t rc;
|
|
(void)tconn;
|
|
rc = Curl_qc_decrypt(dest, destlen, ciphertext, ciphertextlen,
|
|
&conn->quic.crypto_ctx,
|
|
key, keylen, nonce, noncelen, ad, adlen);
|
|
if(rc < 0)
|
|
return NGTCP2_ERR_TLS_DECRYPT;
|
|
return rc;
|
|
}
|
|
|
|
static int cb_recv_stream_data(ngtcp2_conn *tconn, int64_t stream_id,
|
|
int fin, uint64_t offset,
|
|
const uint8_t *buf, size_t buflen,
|
|
void *user_data, void *stream_user_data)
|
|
{
|
|
struct connectdata *conn = (struct connectdata *)user_data;
|
|
(void)fin;
|
|
(void)offset;
|
|
(void)stream_user_data;
|
|
/* TODO: handle the data */
|
|
infof(conn->data, "Received %ld bytes at %p\n", buflen, buf);
|
|
ngtcp2_conn_extend_max_stream_offset(tconn, stream_id, buflen);
|
|
ngtcp2_conn_extend_max_offset(tconn, buflen);
|
|
return 0;
|
|
}
|
|
|
|
static int cb_acked_crypto_offset(ngtcp2_conn *tconn,
|
|
uint64_t offset, size_t datalen,
|
|
void *user_data)
|
|
{
|
|
struct connectdata *conn = (struct connectdata *)user_data;
|
|
(void)conn;
|
|
(void)tconn;
|
|
(void)offset;
|
|
(void)datalen;
|
|
|
|
/* TODO: uhm... what should it do? */
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
cb_acked_stream_data_offset(ngtcp2_conn *tconn, int64_t stream_id,
|
|
uint64_t offset, size_t datalen, void *user_data,
|
|
void *stream_user_data)
|
|
{
|
|
struct connectdata *conn = (struct connectdata *)user_data;
|
|
(void)conn;
|
|
(void)stream_id;
|
|
(void)tconn;
|
|
(void)offset;
|
|
(void)datalen;
|
|
(void)stream_user_data;
|
|
|
|
/* TODO: implement */
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cb_stream_close(ngtcp2_conn *tconn, int64_t stream_id,
|
|
uint16_t app_error_code,
|
|
void *user_data, void *stream_user_data)
|
|
{
|
|
struct connectdata *conn = (struct connectdata *)user_data;
|
|
(void)conn;
|
|
(void)tconn;
|
|
(void)stream_id;
|
|
(void)app_error_code;
|
|
(void)stream_user_data;
|
|
/* stream is closed... */
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cb_recv_retry(ngtcp2_conn *tconn, const ngtcp2_pkt_hd *hd,
|
|
const ngtcp2_pkt_retry *retry, void *user_data)
|
|
{
|
|
/* Re-generate handshake secrets here because connection ID might change. */
|
|
struct connectdata *conn = (struct connectdata *)user_data;
|
|
(void)tconn;
|
|
(void)hd;
|
|
(void)retry;
|
|
|
|
quic_init_ssl(conn);
|
|
setup_initial_crypto_context(conn);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t cb_in_hp_mask(ngtcp2_conn *tconn, uint8_t *dest, size_t destlen,
|
|
const uint8_t *key, size_t keylen,
|
|
const uint8_t *sample, size_t samplelen,
|
|
void *user_data)
|
|
{
|
|
struct connectdata *conn = (struct connectdata *)user_data;
|
|
ssize_t nwrite;
|
|
(void)tconn;
|
|
|
|
nwrite = Curl_qc_hp_mask(dest, destlen, &conn->quic.hs_crypto_ctx,
|
|
key, keylen, sample, samplelen);
|
|
if(nwrite < 0)
|
|
return NGTCP2_ERR_CALLBACK_FAILURE;
|
|
|
|
return nwrite;
|
|
}
|
|
|
|
static ssize_t cb_hp_mask(ngtcp2_conn *tconn, uint8_t *dest, size_t destlen,
|
|
const uint8_t *key, size_t keylen,
|
|
const uint8_t *sample, size_t samplelen,
|
|
void *user_data)
|
|
{
|
|
struct connectdata *conn = (struct connectdata *)user_data;
|
|
ssize_t nwrite;
|
|
(void)tconn;
|
|
|
|
nwrite = Curl_qc_hp_mask(dest, destlen, &conn->quic.crypto_ctx,
|
|
key, keylen, sample, samplelen);
|
|
if(nwrite < 0)
|
|
return NGTCP2_ERR_CALLBACK_FAILURE;
|
|
|
|
return nwrite;
|
|
}
|
|
|
|
static int cb_extend_max_streams_bidi(ngtcp2_conn *tconn, uint64_t max_streams,
|
|
void *user_data)
|
|
{
|
|
/* struct connectdata *conn = (struct connectdata *)user_data; */
|
|
(void)tconn;
|
|
(void)max_streams;
|
|
(void)user_data;
|
|
return 0;
|
|
}
|
|
|
|
static int cb_get_new_connection_id(ngtcp2_conn *tconn, ngtcp2_cid *cid,
|
|
uint8_t *token, size_t cidlen,
|
|
void *user_data)
|
|
{
|
|
struct connectdata *conn = (struct connectdata *)user_data;
|
|
CURLcode result;
|
|
(void)tconn;
|
|
|
|
result = Curl_rand(conn->data, cid->data, cidlen);
|
|
if(result)
|
|
return 1;
|
|
|
|
result = Curl_rand(conn->data, token, NGTCP2_STATELESS_RESET_TOKENLEN);
|
|
if(result)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void quic_callbacks(ngtcp2_conn_callbacks *c)
|
|
{
|
|
memset(c, 0, sizeof(ngtcp2_conn_callbacks));
|
|
c->client_initial = cb_initial;
|
|
/* recv_client_initial = NULL */
|
|
c->recv_crypto_data = cb_recv_crypto_data;
|
|
c->handshake_completed = cb_handshake_completed;
|
|
/* recv_version_negotiation = NULL */
|
|
c->in_encrypt = cb_in_encrypt;
|
|
c->in_decrypt = cb_in_decrypt;
|
|
c->encrypt = cb_encrypt_data;
|
|
c->decrypt = cb_decrypt_data;
|
|
c->in_hp_mask = cb_in_hp_mask;
|
|
c->hp_mask = cb_hp_mask;
|
|
c->recv_stream_data = cb_recv_stream_data;
|
|
c->acked_crypto_offset = cb_acked_crypto_offset;
|
|
c->acked_stream_data_offset = cb_acked_stream_data_offset;
|
|
/* stream_open = NULL */
|
|
c->stream_close = cb_stream_close;
|
|
/* recv_stateless_reset = NULL */
|
|
c->recv_retry = cb_recv_retry;
|
|
c->extend_max_streams_bidi = cb_extend_max_streams_bidi;
|
|
/* extend_max_streams_uni = NULL */
|
|
/* rand = NULL */
|
|
c->get_new_connection_id = cb_get_new_connection_id;
|
|
/* remove_connection_id = NULL */
|
|
}
|
|
|
|
|
|
CURLcode Curl_quic_connect(struct connectdata *conn,
|
|
curl_socket_t sockfd,
|
|
const struct sockaddr *addr,
|
|
socklen_t addrlen)
|
|
{
|
|
int rc;
|
|
struct quicsocket *qs = &conn->quic;
|
|
CURLcode result;
|
|
ngtcp2_path path; /* TODO: this must be initialized properly */
|
|
(void)sockfd;
|
|
(void)addr;
|
|
(void)addrlen;
|
|
infof(conn->data, "Connecting socket %d over QUIC\n", sockfd);
|
|
|
|
qs->sslctx = quic_ssl_ctx(conn->data);
|
|
if(!qs->sslctx)
|
|
return CURLE_FAILED_INIT; /* TODO: better return code */
|
|
|
|
if(quic_init_ssl(conn))
|
|
return CURLE_FAILED_INIT; /* TODO: better return code */
|
|
|
|
qs->dcid.datalen = NGTCP2_MAX_CIDLEN;
|
|
result = Curl_rand(conn->data, qs->dcid.data, NGTCP2_MAX_CIDLEN);
|
|
if(result)
|
|
return result;
|
|
|
|
qs->scid.datalen = NGTCP2_MAX_CIDLEN;
|
|
result = Curl_rand(conn->data, qs->scid.data, NGTCP2_MAX_CIDLEN);
|
|
if(result)
|
|
return result;
|
|
|
|
quic_settings(&qs->settings);
|
|
quic_callbacks(&qs->callbacks);
|
|
|
|
#ifdef NGTCP2_PROTO_VER_D18
|
|
#define QUICVER NGTCP2_PROTO_VER_D18
|
|
#else
|
|
#error "unsupported ngtcp2 version"
|
|
#endif
|
|
rc = ngtcp2_conn_client_new(&qs->conn, &qs->dcid, &qs->scid,
|
|
&path,
|
|
QUICVER, &qs->callbacks, &qs->settings, conn);
|
|
if(rc)
|
|
return CURLE_FAILED_INIT; /* TODO: create a QUIC error code */
|
|
|
|
rc = setup_initial_crypto_context(conn);
|
|
if(rc)
|
|
return CURLE_FAILED_INIT; /* TODO: better return code */
|
|
|
|
return CURLE_OK;
|
|
}
|
|
|
|
/*
|
|
* Store ngtp2 version info in this buffer, Prefix with a space. Return total
|
|
* length written.
|
|
*/
|
|
int Curl_quic_ver(char *p, size_t len)
|
|
{
|
|
return msnprintf(p, len, " ngtcp2/blabla");
|
|
}
|
|
|
|
CURLcode Curl_quic_is_connected(struct connectdata *conn, int sockindex,
|
|
bool *done)
|
|
{
|
|
(void)conn;
|
|
(void)sockindex;
|
|
*done = FALSE;
|
|
return CURLE_OK;
|
|
}
|
|
#endif
|