diff options
author | Kevin Lin <developer@kevinlin.info> | 2020-02-19 20:59:24 -0800 |
---|---|---|
committer | dormando <dormando@rydia.net> | 2020-03-27 11:21:33 -0700 |
commit | 4e79f166fc15583cae443d9ae09a1e673601fb7e (patch) | |
tree | 22af2a3afad3501b1e75ee7aedfecd2b9f1d35f0 | |
parent | f249724cedcab6605ca8a0769ac4b356a8124f63 (diff) | |
download | memcached-4e79f166fc15583cae443d9ae09a1e673601fb7e.tar.gz |
Add: `-o ssl_session_cache`, disabled by default
Enables server-side TLS session caching.
-rw-r--r-- | doc/protocol.txt | 5 | ||||
-rw-r--r-- | doc/tls.txt | 2 | ||||
-rw-r--r-- | memcached.c | 14 | ||||
-rw-r--r-- | memcached.h | 2 | ||||
-rw-r--r-- | t/lib/MemcachedTest.pm | 4 | ||||
-rw-r--r-- | t/ssl_session_resumption.t | 59 | ||||
-rw-r--r-- | t/ssl_settings.t | 1 | ||||
-rw-r--r-- | tls.c | 26 | ||||
-rw-r--r-- | tls.h | 5 |
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); @@ -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); } @@ -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 |