Skip to content

Commit

Permalink
Add: -o ssl_session_cache, disabled by default
Browse files Browse the repository at this point in the history
Enables server-side TLS session caching.
  • Loading branch information
LINKIWI authored and dormando committed Mar 27, 2020
1 parent f249724 commit 4e79f16
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 3 deletions.
5 changes: 5 additions & 0 deletions doc/protocol.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
Expand Down
2 changes: 1 addition & 1 deletion doc/tls.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
------
Expand Down
14 changes: 13 additions & 1 deletion memcached.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions memcached.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
};
Expand Down Expand Up @@ -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
};

Expand Down
4 changes: 4 additions & 0 deletions t/lib/MemcachedTest.pm
Original file line number Diff line number Diff line change
Expand Up @@ -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');
};
Expand Down
59 changes: 59 additions & 0 deletions t/ssl_session_resumption.t
Original file line number Diff line number Diff line change
@@ -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();
1 change: 1 addition & 0 deletions t/ssl_settings.t
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
26 changes: 25 additions & 1 deletion tls.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;
}

Expand All @@ -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);
}
Expand Down
5 changes: 5 additions & 0 deletions tls.h
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -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

0 comments on commit 4e79f16

Please sign in to comment.