summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin Lin <developer@kevinlin.info>2020-02-19 20:59:24 -0800
committerdormando <dormando@rydia.net>2020-03-27 11:21:33 -0700
commit4e79f166fc15583cae443d9ae09a1e673601fb7e (patch)
tree22af2a3afad3501b1e75ee7aedfecd2b9f1d35f0
parentf249724cedcab6605ca8a0769ac4b356a8124f63 (diff)
downloadmemcached-4e79f166fc15583cae443d9ae09a1e673601fb7e.tar.gz
Add: `-o ssl_session_cache`, disabled by default
Enables server-side TLS session caching.
-rw-r--r--doc/protocol.txt5
-rw-r--r--doc/tls.txt2
-rw-r--r--memcached.c14
-rw-r--r--memcached.h2
-rw-r--r--t/lib/MemcachedTest.pm4
-rw-r--r--t/ssl_session_resumption.t59
-rw-r--r--t/ssl_settings.t1
-rw-r--r--tls.c26
-rw-r--r--tls.h5
9 files changed, 115 insertions, 3 deletions
diff --git a/doc/protocol.txt b/doc/protocol.txt
index 4de52ea..2ca031b 100644
--- a/doc/protocol.txt
+++ b/doc/protocol.txt
@@ -1634,6 +1634,11 @@ 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_new_sessions | 64u | When SSL session caching is |
+| | | enabled, the number of newly |
+| | | created server-side sessions. |
+| | | Available only when compiled |
+| | | with OpenSSL 1.1.1 or newer. |
| time_since_server_cert_refresh | 32u | Number of seconds that have |
| | | elapsed since the last time |
| | | certs were reloaded from disk. |
diff --git a/doc/tls.txt b/doc/tls.txt
index fef56ab..e7fe2ca 100644
--- a/doc/tls.txt
+++ b/doc/tls.txt
@@ -18,7 +18,7 @@ will use new certificates without a need of re-starting the server process.
# Multiple ports with and without TLS : by default all TCP ports are secured. Optionally we can setup
the server to secure a specific TCP port.
-Note that initial implementation does not support session resumption or renegotiation.
+Note that initial implementation does not support session renegotiation.
Design
------
diff --git a/memcached.c b/memcached.c
index 433d469..8701857 100644
--- a/memcached.c
+++ b/memcached.c
@@ -269,6 +269,7 @@ static void settings_init(void) {
settings.ssl_ca_cert = NULL;
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;
#endif
/* By default this string should be NULL for getaddrinfo() */
settings.inter = NULL;
@@ -3232,6 +3233,9 @@ static void server_stats(ADD_STAT add_stats, conn *c) {
#endif
#ifdef TLS
if (settings.ssl_enabled) {
+ if (settings.ssl_session_cache) {
+ APPEND_STAT("ssl_new_sessions", "%llu", (unsigned long long)stats.ssl_new_sessions);
+ }
APPEND_STAT("ssl_handshake_errors", "%llu", (unsigned long long)stats.ssl_handshake_errors);
APPEND_STAT("time_since_server_cert_refresh", "%u", now - settings.ssl_last_cert_refresh_time);
}
@@ -3319,6 +3323,7 @@ static void process_stat_settings(ADD_STAT add_stats, void *c) {
APPEND_STAT("ssl_ciphers", "%s", settings.ssl_ciphers ? settings.ssl_ciphers : "NULL");
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");
#endif
}
@@ -8122,6 +8127,8 @@ static void usage(void) {
" - ssl_ca_cert: PEM format file of acceptable client CA's\n"
" - 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");
verify_default("ssl_keyformat", settings.ssl_keyformat == SSL_FILETYPE_PEM);
verify_default("ssl_verify_mode", settings.ssl_verify_mode == SSL_VERIFY_NONE);
#endif
@@ -8777,6 +8784,7 @@ int main (int argc, char **argv) {
SSL_CIPHERS,
SSL_CA_CERT,
SSL_WBUF_SIZE,
+ SSL_SESSION_CACHE,
#endif
#ifdef MEMCACHED_DEBUG
RELAXED_PRIVILEGES,
@@ -8845,6 +8853,7 @@ int main (int argc, char **argv) {
[SSL_CIPHERS] = "ssl_ciphers",
[SSL_CA_CERT] = "ssl_ca_cert",
[SSL_WBUF_SIZE] = "ssl_wbuf_size",
+ [SSL_SESSION_CACHE] = "ssl_session_cache",
#endif
#ifdef MEMCACHED_DEBUG
[RELAXED_PRIVILEGES] = "relaxed_privileges",
@@ -8911,7 +8920,7 @@ int main (int argc, char **argv) {
char *shortopts =
"a:" /* access mask for unix socket */
- "A" /* enable admin shutdown command */
+ "A" /* enable admin shutdown command */
"Z" /* enable SSL */
"p:" /* TCP port number to listen on */
"s:" /* unix socket path to listen on */
@@ -9516,6 +9525,9 @@ int main (int argc, char **argv) {
}
settings.ssl_wbuf_size *= 1024; /* kilobytes */
break;
+ case SSL_SESSION_CACHE:
+ settings.ssl_session_cache = true;
+ break;
#endif
#ifdef EXTSTORE
case EXT_PAGE_SIZE:
diff --git a/memcached.h b/memcached.h
index bdc38bd..fe53afb 100644
--- a/memcached.h
+++ b/memcached.h
@@ -358,6 +358,7 @@ struct stats {
#endif
#ifdef TLS
uint64_t ssl_handshake_errors; /* TLS failures at accept/handshake time */
+ uint64_t ssl_new_sessions; /* successfully negotiated new (non-reused) TLS sessions */
#endif
struct timeval maxconns_entered; /* last time maxconns entered */
};
@@ -473,6 +474,7 @@ struct settings {
char *ssl_ca_cert; /* certificate with CAs. */
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 */
#endif
};
diff --git a/t/lib/MemcachedTest.pm b/t/lib/MemcachedTest.pm
index c69a12a..dce3c12 100644
--- a/t/lib/MemcachedTest.pm
+++ b/t/lib/MemcachedTest.pm
@@ -384,8 +384,12 @@ sub new_sock {
if ($self->{domainsocket}) {
return IO::Socket::UNIX->new(Peer => $self->{domainsocket});
} elsif (MemcachedTest::enabled_tls_testing()) {
+ my $ssl_session_cache = shift;
+ my $ssl_version = shift;
return eval qq{ IO::Socket::SSL->new(PeerAddr => "$self->{host}:$self->{port}",
SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE,
+ SSL_session_cache => \$ssl_session_cache,
+ SSL_version => '$ssl_version',
SSL_cert_file => '$client_crt',
SSL_key_file => '$client_key');
};
diff --git a/t/ssl_session_resumption.t b/t/ssl_session_resumption.t
new file mode 100644
index 0000000..d2245c8
--- /dev/null
+++ b/t/ssl_session_resumption.t
@@ -0,0 +1,59 @@
+#!/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 $sock;
+my $stats;
+
+my $session_cache = eval qq{ IO::Socket::SSL::Session_Cache->new(1); };
+
+### Disabled SSL session cache
+
+$server = new_memcached();
+$stats = mem_stats($server->sock);
+is($stats->{ssl_new_sessions}, undef,
+ "new SSL sessions not recorded when session cache is disabled");
+my $disabled_initial_total_conns = $stats->{total_connections};
+
+$sock = $server->new_sock($session_cache, 'TLSv1_2');
+$stats = mem_stats($sock);
+cmp_ok($stats->{total_connections}, '>', $disabled_initial_total_conns,
+ "client-side session cache is noop in establishing a new connection");
+is($sock->get_session_reused(), 0, "client-side session cache is unused");
+
+### Enabled SSL session cache
+
+$server = new_memcached("-o ssl_session_cache");
+# Support for session caching in IO::Socket::SSL for TLS v1.3 is incomplete.
+# Here, we will deliberately force TLS v1.2 to test session caching.
+$sock = $server->new_sock($session_cache, 'TLSv1_2');
+$stats = mem_stats($sock);
+cmp_ok($stats->{total_connections}, '>', 0, "initial connection is established");
+SKIP: {
+ skip "sessions counter accuracy requires OpenSSL 1.1.1 or newer", 1;
+ cmp_ok($stats->{ssl_new_sessions}, '>', 0, "successful new SSL session");
+}
+my $enabled_initial_ssl_sessions = $stats->{ssl_new_sessions};
+my $enabled_initial_total_conns = $stats->{total_connections};
+
+# Create a new client with the same session cache
+$sock = $server->new_sock($session_cache, 'TLSv1_2');
+$stats = mem_stats($sock);
+cmp_ok($stats->{total_connections}, '>', $enabled_initial_total_conns,
+ "new connection is established");
+is($stats->{ssl_new_sessions}, $enabled_initial_ssl_sessions,
+ "no new SSL sessions are created on the server");
+is($sock->get_session_reused(), 1,
+ "client-persisted session is reused");
+
+done_testing();
diff --git a/t/ssl_settings.t b/t/ssl_settings.t
index 57f9668..c4d5b33 100644
--- a/t/ssl_settings.t
+++ b/t/ssl_settings.t
@@ -20,6 +20,7 @@ my $cert = getcwd ."/t/". MemcachedTest::SRV_CRT;
my $key = getcwd ."/t/". MemcachedTest::SRV_KEY;
is($settings->{'ssl_enabled'}, 'yes');
+is($settings->{'ssl_session_cache'}, 'no');
is($settings->{'ssl_chain_cert'}, $cert);
is($settings->{'ssl_key'}, $key);
is($settings->{'ssl_verify_mode'}, 0);
diff --git a/tls.c b/tls.c
index ae1cb4d..4b93af8 100644
--- a/tls.c
+++ b/tls.c
@@ -143,7 +143,7 @@ int ssl_init(void) {
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_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1;
SSL_CTX_set_options(settings.ssl_ctx, flags);
// The server certificate, private key and validations.
@@ -167,6 +167,17 @@ int ssl_init(void) {
exit(EX_USAGE);
}
+ // Optional session caching; default disabled.
+ if (settings.ssl_session_cache) {
+ SSL_CTX_sess_set_new_cb(settings.ssl_ctx, ssl_new_session_callback);
+ SSL_CTX_set_session_cache_mode(settings.ssl_ctx, SSL_SESS_CACHE_SERVER);
+ SSL_CTX_set_session_id_context(settings.ssl_ctx,
+ (const unsigned char *) SESSION_ID_CONTEXT,
+ strlen(SESSION_ID_CONTEXT));
+ } else {
+ SSL_CTX_set_session_cache_mode(settings.ssl_ctx, SSL_SESS_CACHE_OFF);
+ }
+
return 0;
}
@@ -189,6 +200,19 @@ void ssl_callback(const SSL *s, int where, int ret) {
}
}
+/*
+ * This method is invoked with every new successfully negotiated SSL session,
+ * when server-side session caching is enabled. Note that this method is not
+ * invoked when a session is reused.
+ */
+int ssl_new_session_callback(SSL *s, SSL_SESSION *sess) {
+ STATS_LOCK();
+ stats.ssl_new_sessions++;
+ STATS_UNLOCK();
+
+ return 0;
+}
+
bool refresh_certs(char **errmsg) {
return load_server_certificates(errmsg);
}
diff --git a/tls.h b/tls.h
index 15dbcd8..3ec2e29 100644
--- a/tls.h
+++ b/tls.h
@@ -1,6 +1,10 @@
#ifndef TLS_H
#define TLS_H
+/* constant session ID context for application-level SSL session scoping.
+ * used in server-side SSL session caching, when enabled. */
+#define SESSION_ID_CONTEXT "memcached"
+
void SSL_LOCK(void);
void SSL_UNLOCK(void);
ssize_t ssl_read(conn *c, void *buf, size_t count);
@@ -10,5 +14,6 @@ ssize_t ssl_write(conn *c, void *buf, size_t count);
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);
#endif