HTTP3: initial (experimental) support

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
This commit is contained in:
Daniel Stenberg 2019-07-21 23:48:58 +02:00
parent 7644abf8e8
commit 3af0e76d1e
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
33 changed files with 2520 additions and 23 deletions

View file

@ -37,6 +37,10 @@ LIB_VTLS_HFILES = vtls/openssl.h vtls/vtls.h vtls/gtls.h \
vtls/wolfssl.h vtls/schannel.h vtls/sectransp.h vtls/gskit.h \
vtls/mbedtls.h vtls/mesalink.h
LIB_VQUIC_CFILES = vquic/ngtcp2.c vquic/ngtcp2-crypto.c vquic/quiche.c
LIB_VQUIC_HFILES = vquic/ngtcp2.h vquic/ngtcp2-crypto.h vquic/quiche.h
LIB_CFILES = file.c timeval.c base64.c hostip.c progress.c formdata.c \
cookie.c http.c sendf.c ftp.c url.c dict.c if2ip.c speedcheck.c \
ldap.c version.c getenv.c escape.c mprintf.c telnet.c netrc.c \
@ -55,7 +59,7 @@ LIB_CFILES = file.c timeval.c base64.c hostip.c progress.c formdata.c \
curl_multibyte.c hostcheck.c conncache.c dotdot.c \
x509asn1.c http2.c smb.c curl_endian.c curl_des.c system_win32.c \
mime.c sha256.c setopt.c curl_path.c curl_ctype.c curl_range.c psl.c \
doh.c urlapi.c curl_get_line.c altsvc.c
doh.c urlapi.c curl_get_line.c altsvc.c quic.c
LIB_HFILES = arpa_telnet.h netrc.h file.h timeval.h hostip.h progress.h \
formdata.h cookie.h http.h sendf.h ftp.h url.h dict.h if2ip.h \
@ -76,9 +80,11 @@ LIB_HFILES = arpa_telnet.h netrc.h file.h timeval.h hostip.h progress.h \
x509asn1.h http2.h sigpipe.h smb.h curl_endian.h curl_des.h \
curl_printf.h system_win32.h rand.h mime.h curl_sha256.h setopt.h \
curl_path.h curl_ctype.h curl_range.h psl.h doh.h urlapi-int.h \
curl_get_line.h altsvc.h
curl_get_line.h altsvc.h quic.h
LIB_RCFILES = libcurl.rc
CSOURCES = $(LIB_CFILES) $(LIB_VAUTH_CFILES) $(LIB_VTLS_CFILES)
HHEADERS = $(LIB_HFILES) $(LIB_VAUTH_HFILES) $(LIB_VTLS_HFILES)
CSOURCES = $(LIB_CFILES) $(LIB_VAUTH_CFILES) $(LIB_VTLS_CFILES) \
$(LIB_VQUIC_CFILES)
HHEADERS = $(LIB_HFILES) $(LIB_VAUTH_HFILES) $(LIB_VTLS_HFILES) \
$(LIB_VQUIC_HFILES)

View file

