Skip to content

Commit

Permalink
More work on TLS and redis-cli
Browse files Browse the repository at this point in the history
  • Loading branch information
attipaci committed Jan 7, 2025
1 parent fe20ab2 commit 6ff6bcd
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 30 deletions.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ subscription client when there are active subscriptions.

We provide (experimental) support for TLS (see the Redis docs on
[TLS support](https://redis.io/docs/latest/operate/oss_and_stack/management/security/encryption/)). Simply configure
the necessary certificates, keys, and cypher parameters as needed using `redisxSetTLS()`, `redisxSetMutualTLS()`, and `redisxSetDHCypherParams()`, e.g.:
the necessary certificates, keys, and cypher parameters as needed, e.g.:

```c
Redis *redis = ...
Expand All @@ -418,8 +418,14 @@ the necessary certificates, keys, and cypher parameters as needed using `redisxS
// Oops, the certificate or key file is not accessible...
...
}

// (optional) Set server name for SNI
redisxSetTLSServerName(redis, "my.redis-server.com");

// (optional) Set ciphers to use
redisxSetTLSCiphers(redisx, "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4");

// (optional) To set parameters for DH-based cyphers
// (optional) Set parameters for DH-based cyphers
status = redisxSetDHCypherParams(redisx, "path/to/redis.dh");
if(status) {
// Oops, the parameter file is not accessible...
Expand Down
9 changes: 6 additions & 3 deletions include/redisx-priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,13 @@ typedef struct {
#if WITH_TLS
typedef struct {
boolean enabled; ///< Whether TLS is enabled.
char *certificate; ///< Certificate (mutual TLS only)
char *key; ///< Private key (mutual TLS only)
char *ca_path; ///< Directory in which CA certificates reside
char *ca_certificate; ///< CA sertificate
char *dh_params; ///< (optional) parameter file for DH based cyphers
char *certificate; ///< Client certificate (mutual TLS only)
char *key; ///< Client private key (mutual TLS only)
char *dh_params; ///< (optional) parameter file for DH based ciphers
char *ciphers; ///< colon separated list of ciphers to try
char *hostname; ///< Server name for SNI
} TLSConfig;
#endif

Expand Down
6 changes: 4 additions & 2 deletions include/redisx.h
Original file line number Diff line number Diff line change
Expand Up @@ -405,9 +405,11 @@ Redis *redisxInitSentinel(const char *serviceName, const RedisServer *serverList
int redisxValidateSentinel(const char *serviceName, const RedisServer *serverList, int nServers);
int redisxCheckValid(const Redis *redis);
void redisxDestroy(Redis *redis);
int redisxSetTLS(Redis *redis, const char *ca_file);
int redisxSetTLS(Redis *redis, const char *ca_path, const char *ca_file);
int redisxSetMutualTLS(Redis *redis, const char *cert_file, const char *key_file);
int redisxSetDHCypherParams(Redis *redis, const char *dh_params_file);
int redisxSetTLSCiphers(Redis *redis, const char *cipher_list);
int redisxSetDHCipherParams(Redis *redis, const char *dh_params_file);
int redisxSetTLSServerName(Redis *redis, const char *host);
int redisxConnect(Redis *redis, boolean usePipeline);
void redisxDisconnect(Redis *redis);
int redisxReconnect(Redis *redis, boolean usePipeline);
Expand Down
Binary file modified man/man1/redisx-cli.1.gz
Binary file not shown.
30 changes: 29 additions & 1 deletion src/redisx-cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,13 @@ int main(int argc, const char *argv[]) {
const char *eval = NULL;
int verbose = 0;
int debug = 0;
int tls = 0;
char *ca_file = NULL;
char *ca_path = NULL;
char *cert_file = NULL;
char *key_file = NULL;
char *ciphers = NULL;
char *sni = NULL;

struct poptOption options[] = { //
{"host", 'h', POPT_ARG_STRING | POPT_ARGFLAG_SHOW_DEFAULT, &host, 0, "Server hostname.", "<hostname>"}, //
Expand All @@ -226,9 +233,19 @@ int main(int argc, const char *argv[]) {
{"delim", 'd', POPT_ARG_STRING | POPT_ARGFLAG_SHOW_DEFAULT, &delim, 0, "Delimiter between elements for raw format. " //
"You can use JSON convention for escaping special characters.", "<string>" //
},
{"group", 'D', POPT_ARG_STRING | POPT_ARGFLAG_SHOW_DEFAULT, &groupDelim, 0, "Group prefix for raw format. " //
{"prefix", 'D', POPT_ARG_STRING | POPT_ARGFLAG_SHOW_DEFAULT, &groupDelim, 0, "Group prefix for raw format. " //
"You can use JSON convention for escaping special characters.", "<string>" //
},
{"tls", 0, POPT_ARG_NONE, &tls, 0, "Establish a secure TLS connection.", NULL}, //
{"sni", 0, POPT_ARG_NONE, &sni, 0, "Server name indication for TLS.", "<host>"}, //
{"cacert", 0, POPT_ARG_STRING, &ca_file, 0, "CA Certificate file to verify with.", "<file>"}, //
{"cacertdir", 0, POPT_ARG_STRING, &ca_path, 0, "Directory where trusted CA certificates are stored. If neither cacert nor cacertdir are "
"specified, the default system-wide trusted root certs configuration will apply.", "<path>" //
},
{"cert", 0, POPT_ARG_STRING, &cert_file, 0, "Client certificate to authenticate with.", "<path>"}, //
{"key", 0, POPT_ARG_STRING, &key_file, 0, "Private key file to authenticate with.", "<path>"}, //
{"tls-ciphers", 0, POPT_ARG_STRING, &ciphers, 0, "Sets the list of preferred ciphers (TLSv1.2 and below). in order of preference from "
"highest to lowest separated by colon (':').", "<list>"}, //
{"show-pushes", 0, POPT_ARG_STRING | POPT_ARGFLAG_SHOW_DEFAULT, &push, 0, "Whether to print RESP3 PUSH messages.", "yes|no" }, //
{"attributes", 0, POPT_ARG_NONE, &attrib, 0, "Show RESP3 attributes also, if available.", NULL}, //
{"eval", 0, POPT_ARG_STRING, &push, 0, "Send an EVAL command using the Lua script at <file>. " //
Expand Down Expand Up @@ -305,6 +322,17 @@ int main(int argc, const char *argv[]) {

if(push) if(strcmp("y", push) == 0 || strcmp("yes", push) == 0) redisxSetPushProcessor(redis, PushProcessor, NULL);

if(tls) {
if(sni) redisxSetTLSServerName(redis, sni);
if(ca_path || ca_file) redisxSetTLS(redis, ca_path, ca_file);
if(cert_file && key_file) redisxSetMutualTLS(redis, cert_file, key_file);
else if(cert_file && key_file) {
fprintf(stderr, "ERROR! Need both --cert and --key options.\n");
exit(1);
}
if(ciphers) redisxSetTLSCiphers(redis, ciphers);
}

xSetDebug(1);
prop_error(fn, redisxConnect(redis, 0));

Expand Down
141 changes: 119 additions & 22 deletions src/redisx-tls.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* @author Attila Kovacs
*/

#define _XOPEN_SOURCE 500 ///< for strdup()

#include <stdio.h>
#include <unistd.h>
#include <string.h>
Expand All @@ -25,7 +27,7 @@ static int initialized = FALSE;
/**
* Shuts down SSL and frees up the SSL-related resources on a client.
*
* @param cp Private client data
* @param cp Private client data.
*
* @sa rConnectTLSClient()
*/
Expand All @@ -44,8 +46,8 @@ void rDestroyClientTLS(ClientPrivate *cp) {
/**
* Connects a client using the specified TLS configuration.
*
* @param cp Private client data
* @param tls TLS configuration
* @param cp Private client data.
* @param tls TLS configuration.
* @return X_SUCCESS (0) if successful, or else an error code &lt;0.
*/
int rConnectTLSClient(ClientPrivate *cp, const TLSConfig *tls) {
Expand Down Expand Up @@ -76,6 +78,11 @@ int rConnectTLSClient(ClientPrivate *cp, const TLSConfig *tls) {
goto abort; // @suppress("Goto statement used")
}

if(tls->ca_certificate || tls->ca_path) if(!SSL_CTX_load_verify_locations(cp->ctx, tls->ca_certificate, tls->ca_path)) {
x_error(0, errno, fn, "Failed to set CA certificate: %s / %s", tls->ca_path, tls->ca_certificate);
goto abort; // @suppress("Goto statement used")
}

if(tls->certificate && tls->key) {
/* Set the key and cert */
if (SSL_CTX_use_certificate_file(cp->ctx, tls->certificate, SSL_FILETYPE_PEM) <= 0) {
Expand All @@ -89,14 +96,29 @@ int rConnectTLSClient(ClientPrivate *cp, const TLSConfig *tls) {
if(redisxIsVerbose()) ERR_print_errors_fp(stderr);
goto abort; // @suppress("Goto statement used")
}

if(!SSL_CTX_check_private_key(cp->ctx)) {
x_error(0, errno, fn, "Private key does not match the certificate public key.");
goto abort; // @suppress("Goto statement used")
}
}

if(tls->ciphers) if(!SSL_CTX_set_cipher_list(cp->ctx, tls->ciphers)) {
x_error(0, errno, fn, "Failed to set ciphers %s", tls->ciphers);
goto abort; // @suppress("Goto statement used")
}

if(tls->dh_params) if(!SSL_CTX_set_tmp_dh(cp->ctx, tls->dh_params)) {
x_error(0, errno, fn, "Failed to set DH-based cypher parameters from: %s", tls->dh_params);
goto abort; // @suppress("Goto statement used")
}

cp->ssl = SSL_new(cp->ctx);

SSL_set_fd(cp->ssl, cp->socket);

if(tls->hostname) SSL_set_tlsext_host_name(cp->ssl, tls->hostname);

if(!SSL_connect(cp->ssl)) {
x_error(0, errno, fn, "TLS connect failed");
goto abort; // @suppress("Goto statement used")
Expand Down Expand Up @@ -146,31 +168,34 @@ int rConnectTLSClient(ClientPrivate *cp, const TLSConfig *tls) {
* mutual authentication. Additionally, you might also want to set parameters for DH-based cyphers if
* needed using redisxSetDHCypherParams().
*
* @param redis A Redis instance
* @param ca_file Path to the CA certificate file
* @param redis A Redis instance.
* @param ca_path Directory containing CA certificates. It may be NULL to use the default locations.
* @param ca_file CA certificate file rel. to specified directory. It may be NULL to use default certificate.
* @return X_SUCCESS (0) if successful, or else an error code &lt;0.
*
* @sa redisxSetMutualTLS()
* @sa redisxSetDHCypherParams()
* @sa redisxSetDHCipherParams()
* @sa redisxSetTLSCiphers()
* @sa redisxSetTLSServerName()
*/
int redisxSetTLS(Redis *redis, const char *ca_file) {
static const char *fn = "redisxSetCA";
int redisxSetTLS(Redis *redis, const char *ca_path, const char *ca_file) {
static const char *fn = "redisxSetTLS";

#if WITH_TLS
RedisPrivate *p;
TLSConfig *tls;

if(!ca_file) return x_error(X_NULL, EINVAL, fn, "CA file is NULL");

if(access(ca_file, R_OK) != 0) return x_error(X_FAILURE, errno, fn, "CA file not readable: %s", ca_file);
if(ca_path) if(access(ca_path, R_OK | X_OK) != 0) return x_error(X_FAILURE, errno, fn, "CA path not accessible: %s", ca_file);
if(ca_file) if(access(ca_file, R_OK) != 0) return x_error(X_FAILURE, errno, fn, "CA file not readable: %s", ca_file);

prop_error(fn, rConfigLock(redis));

p = (RedisPrivate *) redis->priv;
tls = &p->config.tls;

tls->enabled = TRUE;
tls->ca_certificate = (char *) ca_file;
tls->ca_path = ca_path ? strdup(ca_path) : NULL;
tls->ca_certificate = ca_file ? strdup(ca_file) : NULL;

rConfigUnlock(redis);

Expand All @@ -190,9 +215,9 @@ int redisxSetTLS(Redis *redis, const char *ca_file) {
* configure Redis servers to verify one way only with a CA certificate, in which case you don't need to call this to
* configure the client.
*
* @param redis A Redis instance
* @param cert_file Path to the server's certificate file
* @param key_file Path to the server'sprivate key file
* @param redis A Redis instance.
* @param cert_file Path to the server's certificate file.
* @param key_file Path to the server'sprivate key file.
* @return X_SUCCESS (0) if successful, or else an error code &lt;0.
*
* @sa redisxSetTLS()
Expand All @@ -214,8 +239,8 @@ int redisxSetMutualTLS(Redis *redis, const char *cert_file, const char *key_file
p = (RedisPrivate *) redis->priv;
tls = &p->config.tls;

tls->certificate = (char *) cert_file;
tls->key = (char *) key_file;
tls->certificate = strdup(cert_file);
tls->key = strdup(key_file);

rConfigUnlock(redis);

Expand All @@ -229,18 +254,54 @@ int redisxSetMutualTLS(Redis *redis, const char *cert_file, const char *key_file
#endif
}

/**
* Sets the TLS ciphers to try.
*
* @param redis A Redis instance.
* @param cipher_list a colon (:) separated list of ciphers.
* @return X_SUCCESS (0) if successful, or else an error code &lt;0.
*
* @sa redisxSetTLS()
* @sa redisxSetDHCipherParams()
*/
int redisxSetTLSCiphers(Redis *redis, const char *cipher_list) {
static const char *fn = "redisxSetTLSCiphers";

#if WITH_TLS
RedisPrivate *p;
TLSConfig *tls;

prop_error(fn, rConfigLock(redis));

p = (RedisPrivate *) redis->priv;
tls = &p->config.tls;

tls->enabled = TRUE;
tls->ca_certificate = strdup(cipher_list);

rConfigUnlock(redis);

return X_SUCCESS;
#else
(void) redis;
(void) ca_file;

return x_error(X_FAILURE, ENOSYS, fn, "RedisX was built without TLS support");
#endif
}

/**
* Sets parameters for DH-based cyphers when using a TLS encrypted connection to Redis.
*
* @param redis A Redis instance
* @param dh_params_file Path to the DH-based cypher parameters file
* @param redis A Redis instance.
* @param dh_params_file Path to the DH-based cypher parameters file.
* @return X_SUCCESS (0) if successful, or else an error code &lt;0.
*
* @sa redisxSetTLS()
* @sa redisxSetTLSCiphers()
*/
int redisxSetDHCypherParams(Redis *redis, const char *dh_params_file) {
static const char *fn = "redisxSetDHCypherParams";
int redisxSetDHCipherParams(Redis *redis, const char *dh_params_file) {
static const char *fn = "redisxSetDHCipherParams";

#if WITH_TLS
RedisPrivate *p;
Expand All @@ -255,7 +316,7 @@ int redisxSetDHCypherParams(Redis *redis, const char *dh_params_file) {
p = (RedisPrivate *) redis->priv;
tls = &p->config.tls;

tls->dh_params = (char *) dh_params_file;
tls->dh_params = strdup(dh_params_file);

rConfigUnlock(redis);

Expand All @@ -267,3 +328,39 @@ int redisxSetDHCypherParams(Redis *redis, const char *dh_params_file) {
return x_error(X_FAILURE, ENOSYS, fn, "RedisX was built without TLS support");
#endif
}

/**
* Sets the Server name for TLS Server Name Indication (SNI), an optional extra later of security.
*
* @param redis A Redis instance.
* @param host server name to use for SNI.
* @return X_SUCCESS (0)
*
* @sa redisxSetTLS()
*/
int redisxSetTLSServerName(Redis *redis, const char *host) {
static const char *fn = "redisxSetDHCipherParams";

#if WITH_TLS
RedisPrivate *p;
TLSConfig *tls;

prop_error(fn, rConfigLock(redis));

p = (RedisPrivate *) redis->priv;
tls = &p->config.tls;

tls->hostname = strdup(host);

rConfigUnlock(redis);

return X_SUCCESS;
#else
(void) redis;
(void) host;

return x_error(X_FAILURE, ENOSYS, fn, "RedisX was built without TLS support");
#endif

}

0 comments on commit 6ff6bcd

Please sign in to comment.