summaryrefslogtreecommitdiff
path: root/sslio.c
diff options
context:
space:
mode:
Diffstat (limited to 'sslio.c')
-rw-r--r--sslio.c343
1 files changed, 269 insertions, 74 deletions
diff --git a/sslio.c b/sslio.c
index f2f50a8..5c76370 100644
--- a/sslio.c
+++ b/sslio.c
@@ -1,5 +1,37 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ * Copyright (c) 2019, Redis Labs
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
#include "hiredis.h"
-#include "sslio.h"
+#include "async.h"
#include <assert.h>
#ifdef HIREDIS_SSL
@@ -7,10 +39,45 @@
#include <errno.h>
#include <string.h>
+#include <openssl/ssl.h>
#include <openssl/err.h>
+#include "async_private.h"
+
void __redisSetError(redisContext *c, int type, const char *str);
+/* The SSL context is attached to SSL/TLS connections as a privdata. */
+typedef struct redisSSLContext {
+ /**
+ * OpenSSL SSL_CTX; It is optional and will not be set when using
+ * user-supplied SSL.
+ */
+ SSL_CTX *ssl_ctx;
+
+ /**
+ * OpenSSL SSL object.
+ */
+ SSL *ssl;
+
+ /**
+ * SSL_write() requires to be called again with the same arguments it was
+ * previously called with in the event of an SSL_read/SSL_write situation
+ */
+ size_t lastLen;
+
+ /** Whether the SSL layer requires read (possibly before a write) */
+ int wantRead;
+
+ /**
+ * Whether a write was requested prior to a read. If set, the write()
+ * should resume whenever a read takes place, if possible
+ */
+ int pendingWrite;
+} redisSSLContext;
+
+/* Forward declaration */
+redisContextFuncs redisContextSSLFuncs;
+
#ifdef HIREDIS_SSL_TRACE
/**
* Callback used for debugging
@@ -43,6 +110,16 @@ static void sslLogCallback(const SSL *ssl, int where, int ret) {
}
#endif
+/**
+ * OpenSSL global initialization and locking handling callbacks.
+ * Note that this is only required for OpenSSL < 1.1.0.
+ */
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+#define HIREDIS_USE_CRYPTO_LOCKS
+#endif
+
+#ifdef HIREDIS_USE_CRYPTO_LOCKS
typedef pthread_mutex_t sslLockType;
static void sslLockInit(sslLockType *l) {
pthread_mutex_init(l, NULL);
@@ -81,102 +158,129 @@ static void initOpensslLocks(void) {
}
CRYPTO_set_locking_callback(opensslDoLock);
}
+#endif /* HIREDIS_USE_CRYPTO_LOCKS */
-void redisFreeSsl(redisSsl *ssl){
- if (ssl->ctx) {
- SSL_CTX_free(ssl->ctx);
+/**
+ * SSL Connection initialization.
+ */
+
+static int redisSSLConnect(redisContext *c, SSL_CTX *ssl_ctx, SSL *ssl) {
+ if (c->privdata) {
+ __redisSetError(c, REDIS_ERR_OTHER, "redisContext was already associated");
+ return REDIS_ERR;
}
- if (ssl->ssl) {
- SSL_free(ssl->ssl);
+ c->privdata = calloc(1, sizeof(redisSSLContext));
+
+ c->funcs = &redisContextSSLFuncs;
+ redisSSLContext *rssl = c->privdata;
+
+ rssl->ssl_ctx = ssl_ctx;
+ rssl->ssl = ssl;
+
+ SSL_set_mode(rssl->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+ SSL_set_fd(rssl->ssl, c->fd);
+ SSL_set_connect_state(rssl->ssl);
+
+ ERR_clear_error();
+ int rv = SSL_connect(rssl->ssl);
+ if (rv == 1) {
+ return REDIS_OK;
+ }
+
+ rv = SSL_get_error(rssl->ssl, rv);
+ if (((c->flags & REDIS_BLOCK) == 0) &&
+ (rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE)) {
+ return REDIS_OK;
+ }
+
+ if (c->err == 0) {
+ char err[512];
+ if (rv == SSL_ERROR_SYSCALL)
+ snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",strerror(errno));
+ else {
+ unsigned long e = ERR_peek_last_error();
+ snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",
+ ERR_reason_error_string(e));
+ }
+ __redisSetError(c, REDIS_ERR_IO, err);
}
- free(ssl);
+ return REDIS_ERR;
+}
+
+int redisInitiateSSL(redisContext *c, SSL *ssl) {
+ return redisSSLConnect(c, NULL, ssl);
}
-int redisSslCreate(redisContext *c, const char *capath, const char *certpath,
- const char *keypath, const char *servername) {
- assert(!c->ssl);
- c->ssl = calloc(1, sizeof(*c->ssl));
+int redisSecureConnection(redisContext *c, const char *capath,
+ const char *certpath, const char *keypath, const char *servername) {
+
+ SSL_CTX *ssl_ctx = NULL;
+ SSL *ssl = NULL;
+
+ /* Initialize global OpenSSL stuff */
static int isInit = 0;
if (!isInit) {
isInit = 1;
SSL_library_init();
+#ifdef HIREDIS_USE_CRYPTO_LOCKS
initOpensslLocks();
+#endif
+ }
+
+ ssl_ctx = SSL_CTX_new(SSLv23_client_method());
+ if (!ssl_ctx) {
+ __redisSetError(c, REDIS_ERR_OTHER, "Failed to create SSL_CTX");
+ goto error;
}
- redisSsl *s = c->ssl;
- s->ctx = SSL_CTX_new(SSLv23_client_method());
#ifdef HIREDIS_SSL_TRACE
- SSL_CTX_set_info_callback(s->ctx, sslLogCallback);
+ SSL_CTX_set_info_callback(ssl_ctx, sslLogCallback);
#endif
- SSL_CTX_set_mode(s->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
- SSL_CTX_set_options(s->ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
- SSL_CTX_set_verify(s->ctx, SSL_VERIFY_PEER, NULL);
-
+ 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, "certpath and keypath must be specified together");
- return REDIS_ERR;
+ __redisSetError(c, REDIS_ERR_OTHER, "certpath and keypath must be specified together");
+ goto error;
}
if (capath) {
- if (!SSL_CTX_load_verify_locations(s->ctx, capath, NULL)) {
- __redisSetError(c, REDIS_ERR, "Invalid CA certificate");
- return REDIS_ERR;
+ 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(s->ctx, certpath)) {
- __redisSetError(c, REDIS_ERR, "Invalid client certificate");
- return REDIS_ERR;
+ 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(s->ctx, keypath, SSL_FILETYPE_PEM)) {
- __redisSetError(c, REDIS_ERR, "Invalid client key");
- return REDIS_ERR;
+ if (!SSL_CTX_use_PrivateKey_file(ssl_ctx, keypath, SSL_FILETYPE_PEM)) {
+ __redisSetError(c, REDIS_ERR_OTHER, "Invalid client key");
+ goto error;
}
}
- s->ssl = SSL_new(s->ctx);
- if (!s->ssl) {
- __redisSetError(c, REDIS_ERR, "Couldn't create new SSL instance");
- return REDIS_ERR;
+ ssl = SSL_new(ssl_ctx);
+ if (!ssl) {
+ __redisSetError(c, REDIS_ERR_OTHER, "Couldn't create new SSL instance");
+ goto error;
}
if (servername) {
- if (!SSL_set_tlsext_host_name(s->ssl, servername)) {
- __redisSetError(c, REDIS_ERR, "Couldn't set server name indication");
- return REDIS_ERR;
+ if (!SSL_set_tlsext_host_name(ssl, servername)) {
+ __redisSetError(c, REDIS_ERR_OTHER, "Couldn't set server name indication");
+ goto error;
}
}
- SSL_set_fd(s->ssl, c->fd);
- SSL_set_connect_state(s->ssl);
+ return redisSSLConnect(c, ssl_ctx, ssl);
- c->flags |= REDIS_SSL;
- ERR_clear_error();
- int rv = SSL_connect(c->ssl->ssl);
- if (rv == 1) {
- return REDIS_OK;
- }
-
- rv = SSL_get_error(s->ssl, rv);
- if (((c->flags & REDIS_BLOCK) == 0) &&
- (rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE)) {
- return REDIS_OK;
- }
-
- if (c->err == 0) {
- char err[512];
- if (rv == SSL_ERROR_SYSCALL)
- snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",strerror(errno));
- else {
- unsigned long e = ERR_peek_last_error();
- snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",
- ERR_reason_error_string(e));
- }
- __redisSetError(c, REDIS_ERR_IO, err);
- }
+error:
+ if (ssl) SSL_free(ssl);
+ if (ssl_ctx) SSL_CTX_free(ssl_ctx);
return REDIS_ERR;
}
-static int maybeCheckWant(redisSsl *rssl, int rv) {
+static int maybeCheckWant(redisSSLContext *rssl, int rv) {
/**
* If the error is WANT_READ or WANT_WRITE, the appropriate flags are set
* and true is returned. False is returned otherwise
@@ -192,15 +296,36 @@ static int maybeCheckWant(redisSsl *rssl, int rv) {
}
}
-int redisSslRead(redisContext *c, char *buf, size_t bufcap) {
- int nread = SSL_read(c->ssl->ssl, buf, bufcap);
+/**
+ * Implementation of redisContextFuncs for SSL connections.
+ */
+
+static void redisSSLFreeContext(void *privdata){
+ redisSSLContext *rsc = privdata;
+
+ if (!rsc) return;
+ if (rsc->ssl) {
+ SSL_free(rsc->ssl);
+ rsc->ssl = NULL;
+ }
+ if (rsc->ssl_ctx) {
+ SSL_CTX_free(rsc->ssl_ctx);
+ rsc->ssl_ctx = NULL;
+ }
+ free(rsc);
+}
+
+static int redisSSLRead(redisContext *c, char *buf, size_t bufcap) {
+ redisSSLContext *rssl = c->privdata;
+
+ int nread = SSL_read(rssl->ssl, buf, bufcap);
if (nread > 0) {
return nread;
} else if (nread == 0) {
__redisSetError(c, REDIS_ERR_EOF, "Server closed the connection");
return -1;
} else {
- int err = SSL_get_error(c->ssl->ssl, nread);
+ int err = SSL_get_error(rssl->ssl, nread);
if (c->flags & REDIS_BLOCK) {
/**
* In blocking mode, we should never end up in a situation where
@@ -223,7 +348,7 @@ int redisSslRead(redisContext *c, char *buf, size_t bufcap) {
/**
* We can very well get an EWOULDBLOCK/EAGAIN, however
*/
- if (maybeCheckWant(c->ssl, err)) {
+ if (maybeCheckWant(rssl, err)) {
return 0;
} else {
__redisSetError(c, REDIS_ERR_IO, NULL);
@@ -232,17 +357,19 @@ int redisSslRead(redisContext *c, char *buf, size_t bufcap) {
}
}
-int redisSslWrite(redisContext *c) {
- size_t len = c->ssl->lastLen ? c->ssl->lastLen : sdslen(c->obuf);
- int rv = SSL_write(c->ssl->ssl, c->obuf, len);
+static int redisSSLWrite(redisContext *c) {
+ redisSSLContext *rssl = c->privdata;
+
+ size_t len = rssl->lastLen ? rssl->lastLen : sdslen(c->obuf);
+ int rv = SSL_write(rssl->ssl, c->obuf, len);
if (rv > 0) {
- c->ssl->lastLen = 0;
+ rssl->lastLen = 0;
} else if (rv < 0) {
- c->ssl->lastLen = len;
+ rssl->lastLen = len;
- int err = SSL_get_error(c->ssl->ssl, rv);
- if ((c->flags & REDIS_BLOCK) == 0 && maybeCheckWant(c->ssl, err)) {
+ int err = SSL_get_error(rssl->ssl, rv);
+ if ((c->flags & REDIS_BLOCK) == 0 && maybeCheckWant(rssl, err)) {
return 0;
} else {
__redisSetError(c, REDIS_ERR_IO, NULL);
@@ -252,4 +379,72 @@ int redisSslWrite(redisContext *c) {
return rv;
}
+static void redisSSLAsyncRead(redisAsyncContext *ac) {
+ int rv;
+ redisSSLContext *rssl = ac->c.privdata;
+ redisContext *c = &ac->c;
+
+ rssl->wantRead = 0;
+
+ if (rssl->pendingWrite) {
+ int done;
+
+ /* This is probably just a write event */
+ rssl->pendingWrite = 0;
+ rv = redisBufferWrite(c, &done);
+ if (rv == REDIS_ERR) {
+ __redisAsyncDisconnect(ac);
+ return;
+ } else if (!done) {
+ _EL_ADD_WRITE(ac);
+ }
+ }
+
+ rv = redisBufferRead(c);
+ if (rv == REDIS_ERR) {
+ __redisAsyncDisconnect(ac);
+ } else {
+ _EL_ADD_READ(ac);
+ redisProcessCallbacks(ac);
+ }
+}
+
+static void redisSSLAsyncWrite(redisAsyncContext *ac) {
+ int rv, done = 0;
+ redisSSLContext *rssl = ac->c.privdata;
+ redisContext *c = &ac->c;
+
+ rssl->pendingWrite = 0;
+ rv = redisBufferWrite(c, &done);
+ if (rv == REDIS_ERR) {
+ __redisAsyncDisconnect(ac);
+ return;
+ }
+
+ if (!done) {
+ if (rssl->wantRead) {
+ /* Need to read-before-write */
+ rssl->pendingWrite = 1;
+ _EL_DEL_WRITE(ac);
+ } else {
+ /* No extra reads needed, just need to write more */
+ _EL_ADD_WRITE(ac);
+ }
+ } else {
+ /* Already done! */
+ _EL_DEL_WRITE(ac);
+ }
+
+ /* Always reschedule a read */
+ _EL_ADD_READ(ac);
+}
+
+redisContextFuncs redisContextSSLFuncs = {
+ .free_privdata = redisSSLFreeContext,
+ .async_read = redisSSLAsyncRead,
+ .async_write = redisSSLAsyncWrite,
+ .read = redisSSLRead,
+ .write = redisSSLWrite
+};
+
#endif