New SSL API to replace redisSecureConnection().

This commit is contained in:
Yossi Gottlieb 2020-05-22 14:10:08 +03:00
parent 8e0264cfd6
commit 190bca88d0
6 changed files with 321 additions and 152 deletions

View File

@ -438,42 +438,68 @@ First, you'll need to make sure you include the SSL header file:
#include "hiredis_ssl.h" #include "hiredis_ssl.h"
``` ```
SSL can only be enabled on a `redisContext` connection after the connection has You will also need to link against `libhiredis_ssl`, **in addition** to
been established and before any command has been processed. For example: `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 ```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); c = redisConnect("localhost", 6443);
if (c == NULL || c->err) { if (c == NULL || c->err) {
/* Handle error and abort... */ /* Handle error and abort... */
} }
if (redisSecureConnection(c, /* Negotiate SSL/TLS */
"cacertbundle.crt", /* File name of trusted CA/ca bundle file */ if (redisInitiateSSLWithContext(c, ssl) != REDIS_OK) {
"client_cert.pem", /* File name of client certificate file */ /* Handle error, in c->err / c->errstr */
"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... */
} }
``` ```
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.
## AUTHORS ## AUTHORS
Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and

View File

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

View File

@ -7,6 +7,8 @@
int main(int argc, char **argv) { int main(int argc, char **argv) {
unsigned int j; unsigned int j;
redisSSLContext *ssl;
redisSSLContextError ssl_error;
redisContext *c; redisContext *c;
redisReply *reply; redisReply *reply;
if (argc < 4) { if (argc < 4) {
@ -19,6 +21,14 @@ int main(int argc, char **argv) {
const char *key = argv[4]; const char *key = argv[4];
const char *ca = argc > 4 ? argv[5] : NULL; 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 struct timeval tv = { 1, 500000 }; // 1.5 seconds
redisOptions options = {0}; redisOptions options = {0};
REDIS_OPTIONS_SET_TCP(&options, hostname, port); REDIS_OPTIONS_SET_TCP(&options, hostname, port);
@ -35,7 +45,7 @@ int main(int argc, char **argv) {
exit(1); exit(1);
} }
if (redisSecureConnection(c, ca, cert, key, "sni") != REDIS_OK) { if (redisInitiateSSLWithContext(c, ssl) != REDIS_OK) {
printf("Couldn't initialize SSL!\n"); printf("Couldn't initialize SSL!\n");
printf("Error: %s\n", c->errstr); printf("Error: %s\n", c->errstr);
redisFree(c); redisFree(c);
@ -93,5 +103,7 @@ int main(int argc, char **argv) {
/* Disconnects and frees the context */ /* Disconnects and frees the context */
redisFree(c); redisFree(c);
redisFreeSSLContext(ssl);
return 0; return 0;
} }

View File

@ -41,15 +41,81 @@ extern "C" {
*/ */
struct ssl_st; struct ssl_st;
/** /* A wrapper around OpenSSL SSL_CTX to allow easy SSL use without directly
* Secure the connection using SSL. This should be done before any command is * calling OpenSSL.
* executed on the connection.
*/ */
int redisSecureConnection(redisContext *c, const char *capath, const char *certpath, typedef struct redisSSLContext redisSSLContext;
const char *keypath, const char *servername);
/** /**
* Initiate SSL/TLS negotiation on a provided context. * 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);
/**
* 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.
*/
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 OpenSSL SSL object.
*/ */
int redisInitiateSSL(redisContext *c, struct ssl_st *ssl); int redisInitiateSSL(redisContext *c, struct ssl_st *ssl);

266
ssl.c
View File

@ -47,17 +47,20 @@
#include "win32.h" #include "win32.h"
#include "async_private.h" #include "async_private.h"
#include "hiredis_ssl.h"
void __redisSetError(redisContext *c, int type, const char *str); void __redisSetError(redisContext *c, int type, const char *str);
/* The SSL context is attached to SSL/TLS connections as a privdata. */ struct redisSSLContext {
typedef struct redisSSLContext { /* Associated OpenSSL SSL_CTX as created by redisCreateSSLContext() */
/**
* OpenSSL SSL_CTX; It is optional and will not be set when using
* user-supplied SSL.
*/
SSL_CTX *ssl_ctx; SSL_CTX *ssl_ctx;
/* Requested SNI, or NULL */
char *server_name;
};
/* The SSL connection context is attached to SSL/TLS connections as a privdata. */
typedef struct redisSSL {
/** /**
* OpenSSL SSL object. * OpenSSL SSL object.
*/ */
@ -77,43 +80,11 @@ typedef struct redisSSLContext {
* should resume whenever a read takes place, if possible * should resume whenever a read takes place, if possible
*/ */
int pendingWrite; int pendingWrite;
} redisSSLContext; } redisSSL;
/* Forward declaration */ /* Forward declaration */
redisContextFuncs redisContextSSLFuncs; redisContextFuncs redisContextSSLFuncs;
#ifdef HIREDIS_SSL_TRACE
/**
* Callback used for debugging
*/
static void sslLogCallback(const SSL *ssl, int where, int ret) {
const char *retstr;
int should_log = 0;
/* Ignore low-level SSL stuff */
if (where & SSL_CB_ALERT) {
should_log = 1;
}
if (where == SSL_CB_HANDSHAKE_START || where == SSL_CB_HANDSHAKE_DONE) {
should_log = 1;
}
if ((where & SSL_CB_EXIT) && ret == 0) {
should_log = 1;
}
if (!should_log) {
return;
}
retstr = SSL_alert_type_string(ret);
printf("ST(0x%x). %s. R(0x%x)%s\n", where, SSL_state_string_long(ssl), ret, retstr);
if (where == SSL_CB_HANDSHAKE_DONE) {
printf("Using SSL version %s. Cipher=%s\n", SSL_get_version(ssl), SSL_get_cipher_name(ssl));
}
}
#endif
/** /**
* OpenSSL global initialization and locking handling callbacks. * OpenSSL global initialization and locking handling callbacks.
* Note that this is only required for OpenSSL < 1.1.0. * Note that this is only required for OpenSSL < 1.1.0.
@ -182,26 +153,130 @@ static int initOpensslLocks(void) {
} }
#endif /* HIREDIS_USE_CRYPTO_LOCKS */ #endif /* HIREDIS_USE_CRYPTO_LOCKS */
int redisInitOpenSSL(void)
{
SSL_library_init();
#ifdef HIREDIS_USE_CRYPTO_LOCKS
initOpensslLocks();
#endif
return REDIS_OK;
}
/**
* redisSSLContext helper context destruction.
*/
const char *redisSSLContextGetError(redisSSLContextError error)
{
switch (error) {
case REDIS_SSL_CTX_NONE:
return "No Error";
case REDIS_SSL_CTX_CREATE_FAILED:
return "Failed to create OpenSSL SSL_CTX";
case REDIS_SSL_CTX_CERT_KEY_REQUIRED:
return "Client cert and key must both be specified or skipped";
case REDIS_SSL_CTX_CA_CERT_LOAD_FAILED:
return "Failed to load CA Certificate or CA Path";
case REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED:
return "Failed to load client certificate";
case REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED:
return "Failed to load private key";
default:
return "Unknown error code";
}
}
void redisFreeSSLContext(redisSSLContext *ctx)
{
if (!ctx)
return;
if (ctx->server_name) {
hi_free(ctx->server_name);
ctx->server_name = NULL;
}
if (ctx->ssl_ctx) {
SSL_CTX_free(ctx->ssl_ctx);
ctx->ssl_ctx = NULL;
}
hi_free(ctx);
}
/**
* redisSSLContext helper context initialization.
*/
redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *capath,
const char *cert_filename, const char *private_key_filename,
const char *server_name, redisSSLContextError *error)
{
redisSSLContext *ctx = hi_calloc(1, sizeof(redisSSLContext));
ctx->ssl_ctx = SSL_CTX_new(SSLv23_client_method());
if (!ctx->ssl_ctx) {
if (error) *error = REDIS_SSL_CTX_CREATE_FAILED;
goto error;
}
SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
SSL_CTX_set_verify(ctx->ssl_ctx, SSL_VERIFY_PEER, NULL);
if ((cert_filename != NULL && private_key_filename == NULL) ||
(private_key_filename != NULL && cert_filename == NULL)) {
if (error) *error = REDIS_SSL_CTX_CERT_KEY_REQUIRED;
goto error;
}
if (capath || cacert_filename) {
if (!SSL_CTX_load_verify_locations(ctx->ssl_ctx, cacert_filename, capath)) {
if (error) *error = REDIS_SSL_CTX_CA_CERT_LOAD_FAILED;
goto error;
}
}
if (cert_filename) {
if (!SSL_CTX_use_certificate_chain_file(ctx->ssl_ctx, cert_filename)) {
if (error) *error = REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED;
goto error;
}
if (!SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, private_key_filename, SSL_FILETYPE_PEM)) {
if (error) *error = REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED;
goto error;
}
}
if (server_name)
ctx->server_name = hi_strdup(server_name);
return ctx;
error:
redisFreeSSLContext(ctx);
return NULL;
}
/** /**
* SSL Connection initialization. * SSL Connection initialization.
*/ */
static int redisSSLConnect(redisContext *c, SSL_CTX *ssl_ctx, SSL *ssl) {
redisSSLContext *rssl;
static int redisSSLConnect(redisContext *c, SSL *ssl) {
if (c->privdata) { if (c->privdata) {
__redisSetError(c, REDIS_ERR_OTHER, "redisContext was already associated"); __redisSetError(c, REDIS_ERR_OTHER, "redisContext was already associated");
return REDIS_ERR; return REDIS_ERR;
} }
rssl = hi_calloc(1, sizeof(redisSSLContext)); redisSSL *rssl = hi_calloc(1, sizeof(redisSSL));
if (rssl == NULL) { if (rssl == NULL) {
__redisSetError(c, REDIS_ERR_OOM, "Out of memory"); __redisSetError(c, REDIS_ERR_OOM, "Out of memory");
return REDIS_ERR; return REDIS_ERR;
} }
c->funcs = &redisContextSSLFuncs; c->funcs = &redisContextSSLFuncs;
rssl->ssl_ctx = ssl_ctx;
rssl->ssl = ssl; rssl->ssl = ssl;
SSL_set_mode(rssl->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); SSL_set_mode(rssl->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
@ -238,84 +313,53 @@ static int redisSSLConnect(redisContext *c, SSL_CTX *ssl_ctx, SSL *ssl) {
return REDIS_ERR; return REDIS_ERR;
} }
/**
* A wrapper around redisSSLConnect() for users who manage their own context and
* create their own SSL object.
*/
int redisInitiateSSL(redisContext *c, SSL *ssl) { int redisInitiateSSL(redisContext *c, SSL *ssl) {
return redisSSLConnect(c, NULL, ssl); return redisSSLConnect(c, ssl);
} }
int redisSecureConnection(redisContext *c, const char *capath, /**
const char *certpath, const char *keypath, const char *servername) { * A wrapper around redisSSLConnect() for users who use redisSSLContext and don't
* manage their own SSL objects.
*/
SSL_CTX *ssl_ctx = NULL; int redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx)
SSL *ssl = NULL; {
if (!c || !redis_ssl_ctx)
return REDIS_ERR;
/* Initialize global OpenSSL stuff */ /* We want to verify that redisSSLConnect() won't fail on this, as it will
static int isInit = 0; * not own the SSL object in that case and we'll end up leaking.
if (!isInit) { */
isInit = 1; if (c->privdata)
SSL_library_init(); return REDIS_ERR;
#ifdef HIREDIS_USE_CRYPTO_LOCKS
if (initOpensslLocks() == REDIS_ERR) {
__redisSetError(c, REDIS_ERR_OOM, "Out of memory");
goto error;
}
#endif
}
ssl_ctx = SSL_CTX_new(SSLv23_client_method()); SSL *ssl = SSL_new(redis_ssl_ctx->ssl_ctx);
if (!ssl_ctx) {
__redisSetError(c, REDIS_ERR_OTHER, "Failed to create SSL_CTX");
goto error;
}
#ifdef HIREDIS_SSL_TRACE
SSL_CTX_set_info_callback(ssl_ctx, sslLogCallback);
#endif
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 ((certpath != NULL && keypath == NULL) || (keypath != NULL && certpath == NULL)) {
__redisSetError(c, REDIS_ERR_OTHER, "certpath and keypath must be specified together");
goto error;
}
if (capath) {
if (!SSL_CTX_load_verify_locations(ssl_ctx, capath, NULL)) {
__redisSetError(c, REDIS_ERR_OTHER, "Invalid CA certificate");
goto error;
}
}
if (certpath) {
if (!SSL_CTX_use_certificate_chain_file(ssl_ctx, certpath)) {
__redisSetError(c, REDIS_ERR_OTHER, "Invalid client certificate");
goto error;
}
if (!SSL_CTX_use_PrivateKey_file(ssl_ctx, keypath, SSL_FILETYPE_PEM)) {
__redisSetError(c, REDIS_ERR_OTHER, "Invalid client key");
goto error;
}
}
ssl = SSL_new(ssl_ctx);
if (!ssl) { if (!ssl) {
__redisSetError(c, REDIS_ERR_OTHER, "Couldn't create new SSL instance"); __redisSetError(c, REDIS_ERR_OTHER, "Couldn't create new SSL instance");
goto error; goto error;
} }
if (servername) {
if (!SSL_set_tlsext_host_name(ssl, servername)) { if (redis_ssl_ctx->server_name) {
__redisSetError(c, REDIS_ERR_OTHER, "Couldn't set server name indication"); if (!SSL_set_tlsext_host_name(ssl, redis_ssl_ctx->server_name)) {
__redisSetError(c, REDIS_ERR_OTHER, "Failed to set server_name/SNI");
goto error; goto error;
} }
} }
if (redisSSLConnect(c, ssl_ctx, ssl) == REDIS_OK) return redisSSLConnect(c, ssl);
return REDIS_OK;
error: error:
if (ssl) SSL_free(ssl); if (ssl)
if (ssl_ctx) SSL_CTX_free(ssl_ctx); SSL_free(ssl);
return REDIS_ERR; return REDIS_ERR;
} }
static int maybeCheckWant(redisSSLContext *rssl, int rv) { static int maybeCheckWant(redisSSL *rssl, int rv) {
/** /**
* If the error is WANT_READ or WANT_WRITE, the appropriate flags are set * If the error is WANT_READ or WANT_WRITE, the appropriate flags are set
* and true is returned. False is returned otherwise * and true is returned. False is returned otherwise
@ -335,23 +379,19 @@ static int maybeCheckWant(redisSSLContext *rssl, int rv) {
* Implementation of redisContextFuncs for SSL connections. * Implementation of redisContextFuncs for SSL connections.
*/ */
static void redisSSLFreeContext(void *privdata){ static void redisSSLFree(void *privdata){
redisSSLContext *rsc = privdata; redisSSL *rsc = privdata;
if (!rsc) return; if (!rsc) return;
if (rsc->ssl) { if (rsc->ssl) {
SSL_free(rsc->ssl); SSL_free(rsc->ssl);
rsc->ssl = NULL; rsc->ssl = NULL;
} }
if (rsc->ssl_ctx) {
SSL_CTX_free(rsc->ssl_ctx);
rsc->ssl_ctx = NULL;
}
hi_free(rsc); hi_free(rsc);
} }
static int redisSSLRead(redisContext *c, char *buf, size_t bufcap) { static int redisSSLRead(redisContext *c, char *buf, size_t bufcap) {
redisSSLContext *rssl = c->privdata; redisSSL *rssl = c->privdata;
int nread = SSL_read(rssl->ssl, buf, bufcap); int nread = SSL_read(rssl->ssl, buf, bufcap);
if (nread > 0) { if (nread > 0) {
@ -393,7 +433,7 @@ static int redisSSLRead(redisContext *c, char *buf, size_t bufcap) {
} }
static int redisSSLWrite(redisContext *c) { static int redisSSLWrite(redisContext *c) {
redisSSLContext *rssl = c->privdata; redisSSL *rssl = c->privdata;
size_t len = rssl->lastLen ? rssl->lastLen : sdslen(c->obuf); size_t len = rssl->lastLen ? rssl->lastLen : sdslen(c->obuf);
int rv = SSL_write(rssl->ssl, c->obuf, len); int rv = SSL_write(rssl->ssl, c->obuf, len);
@ -416,7 +456,7 @@ static int redisSSLWrite(redisContext *c) {
static void redisSSLAsyncRead(redisAsyncContext *ac) { static void redisSSLAsyncRead(redisAsyncContext *ac) {
int rv; int rv;
redisSSLContext *rssl = ac->c.privdata; redisSSL *rssl = ac->c.privdata;
redisContext *c = &ac->c; redisContext *c = &ac->c;
rssl->wantRead = 0; rssl->wantRead = 0;
@ -446,7 +486,7 @@ static void redisSSLAsyncRead(redisAsyncContext *ac) {
static void redisSSLAsyncWrite(redisAsyncContext *ac) { static void redisSSLAsyncWrite(redisAsyncContext *ac) {
int rv, done = 0; int rv, done = 0;
redisSSLContext *rssl = ac->c.privdata; redisSSL *rssl = ac->c.privdata;
redisContext *c = &ac->c; redisContext *c = &ac->c;
rssl->pendingWrite = 0; rssl->pendingWrite = 0;
@ -475,7 +515,7 @@ static void redisSSLAsyncWrite(redisAsyncContext *ac) {
} }
redisContextFuncs redisContextSSLFuncs = { redisContextFuncs redisContextSSLFuncs = {
.free_privdata = redisSSLFreeContext, .free_privdata = redisSSLFree,
.async_read = redisSSLAsyncRead, .async_read = redisSSLAsyncRead,
.async_write = redisSSLAsyncWrite, .async_write = redisSSLAsyncWrite,
.read = redisSSLRead, .read = redisSSLRead,

21
test.c
View File

@ -48,6 +48,10 @@ struct config {
} ssl; } ssl;
}; };
#ifdef HIREDIS_TEST_SSL
redisSSLContext *_ssl_ctx = NULL;
#endif
/* The following lines make up our testing "framework" :) */ /* The following lines make up our testing "framework" :) */
static int tests = 0, fails = 0, skips = 0; static int tests = 0, fails = 0, skips = 0;
#define test(_s) { printf("#%02d ", ++tests); printf(_s); } #define test(_s) { printf("#%02d ", ++tests); printf(_s); }
@ -113,9 +117,9 @@ static int disconnect(redisContext *c, int keep_fd) {
return -1; return -1;
} }
static void do_ssl_handshake(redisContext *c, struct config config) { static void do_ssl_handshake(redisContext *c) {
#ifdef HIREDIS_TEST_SSL #ifdef HIREDIS_TEST_SSL
redisSecureConnection(c, config.ssl.ca_cert, config.ssl.cert, config.ssl.key, NULL); redisInitiateSSLWithContext(c, _ssl_ctx);
if (c->err) { if (c->err) {
printf("SSL error: %s\n", c->errstr); printf("SSL error: %s\n", c->errstr);
redisFree(c); redisFree(c);
@ -123,7 +127,6 @@ static void do_ssl_handshake(redisContext *c, struct config config) {
} }
#else #else
(void) c; (void) c;
(void) config;
#endif #endif
} }
@ -158,7 +161,7 @@ static redisContext *do_connect(struct config config) {
} }
if (config.type == CONN_SSL) { if (config.type == CONN_SSL) {
do_ssl_handshake(c, config); do_ssl_handshake(c);
} }
return select_database(c); return select_database(c);
@ -168,7 +171,7 @@ static void do_reconnect(redisContext *c, struct config config) {
redisReconnect(c); redisReconnect(c);
if (config.type == CONN_SSL) { if (config.type == CONN_SSL) {
do_ssl_handshake(c, config); do_ssl_handshake(c);
} }
} }
@ -1147,6 +1150,11 @@ int main(int argc, char **argv) {
#ifdef HIREDIS_TEST_SSL #ifdef HIREDIS_TEST_SSL
if (cfg.ssl.port && cfg.ssl.host) { if (cfg.ssl.port && cfg.ssl.host) {
redisInitOpenSSL();
_ssl_ctx = redisCreateSSLContext(cfg.ssl.ca_cert, NULL, cfg.ssl.cert, cfg.ssl.key, NULL, NULL);
assert(_ssl_ctx != NULL);
printf("\nTesting against SSL connection (%s:%d):\n", cfg.ssl.host, cfg.ssl.port); printf("\nTesting against SSL connection (%s:%d):\n", cfg.ssl.host, cfg.ssl.port);
cfg.type = CONN_SSL; cfg.type = CONN_SSL;
@ -1156,6 +1164,9 @@ int main(int argc, char **argv) {
test_invalid_timeout_errors(cfg); test_invalid_timeout_errors(cfg);
test_append_formatted_commands(cfg); test_append_formatted_commands(cfg);
if (throughput) test_throughput(cfg); if (throughput) test_throughput(cfg);
redisFreeSSLContext(_ssl_ctx);
_ssl_ctx = NULL;
} }
#endif #endif