Commit 0c145449 authored by Mark Nunberg's avatar Mark Nunberg
Browse files

Initial SSL (sync) implementation

parent 4d00404b
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com> # Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com>
# This file is released under the BSD license, see the COPYING file # This file is released under the BSD license, see the COPYING file
OBJ=net.o hiredis.o sds.o async.o read.o 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 EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib hiredis-example-ssl
TESTS=hiredis-test TESTS=hiredis-test
LIBNAME=libhiredis LIBNAME=libhiredis
PKGCONFNAME=hiredis.pc PKGCONFNAME=hiredis.pc
...@@ -53,6 +53,10 @@ DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(L ...@@ -53,6 +53,10 @@ DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(L
STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
STLIB_MAKE_CMD=$(AR) rcs $(STLIBNAME) 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 # Platform-specific overrides
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
ifeq ($(uname_S),SunOS) ifeq ($(uname_S),SunOS)
...@@ -70,10 +74,11 @@ all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME) ...@@ -70,10 +74,11 @@ all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME)
# Deps (use make dep to generate this) # 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 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 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 net.o: net.c fmacros.h net.h hiredis.h read.h sds.h
read.o: read.c fmacros.h read.h sds.h read.o: read.c fmacros.h read.h sds.h
sds.o: sds.c 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 test.o: test.c fmacros.h hiredis.h read.h sds.h
$(DYLIBNAME): $(OBJ) $(DYLIBNAME): $(OBJ)
...@@ -101,6 +106,9 @@ hiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME) ...@@ -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) hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -framework CoreFoundation $(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 ifndef AE_DIR
hiredis-example-ae: hiredis-example-ae:
@echo "Please specify AE_DIR (e.g. <redis repository>/src)" @echo "Please specify AE_DIR (e.g. <redis repository>/src)"
...@@ -158,7 +166,7 @@ clean: ...@@ -158,7 +166,7 @@ clean:
rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov
dep: dep:
$(CC) -MM *.c $(CC) $(CPPFLAGS) $(CFLAGS) -MM *.c
INSTALL?= cp -pPR INSTALL?= cp -pPR
......
...@@ -42,6 +42,7 @@ ...@@ -42,6 +42,7 @@
#include "hiredis.h" #include "hiredis.h"
#include "net.h" #include "net.h"
#include "sds.h" #include "sds.h"
#include "sslio.h"
static redisReply *createReplyObject(int type); static redisReply *createReplyObject(int type);
static void *createStringObject(const redisReadTask *task, char *str, size_t len); static void *createStringObject(const redisReadTask *task, char *str, size_t len);
...@@ -614,7 +615,9 @@ void redisFree(redisContext *c) { ...@@ -614,7 +615,9 @@ void redisFree(redisContext *c) {
free(c->unix_sock.path); free(c->unix_sock.path);
free(c->timeout); free(c->timeout);
free(c->saddr); free(c->saddr);
free(c); if (c->ssl) {
redisFreeSsl(c->ssl);
}
} }
int redisFreeKeepFd(redisContext *c) { int redisFreeKeepFd(redisContext *c) {
...@@ -760,6 +763,11 @@ redisContext *redisConnectFd(int fd) { ...@@ -760,6 +763,11 @@ redisContext *redisConnectFd(int fd) {
return c; 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. */ /* Set read/write timeout on a blocking socket. */
int redisSetTimeout(redisContext *c, const struct timeval tv) { int redisSetTimeout(redisContext *c, const struct timeval tv) {
if (c->flags & REDIS_BLOCK) if (c->flags & REDIS_BLOCK)
...@@ -774,6 +782,24 @@ int redisEnableKeepAlive(redisContext *c) { ...@@ -774,6 +782,24 @@ int redisEnableKeepAlive(redisContext *c) {
return REDIS_OK; 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 /* 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. * and read some bytes from the socket and feed them to the reply parser.
* *
...@@ -787,26 +813,33 @@ int redisBufferRead(redisContext *c) { ...@@ -787,26 +813,33 @@ int redisBufferRead(redisContext *c) {
if (c->err) if (c->err)
return REDIS_ERR; return REDIS_ERR;
nread = read(c->fd,buf,sizeof(buf)); nread = c->flags & REDIS_SSL ?
if (nread == -1) { redisSslRead(c, buf, sizeof(buf)) : rawRead(c, buf, sizeof(buf));
if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { if (nread > 0) {
/* Try again later */ if (redisReaderFeed(c->reader, buf, nread) != REDIS_OK) {
} else { __redisSetError(c, c->reader->err, c->reader->errstr);
__redisSetError(c,REDIS_ERR_IO,NULL);
return REDIS_ERR; return REDIS_ERR;
} else {
} }
} else if (nread == 0) { } else if (nread < 0) {
__redisSetError(c,REDIS_ERR_EOF,"Server closed the connection");
return REDIS_ERR; 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; 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. /* Write the output buffer to the socket.
* *
* Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was * Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was
...@@ -817,21 +850,15 @@ int redisBufferRead(redisContext *c) { ...@@ -817,21 +850,15 @@ int redisBufferRead(redisContext *c) {
* c->errstr to hold the appropriate error string. * c->errstr to hold the appropriate error string.
*/ */
int redisBufferWrite(redisContext *c, int *done) { int redisBufferWrite(redisContext *c, int *done) {
int nwritten;
/* Return early when the context has seen an error. */ /* Return early when the context has seen an error. */
if (c->err) if (c->err)
return REDIS_ERR; return REDIS_ERR;
if (sdslen(c->obuf) > 0) { if (sdslen(c->obuf) > 0) {
nwritten = write(c->fd,c->obuf,sdslen(c->obuf)); int nwritten = (c->flags & REDIS_SSL) ? redisSslWrite(c) : rawWrite(c);
if (nwritten == -1) { if (nwritten < 0) {
if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { return REDIS_ERR;
/* Try again later */
} else {
__redisSetError(c,REDIS_ERR_IO,NULL);
return REDIS_ERR;
}
} else if (nwritten > 0) { } else if (nwritten > 0) {
if (nwritten == (signed)sdslen(c->obuf)) { if (nwritten == (signed)sdslen(c->obuf)) {
sdsfree(c->obuf); sdsfree(c->obuf);
......
...@@ -74,6 +74,9 @@ ...@@ -74,6 +74,9 @@
/* Flag that is set when we should set SO_REUSEADDR before calling bind() */ /* Flag that is set when we should set SO_REUSEADDR before calling bind() */
#define REDIS_REUSEADDR 0x80 #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 */ #define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */
/* number of times we retry to connect in the case of EADDRNOTAVAIL and /* number of times we retry to connect in the case of EADDRNOTAVAIL and
...@@ -112,6 +115,8 @@ enum redisConnectionType { ...@@ -112,6 +115,8 @@ enum redisConnectionType {
REDIS_CONN_UNIX REDIS_CONN_UNIX
}; };
struct redisSsl;
/* Context for a connection to Redis */ /* Context for a connection to Redis */
typedef struct redisContext { typedef struct redisContext {
int err; /* Error flags, 0 when there is no error */ int err; /* Error flags, 0 when there is no error */
...@@ -137,6 +142,9 @@ typedef struct redisContext { ...@@ -137,6 +142,9 @@ typedef struct redisContext {
/* For non-blocking connect */ /* For non-blocking connect */
struct sockadr *saddr; struct sockadr *saddr;
size_t addrlen; size_t addrlen;
/* For SSL communication */
struct redisSsl *ssl;
} redisContext; } redisContext;
redisContext *redisConnect(const char *ip, int port); redisContext *redisConnect(const char *ip, int port);
...@@ -151,6 +159,13 @@ redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval ...@@ -151,6 +159,13 @@ redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval
redisContext *redisConnectUnixNonBlock(const char *path); redisContext *redisConnectUnixNonBlock(const char *path);
redisContext *redisConnectFd(int fd); 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. * Reconnect the given context using the saved information.
* *
......
#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
#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 */
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment