diff --git a/memcached.c b/memcached.c index 20ead54f08..cc0d890268 100644 --- a/memcached.c +++ b/memcached.c @@ -3480,7 +3480,7 @@ static void maximize_sndbuf(const int sfd) { static int server_socket(const char *interface, int port, enum network_transport transport, - FILE *portnumber_file, bool ssl_enabled, + FILE *portnumber_file, uint8_t ssl_enabled, uint64_t conntag, enum protocol bproto) { int sfd; @@ -3658,10 +3658,14 @@ static int server_socket(const char *interface, static int server_sockets(int port, enum network_transport transport, FILE *portnumber_file) { - bool ssl_enabled = false; + uint8_t ssl_enabled = MC_SSL_DISABLED; const char *notls = "notls"; - ssl_enabled = settings.ssl_enabled; + const char *btls = "btls"; + const char *mtls = "mtls"; + if (settings.ssl_enabled) { + ssl_enabled = MC_SSL_ENABLED_DEFAULT; + } if (settings.inter == NULL) { return server_socket(settings.inter, port, transport, portnumber_file, ssl_enabled, 0, settings.binding_protocol); @@ -3682,7 +3686,9 @@ static int server_sockets(int port, enum network_transport transport, p = strtok_r(NULL, ";,", &b)) { uint64_t conntag = 0; int the_port = port; - ssl_enabled = settings.ssl_enabled; + if (settings.ssl_enabled) { + ssl_enabled = MC_SSL_ENABLED_DEFAULT; + } // "notls" option is valid only when memcached is run with SSL enabled. if (strncmp(p, notls, strlen(notls)) == 0) { if (!settings.ssl_enabled) { @@ -3690,8 +3696,24 @@ static int server_sockets(int port, enum network_transport transport, free(list); return 1; } - ssl_enabled = false; + ssl_enabled = MC_SSL_DISABLED; p += strlen(notls) + 1; + } else if (strncmp(p, btls, strlen(btls)) == 0) { + if (!settings.ssl_enabled) { + fprintf(stderr, "'btls' option is valid only when SSL is enabled\n"); + free(list); + return 1; + } + ssl_enabled = MC_SSL_ENABLED_NOPEER; + p += strlen(btls) + 1; + } else if (strncmp(p, mtls, strlen(mtls)) == 0) { + if (!settings.ssl_enabled) { + fprintf(stderr, "'otls' option is valid only when SSL is enabled\n"); + free(list); + return 1; + } + ssl_enabled = MC_SSL_ENABLED_PEER; + p += strlen(mtls) + 1; } // Allow forcing the protocol of this listener. diff --git a/memcached.h b/memcached.h index 57fa5389f1..6d3a1b72e5 100644 --- a/memcached.h +++ b/memcached.h @@ -834,7 +834,7 @@ struct conn { bool close_after_write; /** flush write then move to close connection */ bool rbuf_malloced; /** read buffer was malloc'ed for ascii mget, needs free() */ bool item_malloced; /** item for conn_nread state is a temporary malloc */ - bool ssl_enabled; + uint8_t ssl_enabled; #ifdef TLS void *ssl; char *ssl_wbuf; diff --git a/t/lib/MemcachedTest.pm b/t/lib/MemcachedTest.pm index ffbc51733d..ce09014dad 100644 --- a/t/lib/MemcachedTest.pm +++ b/t/lib/MemcachedTest.pm @@ -506,6 +506,19 @@ sub new_sock { } } +# needed for a specific test +sub new_nocert_tls_sock { + my $self = shift; + if (MemcachedTest::enabled_tls_testing()) { + my $port = shift; + my $ssl_version = shift; + return eval qq{ IO::Socket::SSL->new(PeerAddr => "$self->{host}:$port", + SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE, + SSL_version => '$ssl_version'); + }; + } +} + sub new_udp_sock { my $self = shift; return IO::Socket::INET->new(PeerAddr => '127.0.0.1', diff --git a/t/ssl_ports.t b/t/ssl_ports.t index 4f1c3b8166..2d23b00dff 100644 --- a/t/ssl_ports.t +++ b/t/ssl_ports.t @@ -15,8 +15,9 @@ if (!enabled_tls_testing()) { my $tcp_port = free_port(); my $ssl_port = free_port(); +my $mtls_port = free_port(); -my $server = new_memcached("-l notls:127.0.0.1:$tcp_port,127.0.0.1:$ssl_port", $ssl_port); +my $server = new_memcached("-l notls:127.0.0.1:$tcp_port,127.0.0.1:$ssl_port,mtls:127.0.0.1:$mtls_port", $ssl_port); my $sock = $server->sock; # Make sure we can talk over SSL @@ -28,4 +29,12 @@ is(scalar <$sock>, "STORED\r\n", "stored foo"); my $tcp_sock = IO::Socket::INET->new(PeerAddr => "127.0.0.1:$tcp_port"); mem_get_is($tcp_sock, "foo:123", "foo set over SSL"); +# verify mTLS failure +# not trying very hard but: if the above works and this doesn't, it's going to +# be the peer cert. If someone wants to tryhard you can try inspecting +# $SSL_ERROR and/or checking `watch connevents` stream +my $mtls_sock = $server->new_nocert_tls_sock($mtls_port, 'TLSv1_3'); +print $mtls_sock "version\r\n"; +is(scalar <$mtls_sock>, undef, "failed to connect without peer cert"); + done_testing() diff --git a/tls.c b/tls.c index fed4f98058..caf2f97a72 100644 --- a/tls.c +++ b/tls.c @@ -61,6 +61,15 @@ void *ssl_accept(conn *c, int sfd, bool *fail) { return NULL; } SSL_set_fd(ssl, sfd); + + if (c->ssl_enabled == MC_SSL_ENABLED_NOPEER) { + // Don't enforce peer certs for this socket. + SSL_set_verify(ssl, SSL_VERIFY_NONE, NULL); + } else if (c->ssl_enabled == MC_SSL_ENABLED_PEER) { + // Force peer validation for this socket. + SSL_set_verify(ssl, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + } + ERR_clear_error(); int ret = SSL_accept(ssl); if (ret <= 0) { diff --git a/tls.h b/tls.h index f1a1f3e554..b183e478a3 100644 --- a/tls.h +++ b/tls.h @@ -1,6 +1,11 @@ #ifndef TLS_H #define TLS_H +#define MC_SSL_DISABLED 0 +#define MC_SSL_ENABLED_DEFAULT 1 +#define MC_SSL_ENABLED_NOPEER 2 +#define MC_SSL_ENABLED_PEER 3 + #ifdef TLS void *ssl_accept(conn *c, int sfd, bool *fail); int ssl_init(void);