diff --git a/redis.conf b/redis.conf index 1f966badb..408426f15 100644 --- a/redis.conf +++ b/redis.conf @@ -148,10 +148,12 @@ tcp-keepalive 300 # # tls-dh-params-file redis.dh -# Configure a CA certificate(s) bundle to authenticate TLS/SSL clients and -# peers. +# Configure a CA certificate(s) bundle or directory to authenticate TLS/SSL +# clients and peers. Redis requires an explicit configuration of at least one +# of these, and will not implicitly use the system wide configuration. # # tls-ca-cert-file ca.crt +# tls-ca-cert-dir /etc/ssl/certs # If TLS/SSL clients are required to authenticate using a client side # certificate, use this directive. diff --git a/src/config.c b/src/config.c index bb04cb45b..fff522815 100644 --- a/src/config.c +++ b/src/config.c @@ -821,6 +821,9 @@ void loadServerConfigFromString(char *config) { } else if (!strcasecmp(argv[0],"tls-ca-cert-file") && argc == 2) { zfree(server.tls_ctx_config.ca_cert_file); server.tls_ctx_config.ca_cert_file = zstrdup(argv[1]); + } else if (!strcasecmp(argv[0],"tls-ca-cert-dir") && argc == 2) { + zfree(server.tls_ctx_config.ca_cert_dir); + server.tls_ctx_config.ca_cert_dir = zstrdup(argv[1]); } else if (!strcasecmp(argv[0],"tls-protocols") && argc >= 2) { zfree(server.tls_ctx_config.protocols); server.tls_ctx_config.protocols = zstrdup(argv[1]); @@ -1319,6 +1322,16 @@ void configSetCommand(client *c) { } zfree(server.tls_ctx_config.ca_cert_file); server.tls_ctx_config.ca_cert_file = zstrdup(o->ptr); + } config_set_special_field("tls-ca-cert-dir") { + redisTLSContextConfig tmpctx = server.tls_ctx_config; + tmpctx.ca_cert_dir = (char *) o->ptr; + if (tlsConfigure(&tmpctx) == C_ERR) { + addReplyError(c, + "Unable to configure tls-ca-cert-dir. Check server logs."); + return; + } + zfree(server.tls_ctx_config.ca_cert_dir); + server.tls_ctx_config.ca_cert_dir = zstrdup(o->ptr); } config_set_bool_field("tls-auth-clients", server.tls_auth_clients) { } config_set_bool_field("tls-replication", server.tls_replication) { } config_set_bool_field("tls-cluster", server.tls_cluster) { @@ -1439,6 +1452,7 @@ void configGetCommand(client *c) { config_get_string_field("tls-key-file",server.tls_ctx_config.key_file); config_get_string_field("tls-dh-params-file",server.tls_ctx_config.dh_params_file); config_get_string_field("tls-ca-cert-file",server.tls_ctx_config.ca_cert_file); + config_get_string_field("tls-ca-cert-dir",server.tls_ctx_config.ca_cert_dir); config_get_string_field("tls-protocols",server.tls_ctx_config.protocols); config_get_string_field("tls-ciphers",server.tls_ctx_config.ciphers); config_get_string_field("tls-ciphersuites",server.tls_ctx_config.ciphersuites); @@ -2347,6 +2361,7 @@ int rewriteConfig(char *path) { rewriteConfigStringOption(state,"tls-key-file",server.tls_ctx_config.key_file,NULL); rewriteConfigStringOption(state,"tls-dh-params-file",server.tls_ctx_config.dh_params_file,NULL); rewriteConfigStringOption(state,"tls-ca-cert-file",server.tls_ctx_config.ca_cert_file,NULL); + rewriteConfigStringOption(state,"tls-ca-cert-dir",server.tls_ctx_config.ca_cert_dir,NULL); rewriteConfigStringOption(state,"tls-protocols",server.tls_ctx_config.protocols,NULL); rewriteConfigStringOption(state,"tls-ciphers",server.tls_ctx_config.ciphers,NULL); rewriteConfigStringOption(state,"tls-ciphersuites",server.tls_ctx_config.ciphersuites,NULL); diff --git a/src/redis-cli.c b/src/redis-cli.c index c7145927a..01211ee24 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -48,6 +48,7 @@ #include #ifdef USE_OPENSSL +#include #include #endif #include /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */ @@ -194,6 +195,7 @@ static struct config { int tls; char *sni; char *cacert; + char *cacertdir; char *cert; char *key; long repeat; @@ -762,9 +764,61 @@ static int cliSelect(void) { /* Wrapper around redisSecureConnection to avoid hiredis_ssl dependencies if * not building with TLS support. */ -static int cliSecureConnection(redisContext *c) { +static int cliSecureConnection(redisContext *c, const char **err) { #ifdef USE_OPENSSL - return redisSecureConnection(c, config.cacert, config.cert, config.key, config.sni); + static SSL_CTX *ssl_ctx = NULL; + + if (!ssl_ctx) { + ssl_ctx = SSL_CTX_new(SSLv23_client_method()); + if (!ssl_ctx) { + *err = "Failed to create SSL_CTX"; + goto error; + } + + SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL); + + if (config.cacert || config.cacertdir) { + if (!SSL_CTX_load_verify_locations(ssl_ctx, config.cacert, config.cacertdir)) { + *err = "Invalid CA Certificate File/Directory"; + goto error; + } + } else { + if (!SSL_CTX_set_default_verify_paths(ssl_ctx)) { + *err = "Failed to use default CA paths"; + goto error; + } + } + + if (config.cert && !SSL_CTX_use_certificate_chain_file(ssl_ctx, config.cert)) { + *err = "Invalid client certificate"; + goto error; + } + + if (config.key && !SSL_CTX_use_PrivateKey_file(ssl_ctx, config.key, SSL_FILETYPE_PEM)) { + *err = "Invalid private key"; + goto error; + } + } + + SSL *ssl = SSL_new(ssl_ctx); + if (!ssl) { + *err = "Failed to create SSL object"; + return REDIS_ERR; + } + + if (config.sni && !SSL_set_tlsext_host_name(ssl, config.sni)) { + *err = "Failed to configure SNI"; + SSL_free(ssl); + return REDIS_ERR; + } + + return redisInitiateSSL(c, ssl); + +error: + SSL_CTX_free(ssl_ctx); + ssl_ctx = NULL; + return REDIS_ERR; #else (void) c; return REDIS_OK; @@ -788,9 +842,9 @@ static int cliConnect(int flags) { } if (!context->err && config.tls) { - if (cliSecureConnection(context) == REDIS_ERR && !context->err) { - /* TODO: this check should be redundant, redis-cli should set err=1 */ - fprintf(stderr, "Could not negotiate a TLS connection.\n"); + const char *err = NULL; + if (cliSecureConnection(context, &err) == REDIS_ERR && err) { + fprintf(stderr, "Could not negotiate a TLS connection: %s\n", err); context = NULL; redisFree(context); return REDIS_ERR; @@ -1277,7 +1331,11 @@ static redisReply *reconnectingRedisCommand(redisContext *c, const char *fmt, .. redisFree(c); c = redisConnect(config.hostip,config.hostport); if (!c->err && config.tls) { - cliSecureConnection(c); + const char *err = NULL; + if (cliSecureConnection(c, &err) == REDIS_ERR && err) { + fprintf(stderr, "TLS Error: %s\n", err); + exit(1); + } } usleep(1000000); } @@ -1473,6 +1531,8 @@ static int parseOptions(int argc, char **argv) { config.tls = 1; } else if (!strcmp(argv[i],"--sni")) { config.sni = argv[++i]; + } else if (!strcmp(argv[i],"--cacertdir")) { + config.cacertdir = argv[++i]; } else if (!strcmp(argv[i],"--cacert")) { config.cacert = argv[++i]; } else if (!strcmp(argv[i],"--cert")) { @@ -1571,6 +1631,9 @@ static void usage(void) { #ifdef USE_OPENSSL " --tls Establish a secure TLS connection.\n" " --cacert CA Certificate file to verify with.\n" +" --cacertdir Directory where trusted CA certificates are stored.\n" +" If neither cacert nor cacertdir are specified, the default\n" +" system-wide trusted root certs configuration will apply.\n" " --cert Client certificate to authenticate with.\n" " --key Private key file to authenticate with.\n" #endif @@ -2390,7 +2453,13 @@ static int clusterManagerNodeConnect(clusterManagerNode *node) { if (node->context) redisFree(node->context); node->context = redisConnect(node->ip, node->port); if (!node->context->err && config.tls) { - cliSecureConnection(node->context); + const char *err = NULL; + if (cliSecureConnection(node->context, &err) == REDIS_ERR && err) { + fprintf(stderr,"TLS Error: %s\n", err); + redisFree(node->context); + node->context = NULL; + return 0; + } } if (node->context->err) { fprintf(stderr,"Could not connect to Redis at "); diff --git a/src/server.h b/src/server.h index 5c7849732..85e5de97a 100644 --- a/src/server.h +++ b/src/server.h @@ -1038,6 +1038,7 @@ typedef struct redisTLSContextConfig { char *key_file; char *dh_params_file; char *ca_cert_file; + char *ca_cert_dir; char *protocols; char *ciphers; char *ciphersuites; diff --git a/src/tls.c b/src/tls.c index a77fbfe3d..5fac6902b 100644 --- a/src/tls.c +++ b/src/tls.c @@ -125,8 +125,8 @@ int tlsConfigure(redisTLSContextConfig *ctx_config) { goto error; } - if (!ctx_config->ca_cert_file) { - serverLog(LL_WARNING, "No tls-ca-cert-file configured!"); + if (!ctx_config->ca_cert_file && !ctx_config->ca_cert_dir) { + serverLog(LL_WARNING, "Either tls-ca-cert-file or tls-ca-cert-dir must be configured!"); goto error; } @@ -182,9 +182,9 @@ int tlsConfigure(redisTLSContextConfig *ctx_config) { goto error; } - if (SSL_CTX_load_verify_locations(ctx, ctx_config->ca_cert_file, NULL) <= 0) { + if (SSL_CTX_load_verify_locations(ctx, ctx_config->ca_cert_file, ctx_config->ca_cert_dir) <= 0) { ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf)); - serverLog(LL_WARNING, "Failed to load CA certificate(s) file: %s: %s", ctx_config->ca_cert_file, errbuf); + serverLog(LL_WARNING, "Failed to configure CA certificate(s) file/directory: %s", errbuf); goto error; }