aboutsummaryrefslogtreecommitdiff
path: root/lib/vquic
diff options
context:
space:
mode:
authorDaniel Stenberg <daniel@haxx.se>2019-07-21 23:48:58 +0200
committerDaniel Stenberg <daniel@haxx.se>2019-07-21 23:49:03 +0200
commit3af0e76d1e71995b7790c74e79b76af86ee7c681 (patch)
treeb80190acaf03d83f5f408cc6da3ec1a9f831d8d3 /lib/vquic
parent7644abf8e8101910ed86ab2869b7cc4031b27720 (diff)
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
Diffstat (limited to 'lib/vquic')
-rw-r--r--lib/vquic/ngtcp2-crypto.c520
-rw-r--r--lib/vquic/ngtcp2-crypto.h93
-rw-r--r--lib/vquic/ngtcp2.c1029
-rw-r--r--lib/vquic/ngtcp2.h65
-rw-r--r--lib/vquic/quiche.c229
-rw-r--r--lib/vquic/quiche.h47
6 files changed, 1983 insertions, 0 deletions
diff --git a/lib/vquic/ngtcp2-crypto.c b/lib/vquic/ngtcp2-crypto.c
new file mode 100644
index 000000000..dc060c91b
--- /dev/null
+++ b/lib/vquic/ngtcp2-crypto.c
@@ -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
diff --git a/lib/vquic/ngtcp2-crypto.h b/lib/vquic/ngtcp2-crypto.h
new file mode 100644
index 000000000..f91b4e0ea
--- /dev/null
+++ b/lib/vquic/ngtcp2-crypto.h
@@ -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 */
diff --git a/lib/vquic/ngtcp2.c b/lib/vquic/ngtcp2.c
new file mode 100644
index 000000000..a8c563ce1
--- /dev/null
+++ b/lib/vquic/ngtcp2.c
@@ -0,0 +1,1029 @@
+/***************************************************************************
+ * _ _ ____ _
+ * 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, &params, 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, &params);
+ 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(
+ &params, 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,
+ &params);
+ 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
diff --git a/lib/vquic/ngtcp2.h b/lib/vquic/ngtcp2.h
new file mode 100644
index 000000000..8342c2c9b
--- /dev/null
+++ b/lib/vquic/ngtcp2.h
@@ -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 */
diff --git a/lib/vquic/quiche.c b/lib/vquic/quiche.c
new file mode 100644
index 000000000..f6c4ad42c
--- /dev/null
+++ b/lib/vquic/quiche.c
@@ -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
diff --git a/lib/vquic/quiche.h b/lib/vquic/quiche.h
new file mode 100644
index 000000000..cf5432962
--- /dev/null
+++ b/lib/vquic/quiche.h
@@ -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 */