Initial SSL (sync) implementation
This commit is contained in:
parent
4d00404b8f
commit
0c14544906
16
Makefile
16
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
|
||||
|
||||
|
73
hiredis.c
73
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,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);
|
||||
|
15
hiredis.h
15
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);
|
||||
@ -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
197
sslio.c
Normal 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
48
sslio.h
Normal 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 */
|
Loading…
Reference in New Issue
Block a user