@ -706,7 +706,8 @@ Curl_addrinfo *Curl_resolver_getaddrinfo(struct connectdata *conn,
memset(&hints, 0, sizeof(hints));
hints.ai_family = pf;
hints.ai_socktype = conn->socktype;
hints.ai_socktype = (conn->transport == TRNSPRT_TCP)?
SOCK_STREAM : SOCK_DGRAM;
msnprintf(sbuf, sizeof(sbuf), "%d", port);

View file

@ -75,6 +75,7 @@
#include "conncache.h"
#include "multihandle.h"
#include "system_win32.h"
#include "quic.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
@ -683,8 +684,8 @@ UNITTEST bool getaddressinfo(struct sockaddr *sa, char *addr,
connection */
void Curl_updateconninfo(struct connectdata *conn, curl_socket_t sockfd)
{
if(conn->socktype == SOCK_DGRAM)
/* there's no connection! */
if(conn->transport != TRNSPRT_TCP)
/* there's no TCP connection! */
return;
#if defined(HAVE_GETPEERNAME) || defined(HAVE_GETSOCKNAME)
@ -1099,8 +1100,8 @@ static CURLcode singleipconnect(struct connectdata *conn,
if(conn->num_addr > 1)
Curl_expire(data, conn->timeoutms_per_addr, EXPIRE_DNS_PER_NAME);
/* Connect TCP sockets, bind UDP */
if(!isconnected && (conn->socktype == SOCK_STREAM)) {
/* Connect TCP and QUIC sockets */
if(!isconnected && (conn->transport != TRNSPRT_UDP)) {
if(conn->bits.tcp_fastopen) {
#if defined(CONNECT_DATA_IDEMPOTENT) /* Darwin */
# if defined(HAVE_BUILTIN_AVAILABLE)
@ -1152,6 +1153,15 @@ static CURLcode singleipconnect(struct connectdata *conn,
return CURLE_OK;
}
#ifdef ENABLE_QUIC
if(!isconnected && (conn->transport == TRNSPRT_QUIC)) {
result = Curl_quic_connect(conn, sockfd, &addr.sa_addr, addr.addrlen);
if(result)
return result;
rc = 0; /* connect success */
}
#endif
if(-1 == rc) {
switch(error) {
case EINPROGRESS:
@ -1386,8 +1396,9 @@ CURLcode Curl_socket(struct connectdata *conn,
*/
addr->family = ai->ai_family;
addr->socktype = conn->socktype;
addr->protocol = conn->socktype == SOCK_DGRAM?IPPROTO_UDP:ai->ai_protocol;
addr->socktype = (conn->transport == TRNSPRT_TCP) ? SOCK_STREAM : SOCK_DGRAM;
addr->protocol = conn->transport != TRNSPRT_TCP ? IPPROTO_UDP :
ai->ai_protocol;
addr->addrlen = ai->ai_addrlen;
if(addr->addrlen > sizeof(struct Curl_sockaddr_storage))

View file

@ -827,4 +827,8 @@ int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf,
#define UNITTEST static
#endif
#if defined(USE_NGTCP2) || defined(USE_QUICHE)
#define ENABLE_QUIC
#endif
#endif /* HEADER_CURL_SETUP_H */

View file

@ -165,7 +165,8 @@ Curl_addrinfo *Curl_getaddrinfo(struct connectdata *conn,
memset(&hints, 0, sizeof(hints));
hints.ai_family = pf;
hints.ai_socktype = conn->socktype;
hints.ai_socktype = (conn->transport == TRNSPRT_TCP) ?
SOCK_STREAM : SOCK_DGRAM;
#ifndef USE_RESOLVE_ON_IPS
/*

View file

@ -171,10 +171,22 @@ static CURLcode http_setup_conn(struct connectdata *conn)
Curl_mime_initpart(&http->form, conn->data);
data->req.protop = http;
if(!CONN_INUSE(conn))
/* if not already multi-using, setup connection details */
Curl_http2_setup_conn(conn);
Curl_http2_setup_req(data);
if(data->set.h3opts & CURLH3_DIRECT) {
if(conn->handler->flags & PROTOPT_SSL)
/* Only go h3-direct on HTTPS URLs. It needs a UDP socket and does the
QUIC dance. */
conn->transport = TRNSPRT_QUIC;
else {
failf(data, "HTTP/3 requested for non-HTTPS URL");
return CURLE_URL_MALFORMAT;
}
}
else {
if(!CONN_INUSE(conn))
/* if not already multi-using, setup connection details */
Curl_http2_setup_conn(conn);
Curl_http2_setup_req(data);
}
return CURLE_OK;
}
@ -1555,6 +1567,15 @@ static CURLcode https_connecting(struct connectdata *conn, bool *done)
CURLcode result;
DEBUGASSERT((conn) && (conn->handler->flags & PROTOPT_SSL));
#ifdef ENABLE_QUIC
if(conn->transport == TRNSPRT_QUIC) {
result = Curl_quic_is_connected(conn, FIRSTSOCKET, done);
if(result)
connclose(conn, "Failed HTTPS connection (over QUIC)");
return result;
}
#endif
/* perform SSL initialization for this socket */
result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, done);
if(result)

38
lib/quic.c Normal file
View file

@ -0,0 +1,38 @@
/***************************************************************************
* _ _ ____ _
* 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 ENABLE_QUIC
#include "quic.h"
/* backend-independent QUIC functionality */
const char *Curl_quic_backend(void)
{
#ifdef USE_NGTCP2
return "ngtcp2";
#endif
#ifdef USE_QUICHE
return "quiche";
#endif
}
#endif

54
lib/quic.h Normal file
View file

@ -0,0 +1,54 @@
#ifndef HEADER_CURL_QUIC_H
#define HEADER_CURL_QUIC_H
/***************************************************************************
* _ _ ____ _
* 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 ENABLE_QUIC
#ifdef USE_NGTCP2
#include "vquic/ngtcp2.h"
#endif
#ifdef USE_QUICHE
#include "vquic/quiche.h"
#endif
#include "urldata.h"
/* generic */
const char *Curl_quic_backend(void);
/* functions provided by the specific backends */
CURLcode Curl_quic_connect(struct connectdata *conn,
curl_socket_t sockfd,
const struct sockaddr *addr,
socklen_t addrlen);
CURLcode Curl_quic_is_connected(struct connectdata *conn, int sockindex,
bool *done);
int Curl_quic_ver(char *p, size_t len);
#else
/* no QUIC */
#define Curl_quic_backend() ""
#endif
#endif /* HEADER_CURL_QUIC_H */

View file

@ -2744,6 +2744,14 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
return result;
break;
#endif
case CURLOPT_H3:
#ifdef ENABLE_QUIC
arg = va_arg(param, long);
data->set.h3opts = arg;
#else
return CURLE_NOT_BUILT_IN;
#endif
break;
default:
/* unknown tag and its companion, just ignore: */
result = CURLE_UNKNOWN_OPTION;

View file

@ -1376,7 +1376,7 @@ static CURLcode tftp_setup_connection(struct connectdata * conn)
struct Curl_easy *data = conn->data;
char *type;
conn->socktype = SOCK_DGRAM; /* UDP datagram based */
conn->transport = TRNSPRT_UDP;
/* TFTP URLs support an extension like ";mode=<typecode>" that
* we'll try to get now! */

View file

@ -2110,7 +2110,7 @@ static CURLcode setup_connection_internals(struct connectdata *conn)
{
const struct Curl_handler * p;
CURLcode result;
conn->socktype = SOCK_STREAM; /* most of them are TCP streams */
conn->transport = TRNSPRT_TCP; /* most of them are TCP streams */
/* Perform setup complement if some. */
p = conn->handler;

View file

@ -129,6 +129,7 @@ typedef ssize_t (Curl_recv)(struct connectdata *conn, /* connection data */
#include "smb.h"
#include "wildcard.h"
#include "multihandle.h"
#include "quic.h"
#ifdef HAVE_GSSAPI
# ifdef HAVE_GSSGNU
@ -831,7 +832,15 @@ struct connectdata {
unsigned int scope_id; /* Scope id for IPv6 */
int socktype; /* SOCK_STREAM or SOCK_DGRAM */
enum {
TRNSPRT_TCP = 3,
TRNSPRT_UDP = 4,
TRNSPRT_QUIC = 5
} transport;
#ifdef ENABLE_QUIC
struct quicsocket quic;
#endif
struct hostname host;
char *hostname_resolve; /* host name to resolve to address, allocated */
@ -1672,6 +1681,7 @@ struct UserDefined {
CURLU *uh; /* URL handle for the current parsed URL */
void *trailer_data; /* pointer to pass to trailer data callback */
curl_trailer_callback trailer_callback; /* trailing data callback */
long h3opts; /* the CURLOPT_H3 bitmask */
bit is_fread_set:1; /* has read callback been set to non-NULL? */
bit is_fwrite_set:1; /* has write callback been set to non-NULL? */
bit free_referer:1; /* set TRUE if 'referer' points to a string we

View file

@ -27,6 +27,7 @@
#include "vtls/vtls.h"
#include "http2.h"
#include "ssh.h"
#include "quic.h"
#include "curl_printf.h"
#ifdef USE_ARES
@ -187,6 +188,11 @@ char *curl_version(void)
left -= len;
ptr += len;
#endif
#ifdef ENABLE_QUIC
len = Curl_quic_ver(ptr, left);
left -= len;
ptr += len;
#endif
#ifdef USE_LIBRTMP
{
char suff[2];
@ -358,6 +364,9 @@ static curl_version_info_data version_info = {
#if defined(USE_NGHTTP2)
| CURL_VERSION_HTTP2
#endif
#if defined(ENABLE_QUIC)
| CURL_VERSION_HTTP3
#endif
#if defined(USE_UNIX_SOCKETS)
| CURL_VERSION_UNIX_SOCKETS
#endif

520
lib/vquic/ngtcp2-crypto.c Normal file
View file

@ -0,0 +1,520 @@
/***************************************************************************
* _ _ ____ _
* 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/ssl.h>
#include <openssl/evp.h>
#include <openssl/kdf.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"
void Curl_qc_prf_sha256(struct Context *ctx)
{
ctx->prf = EVP_sha256();
}
void Curl_qc_aead_aes_128_gcm(struct Context *ctx)
{
ctx->aead = EVP_aes_128_gcm();
ctx->hp = EVP_aes_128_ctr();
}
size_t Curl_qc_aead_nonce_length(const struct Context *ctx)
{
return EVP_CIPHER_iv_length(ctx->aead);
}
int Curl_qc_negotiated_prf(struct Context *ctx, SSL *ssl)
{
switch(SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) {
case 0x03001301u: /* TLS_AES_128_GCM_SHA256 */
case 0x03001303u: /* TLS_CHACHA20_POLY1305_SHA256 */
ctx->prf = EVP_sha256();
return 0;
case 0x03001302u: /* TLS_AES_256_GCM_SHA384 */
ctx->prf = EVP_sha384();
return 0;
default:
return -1;
}
}
int Curl_qc_negotiated_aead(struct Context *ctx, SSL *ssl)
{
switch(SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) {
case 0x03001301u: /* TLS_AES_128_GCM_SHA256 */
ctx->aead = EVP_aes_128_gcm();
ctx->hp = EVP_aes_128_ctr();
return 0;
case 0x03001302u: /* TLS_AES_256_GCM_SHA384 */
ctx->aead = EVP_aes_256_gcm();
ctx->hp = EVP_aes_256_ctr();
return 0;
case 0x03001303u: /* TLS_CHACHA20_POLY1305_SHA256 */
ctx->aead = EVP_chacha20_poly1305();
ctx->hp = EVP_chacha20();
return 0;
default:
return -1;
}
}
ssize_t Curl_qc_encrypt_pn(uint8_t *dest, size_t destlen,
const uint8_t *plaintext, size_t plaintextlen,
const struct Context *ctx,
const uint8_t *key, size_t keylen,
const uint8_t *nonce, size_t noncelen)
{
EVP_CIPHER_CTX *actx = EVP_CIPHER_CTX_new();
size_t outlen = 0;
int len;
(void)destlen;
(void)keylen;
(void)noncelen;
if(!actx)
return -1;
if(EVP_EncryptInit_ex(actx, ctx->hp, NULL, key, nonce) != 1)
goto error;
if(EVP_EncryptUpdate(actx, dest, &len, plaintext, (int)plaintextlen) != 1)
goto error;
assert(len > 0);
outlen = len;
if(EVP_EncryptFinal_ex(actx, dest + outlen, &len) != 1)
goto error;
assert(len == 0);
/* outlen += len; */
EVP_CIPHER_CTX_free(actx);
return outlen;
error:
EVP_CIPHER_CTX_free(actx);
return -1;
}
static int hkdf_expand(uint8_t *dest, size_t destlen, const uint8_t *secret,
size_t secretlen, const uint8_t *info, size_t infolen,
const struct Context *ctx)
{
void *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
if(!pctx)
return -1;
if(EVP_PKEY_derive_init(pctx) != 1)
goto err;
if(EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) != 1)
goto err;
if(EVP_PKEY_CTX_set_hkdf_md(pctx, ctx->prf) != 1)
goto err;
if(EVP_PKEY_CTX_set1_hkdf_salt(pctx, "", 0) != 1)
goto err;
if(EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, (int)secretlen) != 1)
goto err;
if(EVP_PKEY_CTX_add1_hkdf_info(pctx, info, (int)infolen) != 1)
goto err;
if(EVP_PKEY_derive(pctx, dest, &destlen) != 1)
goto err;
return 0;
err:
EVP_PKEY_CTX_free(pctx);
return -1;
}
static int hkdf_extract(uint8_t *dest, size_t destlen,
const uint8_t *secret, size_t secretlen,
const uint8_t *salt, size_t saltlen,
const struct Context *ctx)
{
void *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
if(!pctx)
return -1;
if(EVP_PKEY_derive_init(pctx) != 1)
goto err;
if(EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) != 1) {
goto err;
}
if(EVP_PKEY_CTX_set_hkdf_md(pctx, ctx->prf) != 1) {
goto err;
}
if(EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, (int)saltlen) != 1) {
goto err;
}
if(EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, (int)secretlen) != 1) {
goto err;
}
if(EVP_PKEY_derive(pctx, dest, &destlen) != 1) {
goto err;
}
EVP_PKEY_CTX_free(pctx);
return 0;
err:
EVP_PKEY_CTX_free(pctx);
return -1;
}
static int qhkdf_expand(uint8_t *dest, size_t destlen,
const uint8_t *secret, size_t secretlen,
const uint8_t *qlabel, size_t qlabellen,
const struct Context *ctx)
{
uint8_t info[256];
static const char LABEL[] = "quic ";
uint8_t *p = &info[0];
*p++ = (destlen / 256) & 0xff;
*p++ = destlen % 256;
*p++ = (strlen(LABEL) + qlabellen) & 0xff;
memcpy(p, LABEL, strlen(LABEL));
p += strlen(LABEL);
memcpy(p, qlabel, qlabellen);
p += qlabellen;
*p++ = 0;
return hkdf_expand(dest, destlen, secret, secretlen, &info[0],
p - &info[0], ctx);
}
static size_t aead_key_length(const struct Context *ctx)
{
return EVP_CIPHER_key_length(ctx->aead);
}
static size_t aead_tag_length(const struct Context *ctx)
{
if(ctx->aead == EVP_aes_128_gcm() || ctx->aead == EVP_aes_256_gcm()) {
return EVP_GCM_TLS_TAG_LEN;
}
if(ctx->aead == EVP_chacha20_poly1305()) {
return EVP_CHACHAPOLY_TLS_TAG_LEN;
}
assert(0);
}
size_t Curl_qc_aead_max_overhead(const struct Context *ctx)
{
return aead_tag_length(ctx);
}
ssize_t Curl_qc_encrypt(uint8_t *dest, size_t destlen,
const uint8_t *plaintext, size_t plaintextlen,
const struct Context *ctx,
const uint8_t *key, size_t keylen,
const uint8_t *nonce, size_t noncelen,
const uint8_t *ad, size_t adlen)
{
size_t taglen = aead_tag_length(ctx);
EVP_CIPHER_CTX *actx;
size_t outlen = 0;
int len;
(void)keylen;
if(destlen < plaintextlen + taglen) {
return -1;
}
actx = EVP_CIPHER_CTX_new();
if(!actx)
return -1;
if(EVP_EncryptInit_ex(actx, ctx->aead, NULL, NULL, NULL) != 1)
goto error;
if(EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_IVLEN,
(int)noncelen, NULL) != 1)
goto error;
if(EVP_EncryptInit_ex(actx, NULL, NULL, key, nonce) != 1)
goto error;
if(EVP_EncryptUpdate(actx, NULL, &len, ad, (int)adlen) != 1)
goto error;
if(EVP_EncryptUpdate(actx, dest, &len, plaintext, (int)plaintextlen) != 1)
goto error;
outlen = len;
if(EVP_EncryptFinal_ex(actx, dest + outlen, &len) != 1)
goto error;
outlen += len;
assert(outlen + taglen <= destlen);
if(EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_GET_TAG,
(int)taglen, dest + outlen) != 1)
goto error;
outlen += taglen;
EVP_CIPHER_CTX_free(actx);
return outlen;
error:
EVP_CIPHER_CTX_free(actx);
return -1;
}
ssize_t Curl_qc_decrypt(uint8_t *dest, size_t destlen,
const uint8_t *ciphertext, size_t ciphertextlen,
const struct Context *ctx,
const uint8_t *key, size_t keylen,
const uint8_t *nonce, size_t noncelen,
const uint8_t *ad, size_t adlen)
{
size_t taglen = aead_tag_length(ctx);
const uint8_t *tag;
EVP_CIPHER_CTX *actx;
size_t outlen;
int len;
(void)keylen;
if(taglen > ciphertextlen || destlen + taglen < ciphertextlen) {
return -1;
}
ciphertextlen -= taglen;
tag = ciphertext + ciphertextlen;
actx = EVP_CIPHER_CTX_new();
if(!actx)
return -1;
if(EVP_DecryptInit_ex(actx, ctx->aead, NULL, NULL, NULL) != 1)
goto error;
if(EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_IVLEN, (int)noncelen, NULL) !=
1)
goto error;
if(EVP_DecryptInit_ex(actx, NULL, NULL, key, nonce) != 1)
goto error;
if(EVP_DecryptUpdate(actx, NULL, &len, ad, (int)adlen) != 1)
goto error;
if(EVP_DecryptUpdate(actx, dest, &len, ciphertext, (int)ciphertextlen) != 1)
goto error;
outlen = len;
if(EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_TAG,
(int)taglen, (char *)tag) != 1)
goto error;
if(EVP_DecryptFinal_ex(actx, dest + outlen, &len) != 1)
goto error;
outlen += len;
EVP_CIPHER_CTX_free(actx);
return outlen;
error:
EVP_CIPHER_CTX_free(actx);
return -1;
}
int Curl_qc_derive_initial_secret(uint8_t *dest, size_t destlen,
const ngtcp2_cid *secret,
const uint8_t *salt,
size_t saltlen)
{
struct Context ctx;
Curl_qc_prf_sha256(&ctx);
return hkdf_extract(dest, destlen, secret->data, secret->datalen, salt,
saltlen, &ctx);
}
int Curl_qc_derive_client_initial_secret(uint8_t *dest,
size_t destlen,
const uint8_t *secret,
size_t secretlen)
{
static uint8_t LABEL[] = "client in";
struct Context ctx;
Curl_qc_prf_sha256(&ctx);
return qhkdf_expand(dest, destlen, secret, secretlen, LABEL,
strlen((char *)LABEL), &ctx);
}
ssize_t Curl_qc_derive_packet_protection_key(uint8_t *dest, size_t destlen,
const uint8_t *secret,
size_t secretlen,
const struct Context *ctx)
{
int rv;
static uint8_t LABEL_KEY[] = "key";
size_t keylen = aead_key_length(ctx);
if(keylen > destlen) {
return -1;
}
rv = qhkdf_expand(dest, keylen, secret, secretlen, LABEL_KEY,
strlen((char *)LABEL_KEY), ctx);
if(rv) {
return -1;
}
return keylen;
}
ssize_t Curl_qc_derive_packet_protection_iv(uint8_t *dest, size_t destlen,
const uint8_t *secret,
size_t secretlen,
const struct Context *ctx)
{
int rv;
static uint8_t LABEL_IV[] = "iv";
size_t ivlen = CURLMAX(8, Curl_qc_aead_nonce_length(ctx));
if(ivlen > destlen) {
return -1;
}
rv = qhkdf_expand(dest, ivlen, secret, secretlen, LABEL_IV,
strlen((char *)LABEL_IV), ctx);
if(rv) {
return -1;
}
return ivlen;
}
int Curl_qc_derive_server_initial_secret(uint8_t *dest, size_t destlen,
const uint8_t *secret,
size_t secretlen)
{
static uint8_t LABEL[] = "server in";
struct Context ctx;
Curl_qc_prf_sha256(&ctx);
return qhkdf_expand(dest, destlen, secret, secretlen, LABEL,
strlen((char *)LABEL), &ctx);
}
static int
hkdf_expand_label(uint8_t *dest, size_t destlen, const uint8_t *secret,
size_t secretlen, const uint8_t *label, size_t labellen,
const struct Context *ctx)
{
uint8_t info[256];
static const uint8_t LABEL[] = "tls13 ";
uint8_t *p = &info[0];
*p++ = (destlen / 256)&0xff;
*p++ = destlen % 256;
*p++ = (strlen((char *)LABEL) + labellen)&0xff;
memcpy(p, LABEL, strlen((char *)LABEL));
p += strlen((char *)LABEL);
memcpy(p, label, labellen);
p += labellen;
*p++ = 0;
return hkdf_expand(dest, destlen, secret, secretlen, &info[0],
p - &info[0], ctx);
}
ssize_t
Curl_qc_derive_header_protection_key(uint8_t *dest, size_t destlen,
const uint8_t *secret, size_t secretlen,
const struct Context *ctx)
{
int rv;
static uint8_t LABEL[] = "quic hp";
size_t keylen = aead_key_length(ctx);
if(keylen > destlen)
return -1;
rv = hkdf_expand_label(dest, keylen, secret, secretlen, LABEL,
strlen((char *)LABEL), ctx);
if(rv)
return -1;
return keylen;
}
ssize_t Curl_qc_hp_mask(uint8_t *dest, size_t destlen,
const struct Context *ctx,
const uint8_t *key, size_t keylen,
const uint8_t *sample, size_t samplelen)
{
static uint8_t PLAINTEXT[] = "\x00\x00\x00\x00\x00";
EVP_CIPHER_CTX *actx;
size_t outlen = 0;
int len;
(void)destlen; /* TODO: make use of these! */
(void)keylen;
(void)samplelen;
actx = EVP_CIPHER_CTX_new();
if(!actx)
return -1;
if(EVP_EncryptInit_ex(actx, ctx->hp, NULL, key, sample) != 1)
goto error;
if(EVP_EncryptUpdate(actx, dest, &len, PLAINTEXT,
(int)strlen((char *)PLAINTEXT)) != 1)
goto error;
DEBUGASSERT(len == 5);
outlen = len;
if(EVP_EncryptFinal_ex(actx, dest + outlen, &len) != 1)
goto error;
DEBUGASSERT(len == 0);
return outlen;
error:
EVP_CIPHER_CTX_free(actx);
return -1;
}
#endif

93
lib/vquic/ngtcp2-crypto.h Normal file
View file

@ -0,0 +1,93 @@
#ifndef HEADER_CURL_VQUIC_NGTCP2_CRYPTO_H
#define HEADER_CURL_VQUIC_NGTCP2_CRYPTO_H
/***************************************************************************
* _ _ ____ _
* 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
struct Context {
#if defined(OPENSSL_IS_BORINGSSL)
const EVP_AEAD *aead;
#else /* !OPENSSL_IS_BORINGSSL */
const EVP_CIPHER *aead;
#endif /* !OPENSSL_IS_BORINGSSL */
const EVP_CIPHER *hp;
const EVP_MD *prf;
uint8_t tx_secret[64];
uint8_t rx_secret[64];
size_t secretlen;
};
void Curl_qc_prf_sha256(struct Context *ctx);
void Curl_qc_aead_aes_128_gcm(struct Context *ctx);
size_t Curl_qc_aead_nonce_length(const struct Context *ctx);
int Curl_qc_negotiated_prf(struct Context *ctx, SSL *ssl);
int Curl_qc_negotiated_aead(struct Context *ctx, SSL *ssl);
size_t Curl_qc_aead_max_overhead(const struct Context *ctx);
ssize_t Curl_qc_encrypt(uint8_t *dest, size_t destlen,
const uint8_t *plaintext, size_t plaintextlen,
const struct Context *ctx,
const uint8_t *key, size_t keylen,
const uint8_t *nonce, size_t noncelen,
const uint8_t *ad, size_t adlen);
ssize_t Curl_qc_decrypt(uint8_t *dest, size_t destlen,
const uint8_t *ciphertext, size_t ciphertextlen,
const struct Context *ctx,
const uint8_t *key, size_t keylen,
const uint8_t *nonce, size_t noncelen,
const uint8_t *ad, size_t adlen);
ssize_t Curl_qc_encrypt_pn(uint8_t *dest, size_t destlen,
const uint8_t *plaintext, size_t plaintextlen,
const struct Context *ctx,
const uint8_t *key, size_t keylen,
const uint8_t *nonce, size_t noncelen);
int Curl_qc_derive_initial_secret(uint8_t *dest, size_t destlen,
const ngtcp2_cid *secret,
const uint8_t *salt,
size_t saltlen);
int Curl_qc_derive_client_initial_secret(uint8_t *dest,
size_t destlen,
const uint8_t *secret,
size_t secretlen);
ssize_t Curl_qc_derive_packet_protection_key(uint8_t *dest, size_t destlen,
const uint8_t *secret,
size_t secretlen,
const struct Context *ctx);
ssize_t Curl_qc_derive_packet_protection_iv(uint8_t *dest, size_t destlen,
const uint8_t *secret,
size_t secretlen,
const struct Context *ctx);
int Curl_qc_derive_server_initial_secret(uint8_t *dest, size_t destlen,
const uint8_t *secret,
size_t secretlen);
ssize_t
Curl_qc_derive_header_protection_key(uint8_t *dest, size_t destlen,
const uint8_t *secret, size_t secretlen,
const struct Context *ctx);
ssize_t Curl_qc_hp_mask(uint8_t *dest, size_t destlen,
const struct Context *ctx,
const uint8_t *key, size_t keylen,
const uint8_t *sample, size_t samplelen);
#endif /* USE_NGTCP2 */
#endif /* HEADER_CURL_VQUIC_NGTCP2_CRYPTO_H */

1029
lib/vquic/ngtcp2.c Normal file

File diff suppressed because it is too large Load diff

65
lib/vquic/ngtcp2.h Normal file
View file

@ -0,0 +1,65 @@
#ifndef HEADER_CURL_VQUIC_NGTCP2_H
#define HEADER_CURL_VQUIC_NGTCP2_H
/***************************************************************************
* _ _ ____ _
* 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/ssl.h>
#include "ngtcp2-crypto.h"
struct quic_handshake {
char *buf; /* pointer to the buffer */
size_t alloclen; /* size of allocation */
size_t len; /* size of content in buffer */
size_t nread; /* how many bytes have been read */
};
struct quicsocket {
ngtcp2_conn *conn;
ngtcp2_cid dcid;
ngtcp2_cid scid;
uint32_t version;
ngtcp2_conn_callbacks callbacks;
ngtcp2_settings settings;
SSL_CTX *sslctx;
SSL *ssl;
struct Context crypto_ctx;
struct Context hs_crypto_ctx;
struct quic_handshake handshake;
/* the last TLS alert description generated by the local endpoint */
uint8_t tls_alert;
};
#include "urldata.h"
CURLcode Curl_quic_connect(struct connectdata *conn,
curl_socket_t sockfd,
const struct sockaddr *addr,
socklen_t addrlen);
int Curl_quic_ver(char *p, size_t len);
#endif
#endif /* HEADER_CURL_VQUIC_NGTCP2_H */

