Initial SSL (sync) implementation

This commit is contained in:
Mark Nunberg 2017-11-27 13:10:21 +00:00
parent 4d00404b8f
commit 0c14544906
5 changed files with 322 additions and 27 deletions

View File

@ -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

View File

@ -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,24 +813,31 @@ int redisBufferRead(redisContext *c) {
if (c->err)
return REDIS_ERR;
nread = read(c->fd,buf,sizeof(buf));
if (nread == -1) {
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) {
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 REDIS_ERR;
}
} else if (nread == 0) {
__redisSetError(c,REDIS_ERR_EOF,"Server closed the connection");
return REDIS_ERR;
} else {
if (redisReaderFeed(c->reader,buf,nread) != REDIS_OK) {
__redisSetError(c,c->reader->err,c->reader->errstr);
return REDIS_ERR;
__redisSetError(c, REDIS_ERR_IO, NULL);
return -1;
}
}
return REDIS_OK;
return nwritten;
}
/* Write the output buffer to the socket.
@ -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);

View File

@ -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);
@ -151,6 +159,13 @@ redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval
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.
*

197
sslio.c Normal file
View File

@ -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

48
sslio.h Normal file
View File

@ -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 */