From c4ef1efdb7fc22ac853acaf14531ab24c3c20a4c Mon Sep 17 00:00:00 2001 From: Yossi Gottlieb Date: Mon, 22 Mar 2021 13:27:46 +0200 Subject: [PATCH] Add support for reading encrypted keyfiles. (#8644) --- redis.conf | 10 ++++++++++ src/config.c | 2 ++ src/server.h | 2 ++ src/tls.c | 28 ++++++++++++++++++++++++---- tests/unit/tls.tcl | 15 +++++++++++++++ 5 files changed, 53 insertions(+), 4 deletions(-) diff --git a/redis.conf b/redis.conf index 6bdef8843..057149ed0 100644 --- a/redis.conf +++ b/redis.conf @@ -150,6 +150,11 @@ tcp-keepalive 300 # # tls-cert-file redis.crt # tls-key-file redis.key +# +# If the key file is encrypted using a passphrase, it can be included here +# as well. +# +# tls-key-file-pass secret # Normally Redis uses the same certificate for both server functions (accepting # connections) and client functions (replicating from a master, establishing @@ -162,6 +167,11 @@ tcp-keepalive 300 # # tls-client-cert-file client.crt # tls-client-key-file client.key +# +# If the key file is encrypted using a passphrase, it can be included here +# as well. +# +# tls-client-key-file-pass secret # Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange: # diff --git a/src/config.c b/src/config.c index 2db13820c..21b0b5e99 100644 --- a/src/config.c +++ b/src/config.c @@ -2543,8 +2543,10 @@ standardConfig configs[] = { createBoolConfig("tls-session-caching", NULL, MODIFIABLE_CONFIG, server.tls_ctx_config.session_caching, 1, NULL, updateTlsCfgBool), createStringConfig("tls-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.cert_file, NULL, NULL, updateTlsCfg), createStringConfig("tls-key-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.key_file, NULL, NULL, updateTlsCfg), + createStringConfig("tls-key-file-pass", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.key_file_pass, NULL, NULL, updateTlsCfg), createStringConfig("tls-client-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_cert_file, NULL, NULL, updateTlsCfg), createStringConfig("tls-client-key-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_key_file, NULL, NULL, updateTlsCfg), + createStringConfig("tls-client-key-file-pass", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_key_file_pass, NULL, NULL, updateTlsCfg), createStringConfig("tls-dh-params-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.dh_params_file, NULL, NULL, updateTlsCfg), createStringConfig("tls-ca-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ca_cert_file, NULL, NULL, updateTlsCfg), createStringConfig("tls-ca-cert-dir", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ca_cert_dir, NULL, NULL, updateTlsCfg), diff --git a/src/server.h b/src/server.h index f0fbefef0..9bce17d0f 100644 --- a/src/server.h +++ b/src/server.h @@ -1119,8 +1119,10 @@ typedef struct socketFds { typedef struct redisTLSContextConfig { char *cert_file; /* Server side and optionally client side cert file name */ char *key_file; /* Private key filename for cert_file */ + char *key_file_pass; /* Optional password for key_file */ char *client_cert_file; /* Certificate to use as a client; if none, use cert_file */ char *client_key_file; /* Private key filename for client_cert_file */ + char *client_key_file_pass; /* Optional password for client_key_file */ char *dh_params_file; char *ca_cert_file; char *ca_cert_dir; diff --git a/src/tls.c b/src/tls.c index 810f0faab..ffd3b0ad0 100644 --- a/src/tls.c +++ b/src/tls.c @@ -179,11 +179,28 @@ void tlsCleanup(void) { #endif } +/* Callback for passing a keyfile password stored as an sds to OpenSSL */ +static int tlsPasswordCallback(char *buf, int size, int rwflag, void *u) { + UNUSED(rwflag); + + const char *pass = u; + size_t pass_len; + + if (!pass) return -1; + pass_len = strlen(pass); + if (pass_len > (size_t) size) return -1; + memcpy(buf, pass, pass_len); + + return (int) pass_len; +} + /* Create a *base* SSL_CTX using the SSL configuration provided. The base context * includes everything that's common for both client-side and server-side connections. */ -static SSL_CTX *createSSLContext(redisTLSContextConfig *ctx_config, int protocols, - const char *cert_file, const char *key_file) { +static SSL_CTX *createSSLContext(redisTLSContextConfig *ctx_config, int protocols, int client) { + const char *cert_file = client ? ctx_config->client_cert_file : ctx_config->cert_file; + const char *key_file = client ? ctx_config->client_key_file : ctx_config->key_file; + const char *key_file_pass = client ? ctx_config->client_key_file_pass : ctx_config->key_file_pass; char errbuf[256]; SSL_CTX *ctx = NULL; @@ -215,6 +232,9 @@ static SSL_CTX *createSSLContext(redisTLSContextConfig *ctx_config, int protocol SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE|SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + SSL_CTX_set_default_passwd_cb(ctx, tlsPasswordCallback); + SSL_CTX_set_default_passwd_cb_userdata(ctx, (void *) key_file_pass); + if (SSL_CTX_use_certificate_chain_file(ctx, cert_file) <= 0) { ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf)); serverLog(LL_WARNING, "Failed to load certificate: %s: %s", cert_file, errbuf); @@ -281,7 +301,7 @@ int tlsConfigure(redisTLSContextConfig *ctx_config) { if (protocols == -1) goto error; /* Create server side/generla context */ - ctx = createSSLContext(ctx_config, protocols, ctx_config->cert_file, ctx_config->key_file); + ctx = createSSLContext(ctx_config, protocols, 0); if (!ctx) goto error; if (ctx_config->session_caching) { @@ -332,7 +352,7 @@ int tlsConfigure(redisTLSContextConfig *ctx_config) { /* If a client-side certificate is configured, create an explicit client context */ if (ctx_config->client_cert_file && ctx_config->client_key_file) { - client_ctx = createSSLContext(ctx_config, protocols, ctx_config->client_cert_file, ctx_config->client_key_file); + client_ctx = createSSLContext(ctx_config, protocols, 1); if (!client_ctx) goto error; } diff --git a/tests/unit/tls.tcl b/tests/unit/tls.tcl index 14e06fcdf..29fe39fbf 100644 --- a/tests/unit/tls.tcl +++ b/tests/unit/tls.tcl @@ -139,5 +139,20 @@ start_server {tags {"tls"}} { $rd PING $rd close } + + test {TLS: Working with an encrypted keyfile} { + # Create an encrypted version + set keyfile [lindex [r config get tls-key-file] 1] + set keyfile_encrypted "$keyfile.encrypted" + exec -ignorestderr openssl rsa -in $keyfile -out $keyfile_encrypted -aes256 -passout pass:1234 2>/dev/null + + # Using it without a password fails + catch {r config set tls-key-file $keyfile_encrypted} e + assert_match {*Unable to update TLS*} $e + + # Now use a password + r config set tls-key-file-pass 1234 + r config set tls-key-file $keyfile_encrypted + } } }