Skip to content

Commit

Permalink
Merge branch 'new-ssl-api'
Browse files Browse the repository at this point in the history
  • Loading branch information
michael-grunder committed May 30, 2020
2 parents e553e0f + 904bf7f commit 4152bfc
Show file tree
Hide file tree
Showing 6 changed files with 322 additions and 151 deletions.
78 changes: 52 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -438,42 +438,68 @@ First, you'll need to make sure you include the SSL header file:
#include "hiredis_ssl.h"
```

SSL can only be enabled on a `redisContext` connection after the connection has
been established and before any command has been processed. For example:
You will also need to link against `libhiredis_ssl`, **in addition** to
`libhiredis` and add `-lssl -lcrypto` to satisfy its dependencies.

Hiredis implements SSL/TLS on top of its normal `redisContext` or
`redisAsyncContext`, so you will need to establish a connection first and then
initiate an SSL/TLS handshake.

#### Hiredis OpenSSL Wrappers

Before Hiredis can negotiate an SSL/TLS connection, it is necessary to
initialize OpenSSL and create a context. You can do that in two ways:

1. Work directly with the OpenSSL API to initialize the library's global context
and create `SSL_CTX *` and `SSL *` contexts. With an `SSL *` object you can
call `redisInitiateSSL()`.
2. Work with a set of Hiredis-provided wrappers around OpenSSL, create a
`redisSSLContext` object to hold configuration and use
`redisInitiateSSLWithContext()` to initiate the SSL/TLS handshake.

```c
/* An Hiredis SSL context. It holds SSL configuration and can be reused across
* many contexts.
*/
redisSSLContext *ssl;

/* An error variable to indicate what went wrong, if the context fails to
* initialize.
*/
redisSSLContextError ssl_error;

/* Initialize global OpenSSL state.
*
* You should call this only once when your app initializes, and only if
* you don't explicitly or implicitly initialize OpenSSL it elsewhere.
*/
redisInitOpenSSL();

/* Create SSL context */
ssl = redisCreateSSLContext(
"cacertbundle.crt", /* File name of trusted CA/ca bundle file, optional */
"/path/to/certs", /* Path of trusted certificates, optional */
"client_cert.pem", /* File name of client certificate file, optional */
"client_key.pem", /* File name of client private key, optional */
"redis.mydomain.com", /* Server name to request (SNI), optional */
&ssl_error
) != REDIS_OK) {
printf("SSL error: %s\n", redisSSLContextGetError(ssl_error);
/* Abort... */
}

/* Create Redis context and establish connection */
c = redisConnect("localhost", 6443);
if (c == NULL || c->err) {
/* Handle error and abort... */
}

if (redisSecureConnection(c,
"cacertbundle.crt", /* File name of trusted CA/ca bundle file */
"client_cert.pem", /* File name of client certificate file */
"client_key.pem", /* File name of client private key */
"redis.mydomain.com" /* Server name to request (SNI) */
) != REDIS_OK) {
printf("SSL error: %s\n", c->errstr);
/* Abort... */
/* Negotiate SSL/TLS */
if (redisInitiateSSLWithContext(c, ssl) != REDIS_OK) {
/* Handle error, in c->err / c->errstr */
}
```

You will also need to link against `libhiredis_ssl`, **in addition** to
`libhiredis` and add `-lssl -lcrypto` to satisfy its dependencies.

### OpenSSL Global State Initialization

OpenSSL needs to have certain global state initialized before it can be used.
Using `redisSecureConnection()` will handle this automatically on the first
call.

**If the calling application itself also initializes and uses OpenSSL directly,
`redisSecureConnection()` must not be used.**

Instead, use `redisInitiateSSL()` which also provides greater control over the
configuration of the SSL connection, as the caller is responsible to create a
connection context using `SSL_new()` and configure it as required.

## Allocator injection

Hiredis uses a pass-thru structure of function pointers defined in
Expand Down
16 changes: 15 additions & 1 deletion examples/example-libevent-ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,25 @@ int main (int argc, char **argv) {
const char *certKey = argv[5];
const char *caCert = argc > 5 ? argv[6] : NULL;

redisSSLContext *ssl;
redisSSLContextError ssl_error;

redisInitOpenSSL();

ssl = redisCreateSSLContext(caCert, NULL,
cert, certKey, NULL, &ssl_error);
if (!ssl) {
printf("Error: %s\n", redisSSLContextGetError(ssl_error));
return 1;
}

redisAsyncContext *c = redisAsyncConnect(hostname, port);
if (c->err) {
/* Let *c leak for now... */
printf("Error: %s\n", c->errstr);
return 1;
}
if (redisSecureConnection(&c->c, caCert, cert, certKey, "sni") != REDIS_OK) {
if (redisInitiateSSLWithContext(&c->c, ssl) != REDIS_OK) {
printf("SSL Error!\n");
exit(1);
}
Expand All @@ -69,5 +81,7 @@ int main (int argc, char **argv) {
redisAsyncCommand(c, NULL, NULL, "SET key %b", value, nvalue);
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
event_base_dispatch(base);

redisFreeSSLContext(ssl);
return 0;
}
14 changes: 13 additions & 1 deletion examples/example-ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

int main(int argc, char **argv) {
unsigned int j;
redisSSLContext *ssl;
redisSSLContextError ssl_error;
redisContext *c;
redisReply *reply;
if (argc < 4) {
Expand All @@ -19,6 +21,14 @@ int main(int argc, char **argv) {
const char *key = argv[4];
const char *ca = argc > 4 ? argv[5] : NULL;

redisInitOpenSSL();
ssl = redisCreateSSLContext(ca, NULL, cert, key, NULL, &ssl_error);
if (!ssl) {
printf("SSL Context error: %s\n",
redisSSLContextGetError(ssl_error));
exit(1);
}

struct timeval tv = { 1, 500000 }; // 1.5 seconds
redisOptions options = {0};
REDIS_OPTIONS_SET_TCP(&options, hostname, port);
Expand All @@ -35,7 +45,7 @@ int main(int argc, char **argv) {
exit(1);
}

if (redisSecureConnection(c, ca, cert, key, "sni") != REDIS_OK) {
if (redisInitiateSSLWithContext(c, ssl) != REDIS_OK) {
printf("Couldn't initialize SSL!\n");
printf("Error: %s\n", c->errstr);
redisFree(c);
Expand Down Expand Up @@ -93,5 +103,7 @@ int main(int argc, char **argv) {
/* Disconnects and frees the context */
redisFree(c);

redisFreeSSLContext(ssl);

return 0;
}
76 changes: 71 additions & 5 deletions hiredis_ssl.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,81 @@ extern "C" {
*/
struct ssl_st;

/* A wrapper around OpenSSL SSL_CTX to allow easy SSL use without directly
* calling OpenSSL.
*/
typedef struct redisSSLContext redisSSLContext;

/**
* Initialization errors that redisCreateSSLContext() may return.
*/

typedef enum {
REDIS_SSL_CTX_NONE = 0, /* No Error */
REDIS_SSL_CTX_CREATE_FAILED, /* Failed to create OpenSSL SSL_CTX */
REDIS_SSL_CTX_CERT_KEY_REQUIRED, /* Client cert and key must both be specified or skipped */
REDIS_SSL_CTX_CA_CERT_LOAD_FAILED, /* Failed to load CA Certificate or CA Path */
REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED, /* Failed to load client certificate */
REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED /* Failed to load private key */
} redisSSLContextError;

/**
* Return the error message corresponding with the specified error code.
*/

const char *redisSSLContextGetError(redisSSLContextError error);

/**
* Helper function to initialize the OpenSSL library.
*
* OpenSSL requires one-time initialization before it can be used. Callers should
* call this function only once, and only if OpenSSL is not directly initialized
* elsewhere.
*/
int redisInitOpenSSL(void);

/**
* Secure the connection using SSL. This should be done before any command is
* executed on the connection.
* Helper function to initialize an OpenSSL context that can be used
* to initiate SSL connections.
*
* cacert_filename is an optional name of a CA certificate/bundle file to load
* and use for validation.
*
* capath is an optional directory path where trusted CA certificate files are
* stored in an OpenSSL-compatible structure.
*
* cert_filename and private_key_filename are optional names of a client side
* certificate and private key files to use for authentication. They need to
* be both specified or omitted.
*
* server_name is an optional and will be used as a server name indication
* (SNI) TLS extension.
*
* If error is non-null, it will be populated in case the context creation fails
* (returning a NULL).
*/

redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *capath,
const char *cert_filename, const char *private_key_filename,
const char *server_name, redisSSLContextError *error);

/**
* Free a previously created OpenSSL context.
*/
int redisSecureConnection(redisContext *c, const char *capath, const char *certpath,
const char *keypath, const char *servername);
void redisFreeSSLContext(redisSSLContext *redis_ssl_ctx);

/**
* Initiate SSL on an existing redisContext.
*
* This is similar to redisInitiateSSL() but does not require the caller
* to directly interact with OpenSSL, and instead uses a redisSSLContext
* previously created using redisCreateSSLContext().
*/

int redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx);

/**
* Initiate SSL/TLS negotiation on a provided context.
* Initiate SSL/TLS negotiation on a provided OpenSSL SSL object.
*/

int redisInitiateSSL(redisContext *c, struct ssl_st *ssl);
Expand Down
Loading

0 comments on commit 4152bfc

Please sign in to comment.