diff --git a/gennghttpxfun.py b/gennghttpxfun.py index 5aaf7483..170ee8c6 100755 --- a/gennghttpxfun.py +++ b/gennghttpxfun.py @@ -158,6 +158,8 @@ OPTIONS = [ "client-no-http2-cipher-black-list", "client-ciphers", "accesslog-write-early", + "tls-min-proto-version", + "tls-max-proto-version", ] LOGVARS = [ diff --git a/src/shrpx.cc b/src/shrpx.cc index c65a3d7c..87b4577a 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -1368,7 +1368,8 @@ constexpr auto DEFAULT_NPN_LIST = StringRef::from_lit("h2,h2-16,h2-14," } // namespace namespace { -constexpr auto DEFAULT_TLS_PROTO_LIST = StringRef::from_lit("TLSv1.2,TLSv1.1"); +constexpr auto DEFAULT_TLS_MIN_PROTO_VERSION = StringRef::from_lit("TLSv1.1"); +constexpr auto DEFAULT_TLS_MAX_PROTO_VERSION = StringRef::from_lit("TLSv1.2"); } // namespace namespace { @@ -1426,6 +1427,8 @@ void fill_default_config(Config *config) { tlsconf.ciphers = StringRef::from_lit(nghttp2::ssl::DEFAULT_CIPHER_LIST); tlsconf.client.ciphers = StringRef::from_lit(nghttp2::ssl::DEFAULT_CIPHER_LIST); + tlsconf.min_proto_version = TLS1_1_VERSION; + tlsconf.max_proto_version = TLS1_2_VERSION; #if OPENSSL_1_1_API tlsconf.ecdh_curves = StringRef::from_lit("X25519:P-256:P-384:P-521"); #else // !OPENSSL_1_1_API @@ -2054,18 +2057,26 @@ SSL/TLS: --client-cert-file= Path to file that contains client certificate used in backend client authentication. - --tls-proto-list= - Comma delimited list of SSL/TLS protocol to be enabled. - The following protocols are available: TLSv1.2, TLSv1.1 - and TLSv1.0. The name matching is done in - case-insensitive manner. The parameter must be - delimited by a single comma only and any white spaces - are treated as a part of protocol string. If the - protocol list advertised by client does not overlap this - list, you will receive the error message "unknown - protocol". + --tls-min-proto-version= + Specify minimum SSL/TLS protocol. The following + protocols are available: TLSv1.2, TLSv1.1 and TLSv1.0. + The name matching is done in case-insensitive manner. + The versions between --tls-min-proto-version and + --tls-max-proto-version are enabled. If the protocol + list advertised by client does not overlap this range, + you will receive the error message "unknown protocol". Default: )" - << DEFAULT_TLS_PROTO_LIST << R"( + << DEFAULT_TLS_MIN_PROTO_VERSION << R"( + --tls-max-proto-version= + Specify maximum SSL/TLS protocol. The following + protocols are available: TLSv1.2, TLSv1.1 and TLSv1.0. + The name matching is done in case-insensitive manner. + The versions between --tls-min-proto-version and + --tls-max-proto-version are enabled. If the protocol + list advertised by client does not overlap this range, + you will receive the error message "unknown protocol". + Default: )" + << DEFAULT_TLS_MAX_PROTO_VERSION << R"( --tls-ticket-key-file= Path to file that contains random data to construct TLS session ticket parameters. If aes-128-cbc is given in @@ -2717,11 +2728,18 @@ int process_options(Config *config, if (tlsconf.npn_list.empty()) { tlsconf.npn_list = util::split_str(DEFAULT_NPN_LIST, ','); } - if (tlsconf.tls_proto_list.empty()) { - tlsconf.tls_proto_list = util::split_str(DEFAULT_TLS_PROTO_LIST, ','); + + if (!tlsconf.tls_proto_list.empty()) { + tlsconf.tls_proto_mask = ssl::create_tls_proto_mask(tlsconf.tls_proto_list); } - tlsconf.tls_proto_mask = ssl::create_tls_proto_mask(tlsconf.tls_proto_list); + // TODO We depends on the ordering of protocol version macro in + // OpenSSL. + if (tlsconf.min_proto_version > tlsconf.max_proto_version) { + LOG(ERROR) << "tls-max-proto-version must be equal to or larger than " + "tls-min-proto-version"; + return -1; + } if (ssl::set_alpn_prefs(tlsconf.alpn_prefs, tlsconf.npn_list) != 0) { return -1; @@ -3216,6 +3234,10 @@ int main(int argc, char **argv) { &flag, 149}, {SHRPX_OPT_CLIENT_CIPHERS.c_str(), required_argument, &flag, 150}, {SHRPX_OPT_ACCESSLOG_WRITE_EARLY.c_str(), no_argument, &flag, 151}, + {SHRPX_OPT_TLS_MIN_PROTO_VERSION.c_str(), required_argument, &flag, + 152}, + {SHRPX_OPT_TLS_MAX_PROTO_VERSION.c_str(), required_argument, &flag, + 153}, {nullptr, 0, nullptr, 0}}; int option_index = 0; @@ -3928,6 +3950,16 @@ int main(int argc, char **argv) { cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_WRITE_EARLY, StringRef::from_lit("yes")); break; + case 152: + // --tls-min-proto-version + cmdcfgs.emplace_back(SHRPX_OPT_TLS_MIN_PROTO_VERSION, + StringRef{optarg}); + break; + case 153: + // --tls-max-proto-version + cmdcfgs.emplace_back(SHRPX_OPT_TLS_MAX_PROTO_VERSION, + StringRef{optarg}); + break; default: break; } diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index 60ac5f6d..f018be0e 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -638,6 +638,21 @@ int parse_duration(ev_tstamp *dest, const StringRef &opt, } } // namespace +namespace { +int parse_tls_proto_version(int &dest, const StringRef &opt, + const StringRef &optarg) { + auto v = ssl::proto_version_from_string(optarg); + if (v == -1) { + LOG(ERROR) << opt << ": invalid TLS protocol version: " << optarg; + return -1; + } + + dest = v; + + return 0; +} +} // namespace + struct MemcachedConnectionParams { bool tls; }; @@ -1786,6 +1801,14 @@ int option_lookup_token(const char *name, size_t namelen) { return SHRPX_OPTID_ACCEPT_PROXY_PROTOCOL; } break; + case 'n': + if (util::strieq_l("tls-max-proto-versio", name, 20)) { + return SHRPX_OPTID_TLS_MAX_PROTO_VERSION; + } + if (util::strieq_l("tls-min-proto-versio", name, 20)) { + return SHRPX_OPTID_TLS_MIN_PROTO_VERSION; + } + break; case 'r': if (util::strieq_l("tls-ticket-key-ciphe", name, 20)) { return SHRPX_OPTID_TLS_TICKET_KEY_CIPHER; @@ -2710,6 +2733,8 @@ int parse_config(Config *config, int optid, const StringRef &opt, return 0; } case SHRPX_OPTID_TLS_PROTO_LIST: { + LOG(WARN) << opt << ": deprecated. Use tls-min-proto-version and " + "tls-max-proto-version instead."; auto list = util::split_str(optarg, ','); config->tls.tls_proto_list.resize(list.size()); for (size_t i = 0; i < list.size(); ++i) { @@ -3327,6 +3352,10 @@ int parse_config(Config *config, int optid, const StringRef &opt, config->logging.access.write_early = util::strieq_l("yes", optarg); return 0; + case SHRPX_OPTID_TLS_MIN_PROTO_VERSION: + return parse_tls_proto_version(config->tls.min_proto_version, opt, optarg); + case SHRPX_OPTID_TLS_MAX_PROTO_VERSION: + return parse_tls_proto_version(config->tls.max_proto_version, opt, optarg); case SHRPX_OPTID_CONF: LOG(WARN) << "conf: ignored"; diff --git a/src/shrpx_config.h b/src/shrpx_config.h index 6d494131..3fba48de 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -327,6 +327,10 @@ constexpr auto SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLACK_LIST = constexpr auto SHRPX_OPT_CLIENT_CIPHERS = StringRef::from_lit("client-ciphers"); constexpr auto SHRPX_OPT_ACCESSLOG_WRITE_EARLY = StringRef::from_lit("accesslog-write-early"); +constexpr auto SHRPX_OPT_TLS_MIN_PROTO_VERSION = + StringRef::from_lit("tls-min-proto-version"); +constexpr auto SHRPX_OPT_TLS_MAX_PROTO_VERSION = + StringRef::from_lit("tls-max-proto-version"); constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8; @@ -594,6 +598,10 @@ struct TLSConfig { StringRef ciphers; StringRef ecdh_curves; StringRef cacert; + // The minimum and maximum TLS version. These values are defined in + // OpenSSL header file. + int min_proto_version; + int max_proto_version; bool insecure; bool no_http2_cipher_black_list; }; @@ -1020,6 +1028,8 @@ enum { SHRPX_OPTID_SYSLOG_FACILITY, SHRPX_OPTID_TLS_DYN_REC_IDLE_TIMEOUT, SHRPX_OPTID_TLS_DYN_REC_WARMUP_THRESHOLD, + SHRPX_OPTID_TLS_MAX_PROTO_VERSION, + SHRPX_OPTID_TLS_MIN_PROTO_VERSION, SHRPX_OPTID_TLS_PROTO_LIST, SHRPX_OPTID_TLS_SCT_DIR, SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED, diff --git a/src/shrpx_ssl.cc b/src/shrpx_ssl.cc index 25e13221..26379586 100644 --- a/src/shrpx_ssl.cc +++ b/src/shrpx_ssl.cc @@ -639,6 +639,55 @@ long int create_tls_proto_mask(const std::vector &tls_proto_list) { return res; } +#if defined(OPENSSL_IS_BORINGSSL) +namespace { +int ssl_ctx_set_proto_versions(SSL_CTX *ssl_ctx, int min, int max) { + SSL_CTX_set_min_version(ssl_ctx, min); + SSL_CTX_set_max_version(ssl_ctx, max); + return 0; +} +} // namespace +#elif OPENSSL_1_1_API +namespace { +int ssl_ctx_set_proto_versions(SSL_CTX *ssl_ctx, int min, int max) { + if (SSL_CTX_set_min_proto_version(ssl_ctx, min) != 1 || + SSL_CTX_set_max_proto_version(ssl_ctx, max) != 1) { + return -1; + } + return 0; +} +} // namespace +#else // !OPENSSL_1_1_API +namespace { +int ssl_ctx_set_proto_versions(SSL_CTX *ssl_ctx, int min, int max) { + long int opts = 0; + + // TODO We depends on the ordering of protocol version macro in + // OpenSSL. + if (min > TLS1_VERSION) { + opts |= SSL_OP_NO_TLSv1; + } + if (min > TLS1_1_VERSION) { + opts |= SSL_OP_NO_TLSv1_1; + } + if (min > TLS1_2_VERSION) { + opts |= SSL_OP_NO_TLSv1_2; + } + + if (max < TLS1_2_VERSION) { + opts |= SSL_OP_NO_TLSv1_2; + } + if (max < TLS1_1_VERSION) { + opts |= SSL_OP_NO_TLSv1_1; + } + + SSL_CTX_set_options(ssl_ctx, opts); + + return 0; +} +} // namespace +#endif // !OPENSSL_1_1_API + SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file, const std::vector &sct_data #ifdef HAVE_NEVERBLEED @@ -663,6 +712,13 @@ SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file, SSL_CTX_set_options(ssl_ctx, ssl_opts | tlsconf.tls_proto_mask); + if (ssl_ctx_set_proto_versions(ssl_ctx, tlsconf.min_proto_version, + tlsconf.max_proto_version) != 0) { + LOG(FATAL) << "Could not set TLS protocol version: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + const unsigned char sid_ctx[] = "shrpx"; SSL_CTX_set_session_id_context(ssl_ctx, sid_ctx, sizeof(sid_ctx) - 1); SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_SERVER); @@ -897,6 +953,13 @@ SSL_CTX *create_ssl_client_context( SSL_CTX_set_options(ssl_ctx, ssl_opts | tlsconf.tls_proto_mask); + if (ssl_ctx_set_proto_versions(ssl_ctx, tlsconf.min_proto_version, + tlsconf.max_proto_version) != 0) { + LOG(FATAL) << "Could not set TLS protocol version: " + << ERR_error_string(ERR_get_error(), nullptr); + DIE(); + } + if (SSL_CTX_set_cipher_list(ssl_ctx, tlsconf.client.ciphers.c_str()) == 0) { LOG(FATAL) << "SSL_CTX_set_cipher_list " << tlsconf.client.ciphers << " failed: " << ERR_error_string(ERR_get_error(), nullptr); @@ -1678,6 +1741,19 @@ SSL_SESSION *reuse_tls_session(const TLSSessionCache &cache) { return d2i_SSL_SESSION(nullptr, &p, cache.session_data.size()); } +int proto_version_from_string(const StringRef &v) { + if (util::strieq_l("TLSv1.2", v)) { + return TLS1_2_VERSION; + } + if (util::strieq_l("TLSv1.1", v)) { + return TLS1_1_VERSION; + } + if (util::strieq_l("TLSv1.0", v)) { + return TLS1_VERSION; + } + return -1; +} + } // namespace ssl } // namespace shrpx diff --git a/src/shrpx_ssl.h b/src/shrpx_ssl.h index 11865019..db472f04 100644 --- a/src/shrpx_ssl.h +++ b/src/shrpx_ssl.h @@ -258,6 +258,11 @@ SSL_SESSION *reuse_tls_session(const TLSSessionCache &addr); // the returned object using X509_free(). X509 *load_certificate(const char *filename); +// Returns TLS version from |v|. The returned value is defined in +// OpenSSL header file. This function returns -1 if |v| is not valid +// TLS version string. +int proto_version_from_string(const StringRef &v); + } // namespace ssl } // namespace shrpx