nghttpx: Specify TLS protocol by version range

This commit deprecates --tls-proto-list option, and adds 2 new
options: --tls-min-proto-version and --tls-max-proto-version to
specify minimum and maximum protocol version respectively.  Versions
between the two are enabled.  The deprecated --tls-proto-list has
empty default value, and acts like enabling only specific protocol
versions in the range for now.
This commit is contained in:
Tatsuhiro Tsujikawa 2017-02-13 23:34:45 +09:00
parent 001d45efad
commit b36e53cccd
6 changed files with 169 additions and 15 deletions

View File

@ -158,6 +158,8 @@ OPTIONS = [
"client-no-http2-cipher-black-list", "client-no-http2-cipher-black-list",
"client-ciphers", "client-ciphers",
"accesslog-write-early", "accesslog-write-early",
"tls-min-proto-version",
"tls-max-proto-version",
] ]
LOGVARS = [ LOGVARS = [

View File

@ -1368,7 +1368,8 @@ constexpr auto DEFAULT_NPN_LIST = StringRef::from_lit("h2,h2-16,h2-14,"
} // namespace } // namespace
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
namespace { namespace {
@ -1426,6 +1427,8 @@ void fill_default_config(Config *config) {
tlsconf.ciphers = StringRef::from_lit(nghttp2::ssl::DEFAULT_CIPHER_LIST); tlsconf.ciphers = StringRef::from_lit(nghttp2::ssl::DEFAULT_CIPHER_LIST);
tlsconf.client.ciphers = tlsconf.client.ciphers =
StringRef::from_lit(nghttp2::ssl::DEFAULT_CIPHER_LIST); 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 #if OPENSSL_1_1_API
tlsconf.ecdh_curves = StringRef::from_lit("X25519:P-256:P-384:P-521"); tlsconf.ecdh_curves = StringRef::from_lit("X25519:P-256:P-384:P-521");
#else // !OPENSSL_1_1_API #else // !OPENSSL_1_1_API
@ -2054,18 +2057,26 @@ SSL/TLS:
--client-cert-file=<PATH> --client-cert-file=<PATH>
Path to file that contains client certificate used in Path to file that contains client certificate used in
backend client authentication. backend client authentication.
--tls-proto-list=<LIST> --tls-min-proto-version=<VER>
Comma delimited list of SSL/TLS protocol to be enabled. Specify minimum SSL/TLS protocol. The following
The following protocols are available: TLSv1.2, TLSv1.1 protocols are available: TLSv1.2, TLSv1.1 and TLSv1.0.
and TLSv1.0. The name matching is done in The name matching is done in case-insensitive manner.
case-insensitive manner. The parameter must be The versions between --tls-min-proto-version and
delimited by a single comma only and any white spaces --tls-max-proto-version are enabled. If the protocol
are treated as a part of protocol string. If the list advertised by client does not overlap this range,
protocol list advertised by client does not overlap this you will receive the error message "unknown protocol".
list, you will receive the error message "unknown
protocol".
Default: )" Default: )"
<< DEFAULT_TLS_PROTO_LIST << R"( << DEFAULT_TLS_MIN_PROTO_VERSION << R"(
--tls-max-proto-version=<VER>
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> --tls-ticket-key-file=<PATH>
Path to file that contains random data to construct TLS Path to file that contains random data to construct TLS
session ticket parameters. If aes-128-cbc is given in session ticket parameters. If aes-128-cbc is given in
@ -2717,11 +2728,18 @@ int process_options(Config *config,
if (tlsconf.npn_list.empty()) { if (tlsconf.npn_list.empty()) {
tlsconf.npn_list = util::split_str(DEFAULT_NPN_LIST, ','); 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) { if (ssl::set_alpn_prefs(tlsconf.alpn_prefs, tlsconf.npn_list) != 0) {
return -1; return -1;
@ -3216,6 +3234,10 @@ int main(int argc, char **argv) {
&flag, 149}, &flag, 149},
{SHRPX_OPT_CLIENT_CIPHERS.c_str(), required_argument, &flag, 150}, {SHRPX_OPT_CLIENT_CIPHERS.c_str(), required_argument, &flag, 150},
{SHRPX_OPT_ACCESSLOG_WRITE_EARLY.c_str(), no_argument, &flag, 151}, {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}}; {nullptr, 0, nullptr, 0}};
int option_index = 0; int option_index = 0;
@ -3928,6 +3950,16 @@ int main(int argc, char **argv) {
cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_WRITE_EARLY, cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_WRITE_EARLY,
StringRef::from_lit("yes")); StringRef::from_lit("yes"));
break; 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: default:
break; break;
} }

View File

@ -638,6 +638,21 @@ int parse_duration(ev_tstamp *dest, const StringRef &opt,
} }
} // namespace } // 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 { struct MemcachedConnectionParams {
bool tls; bool tls;
}; };
@ -1786,6 +1801,14 @@ int option_lookup_token(const char *name, size_t namelen) {
return SHRPX_OPTID_ACCEPT_PROXY_PROTOCOL; return SHRPX_OPTID_ACCEPT_PROXY_PROTOCOL;
} }
break; 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': case 'r':
if (util::strieq_l("tls-ticket-key-ciphe", name, 20)) { if (util::strieq_l("tls-ticket-key-ciphe", name, 20)) {
return SHRPX_OPTID_TLS_TICKET_KEY_CIPHER; return SHRPX_OPTID_TLS_TICKET_KEY_CIPHER;
@ -2710,6 +2733,8 @@ int parse_config(Config *config, int optid, const StringRef &opt,
return 0; return 0;
} }
case SHRPX_OPTID_TLS_PROTO_LIST: { 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, ','); auto list = util::split_str(optarg, ',');
config->tls.tls_proto_list.resize(list.size()); config->tls.tls_proto_list.resize(list.size());
for (size_t i = 0; i < list.size(); ++i) { 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); config->logging.access.write_early = util::strieq_l("yes", optarg);
return 0; 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: case SHRPX_OPTID_CONF:
LOG(WARN) << "conf: ignored"; LOG(WARN) << "conf: ignored";

View File

@ -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_CLIENT_CIPHERS = StringRef::from_lit("client-ciphers");
constexpr auto SHRPX_OPT_ACCESSLOG_WRITE_EARLY = constexpr auto SHRPX_OPT_ACCESSLOG_WRITE_EARLY =
StringRef::from_lit("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; constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8;
@ -594,6 +598,10 @@ struct TLSConfig {
StringRef ciphers; StringRef ciphers;
StringRef ecdh_curves; StringRef ecdh_curves;
StringRef cacert; 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 insecure;
bool no_http2_cipher_black_list; bool no_http2_cipher_black_list;
}; };
@ -1020,6 +1028,8 @@ enum {
SHRPX_OPTID_SYSLOG_FACILITY, SHRPX_OPTID_SYSLOG_FACILITY,
SHRPX_OPTID_TLS_DYN_REC_IDLE_TIMEOUT, SHRPX_OPTID_TLS_DYN_REC_IDLE_TIMEOUT,
SHRPX_OPTID_TLS_DYN_REC_WARMUP_THRESHOLD, 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_PROTO_LIST,
SHRPX_OPTID_TLS_SCT_DIR, SHRPX_OPTID_TLS_SCT_DIR,
SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED, SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED,

View File

@ -639,6 +639,55 @@ long int create_tls_proto_mask(const std::vector<StringRef> &tls_proto_list) {
return res; 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, SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file,
const std::vector<uint8_t> &sct_data const std::vector<uint8_t> &sct_data
#ifdef HAVE_NEVERBLEED #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); 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"; const unsigned char sid_ctx[] = "shrpx";
SSL_CTX_set_session_id_context(ssl_ctx, sid_ctx, sizeof(sid_ctx) - 1); 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); 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); 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) { if (SSL_CTX_set_cipher_list(ssl_ctx, tlsconf.client.ciphers.c_str()) == 0) {
LOG(FATAL) << "SSL_CTX_set_cipher_list " << tlsconf.client.ciphers LOG(FATAL) << "SSL_CTX_set_cipher_list " << tlsconf.client.ciphers
<< " failed: " << ERR_error_string(ERR_get_error(), nullptr); << " 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()); 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 ssl
} // namespace shrpx } // namespace shrpx

View File

@ -258,6 +258,11 @@ SSL_SESSION *reuse_tls_session(const TLSSessionCache &addr);
// the returned object using X509_free(). // the returned object using X509_free().
X509 *load_certificate(const char *filename); 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 ssl
} // namespace shrpx } // namespace shrpx