diff --git a/gennghttpxfun.py b/gennghttpxfun.py index 005f2875..d92026ed 100755 --- a/gennghttpxfun.py +++ b/gennghttpxfun.py @@ -109,7 +109,8 @@ OPTIONS = [ "forwarded-by", "forwarded-for", "response-header-field-buffer", - "max-response-header-fields" + "max-response-header-fields", + "no-http2-cipher-black-list" ] LOGVARS = [ diff --git a/src/shrpx.cc b/src/shrpx.cc index fe141ef2..48f6f687 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -1571,6 +1571,10 @@ SSL/TLS: TLS HTTP/2 backends. Default: )" << util::duration_str(get_config()->tls.dyn_rec.idle_timeout) << R"( + --no-http2-cipher-black-list + Allow black listed cipher suite on HTTP/2 connection. + See https://tools.ietf.org/html/rfc7540#appendix-A for + the complete HTTP/2 cipher suites black list. HTTP/2 and SPDY: -c, --http2-max-concurrent-streams= @@ -2367,6 +2371,7 @@ int main(int argc, char **argv) { {SHRPX_OPT_FORWARDED_FOR, required_argument, &flag, 100}, {SHRPX_OPT_RESPONSE_HEADER_FIELD_BUFFER, required_argument, &flag, 101}, {SHRPX_OPT_MAX_RESPONSE_HEADER_FIELDS, required_argument, &flag, 102}, + {SHRPX_OPT_NO_HTTP2_CIPHER_BLACK_LIST, no_argument, &flag, 103}, {nullptr, 0, nullptr, 0}}; int option_index = 0; @@ -2804,6 +2809,10 @@ int main(int argc, char **argv) { // --max-response-header-fields cmdcfgs.emplace_back(SHRPX_OPT_MAX_RESPONSE_HEADER_FIELDS, optarg); break; + case 103: + // --no-http2-cipher-black-list + cmdcfgs.emplace_back(SHRPX_OPT_NO_HTTP2_CIPHER_BLACK_LIST, "yes"); + break; default: break; } diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index a99e5eac..0f3e8824 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -729,6 +729,7 @@ enum { SHRPX_OPTID_MAX_RESPONSE_HEADER_FIELDS, SHRPX_OPTID_MRUBY_FILE, SHRPX_OPTID_NO_HOST_REWRITE, + SHRPX_OPTID_NO_HTTP2_CIPHER_BLACK_LIST, SHRPX_OPTID_NO_LOCATION_REWRITE, SHRPX_OPTID_NO_OCSP, SHRPX_OPTID_NO_SERVER_PUSH, @@ -1268,6 +1269,9 @@ int option_lookup_token(const char *name, size_t namelen) { if (util::strieq_l("backend-keep-alive-timeou", name, 25)) { return SHRPX_OPTID_BACKEND_KEEP_ALIVE_TIMEOUT; } + if (util::strieq_l("no-http2-cipher-black-lis", name, 25)) { + return SHRPX_OPTID_NO_HTTP2_CIPHER_BLACK_LIST; + } break; } break; @@ -2188,6 +2192,10 @@ int parse_config(const char *opt, const char *optarg, return 0; } + case SHRPX_OPTID_NO_HTTP2_CIPHER_BLACK_LIST: + mod_config()->tls.no_http2_cipher_black_list = util::strieq(optarg, "yes"); + + return 0; case SHRPX_OPTID_CONF: LOG(WARN) << "conf: ignored"; diff --git a/src/shrpx_config.h b/src/shrpx_config.h index 134b2518..f485ae6f 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -200,6 +200,8 @@ constexpr char SHRPX_OPT_RESPONSE_HEADER_FIELD_BUFFER[] = "response-header-field-buffer"; constexpr char SHRPX_OPT_MAX_RESPONSE_HEADER_FIELDS[] = "max-response-header-fields"; +constexpr char SHRPX_OPT_NO_HTTP2_CIPHER_BLACK_LIST[] = + "no-http2-cipher-black-list"; constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8; @@ -396,6 +398,7 @@ struct TLSConfig { std::unique_ptr ciphers; std::unique_ptr cacert; bool insecure; + bool no_http2_cipher_black_list; }; struct HttpConfig { diff --git a/src/shrpx_connection.cc b/src/shrpx_connection.cc index 9a1b6aca..6bbaab3b 100644 --- a/src/shrpx_connection.cc +++ b/src/shrpx_connection.cc @@ -488,10 +488,17 @@ int Connection::check_http2_requirement() { !util::check_h2_is_selected(next_proto, next_proto_len)) { return 0; } - if (!nghttp2::ssl::check_http2_requirement(tls.ssl)) { + if (!nghttp2::ssl::check_http2_tls_version(tls.ssl)) { if (LOG_ENABLED(INFO)) { - LOG(INFO) << "TLSv1.2 and/or black listed cipher suite was negotiated. " - "HTTP/2 must not be used."; + LOG(INFO) << "TLSv1.2 was not negotiated. HTTP/2 must not be used."; + } + return -1; + } + if (!get_config()->tls.no_http2_cipher_black_list && + nghttp2::ssl::check_http2_cipher_black_list(tls.ssl)) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "The negotiated cipher suite is in HTTP/2 cipher suite " + "black list. HTTP/2 must not be used."; } return -1; } diff --git a/src/ssl.cc b/src/ssl.cc index d5bb3562..078c5ec4 100644 --- a/src/ssl.cc +++ b/src/ssl.cc @@ -397,19 +397,10 @@ enum { TLS_PSK_WITH_AES_256_CCM_8 = 0xC0A9u, }; -bool check_http2_requirement(SSL *ssl) { - auto tls_ver = SSL_version(ssl); - - switch (tls_ver) { - case TLS1_2_VERSION: - break; - default: - return false; - } - +bool check_http2_cipher_black_list(SSL *ssl) { auto cipher = SSL_get_current_cipher(ssl); - // Cipher suites in RFC 7540 balck list are not allowed in HTTP/2. + // Cipher suites in RFC 7540 black list are not allowed in HTTP/2. switch (SSL_CIPHER_get_id(cipher) & 0xffffu) { case TLS_NULL_WITH_NULL_NULL: case TLS_RSA_WITH_NULL_MD5: @@ -687,10 +678,20 @@ bool check_http2_requirement(SSL *ssl) { case TLS_PSK_WITH_AES_256_CCM: case TLS_PSK_WITH_AES_128_CCM_8: case TLS_PSK_WITH_AES_256_CCM_8: - return false; + return true; } - return true; + return false; +} + +bool check_http2_tls_version(SSL *ssl) { + auto tls_ver = SSL_version(ssl); + + return tls_ver == TLS1_2_VERSION; +} + +bool check_http2_requirement(SSL *ssl) { + return check_http2_tls_version(ssl) && !check_http2_cipher_black_list(ssl); } void libssl_init() { diff --git a/src/ssl.h b/src/ssl.h index d73f543d..61263593 100644 --- a/src/ssl.h +++ b/src/ssl.h @@ -59,6 +59,13 @@ struct TLSSessionInfo { TLSSessionInfo *get_tls_session_info(TLSSessionInfo *tls_info, SSL *ssl); +// Returns true iff the negotiated protocol is TLSv1.2. +bool check_http2_tls_version(SSL *ssl); + +// Returns true iff the negotiated cipher suite is in HTTP/2 cipher +// black list. +bool check_http2_cipher_black_list(SSL *ssl); + // Returns true if SSL/TLS requirement for HTTP/2 is fulfilled. // To fulfill the requirement, the following 2 terms must be hold: //