Repository: redis/redis

Problem statement:
[NEW]Passphrase option for encrypted SSL Private keys
Redis now seems doesn't support Encrypted SSL private keys because there is no option in [config](https://github.com/redis/redis/blob/unstable/redis.conf) to pass it to the server.

A config option like (tls_key_passsphrase) helps in case of SSL private keys are encrypted.



Hints:
Hello @SivaBu-kore, thanks for this input.

From my POV, the only incentive to support encrypted private keys is to improve security. However, simply storing the password in the configuration file pretty much defeats this purpose as again we have clear-text secrets on disk.

We can get some configuration through stdin (which is safe) or command line arguments (less safe, Redis rewrites argv but there's a window of time for exposure). However, those config parameters are still exposed through `CONFIG GET` or `CONFIG REWRITE`.

So, my take here is that before we address the specific SSL issue we need to come up with a secrets management scheme. Secrets would be configuration parameters that:

1. Can only be provided through stdin, command line arguments or `CONFIG SET`.
2. Are never returned on `CONFIG GET`.
3. Are never written as part of `CONFIG REWRITE`.

Some existing configuration parameters can also be considered secrets, but we cannot enforce that for backwards compatibility.
> From my POV, the only incentive to support encrypted private keys is to improve security. However, simply storing the password in the configuration file pretty much defeats this purpose as again we have clear-text secrets on disk.

There's another use-case: Unlike a clear-text key, an encrypted key could be checked in into git, or baked into a container image.

Obviously the points you raise remain valid. I just wanted to point out that adding this configuration option would allow keeping key+certificate under version control.  
As such, I feel some sort of `tls_key_passsphrase` configuration option would add value even without other changes.
Thanks @joostdecock, this is a good point.
When will this function be published，please

Patch:
diff --git a/redis.conf b/redis.conf
index 6bdef884366..057149ed051 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 2f4a13ae89e..b9cd358be2e 100644
--- a/src/config.c
+++ b/src/config.c
@@ -2537,8 +2537,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 3126db8f07e..08fbfbaa4b2 100644
--- a/src/server.h
+++ b/src/server.h
@@ -1113,8 +1113,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 810f0faab20..ffd3b0ad023 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;
     }
 


Test patch:
diff --git a/tests/unit/tls.tcl b/tests/unit/tls.tcl
index 14e06fcdfd2..29fe39fbf72 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
+        }
     }
 }