From d70eb14ce0cd3ab8353ae206e554c604d0d99661 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Mon, 31 Aug 2015 23:30:40 +0900 Subject: [PATCH] nghttpx: Drop connection before TLS finish if h2 requirement is not fulfilled --- src/shrpx_client_handler.cc | 170 +++++++++++++++++------------------- src/shrpx_connection.cc | 84 ++++++++++++++---- src/shrpx_connection.h | 3 + 3 files changed, 151 insertions(+), 106 deletions(-) diff --git a/src/shrpx_client_handler.cc b/src/shrpx_client_handler.cc index b75c66a0..37293a5e 100644 --- a/src/shrpx_client_handler.cc +++ b/src/shrpx_client_handler.cc @@ -452,108 +452,96 @@ void ClientHandler::reset_upstream_write_timeout(ev_tstamp t) { int ClientHandler::validate_next_proto() { const unsigned char *next_proto = nullptr; unsigned int next_proto_len; - int rv; // First set callback for catch all cases on_read_ = &ClientHandler::upstream_read; SSL_get0_next_proto_negotiated(conn_.tls.ssl, &next_proto, &next_proto_len); - for (int i = 0; i < 2; ++i) { - if (next_proto) { - if (LOG_ENABLED(INFO)) { - std::string proto(next_proto, next_proto + next_proto_len); - CLOG(INFO, this) << "The negotiated next protocol: " << proto; - } - if (!ssl::in_proto_list(get_config()->npn_list, next_proto, - next_proto_len)) { - break; - } - if (util::check_h2_is_selected(next_proto, next_proto_len)) { - - on_read_ = &ClientHandler::upstream_http2_connhd_read; - - auto http2_upstream = make_unique(this); - - if (!nghttp2::ssl::check_http2_requirement(conn_.tls.ssl)) { - if (LOG_ENABLED(INFO)) { - LOG(INFO) << "TLSv1.2 was not negotiated. " - << "HTTP/2 must not be negotiated."; - } - - rv = http2_upstream->terminate_session(NGHTTP2_INADEQUATE_SECURITY); - - if (rv != 0) { - return -1; - } - } - - upstream_ = std::move(http2_upstream); - alpn_.assign(next_proto, next_proto + next_proto_len); - - // At this point, input buffer is already filled with some - // bytes. The read callback is not called until new data - // come. So consume input buffer here. - if (on_read() != 0) { - return -1; - } - - return 0; - } else { -#ifdef HAVE_SPDYLAY - uint16_t version = spdylay_npn_get_version(next_proto, next_proto_len); - if (version) { - upstream_ = make_unique(version, this); - - switch (version) { - case SPDYLAY_PROTO_SPDY2: - alpn_ = "spdy/2"; - break; - case SPDYLAY_PROTO_SPDY3: - alpn_ = "spdy/3"; - break; - case SPDYLAY_PROTO_SPDY3_1: - alpn_ = "spdy/3.1"; - break; - default: - alpn_ = "spdy/unknown"; - } - - // At this point, input buffer is already filled with some - // bytes. The read callback is not called until new data - // come. So consume input buffer here. - if (on_read() != 0) { - return -1; - } - - return 0; - } -#endif // HAVE_SPDYLAY - if (next_proto_len == 8 && memcmp("http/1.1", next_proto, 8) == 0) { - upstream_ = make_unique(this); - alpn_ = "http/1.1"; - - // At this point, input buffer is already filled with some - // bytes. The read callback is not called until new data - // come. So consume input buffer here. - if (on_read() != 0) { - return -1; - } - - return 0; - } - } - break; - } #if OPENSSL_VERSION_NUMBER >= 0x10002000L + if (next_proto == nullptr) { SSL_get0_alpn_selected(conn_.tls.ssl, &next_proto, &next_proto_len); -#else // OPENSSL_VERSION_NUMBER < 0x10002000L - break; -#endif // OPENSSL_VERSION_NUMBER < 0x10002000L } - if (!next_proto) { +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + + if (next_proto == nullptr) { if (LOG_ENABLED(INFO)) { CLOG(INFO, this) << "No protocol negotiated. Fallback to HTTP/1.1"; } + + upstream_ = make_unique(this); + alpn_ = "http/1.1"; + + // At this point, input buffer is already filled with some bytes. + // The read callback is not called until new data come. So consume + // input buffer here. + if (on_read() != 0) { + return -1; + } + + return 0; + } + + if (LOG_ENABLED(INFO)) { + std::string proto(next_proto, next_proto + next_proto_len); + CLOG(INFO, this) << "The negotiated next protocol: " << proto; + } + + if (!ssl::in_proto_list(get_config()->npn_list, next_proto, next_proto_len)) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, this) << "The negotiated protocol is not supported"; + } + return -1; + } + + if (util::check_h2_is_selected(next_proto, next_proto_len)) { + on_read_ = &ClientHandler::upstream_http2_connhd_read; + + auto http2_upstream = make_unique(this); + + upstream_ = std::move(http2_upstream); + alpn_.assign(next_proto, next_proto + next_proto_len); + + // At this point, input buffer is already filled with some bytes. + // The read callback is not called until new data come. So consume + // input buffer here. + if (on_read() != 0) { + return -1; + } + + return 0; + } + +#ifdef HAVE_SPDYLAY + auto spdy_version = spdylay_npn_get_version(next_proto, next_proto_len); + if (spdy_version) { + upstream_ = make_unique(spdy_version, this); + + switch (spdy_version) { + case SPDYLAY_PROTO_SPDY2: + alpn_ = "spdy/2"; + break; + case SPDYLAY_PROTO_SPDY3: + alpn_ = "spdy/3"; + break; + case SPDYLAY_PROTO_SPDY3_1: + alpn_ = "spdy/3.1"; + break; + default: + alpn_ = "spdy/unknown"; + } + + // At this point, input buffer is already filled with some bytes. + // The read callback is not called until new data come. So consume + // input buffer here. + if (on_read() != 0) { + return -1; + } + + return 0; + } +#endif // HAVE_SPDYLAY + + if (next_proto_len == 8 && memcmp("http/1.1", next_proto, 8) == 0) { upstream_ = make_unique(this); alpn_ = "http/1.1"; diff --git a/src/shrpx_connection.cc b/src/shrpx_connection.cc index e694fb58..aee91b8d 100644 --- a/src/shrpx_connection.cc +++ b/src/shrpx_connection.cc @@ -35,6 +35,7 @@ #include "shrpx_ssl.h" #include "shrpx_memcached_request.h" #include "memchunk.h" +#include "util.h" using namespace nghttp2; @@ -145,20 +146,7 @@ int shrpx_bio_write(BIO *b, const char *buf, int len) { if (conn->tls.initial_handshake_done) { // After handshake finished, send |buf| of length |len| to the // socket directly. - if (wbuf.rleft()) { - std::array iov; - auto iovcnt = wbuf.riovec(iov.data(), iov.size()); - auto nwrite = conn->writev_clear(iov.data(), iovcnt); - if (nwrite < 0) { - return -1; - } - - wbuf.drain(nwrite); - if (wbuf.rleft()) { - BIO_set_retry_write(b); - return -1; - } - } + assert(wbuf.rleft() == 0); auto nwrite = conn->write_clear(buf, len); if (nwrite < 0) { return -1; @@ -300,6 +288,10 @@ int Connection::tls_handshake() { } } + if (tls.initial_handshake_done) { + return write_tls_pending_handshake(); + } + switch (tls.handshake_state) { case TLS_CONN_WAIT_FOR_SESSION_CACHE: return SHRPX_ERR_INPROGRESS; @@ -365,7 +357,10 @@ int Connection::tls_handshake() { return SHRPX_ERR_INPROGRESS; } - if (tls.wbuf.rleft()) { + // Don't send handshake data if handshake was completed in OpenSSL + // routine. We have to check HTTP/2 requirement if HTTP/2 was + // negotiated before sending finished message to the peer. + if (rv != 1 && tls.wbuf.rleft()) { // First write indicates that resumption stuff has done. if (tls.handshake_state != TLS_CONN_WRITE_STARTED) { tls.handshake_state = TLS_CONN_WRITE_STARTED; @@ -401,8 +396,42 @@ int Connection::tls_handshake() { return SHRPX_ERR_INPROGRESS; } + // Handshake was done + + rv = check_http2_requirement(); + if (rv != 0) { + return -1; + } + + // Just in case + tls.rbuf.disable_peek(true); + tls.initial_handshake_done = true; + return write_tls_pending_handshake(); +} + +int Connection::write_tls_pending_handshake() { + // Send handshake data left in the buffer + while (tls.wbuf.rleft()) { + std::array iov; + auto iovcnt = tls.wbuf.riovec(iov.data(), iov.size()); + auto nwrite = writev_clear(iov.data(), iovcnt); + if (nwrite < 0) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "tls: handshake write error"; + } + return -1; + } + if (nwrite == 0) { + wlimit.startw(); + ev_timer_again(loop, &wt); + + return SHRPX_ERR_INPROGRESS; + } + tls.wbuf.drain(nwrite); + } + // We have to start read watcher, since later stage of code expects // this. rlimit.startw(); @@ -422,6 +451,31 @@ int Connection::tls_handshake() { return 0; } +int Connection::check_http2_requirement() { + const unsigned char *next_proto = nullptr; + unsigned int next_proto_len; + + SSL_get0_next_proto_negotiated(tls.ssl, &next_proto, &next_proto_len); +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + if (next_proto == nullptr) { + SSL_get0_alpn_selected(tls.ssl, &next_proto, &next_proto_len); + } +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + if (next_proto == nullptr || + !util::check_h2_is_selected(next_proto, next_proto_len)) { + return 0; + } + if (!nghttp2::ssl::check_http2_requirement(tls.ssl)) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "TLSv1.2 and/or black listed cipher suite was negotiated. " + "HTTP/2 must not be used."; + } + return -1; + } + + return 0; +} + namespace { const size_t SHRPX_SMALL_WRITE_LIMIT = 1300; const size_t SHRPX_WARMUP_THRESHOLD = 1 << 20; diff --git a/src/shrpx_connection.h b/src/shrpx_connection.h index 1d6a01c5..03e1ece8 100644 --- a/src/shrpx_connection.h +++ b/src/shrpx_connection.h @@ -84,6 +84,9 @@ struct Connection { void prepare_server_handshake(); int tls_handshake(); + int write_tls_pending_handshake(); + + int check_http2_requirement(); // All write_* and writev_clear functions return number of bytes // written. If nothing cannot be written (e.g., there is no