summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Nunberg <mnunberg@haskalah.org>2017-11-27 13:10:21 +0000
committerMark Nunberg <mnunberg@haskalah.org>2019-02-20 09:10:10 -0500
commit0c1454490669f3c95e3c8b0ac6a83582d14e30e0 (patch)
tree858e159c0f3ba74849dd9f7b4a40edd93b6791fe
parent4d00404b8fb47e618474d5538e4a720ac1c95d95 (diff)
Initial SSL (sync) implementation
-rw-r--r--Makefile16
-rw-r--r--hiredis.c73
-rw-r--r--hiredis.h15
-rw-r--r--sslio.c197
-rw-r--r--sslio.h48
5 files changed, 322 insertions, 27 deletions
diff --git a/Makefile b/Makefile
index 07b8a83..ea96419 100644
--- a/Makefile
+++ b/Makefile
@@ -3,8 +3,8 @@
# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com>
# This file is released under the BSD license, see the COPYING file
-OBJ=net.o hiredis.o sds.o async.o read.o
-EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib
+OBJ=net.o hiredis.o sds.o async.o read.o sslio.o
+EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib hiredis-example-ssl
TESTS=hiredis-test
LIBNAME=libhiredis
PKGCONFNAME=hiredis.pc
@@ -53,6 +53,10 @@ DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(L
STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
STLIB_MAKE_CMD=$(AR) rcs $(STLIBNAME)
+OPENSSL_PREFIX=/usr/local/opt/openssl
+CFLAGS+=-I$(OPENSSL_PREFIX)/include
+LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto
+
# Platform-specific overrides
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
ifeq ($(uname_S),SunOS)
@@ -70,10 +74,11 @@ all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME)
# Deps (use make dep to generate this)
async.o: async.c fmacros.h async.h hiredis.h read.h sds.h net.h dict.c dict.h
dict.o: dict.c fmacros.h dict.h
-hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h
+hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h sslio.h
net.o: net.c fmacros.h net.h hiredis.h read.h sds.h
read.o: read.c fmacros.h read.h sds.h
sds.o: sds.c sds.h
+sslio.o: sslio.c sslio.h hiredis.h
test.o: test.c fmacros.h hiredis.h read.h sds.h
$(DYLIBNAME): $(OBJ)
@@ -101,6 +106,9 @@ hiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME)
hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME)
+hiredis-example-ssl: examples/example-ssl.c $(STLIBNAME)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(STLIBNAME)
+
ifndef AE_DIR
hiredis-example-ae:
@echo "Please specify AE_DIR (e.g. <redis repository>/src)"
@@ -158,7 +166,7 @@ clean:
rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov
dep:
- $(CC) -MM *.c
+ $(CC) $(CPPFLAGS) $(CFLAGS) -MM *.c
INSTALL?= cp -pPR
diff --git a/hiredis.c b/hiredis.c
index bfbf483..fae8094 100644
--- a/hiredis.c
+++ b/hiredis.c
@@ -42,6 +42,7 @@
#include "hiredis.h"
#include "net.h"
#include "sds.h"
+#include "sslio.h"
static redisReply *createReplyObject(int type);
static void *createStringObject(const redisReadTask *task, char *str, size_t len);
@@ -614,7 +615,9 @@ void redisFree(redisContext *c) {
free(c->unix_sock.path);
free(c->timeout);
free(c->saddr);
- free(c);
+ if (c->ssl) {
+ redisFreeSsl(c->ssl);
+ }
}
int redisFreeKeepFd(redisContext *c) {
@@ -760,6 +763,11 @@ redisContext *redisConnectFd(int fd) {
return c;
}
+int redisSecureConnection(redisContext *c, const char *caPath,
+ const char *certPath, const char *keyPath) {
+ return redisSslCreate(c, caPath, certPath, keyPath);
+}
+
/* Set read/write timeout on a blocking socket. */
int redisSetTimeout(redisContext *c, const struct timeval tv) {
if (c->flags & REDIS_BLOCK)
@@ -774,6 +782,24 @@ int redisEnableKeepAlive(redisContext *c) {
return REDIS_OK;
}
+static int rawRead(redisContext *c, char *buf, size_t bufcap) {
+ int nread = read(c->fd, buf, bufcap);
+ if (nread == -1) {
+ if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
+ /* Try again later */
+ return 0;
+ } else {
+ __redisSetError(c, REDIS_ERR_IO, NULL);
+ return -1;
+ }
+ } else if (nread == 0) {
+ __redisSetError(c, REDIS_ERR_EOF, "Server closed the connection");
+ return -1;
+ } else {
+ return nread;
+ }
+}
+
/* Use this function to handle a read event on the descriptor. It will try
* and read some bytes from the socket and feed them to the reply parser.
*
@@ -787,26 +813,33 @@ int redisBufferRead(redisContext *c) {
if (c->err)
return REDIS_ERR;
- nread = read(c->fd,buf,sizeof(buf));
- if (nread == -1) {
- if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
- /* Try again later */
- } else {
- __redisSetError(c,REDIS_ERR_IO,NULL);
+ nread = c->flags & REDIS_SSL ?
+ redisSslRead(c, buf, sizeof(buf)) : rawRead(c, buf, sizeof(buf));
+ if (nread > 0) {
+ if (redisReaderFeed(c->reader, buf, nread) != REDIS_OK) {
+ __redisSetError(c, c->reader->err, c->reader->errstr);
return REDIS_ERR;
+ } else {
}
- } else if (nread == 0) {
- __redisSetError(c,REDIS_ERR_EOF,"Server closed the connection");
+ } else if (nread < 0) {
return REDIS_ERR;
- } else {
- if (redisReaderFeed(c->reader,buf,nread) != REDIS_OK) {
- __redisSetError(c,c->reader->err,c->reader->errstr);
- return REDIS_ERR;
- }
}
return REDIS_OK;
}
+static int rawWrite(redisContext *c) {
+ int nwritten = write(c->fd, c->obuf, sdslen(c->obuf));
+ if (nwritten < 0) {
+ if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
+ /* Try again later */
+ } else {
+ __redisSetError(c, REDIS_ERR_IO, NULL);
+ return -1;
+ }
+ }
+ return nwritten;
+}
+
/* Write the output buffer to the socket.
*
* Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was
@@ -817,21 +850,15 @@ int redisBufferRead(redisContext *c) {
* c->errstr to hold the appropriate error string.
*/
int redisBufferWrite(redisContext *c, int *done) {
- int nwritten;
/* Return early when the context has seen an error. */
if (c->err)
return REDIS_ERR;
if (sdslen(c->obuf) > 0) {
- nwritten = write(c->fd,c->obuf,sdslen(c->obuf));
- if (nwritten == -1) {
- if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
- /* Try again later */
- } else {
- __redisSetError(c,REDIS_ERR_IO,NULL);
- return REDIS_ERR;
- }
+ int nwritten = (c->flags & REDIS_SSL) ? redisSslWrite(c) : rawWrite(c);
+ if (nwritten < 0) {
+ return REDIS_ERR;
} else if (nwritten > 0) {
if (nwritten == (signed)sdslen(c->obuf)) {
sdsfree(c->obuf);
diff --git a/hiredis.h b/hiredis.h
index 1b0d5e6..29c0253 100644
--- a/hiredis.h
+++ b/hiredis.h
@@ -74,6 +74,9 @@
/* Flag that is set when we should set SO_REUSEADDR before calling bind() */
#define REDIS_REUSEADDR 0x80
+/* Flag that is set when this connection is done through SSL */
+#define REDIS_SSL 0x100
+
#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */
/* number of times we retry to connect in the case of EADDRNOTAVAIL and
@@ -112,6 +115,8 @@ enum redisConnectionType {
REDIS_CONN_UNIX
};
+struct redisSsl;
+
/* Context for a connection to Redis */
typedef struct redisContext {
int err; /* Error flags, 0 when there is no error */
@@ -137,6 +142,9 @@ typedef struct redisContext {
/* For non-blocking connect */
struct sockadr *saddr;
size_t addrlen;
+ /* For SSL communication */
+ struct redisSsl *ssl;
+
} redisContext;
redisContext *redisConnect(const char *ip, int port);
@@ -152,6 +160,13 @@ redisContext *redisConnectUnixNonBlock(const char *path);
redisContext *redisConnectFd(int fd);
/**
+ * Secure the connection using SSL. This should be done before any command is
+ * executed on the connection.
+ */
+int redisSecureConnection(redisContext *c, const char *capath, const char *certpath,
+ const char *keypath);
+
+/**
* Reconnect the given context using the saved information.
*
* This re-uses the exact same connect options as in the initial connection.
diff --git a/sslio.c b/sslio.c
new file mode 100644
index 0000000..9ee015b
--- /dev/null
+++ b/sslio.c
@@ -0,0 +1,197 @@
+#include "hiredis.h"
+#include "sslio.h"
+
+#include <assert.h>
+#ifndef HIREDIS_NOSSL
+#include <pthread.h>
+
+void __redisSetError(redisContext *c, int type, const char *str);
+
+static void sslLogCallback(const SSL *ssl, int where, int ret) {
+ const char *retstr = "";
+ int should_log = 1;
+ /* 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));
+ }
+}
+
+typedef pthread_mutex_t sslLockType;
+static void sslLockInit(sslLockType *l) {
+ pthread_mutex_init(l, NULL);
+}
+static void sslLockAcquire(sslLockType *l) {
+ pthread_mutex_lock(l);
+}
+static void sslLockRelease(sslLockType *l) {
+ pthread_mutex_unlock(l);
+}
+static pthread_mutex_t *ossl_locks;
+
+static void opensslDoLock(int mode, int lkid, const char *f, int line) {
+ sslLockType *l = ossl_locks + lkid;
+
+ if (mode & CRYPTO_LOCK) {
+ sslLockAcquire(l);
+ } else {
+ sslLockRelease(l);
+ }
+
+ (void)f;
+ (void)line;
+}
+
+static void initOpensslLocks(void) {
+ unsigned ii, nlocks;
+ if (CRYPTO_get_locking_callback() != NULL) {
+ /* Someone already set the callback before us. Don't destroy it! */
+ return;
+ }
+ nlocks = CRYPTO_num_locks();
+ ossl_locks = malloc(sizeof(*ossl_locks) * nlocks);
+ for (ii = 0; ii < nlocks; ii++) {
+ sslLockInit(ossl_locks + ii);
+ }
+ CRYPTO_set_locking_callback(opensslDoLock);
+}
+
+void redisFreeSsl(redisSsl *ssl){
+ if (ssl->ctx) {
+ SSL_CTX_free(ssl->ctx);
+ }
+ if (ssl->ssl) {
+ SSL_free(ssl->ssl);
+ }
+ free(ssl);
+}
+
+int redisSslCreate(redisContext *c, const char *capath, const char *certpath,
+ const char *keypath) {
+ assert(!c->ssl);
+ c->ssl = calloc(1, sizeof(*c->ssl));
+ static int isInit = 0;
+ if (!isInit) {
+ isInit = 1;
+ SSL_library_init();
+ initOpensslLocks();
+ }
+
+ redisSsl *s = c->ssl;
+ s->ctx = SSL_CTX_new(SSLv23_client_method());
+ SSL_CTX_set_info_callback(s->ctx, sslLogCallback);
+ 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);
+
+ if ((certpath != NULL && keypath == NULL) || (keypath != NULL && certpath == NULL)) {
+ __redisSetError(c, REDIS_ERR, "certpath and keypath must be specified together");
+ return REDIS_ERR;
+ }
+
+ if (capath) {
+ if (!SSL_CTX_load_verify_locations(s->ctx, capath, NULL)) {
+ __redisSetError(c, REDIS_ERR, "Invalid CA certificate");
+ return REDIS_ERR;
+ }
+ }
+ 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_PrivateKey_file(s->ctx, keypath, SSL_FILETYPE_PEM)) {
+ __redisSetError(c, REDIS_ERR, "Invalid client key");
+ return REDIS_ERR;
+ }
+ printf("Loaded certificate!\n");
+ }
+
+ s->ssl = SSL_new(s->ctx);
+ if (!s->ssl) {
+ __redisSetError(c, REDIS_ERR, "Couldn't create new SSL instance");
+ return REDIS_ERR;
+ }
+
+ SSL_set_fd(s->ssl, c->fd);
+ SSL_set_connect_state(s->ssl);
+
+ c->flags |= REDIS_SSL;
+ printf("Before SSL_connect()\n");
+ int rv = SSL_connect(c->ssl->ssl);
+ if (rv == 1) {
+ printf("SSL_connect() success!\n");
+ return REDIS_OK;
+ }
+ printf("ConnectRV: %d\n", rv);
+
+ 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) {
+ __redisSetError(c, REDIS_ERR_IO, "SSL_connect() failed");
+ printf("rv: %d\n", rv);
+ }
+ printf("ERROR!!!\n");
+ return REDIS_ERR;
+}
+
+int redisSslRead(redisContext *c, char *buf, size_t bufcap) {
+ int nread = SSL_read(c->ssl->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);
+ if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
+ return 0;
+ } else {
+ __redisSetError(c, REDIS_ERR_IO, NULL);
+ return -1;
+ }
+ }
+}
+
+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);
+
+ if (rv > 0) {
+ c->ssl->lastLen = 0;
+ } else if (rv < 0) {
+ c->ssl->lastLen = len;
+
+ int err = SSL_get_error(c->ssl->ssl, rv);
+ if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
+ return 0;
+ } else {
+ __redisSetError(c, REDIS_ERR_IO, NULL);
+ return -1;
+ }
+ }
+ return rv;
+}
+
+#endif
diff --git a/sslio.h b/sslio.h
new file mode 100644
index 0000000..a410cb3
--- /dev/null
+++ b/sslio.h
@@ -0,0 +1,48 @@
+#ifndef REDIS_SSLIO_H
+#define REDIS_SSLIO_H
+
+
+#ifdef HIREDIS_NOSSL
+typedef struct redisSsl {
+ int dummy;
+} redisSsl;
+static void redisFreeSsl(redisSsl *) {
+}
+static int redisSslCreate(struct redisContext *c) {
+ return REDIS_ERR;
+}
+static int redisSslRead(struct redisContect *c, char *s, size_t, n) {
+ return -1;
+}
+static int redisSslWrite(struct redisContext *c) {
+ return -1;
+}
+#else
+#include <openssl/ssl.h>
+
+/**
+ * This file contains routines for HIREDIS' SSL
+ */
+
+typedef struct redisSsl {
+ SSL *ssl;
+ SSL_CTX *ctx;
+
+ /**
+ * 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;
+} redisSsl;
+
+struct redisContext;
+
+void redisFreeSsl(redisSsl *);
+int redisSslCreate(struct redisContext *c, const char *caPath,
+ const char *certPath, const char *keyPath);
+
+int redisSslRead(struct redisContext *c, char *buf, size_t bufcap);
+int redisSslWrite(struct redisContext *c);
+
+#endif /* !HIREDIS_NOSSL */
+#endif /* HIREDIS_SSLIO_H */