summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin Lin <developer@kevinlin.info>2021-05-31 13:33:48 -0700
committerdormando <dormando@rydia.net>2021-09-27 12:54:20 -0700
commit3a8ca319b35a3b3533b1f1ca55e904cfcb71962c (patch)
tree6a61bc3c9481e8fcbc4d6e3314613cccb63c06cc
parentf8a55c4731ab38b8c1a88cb7bf10fadc209fd78f (diff)
downloadmemcached-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.txt2
-rw-r--r--memcached.c50
-rw-r--r--memcached.h1
-rw-r--r--t/ssl_proto_version.t45
-rw-r--r--t/ssl_settings.t3
-rw-r--r--tls.c24
-rw-r--r--tls.h1
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();
diff --git a/tls.c b/tls.c
index df42308..dcd7435 100644
--- a/tls.c
+++ b/tls.c
@@ -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
diff --git a/tls.h b/tls.h
index 3ec2e29..18e7e0d 100644
--- a/tls.h
+++ b/tls.h
@@ -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