229
lib/vquic/quiche.c Normal file
View file

@ -0,0 +1,229 @@
/***************************************************************************
* _ _ ____ _
* 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_QUICHE
#include <quiche.h>
#include <openssl/err.h>
#include "urldata.h"
#include "sendf.h"
#include "strdup.h"
#include "rand.h"
#include "quic.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 * 1000 /* milliseconds */
static CURLcode process_ingress(struct connectdata *conn,
curl_socket_t sockfd);
static CURLcode flush_egress(struct connectdata *conn, curl_socket_t sockfd);
static Curl_recv quic_stream_recv;
static Curl_send quic_stream_send;
CURLcode Curl_quic_connect(struct connectdata *conn, curl_socket_t sockfd,
const struct sockaddr *addr, socklen_t addrlen)
{
CURLcode result;
struct quicsocket *qs = &conn->quic;
(void)addr;
(void)addrlen;
infof(conn->data, "Connecting socket %d over QUIC\n", sockfd);
qs->cfg = quiche_config_new(QUICHE_PROTOCOL_VERSION);
if(!qs->cfg)
return CURLE_FAILED_INIT; /* TODO: better return code */
quiche_config_set_idle_timeout(qs->cfg, QUIC_IDLE_TIMEOUT);
quiche_config_set_initial_max_data(qs->cfg, QUIC_MAX_DATA);
quiche_config_set_initial_max_stream_data_bidi_local(qs->cfg, QUIC_MAX_DATA);
quiche_config_set_initial_max_stream_data_bidi_remote(qs->cfg, QUIC_MAX_DATA);
quiche_config_set_initial_max_stream_data_uni(qs->cfg, QUIC_MAX_DATA);
quiche_config_set_initial_max_streams_bidi(qs->cfg, QUIC_MAX_STREAMS);
quiche_config_set_initial_max_streams_uni(qs->cfg, QUIC_MAX_STREAMS);
quiche_config_set_application_protos(qs->cfg, (uint8_t *) "\x05hq-20", 6);
result = Curl_rand(conn->data, qs->scid, sizeof(qs->scid));
if(result)
return result;
qs->conn = quiche_connect(conn->host.name, (const uint8_t *) qs->scid,
sizeof(qs->scid), qs->cfg);
if(!qs->conn)
return CURLE_FAILED_INIT; /* TODO: better return code */
result = flush_egress(conn, sockfd);
if(result)
return CURLE_FAILED_INIT; /* TODO: better return code */
infof(conn->data, "Sent QUIC client Initial\n");
return CURLE_OK;
}
CURLcode Curl_quic_is_connected(struct connectdata *conn, int sockindex,
bool *done)
{
CURLcode result;
struct quicsocket *qs = &conn->quic;
curl_socket_t sockfd = conn->sock[sockindex];
result = process_ingress(conn, sockfd);
if(result)
return result;
result = flush_egress(conn, sockfd);
if(result)
return result;
if(quiche_conn_is_established(qs->conn)) {
conn->recv[sockindex] = quic_stream_recv;
conn->send[sockindex] = quic_stream_send;
*done = TRUE;
}
return CURLE_OK;
}
static CURLcode process_ingress(struct connectdata *conn, int sockfd)
{
ssize_t recvd;
struct quicsocket *qs = &conn->quic;
static uint8_t buf[65535];
do {
recvd = recv(sockfd, buf, sizeof(buf), 0);
if((recvd < 0) && ((errno == EAGAIN) || (errno == EWOULDBLOCK)))
break;
if(recvd < 0)
return CURLE_RECV_ERROR;
recvd = quiche_conn_recv(qs->conn, buf, recvd);
if(recvd == QUICHE_ERR_DONE)
break;
if(recvd < 0)
return CURLE_RECV_ERROR;
} while(1);
return CURLE_OK;
}
static CURLcode flush_egress(struct connectdata *conn, int sockfd)
{
ssize_t sent;
struct quicsocket *qs = &conn->quic;
static uint8_t out[1200];
do {
sent = quiche_conn_send(qs->conn, out, sizeof(out));
if(sent == QUICHE_ERR_DONE)
break;
if(sent < 0)
return CURLE_SEND_ERROR;
sent = send(sockfd, out, sent, 0);
if(sent < 0)
return CURLE_SEND_ERROR;
} while(1);
return CURLE_OK;
}
static ssize_t quic_stream_recv(struct connectdata *conn,
int sockindex,
char *buf,
size_t buffersize,
CURLcode *curlcode)
{
bool fin;
ssize_t recvd;
struct quicsocket *qs = &conn->quic;
curl_socket_t sockfd = conn->sock[sockindex];
if(process_ingress(conn, sockfd)) {
*curlcode = CURLE_RECV_ERROR;
return -1;
}
recvd = quiche_conn_stream_recv(qs->conn, 0, (uint8_t *) buf, buffersize, &fin);
if(recvd == QUICHE_ERR_DONE) {
*curlcode = CURLE_AGAIN;
return -1;
}
if(recvd < 0) {
*curlcode = CURLE_RECV_ERROR;
return -1;
}
*curlcode = CURLE_OK;
return recvd;
}
static ssize_t quic_stream_send(struct connectdata *conn,
int sockindex,
const void *mem,
size_t len,
CURLcode *curlcode)
{
ssize_t sent;
struct quicsocket *qs = &conn->quic;
curl_socket_t sockfd = conn->sock[sockindex];
sent = quiche_conn_stream_send(qs->conn, 0, mem, len, true);
if(sent < 0) {
*curlcode = CURLE_SEND_ERROR;
return -1;
}
if(flush_egress(conn, sockfd)) {
*curlcode = CURLE_SEND_ERROR;
return -1;
}
*curlcode = CURLE_OK;
return sent;
}
/*
* Store quiche 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, " quiche");
}
#endif

47
lib/vquic/quiche.h Normal file
View file

@ -0,0 +1,47 @@
#ifndef HEADER_CURL_VQUIC_QUICHE_H
#define HEADER_CURL_VQUIC_QUICHE_H
/***************************************************************************
* _ _ ____ _
* 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_QUICHE
#include <quiche.h>
struct quic_handshake {
char *buf; /* pointer to the buffer */
size_t alloclen; /* size of allocation */
size_t len; /* size of content in buffer */
size_t nread; /* how many bytes have been read */
};
struct quicsocket {
quiche_config *cfg;
quiche_conn *conn;
uint8_t scid[QUICHE_MAX_CONN_ID_LEN];
uint32_t version;
};
#endif
#endif /* HEADER_CURL_VQUIC_QUICHE_H */