diff options
author | Kevin Lin <developer@kevinlin.info> | 2021-05-31 13:33:48 -0700 |
---|---|---|
committer | dormando <dormando@rydia.net> | 2021-09-27 12:54:20 -0700 |
commit | 3a8ca319b35a3b3533b1f1ca55e904cfcb71962c (patch) | |
tree | 6a61bc3c9481e8fcbc4d6e3314613cccb63c06cc | |
parent | f8a55c4731ab38b8c1a88cb7bf10fadc209fd78f (diff) | |
download | memcached-3a8ca319b35a3b3533b1f1ca55e904cfcb71962c.tar.gz |
Configurable minimum supported TLS protocol version
`-o ssl_min_version` can be used to configure the server to only accept
handshakes from clients with a minimum TLS protocol version. Currently
supported options are TLS v1.0, TLS v1.1, TLS v1.2, and TLS v1.3
(OpenSSL 1.1.1+ only).
-rw-r--r-- | doc/protocol.txt | 2 | ||||
-rw-r--r-- | memcached.c | 50 | ||||
-rw-r--r-- | memcached.h | 1 | ||||
-rw-r--r-- | t/ssl_proto_version.t | 45 | ||||
-rw-r--r-- | t/ssl_settings.t | 3 | ||||
-rw-r--r-- | tls.c | 24 | ||||
-rw-r--r-- | tls.h | 1 |
7 files changed, 120 insertions, 6 deletions
diff --git a/doc/protocol.txt b/doc/protocol.txt index b34690b..70341a5 100644 --- a/doc/protocol.txt +++ b/doc/protocol.txt @@ -1672,6 +1672,8 @@ following additional statistics are available via the "stats" command. | ssl_handshake_errors | 64u | Number of times the server has | | | | encountered an OpenSSL error | | | | during handshake (SSL_accept). | +| ssl_min_version | char | Minimum supported TLS version | +| | | for client handshakes. | | ssl_new_sessions | 64u | When SSL session caching is | | | | enabled, the number of newly | | | | created server-side sessions. | diff --git a/memcached.c b/memcached.c index e3bf3c3..c03631f 100644 --- a/memcached.c +++ b/memcached.c @@ -233,6 +233,7 @@ static void settings_init(void) { settings.ssl_last_cert_refresh_time = current_time; settings.ssl_wbuf_size = 16 * 1024; // default is 16KB (SSL max frame size is 17KB) settings.ssl_session_cache = false; + settings.ssl_min_version = TLS1_2_VERSION; #endif /* By default this string should be NULL for getaddrinfo() */ settings.inter = NULL; @@ -1937,6 +1938,7 @@ void process_stat_settings(ADD_STAT add_stats, void *c) { APPEND_STAT("ssl_ca_cert", "%s", settings.ssl_ca_cert ? settings.ssl_ca_cert : "NULL"); APPEND_STAT("ssl_wbuf_size", "%u", settings.ssl_wbuf_size); APPEND_STAT("ssl_session_cache", "%s", settings.ssl_session_cache ? "yes" : "no"); + APPEND_STAT("ssl_min_version", "%s", ssl_proto_text(settings.ssl_min_version)); #endif APPEND_STAT("num_napi_ids", "%s", settings.num_napi_ids); APPEND_STAT("memory_file", "%s", settings.memory_file); @@ -4008,9 +4010,22 @@ static void usage(void) { " - ssl_wbuf_size: size in kilobytes of per-connection SSL output buffer\n" " (default: %u)\n", settings.ssl_wbuf_size / (1 << 10)); printf(" - ssl_session_cache: enable server-side SSL session cache, to support session\n" - " resumption\n"); + " resumption\n" + " - ssl_min_version: minimum protocol version to accept (default: %s)\n" +#if OPENSSL_VERSION_NUMBER >= 0x10101000L + " valid values are 0(%s), 1(%s), 2(%s), or 3(%s).\n", + ssl_proto_text(settings.ssl_min_version), + ssl_proto_text(TLS1_VERSION), ssl_proto_text(TLS1_1_VERSION), + ssl_proto_text(TLS1_2_VERSION), ssl_proto_text(TLS1_3_VERSION)); +#else + " valid values are 0(%s), 1(%s), or 2(%s).\n", + ssl_proto_text(settings.ssl_min_version), + ssl_proto_text(TLS1_VERSION), ssl_proto_text(TLS1_1_VERSION), + ssl_proto_text(TLS1_2_VERSION)); +#endif verify_default("ssl_keyformat", settings.ssl_keyformat == SSL_FILETYPE_PEM); verify_default("ssl_verify_mode", settings.ssl_verify_mode == SSL_VERIFY_NONE); + verify_default("ssl_min_version", settings.ssl_min_version == TLS1_2_VERSION); #endif printf("-N, --napi_ids number of napi ids. see doc/napi_ids.txt for more details\n"); return; @@ -4645,6 +4660,7 @@ int main (int argc, char **argv) { SSL_CA_CERT, SSL_WBUF_SIZE, SSL_SESSION_CACHE, + SSL_MIN_VERSION, #endif #ifdef MEMCACHED_DEBUG RELAXED_PRIVILEGES, @@ -4699,6 +4715,7 @@ int main (int argc, char **argv) { [SSL_CA_CERT] = "ssl_ca_cert", [SSL_WBUF_SIZE] = "ssl_wbuf_size", [SSL_SESSION_CACHE] = "ssl_session_cache", + [SSL_MIN_VERSION] = "ssl_min_version", #endif #ifdef MEMCACHED_DEBUG [RELAXED_PRIVILEGES] = "relaxed_privileges", @@ -5375,6 +5392,37 @@ int main (int argc, char **argv) { case SSL_SESSION_CACHE: settings.ssl_session_cache = true; break; + case SSL_MIN_VERSION: { + int min_version; + if (subopts_value == NULL) { + fprintf(stderr, "Missing ssl_min_version argument\n"); + return 1; + } + if (!safe_strtol(subopts_value, &min_version)) { + fprintf(stderr, "could not parse argument to ssl_min_version\n"); + return 1; + } + switch (min_version) { + case 0: + settings.ssl_min_version = TLS1_VERSION; + break; + case 1: + settings.ssl_min_version = TLS1_1_VERSION; + break; + case 2: + settings.ssl_min_version = TLS1_2_VERSION; + break; +#if OPENSSL_VERSION_NUMBER >= 0x10101000L + case 3: + settings.ssl_min_version = TLS1_3_VERSION; + break; +#endif + default: + fprintf(stderr, "Invalid ssl_min_version. Use help to see valid options.\n"); + return 1; + } + break; + } #endif case MODERN: /* currently no new defaults */ diff --git a/memcached.h b/memcached.h index 7b06bb3..3289bdb 100644 --- a/memcached.h +++ b/memcached.h @@ -496,6 +496,7 @@ struct settings { rel_time_t ssl_last_cert_refresh_time; /* time of the last server certificate refresh */ unsigned int ssl_wbuf_size; /* size of the write buffer used by ssl_sendmsg method */ bool ssl_session_cache; /* enable SSL server session caching */ + int ssl_min_version; /* minimum SSL protocol version to accept */ #endif int num_napi_ids; /* maximum number of NAPI IDs */ char *memory_file; /* warm restart memory file path */ diff --git a/t/ssl_proto_version.t b/t/ssl_proto_version.t new file mode 100644 index 0000000..4262db8 --- /dev/null +++ b/t/ssl_proto_version.t @@ -0,0 +1,45 @@ +#!/usr/bin/perl + +use warnings; +use Test::More; +use FindBin qw($Bin); +use lib "$Bin/lib"; +use MemcachedTest; + +if (!enabled_tls_testing()) { + plan skip_all => 'SSL testing is not enabled'; + exit 0; +} + +my $server; +my $is_tls_13_available = 0; + +eval { + # ssl_min_version=3 is not recognized when compiled with OpenSSL < 1.1.1 + $server = new_memcached('-o ssl_min_version=3'); + $is_tls_13_available = 1; +}; + +SKIP: { + skip 'TLS v1.3 not available', 1 if !$is_tls_13_available; + # Unsupported protocol version + $sock = $server->new_sock(undef, 'TLSv1_2'); + is(undef, $sock, "handshake failure on unsupported proto version"); +} + +$server = new_memcached('-o ssl_min_version=2'); + +# Minimum supported protocol version +$sock = $server->new_sock(undef, 'TLSv1_2'); +print $sock "version\r\n"; +like(scalar <$sock>, qr/VERSION/, "handshake with minimum proto version"); + +SKIP: { + skip 'TLS v1.3 not available', 1 if !$is_tls_13_available; + # Above minimum supported protocol version + $sock = $server->new_sock(undef, 'TLSv1_3'); + print $sock "version\r\n"; + like(scalar <$sock>, qr/VERSION/, "handshake above minimum proto version"); +} + +done_testing(); diff --git a/t/ssl_settings.t b/t/ssl_settings.t index c4d5b33..0a3bf94 100644 --- a/t/ssl_settings.t +++ b/t/ssl_settings.t @@ -28,10 +28,11 @@ is($settings->{'ssl_keyformat'}, 1); is($settings->{'ssl_ciphers'}, 'NULL'); is($settings->{'ssl_ca_cert'}, 'NULL'); is($settings->{'ssl_wbuf_size'}, 16384); +is($settings->{'ssl_min_version'}, 'tlsv1.2'); $server->DESTROY(); $server = new_memcached("-o ssl_wbuf_size=64"); $settings = mem_stats($server->sock, ' settings'); -is($settings->{'ssl_wbuf_size'},65536); +is($settings->{'ssl_wbuf_size'}, 65536); done_testing(); @@ -177,13 +177,12 @@ static bool load_server_certificates(char **errmsg) { */ int ssl_init(void) { assert(settings.ssl_enabled); + // SSL context for the process. All connections will share one // process level context. settings.ssl_ctx = SSL_CTX_new(TLS_server_method()); - // Clients should use at least TLSv1.2 - int flags = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | - SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1; - SSL_CTX_set_options(settings.ssl_ctx, flags); + + SSL_CTX_set_min_proto_version(settings.ssl_ctx, settings.ssl_min_version); // The server certificate, private key and validations. char *error_msg; @@ -249,4 +248,21 @@ int ssl_new_session_callback(SSL *s, SSL_SESSION *sess) { bool refresh_certs(char **errmsg) { return load_server_certificates(errmsg); } + +const char *ssl_proto_text(int version) { + switch (version) { + case TLS1_VERSION: + return "tlsv1.0"; + case TLS1_1_VERSION: + return "tlsv1.1"; + case TLS1_2_VERSION: + return "tlsv1.2"; +#if OPENSSL_VERSION_NUMBER >= 0x10101000L + case TLS1_3_VERSION: + return "tlsv1.3"; +#endif + default: + return "unknown"; + } +} #endif @@ -15,5 +15,6 @@ int ssl_init(void); bool refresh_certs(char **errmsg); void ssl_callback(const SSL *s, int where, int ret); int ssl_new_session_callback(SSL *s, SSL_SESSION *sess); +const char *ssl_proto_text(int version); #endif |