From 0736ee73d346a521ad10c9bcce5839386a1cc47a Mon Sep 17 00:00:00 2001 From: Daiki Ueno Date: Wed, 25 Mar 2020 22:49:02 +0100 Subject: vquic: add support for GnuTLS backend of ngtcp2 Currently, the TLS backend used by vquic/ngtcp2.c is selected at compile time. Therefore OpenSSL support needs to be explicitly disabled. Signed-off-by: Daiki Ueno Closes #5148 --- configure.ac | 57 ++++++++- docs/HTTP3.md | 42 ++++++- lib/vquic/ngtcp2.c | 332 ++++++++++++++++++++++++++++++++++++++++++++++++----- lib/vquic/ngtcp2.h | 9 ++ 4 files changed, 412 insertions(+), 28 deletions(-) diff --git a/configure.ac b/configure.ac index 90a678ec8..889617ffa 100755 --- a/configure.ac +++ b/configure.ac @@ -3528,7 +3528,7 @@ if test X"$want_tcp2" != Xno; then fi -if test "x$NGTCP2_ENABLED" = "x1"; then +if test "x$NGTCP2_ENABLED" = "x1" -a "x$OPENSSL_ENABLED" = "x1"; then dnl backup the pre-ngtcp2_crypto_openssl variables CLEANLDFLAGS="$LDFLAGS" CLEANCPPFLAGS="$CPPFLAGS" @@ -3583,6 +3583,61 @@ if test "x$NGTCP2_ENABLED" = "x1"; then fi fi +if test "x$NGTCP2_ENABLED" = "x1" -a "x$GNUTLS_ENABLED" = "x1"; then + dnl backup the pre-ngtcp2_crypto_gnutls variables + CLEANLDFLAGS="$LDFLAGS" + CLEANCPPFLAGS="$CPPFLAGS" + CLEANLIBS="$LIBS" + + CURL_CHECK_PKGCONFIG(libngtcp2_crypto_gnutls, $want_tcp2_path) + + if test "$PKGCONFIG" != "no" ; then + LIB_NGTCP2_CRYPTO_GNUTLS=`CURL_EXPORT_PCDIR([$want_tcp2_path]) + $PKGCONFIG --libs-only-l libngtcp2_crypto_gnutls` + AC_MSG_NOTICE([-l is $LIB_NGTCP2_CRYPTO_GNUTLS]) + + CPP_NGTCP2_CRYPTO_GNUTLS=`CURL_EXPORT_PCDIR([$want_tcp2_path]) dnl + $PKGCONFIG --cflags-only-I libngtcp2_crypto_gnutls` + AC_MSG_NOTICE([-I is $CPP_NGTCP2_CRYPTO_GNUTLS]) + + LD_NGTCP2_CRYPTO_GNUTLS=`CURL_EXPORT_PCDIR([$want_tcp2_path]) + $PKGCONFIG --libs-only-L libngtcp2_crypto_gnutls` + AC_MSG_NOTICE([-L is $LD_NGTCP2_CRYPTO_GNUTLS]) + + LDFLAGS="$LDFLAGS $LD_NGTCP2_CRYPTO_GNUTLS" + CPPFLAGS="$CPPFLAGS $CPP_NGTCP2_CRYPTO_GNUTLS" + LIBS="$LIB_NGTCP2_CRYPTO_GNUTLS $LIBS" + + if test "x$cross_compiling" != "xyes"; then + DIR_NGTCP2_CRYPTO_GNUTLS=`echo $LD_NGTCP2_CRYPTO_GNUTLS | $SED -e 's/-L//'` + fi + AC_CHECK_LIB(ngtcp2_crypto_gnutls, ngtcp2_crypto_ctx_initial, + [ + AC_CHECK_HEADERS(ngtcp2/ngtcp2_crypto.h, + NGTCP2_ENABLED=1 + AC_DEFINE(USE_NGTCP2_CRYPTO_GNUTLS, 1, [if ngtcp2_crypto_gnutls is in use]) + AC_SUBST(USE_NGTCP2_CRYPTO_GNUTLS, [1]) + CURL_LIBRARY_PATH="$CURL_LIBRARY_PATH:$DIR_NGTCP2_CRYPTO_GNUTLS" + export CURL_LIBRARY_PATH + AC_MSG_NOTICE([Added $DIR_NGTCP2_CRYPTO_GNUTLS to CURL_LIBRARY_PATH]) + ) + ], + dnl not found, revert back to clean variables + LDFLAGS=$CLEANLDFLAGS + CPPFLAGS=$CLEANCPPFLAGS + LIBS=$CLEANLIBS + ) + + else + dnl no ngtcp2_crypto_gnutls pkg-config found, deal with it + if test X"$want_tcp2" != Xdefault; then + dnl To avoid link errors, we do not allow --with-ngtcp2 without + dnl a pkgconfig file + AC_MSG_ERROR([--with-ngtcp2 was specified but could not find ngtcp2_crypto_gnutls pkg-config file.]) + fi + fi +fi + dnl ********************************************************************** dnl Check for nghttp3 (HTTP/3 with ngtcp2) dnl ********************************************************************** diff --git a/docs/HTTP3.md b/docs/HTTP3.md index 55bdd0263..2769439d6 100644 --- a/docs/HTTP3.md +++ b/docs/HTTP3.md @@ -29,7 +29,7 @@ in the master branch using pull-requests, just like ordinary changes. # ngtcp2 version -## Build +## Build with OpenSSL Build (patched) OpenSSL @@ -68,6 +68,46 @@ Build curl % LDFLAGS="-Wl,-rpath,/lib" ./configure --with-ssl= --with-nghttp3= --with-ngtcp2= --enable-alt-svc % make +## Build with GnuTLS + +Build (patched) GnuTLS + + % git clone --depth 1 -b tmp-quic https://gitlab.com/gnutls/gnutls.git + % cd gnutls + % ./bootstrap + % ./configure --disable-doc --prefix= + % make + % make install + +Build nghttp3 + + % cd .. + % git clone https://github.com/ngtcp2/nghttp3 + % cd nghttp3 + % autoreconf -i + % ./configure --prefix= --enable-lib-only + % make + % make install + +Build ngtcp2 + + % cd .. + % git clone https://github.com/ngtcp2/ngtcp2 + % cd ngtcp2 + % autoreconf -i + % ./configure PKG_CONFIG_PATH=/lib/pkgconfig:/lib/pkgconfig LDFLAGS="-Wl,-rpath,/lib" --prefix= + % make + % make install + +Build curl + + % cd .. + % git clone https://github.com/curl/curl + % cd curl + % ./buildconf + % ./configure --without-ssl --with-gnutls= --with-nghttp3= --with-ngtcp2= --enable-alt-svc + % make + # quiche version ## build diff --git a/lib/vquic/ngtcp2.c b/lib/vquic/ngtcp2.c index 22aa53b29..5f7b6e2e0 100644 --- a/lib/vquic/ngtcp2.c +++ b/lib/vquic/ngtcp2.c @@ -26,7 +26,9 @@ #include #include #include +#ifdef USE_OPENSSL #include +#endif #include "urldata.h" #include "sendf.h" #include "strdup.h" @@ -69,10 +71,18 @@ struct h3out { #define QUIC_MAX_STREAMS (256*1024) #define QUIC_MAX_DATA (1*1024*1024) #define QUIC_IDLE_TIMEOUT 60000 /* milliseconds */ + +#ifdef USE_OPENSSL #define QUIC_CIPHERS \ "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_" \ "POLY1305_SHA256:TLS_AES_128_CCM_SHA256" #define QUIC_GROUPS "P-256:X25519:P-384:P-521" +#elif defined(USE_GNUTLS) +#define QUIC_PRIORITY \ + "NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:" \ + "+CHACHA20-POLY1305:+AES-128-CCM:-GROUP-ALL:+GROUP-SECP256R1:" \ + "+GROUP-X25519:+GROUP-SECP384R1:+GROUP-SECP521R1" +#endif static CURLcode ng_process_ingress(struct connectdata *conn, curl_socket_t sockfd, @@ -101,6 +111,7 @@ static void quic_printf(void *user_data, const char *fmt, ...) } #endif +#ifdef USE_OPENSSL static ngtcp2_crypto_level quic_from_ossl_level(OSSL_ENCRYPTION_LEVEL ossl_level) { @@ -117,6 +128,24 @@ quic_from_ossl_level(OSSL_ENCRYPTION_LEVEL ossl_level) assert(0); } } +#elif defined(USE_GNUTLS) +static ngtcp2_crypto_level +quic_from_gtls_level(gnutls_record_encryption_level_t gtls_level) +{ + switch(gtls_level) { + case GNUTLS_ENCRYPTION_LEVEL_INITIAL: + return NGTCP2_CRYPTO_LEVEL_INITIAL; + case GNUTLS_ENCRYPTION_LEVEL_EARLY: + return NGTCP2_CRYPTO_LEVEL_EARLY; + case GNUTLS_ENCRYPTION_LEVEL_HANDSHAKE: + return NGTCP2_CRYPTO_LEVEL_HANDSHAKE; + case GNUTLS_ENCRYPTION_LEVEL_APPLICATION: + return NGTCP2_CRYPTO_LEVEL_APP; + default: + assert(0); + } +} +#endif static int setup_initial_crypto_context(struct quicsocket *qs) { @@ -150,6 +179,7 @@ static void quic_settings(ngtcp2_settings *s, } static FILE *keylog_file; /* not thread-safe */ +#ifdef USE_OPENSSL static void keylog_callback(const SSL *ssl, const char *line) { (void)ssl; @@ -157,41 +187,52 @@ static void keylog_callback(const SSL *ssl, const char *line) fputc('\n', keylog_file); fflush(keylog_file); } - -static int init_ngh3_conn(struct quicsocket *qs); - -static int quic_set_encryption_secrets(SSL *ssl, - OSSL_ENCRYPTION_LEVEL ossl_level, - const uint8_t *rx_secret, - const uint8_t *tx_secret, - size_t secretlen) +#elif defined(USE_GNUTLS) +static int keylog_callback(gnutls_session_t session, const char *label, + const gnutls_datum_t *secret) { - struct quicsocket *qs = (struct quicsocket *)SSL_get_app_data(ssl); - int level = quic_from_ossl_level(ossl_level); - - if(level != NGTCP2_CRYPTO_LEVEL_EARLY && - ngtcp2_crypto_derive_and_install_rx_key( - qs->qconn, ssl, NULL, NULL, NULL, level, rx_secret, secretlen) != 0) - return 0; + gnutls_datum_t crandom; + gnutls_datum_t srandom; + gnutls_datum_t crandom_hex = { NULL, 0 }; + gnutls_datum_t secret_hex = { NULL, 0 }; + int rc = 0; + + gnutls_session_get_random(session, &crandom, &srandom); + if(crandom.size != 32) { + return -1; + } - if(ngtcp2_crypto_derive_and_install_tx_key( - qs->qconn, ssl, NULL, NULL, NULL, level, tx_secret, secretlen) != 0) - return 0; + rc = gnutls_hex_encode2(&crandom, &crandom_hex); + if(rc < 0) { + fprintf(stderr, "gnutls_hex_encode2 failed: %s\n", + gnutls_strerror(rc)); + goto out; + } - if(level == NGTCP2_CRYPTO_LEVEL_APP) { - if(init_ngh3_conn(qs) != CURLE_OK) - return 0; + rc = gnutls_hex_encode2(secret, &secret_hex); + if(rc < 0) { + fprintf(stderr, "gnutls_hex_encode2 failed: %s\n", + gnutls_strerror(rc)); + goto out; } - return 1; + fprintf(keylog_file, "%s %s %s\n", label, crandom_hex.data, secret_hex.data); + fflush(keylog_file); + + out: + gnutls_free(crandom_hex.data); + gnutls_free(secret_hex.data); + return rc; } +#endif -static int quic_add_handshake_data(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level, - const uint8_t *data, size_t len) +static int init_ngh3_conn(struct quicsocket *qs); + +static int write_client_handshake(struct quicsocket *qs, + ngtcp2_crypto_level level, + const uint8_t *data, size_t len) { - struct quicsocket *qs = (struct quicsocket *)SSL_get_app_data(ssl); struct quic_handshake *crypto_data; - ngtcp2_crypto_level level = quic_from_ossl_level(ossl_level); int rv; crypto_data = &qs->crypto_data[level]; @@ -220,6 +261,42 @@ static int quic_add_handshake_data(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level, return 1; } +#ifdef USE_OPENSSL +static int quic_set_encryption_secrets(SSL *ssl, + OSSL_ENCRYPTION_LEVEL ossl_level, + const uint8_t *rx_secret, + const uint8_t *tx_secret, + size_t secretlen) +{ + struct quicsocket *qs = (struct quicsocket *)SSL_get_app_data(ssl); + int level = quic_from_ossl_level(ossl_level); + + if(level != NGTCP2_CRYPTO_LEVEL_EARLY && + ngtcp2_crypto_derive_and_install_rx_key( + qs->qconn, ssl, NULL, NULL, NULL, level, rx_secret, secretlen) != 0) + return 0; + + if(ngtcp2_crypto_derive_and_install_tx_key( + qs->qconn, ssl, NULL, NULL, NULL, level, tx_secret, secretlen) != 0) + return 0; + + if(level == NGTCP2_CRYPTO_LEVEL_APP) { + if(init_ngh3_conn(qs) != CURLE_OK) + return 0; + } + + return 1; +} + +static int quic_add_handshake_data(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level, + const uint8_t *data, size_t len) +{ + struct quicsocket *qs = (struct quicsocket *)SSL_get_app_data(ssl); + ngtcp2_crypto_level level = quic_from_ossl_level(ossl_level); + + return write_client_handshake(qs, level, data, len); +} + static int quic_flush_flight(SSL *ssl) { (void)ssl; @@ -307,6 +384,193 @@ static int quic_init_ssl(struct quicsocket *qs) SSL_set_tlsext_host_name(qs->ssl, hostname); return 0; } +#elif defined(USE_GNUTLS) +static int secret_func(gnutls_session_t ssl, + gnutls_record_encryption_level_t gtls_level, + const void *rx_secret, + const void *tx_secret, size_t secretlen) +{ + struct quicsocket *qs = gnutls_session_get_ptr(ssl); + int level = quic_from_gtls_level(gtls_level); + + if(level != NGTCP2_CRYPTO_LEVEL_EARLY && + ngtcp2_crypto_derive_and_install_rx_key( + qs->qconn, ssl, NULL, NULL, NULL, level, rx_secret, secretlen) != 0) + return 0; + + if(ngtcp2_crypto_derive_and_install_tx_key( + qs->qconn, ssl, NULL, NULL, NULL, level, tx_secret, secretlen) != 0) + return 0; + + if(level == NGTCP2_CRYPTO_LEVEL_APP) { + if(init_ngh3_conn(qs) != CURLE_OK) + return -1; + } + + return 0; +} + +static int read_func(gnutls_session_t ssl, + gnutls_record_encryption_level_t gtls_level, + gnutls_handshake_description_t htype, const void *data, + size_t len) +{ + struct quicsocket *qs = gnutls_session_get_ptr(ssl); + ngtcp2_crypto_level level = quic_from_gtls_level(gtls_level); + int rv; + + if(htype == GNUTLS_HANDSHAKE_CHANGE_CIPHER_SPEC) + return 0; + + rv = write_client_handshake(qs, level, data, len); + if(rv == 0) + return -1; + + return 0; +} + +static int alert_read_func(gnutls_session_t ssl, + gnutls_record_encryption_level_t gtls_level, + gnutls_alert_level_t alert_level, + gnutls_alert_description_t alert_desc) +{ + struct quicsocket *qs = gnutls_session_get_ptr(ssl); + (void)gtls_level; + (void)alert_level; + + qs->tls_alert = alert_desc; + return 1; +} + +static int tp_recv_func(gnutls_session_t ssl, const uint8_t *data, + size_t data_size) +{ + struct quicsocket *qs = gnutls_session_get_ptr(ssl); + ngtcp2_transport_params params; + + if(ngtcp2_decode_transport_params( + ¶ms, NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS, + data, data_size) != 0) + return -1; + + if(ngtcp2_conn_set_remote_transport_params(qs->qconn, ¶ms) != 0) + return -1; + + return 0; +} + +static int tp_send_func(gnutls_session_t ssl, gnutls_buffer_t extdata) +{ + struct quicsocket *qs = gnutls_session_get_ptr(ssl); + uint8_t paramsbuf[64]; + ngtcp2_transport_params params; + ssize_t nwrite; + int rc; + + ngtcp2_conn_get_local_transport_params(qs->qconn, ¶ms); + nwrite = ngtcp2_encode_transport_params( + paramsbuf, sizeof(paramsbuf), NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO, + ¶ms); + if(nwrite < 0) { + fprintf(stderr, "ngtcp2_encode_transport_params: %s\n", + ngtcp2_strerror((int)nwrite)); + return -1; + } + + rc = gnutls_buffer_append_data(extdata, paramsbuf, nwrite); + if(rc < 0) + return rc; + + return (int)nwrite; +} + +static int quic_init_ssl(struct quicsocket *qs) +{ + gnutls_datum_t alpn = {NULL, 0}; + /* this will need some attention when HTTPS proxy over QUIC get fixed */ + const char * const hostname = qs->conn->host.name; + const char *keylog_filename; + int rc; + + if(qs->ssl) + gnutls_deinit(qs->ssl); + + gnutls_init(&qs->ssl, GNUTLS_CLIENT); + gnutls_session_set_ptr(qs->ssl, qs); + + rc = gnutls_priority_set_direct(qs->ssl, QUIC_PRIORITY, NULL); + if(rc < 0) { + fprintf(stderr, "gnutls_priority_set_direct failed: %s\n", + gnutls_strerror(rc)); + return 1; + } + + gnutls_handshake_set_secret_function(qs->ssl, secret_func); + gnutls_handshake_set_read_function(qs->ssl, read_func); + gnutls_alert_set_read_function(qs->ssl, alert_read_func); + + rc = gnutls_session_ext_register(qs->ssl, "QUIC Transport Parameters", + 0xffa5, GNUTLS_EXT_TLS, + tp_recv_func, tp_send_func, + NULL, NULL, NULL, + GNUTLS_EXT_FLAG_TLS | + GNUTLS_EXT_FLAG_CLIENT_HELLO | + GNUTLS_EXT_FLAG_EE); + if(rc < 0) { + fprintf(stderr, "gnutls_session_ext_register failed: %s\n", + gnutls_strerror(rc)); + return 1; + } + + keylog_filename = getenv("SSLKEYLOGFILE"); + if(keylog_filename) { + keylog_file = fopen(keylog_filename, "wb"); + if(keylog_file) { + gnutls_session_set_keylog_function(qs->ssl, keylog_callback); + } + } + + if(qs->cred) + gnutls_certificate_free_credentials(qs->cred); + + rc = gnutls_certificate_allocate_credentials(&qs->cred); + if(rc < 0) { + fprintf(stderr, "gnutls_certificate_allocate_credentials failed: %s\n", + gnutls_strerror(rc)); + return 1; + } + + rc = gnutls_certificate_set_x509_system_trust(qs->cred); + if(rc < 0) { + fprintf(stderr, "gnutls_certificate_set_x509_system_trust failed: %s\n", + gnutls_strerror(rc)); + return 1; + } + + rc = gnutls_credentials_set(qs->ssl, GNUTLS_CRD_CERTIFICATE, qs->cred); + if(rc < 0) { + fprintf(stderr, "gnutls_credentials_set failed: %s\n", + gnutls_strerror(rc)); + return 1; + } + + switch(qs->version) { +#ifdef NGTCP2_PROTO_VER + case NGTCP2_PROTO_VER: + /* strip the first byte from NGTCP2_ALPN_H3 */ + alpn.data = (unsigned char *)NGTCP2_ALPN_H3 + 1; + alpn.size = sizeof(NGTCP2_ALPN_H3) - 2; + break; +#endif + } + if(alpn.data) + gnutls_alpn_set_protocols(qs->ssl, &alpn, 1, 0); + + /* set SNI */ + gnutls_server_name_set(qs->ssl, GNUTLS_NAME_DNS, hostname, strlen(hostname)); + return 0; +} +#endif static int cb_initial(ngtcp2_conn *quic, void *user_data) { @@ -560,9 +824,11 @@ CURLcode Curl_quic_connect(struct connectdata *conn, struct quicsocket *qs = &conn->hequic[sockindex]; char ipbuf[40]; long port; +#ifdef USE_OPENSSL uint8_t paramsbuf[64]; ngtcp2_transport_params params; ssize_t nwrite; +#endif qs->conn = conn; @@ -578,9 +844,11 @@ CURLcode Curl_quic_connect(struct connectdata *conn, sockfd, ipbuf, port); qs->version = NGTCP2_PROTO_VER; +#ifdef USE_OPENSSL qs->sslctx = quic_ssl_ctx(data); if(!qs->sslctx) return CURLE_QUIC_CONNECT_ERROR; +#endif if(quic_init_ssl(qs)) return CURLE_QUIC_CONNECT_ERROR; @@ -617,6 +885,7 @@ CURLcode Curl_quic_connect(struct connectdata *conn, if(rc) return CURLE_QUIC_CONNECT_ERROR; +#ifdef USE_OPENSSL ngtcp2_conn_get_local_transport_params(qs->qconn, ¶ms); nwrite = ngtcp2_encode_transport_params( paramsbuf, sizeof(paramsbuf), NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO, @@ -629,6 +898,7 @@ CURLcode Curl_quic_connect(struct connectdata *conn, if(!SSL_set_quic_transport_params(qs->ssl, paramsbuf, nwrite)) return CURLE_QUIC_CONNECT_ERROR; +#endif rc = setup_initial_crypto_context(qs); if(rc) @@ -680,12 +950,22 @@ static CURLcode ng_disconnect(struct connectdata *conn, struct quicsocket *qs = &conn->hequic[0]; (void)dead_connection; if(qs->ssl) +#ifdef USE_OPENSSL SSL_free(qs->ssl); +#elif defined(USE_GNUTLS) + gnutls_deinit(qs->ssl); +#endif +#ifdef USE_GNUTLS + if(qs->cred) + gnutls_certificate_free_credentials(qs->cred); +#endif for(i = 0; i < 3; i++) free(qs->crypto_data[i].buf); nghttp3_conn_del(qs->h3conn); ngtcp2_conn_del(qs->qconn); +#ifdef USE_OPENSSL SSL_CTX_free(qs->sslctx); +#endif return CURLE_OK; } diff --git a/lib/vquic/ngtcp2.h b/lib/vquic/ngtcp2.h index 30d442fdd..862a00107 100644 --- a/lib/vquic/ngtcp2.h +++ b/lib/vquic/ngtcp2.h @@ -28,7 +28,11 @@ #include #include +#ifdef USE_OPENSSL #include +#elif defined(USE_GNUTLS) +#include +#endif struct quic_handshake { char *buf; /* pointer to the buffer */ @@ -44,8 +48,13 @@ struct quicsocket { ngtcp2_cid scid; uint32_t version; ngtcp2_settings settings; +#ifdef USE_OPENSSL SSL_CTX *sslctx; SSL *ssl; +#elif defined(USE_GNUTLS) + gnutls_certificate_credentials_t cred; + gnutls_session_t ssl; +#endif struct quic_handshake crypto_data[3]; /* the last TLS alert description generated by the local endpoint */ uint8_t tls_alert; -- cgit v1.2.1