diff --git a/R/config.R b/R/config.R index 0389545..e2d0e54 100644 --- a/R/config.R +++ b/R/config.R @@ -65,12 +65,15 @@ redis_config <- function(..., config = list(...)) { ## 2 arg and char/int, unnamed (assume host/port?) defaults <- list( url = Sys_getenv("REDIS_URL", NULL), - scheme = "redis", + scheme = Sys_getenv("REDIS_CONNECTION_SCHEME", "redis"), host = Sys_getenv("REDIS_HOST", "127.0.0.1"), port = as.integer(Sys_getenv("REDIS_PORT", 6379L)), path = NULL, password = NULL, - db = NULL) + db = NULL, + CApath = Sys_getenv("REDIS_SSL_CA_PATH", NULL), + certPath = Sys_getenv("REDIS_SSL_CERT_PATH", NULL), + keyPath = Sys_getenv("REDIS_SSL_KEY_PATH", NULL)) dots <- list(...) if (length(dots) > 0L && !identical(dots, config)) { warning("Ignoring dots in favour of config") diff --git a/R/redis.R b/R/redis.R index 8fe2053..d715a00 100644 --- a/R/redis.R +++ b/R/redis.R @@ -5,6 +5,9 @@ redis_connect <- function(config) { if (config$scheme == "redis") { ptr <- redis_connect_tcp(config$host, config$port) + } else if (config$scheme == "rediss") { + ptr <- redis_connect_tcp_ssl(config$host, config$port, config$CApath, + config$certPath, config$keyPath) } else { ptr <- redis_connect_unix(config$path) } @@ -21,6 +24,11 @@ redis_connect_tcp <- function(host, port) { .Call(Credux_redis_connect, host, as.integer(port)) } +redis_connect_tcp_ssl <- function(host, port, CApath, certPath, keyPath) { + .Call(Credux_redis_connect_ssl, host, as.integer(port), + CApath, certPath, keyPath) +} + redis_connect_unix <- function(path) { .Call(Credux_redis_connect_unix, path) } diff --git a/configure b/configure index 7885df8..9a8232d 100755 --- a/configure +++ b/configure @@ -7,6 +7,7 @@ # Library settings PKG_CONFIG_NAME="hiredis" +PKG_CONFIG_SSL_NAME="hiredis_ssl" PKG_DEB_NAME="libhiredis-dev" PKG_RPM_NAME="hiredis-devel" PKG_BREW_NAME="hiredis" @@ -20,6 +21,12 @@ if [ $(command -v pkg-config) ]; then PKGCONFIG_LIBS=$(pkg-config --libs ${PKG_CONFIG_NAME}) fi +# Use pkg-config to find hiredis_ssl lib if available +if [ $(command -v pkg-config) ]; then + PKGCONFIG_CFLAGS="${PKGCONFIG_CFLAGS} $(pkg-config --cflags --silence-errors ${PKG_CONFIG_SSL_NAME})" + PKGCONFIG_LIBS="${PKGCONFIG_LIBS} $(pkg-config --libs ${PKG_CONFIG_SSL_NAME})" +fi + # Note that cflags may be empty in case of success if [ "$INCLUDE_DIR" ] || [ "$LIB_DIR" ]; then echo "Found INCLUDE_DIR and/or LIB_DIR!" diff --git a/src/Makevars.win b/src/Makevars.win index a1e466d..1ec6bd0 100644 --- a/src/Makevars.win +++ b/src/Makevars.win @@ -1,6 +1,6 @@ # -*- makefile -*- PKG_CPPFLAGS=-I../windows/hiredis-1.0.0/include/hiredis -DSTRICT_R_HEADERS -PKG_LIBS=-L../windows/hiredis-1.0.0/lib${R_ARCH}${CRT} -lhiredis -lws2_32 +PKG_LIBS=-L../windows/hiredis-1.0.0/lib${R_ARCH}${CRT} -lhiredis -lhiredis_ssl -lssl -lcrypto -lcrypt32 -lws2_32 all: clean winlibs diff --git a/src/connection.c b/src/connection.c index 3f285e1..31a3d63 100644 --- a/src/connection.c +++ b/src/connection.c @@ -22,6 +22,61 @@ SEXP redux_redis_connect(SEXP host, SEXP port) { return extPtr; } +SEXP redux_redis_connect_ssl(SEXP host, SEXP port, SEXP CApath, + SEXP CERTpath, SEXP KEYpath) { + + // hiredis SSL connection context and error var + redisSSLContext *redis_ssl_context; + redisSSLContextError redis_ssl_error; + + // Initialise OpenSSL + redisInitOpenSSL(); + + // Set up the SSL connection parameters + redis_ssl_context = redisCreateSSLContext( + CHAR(STRING_ELT(CApath, 0)), + NULL, /* not providing path to trusted certs */ + CHAR(STRING_ELT(CERTpath, 0)), + CHAR(STRING_ELT(KEYpath, 0)), + CHAR(STRING_ELT(host, 0)), + &redis_ssl_error); + + if(redis_ssl_context == NULL || redis_ssl_error != 0) { + error("Failed to create SSL context: %s\n", + (redis_ssl_error != 0) ? + redisSSLContextGetError(redis_ssl_error) : "Unknown error"); + } + + // Initiate a connection with redis + redisContext *context = redisConnect(CHAR(STRING_ELT(host, 0)), + INTEGER(port)[0]); + if (context == NULL) { + error("Creating context failed catastrophically [tcp_ssl]"); // # nocov + } + if (context->err != 0) { + const char * errstr = string_duplicate(context->errstr); + redisFree(context); + + error("Failed to create context: %s", errstr); + } + + // Now we have a connection established, we can negotiate the SSL connection + if (redisInitiateSSLWithContext(context, redis_ssl_context) != REDIS_OK) { + redisFreeSSLContext(redis_ssl_context); + if (context->err != 0) { + const char * errstr_ssl = string_duplicate(context->errstr); + redisFree(context); + error("Failed to initialize SSL connection: %s\n", errstr_ssl); + } + redisFree(context); + error("Failed to initialize SSL connection\n"); + } + SEXP extPtr = PROTECT(R_MakeExternalPtr(context, host, R_NilValue)); + R_RegisterCFinalizer(extPtr, redis_finalize); + UNPROTECT(1); + return extPtr; +} + SEXP redux_redis_connect_unix(SEXP path) { redisContext *context = redisConnectUnix(CHAR(STRING_ELT(path, 0))); if (context == NULL) { diff --git a/src/connection.h b/src/connection.h index 290c88c..9db729f 100644 --- a/src/connection.h +++ b/src/connection.h @@ -1,9 +1,12 @@ #include #include #include +#include #include SEXP redux_redis_connect(SEXP host, SEXP port); +SEXP redux_redis_connect_ssl(SEXP host, SEXP port, + SEXP CApath, SEXP certPath, SEXP keyPath); SEXP redux_redis_connect_unix(SEXP path); SEXP redux_redis_command(SEXP extPtr, SEXP cmd); diff --git a/src/registration.c b/src/registration.c index 01da3e0..0931065 100644 --- a/src/registration.c +++ b/src/registration.c @@ -9,6 +9,7 @@ static const R_CallMethodDef callMethods[] = { {"Credux_redis_connect", (DL_FUNC) &redux_redis_connect, 2}, + {"Credux_redis_connect_ssl", (DL_FUNC) &redux_redis_connect_ssl, 5}, {"Credux_redis_connect_unix", (DL_FUNC) &redux_redis_connect_unix, 1}, {"Credux_redis_command", (DL_FUNC) &redux_redis_command, 2},