123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526 |
- /*
- * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
- * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
- * Copyright (c) 2019, Redis Labs
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * * Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * * Neither the name of Redis nor the names of its contributors may be used
- * to endorse or promote products derived from this software without
- * specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
- #include "hiredis.h"
- #include "async.h"
- #include <assert.h>
- #include <errno.h>
- #include <string.h>
- #ifdef _WIN32
- #include <windows.h>
- #else
- #include <pthread.h>
- #endif
- #include <openssl/ssl.h>
- #include <openssl/err.h>
- #include "win32.h"
- #include "async_private.h"
- #include "hiredis_ssl.h"
- void __redisSetError(redisContext *c, int type, const char *str);
- struct redisSSLContext {
- /* Associated OpenSSL SSL_CTX as created by redisCreateSSLContext() */
- SSL_CTX *ssl_ctx;
- /* Requested SNI, or NULL */
- char *server_name;
- };
- /* The SSL connection context is attached to SSL/TLS connections as a privdata. */
- typedef struct redisSSL {
- /**
- * OpenSSL SSL object.
- */
- SSL *ssl;
- /**
- * 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;
- /** Whether the SSL layer requires read (possibly before a write) */
- int wantRead;
- /**
- * Whether a write was requested prior to a read. If set, the write()
- * should resume whenever a read takes place, if possible
- */
- int pendingWrite;
- } redisSSL;
- /* Forward declaration */
- redisContextFuncs redisContextSSLFuncs;
- /**
- * OpenSSL global initialization and locking handling callbacks.
- * Note that this is only required for OpenSSL < 1.1.0.
- */
- #if OPENSSL_VERSION_NUMBER < 0x10100000L
- #define HIREDIS_USE_CRYPTO_LOCKS
- #endif
- #ifdef HIREDIS_USE_CRYPTO_LOCKS
- #ifdef _WIN32
- typedef CRITICAL_SECTION sslLockType;
- static void sslLockInit(sslLockType* l) {
- InitializeCriticalSection(l);
- }
- static void sslLockAcquire(sslLockType* l) {
- EnterCriticalSection(l);
- }
- static void sslLockRelease(sslLockType* l) {
- LeaveCriticalSection(l);
- }
- #else
- 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);
- }
- #endif
- static sslLockType* 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 int initOpensslLocks(void) {
- unsigned ii, nlocks;
- if (CRYPTO_get_locking_callback() != NULL) {
- /* Someone already set the callback before us. Don't destroy it! */
- return REDIS_OK;
- }
- nlocks = CRYPTO_num_locks();
- ossl_locks = hi_malloc(sizeof(*ossl_locks) * nlocks);
- if (ossl_locks == NULL)
- return REDIS_ERR;
- for (ii = 0; ii < nlocks; ii++) {
- sslLockInit(ossl_locks + ii);
- }
- CRYPTO_set_locking_callback(opensslDoLock);
- return REDIS_OK;
- }
- #endif /* HIREDIS_USE_CRYPTO_LOCKS */
- int redisInitOpenSSL(void)
- {
- SSL_library_init();
- #ifdef HIREDIS_USE_CRYPTO_LOCKS
- initOpensslLocks();
- #endif
- return REDIS_OK;
- }
- /**
- * redisSSLContext helper context destruction.
- */
- const char *redisSSLContextGetError(redisSSLContextError error)
- {
- switch (error) {
- case REDIS_SSL_CTX_NONE:
- return "No Error";
- case REDIS_SSL_CTX_CREATE_FAILED:
- return "Failed to create OpenSSL SSL_CTX";
- case REDIS_SSL_CTX_CERT_KEY_REQUIRED:
- return "Client cert and key must both be specified or skipped";
- case REDIS_SSL_CTX_CA_CERT_LOAD_FAILED:
- return "Failed to load CA Certificate or CA Path";
- case REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED:
- return "Failed to load client certificate";
- case REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED:
- return "Failed to load private key";
- default:
- return "Unknown error code";
- }
- }
- void redisFreeSSLContext(redisSSLContext *ctx)
- {
- if (!ctx)
- return;
- if (ctx->server_name) {
- hi_free(ctx->server_name);
- ctx->server_name = NULL;
- }
- if (ctx->ssl_ctx) {
- SSL_CTX_free(ctx->ssl_ctx);
- ctx->ssl_ctx = NULL;
- }
- hi_free(ctx);
- }
- /**
- * redisSSLContext helper context initialization.
- */
- redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *capath,
- const char *cert_filename, const char *private_key_filename,
- const char *server_name, redisSSLContextError *error)
- {
- redisSSLContext *ctx = hi_calloc(1, sizeof(redisSSLContext));
- if (ctx == NULL)
- goto error;
- ctx->ssl_ctx = SSL_CTX_new(SSLv23_client_method());
- if (!ctx->ssl_ctx) {
- if (error) *error = REDIS_SSL_CTX_CREATE_FAILED;
- goto error;
- }
- SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
- SSL_CTX_set_verify(ctx->ssl_ctx, SSL_VERIFY_PEER, NULL);
- if ((cert_filename != NULL && private_key_filename == NULL) ||
- (private_key_filename != NULL && cert_filename == NULL)) {
- if (error) *error = REDIS_SSL_CTX_CERT_KEY_REQUIRED;
- goto error;
- }
- if (capath || cacert_filename) {
- if (!SSL_CTX_load_verify_locations(ctx->ssl_ctx, cacert_filename, capath)) {
- if (error) *error = REDIS_SSL_CTX_CA_CERT_LOAD_FAILED;
- goto error;
- }
- }
- if (cert_filename) {
- if (!SSL_CTX_use_certificate_chain_file(ctx->ssl_ctx, cert_filename)) {
- if (error) *error = REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED;
- goto error;
- }
- if (!SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, private_key_filename, SSL_FILETYPE_PEM)) {
- if (error) *error = REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED;
- goto error;
- }
- }
- if (server_name)
- ctx->server_name = hi_strdup(server_name);
- return ctx;
- error:
- redisFreeSSLContext(ctx);
- return NULL;
- }
- /**
- * SSL Connection initialization.
- */
- static int redisSSLConnect(redisContext *c, SSL *ssl) {
- if (c->privctx) {
- __redisSetError(c, REDIS_ERR_OTHER, "redisContext was already associated");
- return REDIS_ERR;
- }
- redisSSL *rssl = hi_calloc(1, sizeof(redisSSL));
- if (rssl == NULL) {
- __redisSetError(c, REDIS_ERR_OOM, "Out of memory");
- return REDIS_ERR;
- }
- c->funcs = &redisContextSSLFuncs;
- rssl->ssl = ssl;
- SSL_set_mode(rssl->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
- SSL_set_fd(rssl->ssl, c->fd);
- SSL_set_connect_state(rssl->ssl);
- ERR_clear_error();
- int rv = SSL_connect(rssl->ssl);
- if (rv == 1) {
- c->privctx = rssl;
- return REDIS_OK;
- }
- rv = SSL_get_error(rssl->ssl, rv);
- if (((c->flags & REDIS_BLOCK) == 0) &&
- (rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE)) {
- c->privctx = rssl;
- return REDIS_OK;
- }
- if (c->err == 0) {
- char err[512];
- if (rv == SSL_ERROR_SYSCALL)
- snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",strerror(errno));
- else {
- unsigned long e = ERR_peek_last_error();
- snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",
- ERR_reason_error_string(e));
- }
- __redisSetError(c, REDIS_ERR_IO, err);
- }
- hi_free(rssl);
- return REDIS_ERR;
- }
- /**
- * A wrapper around redisSSLConnect() for users who manage their own context and
- * create their own SSL object.
- */
- int redisInitiateSSL(redisContext *c, SSL *ssl) {
- return redisSSLConnect(c, ssl);
- }
- /**
- * A wrapper around redisSSLConnect() for users who use redisSSLContext and don't
- * manage their own SSL objects.
- */
- int redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx)
- {
- if (!c || !redis_ssl_ctx)
- return REDIS_ERR;
- /* We want to verify that redisSSLConnect() won't fail on this, as it will
- * not own the SSL object in that case and we'll end up leaking.
- */
- if (c->privctx)
- return REDIS_ERR;
- SSL *ssl = SSL_new(redis_ssl_ctx->ssl_ctx);
- if (!ssl) {
- __redisSetError(c, REDIS_ERR_OTHER, "Couldn't create new SSL instance");
- goto error;
- }
- if (redis_ssl_ctx->server_name) {
- if (!SSL_set_tlsext_host_name(ssl, redis_ssl_ctx->server_name)) {
- __redisSetError(c, REDIS_ERR_OTHER, "Failed to set server_name/SNI");
- goto error;
- }
- }
- return redisSSLConnect(c, ssl);
- error:
- if (ssl)
- SSL_free(ssl);
- return REDIS_ERR;
- }
- static int maybeCheckWant(redisSSL *rssl, int rv) {
- /**
- * If the error is WANT_READ or WANT_WRITE, the appropriate flags are set
- * and true is returned. False is returned otherwise
- */
- if (rv == SSL_ERROR_WANT_READ) {
- rssl->wantRead = 1;
- return 1;
- } else if (rv == SSL_ERROR_WANT_WRITE) {
- rssl->pendingWrite = 1;
- return 1;
- } else {
- return 0;
- }
- }
- /**
- * Implementation of redisContextFuncs for SSL connections.
- */
- static void redisSSLFree(void *privctx){
- redisSSL *rsc = privctx;
- if (!rsc) return;
- if (rsc->ssl) {
- SSL_free(rsc->ssl);
- rsc->ssl = NULL;
- }
- hi_free(rsc);
- }
- static ssize_t redisSSLRead(redisContext *c, char *buf, size_t bufcap) {
- redisSSL *rssl = c->privctx;
- int nread = SSL_read(rssl->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(rssl->ssl, nread);
- if (c->flags & REDIS_BLOCK) {
- /**
- * In blocking mode, we should never end up in a situation where
- * we get an error without it being an actual error, except
- * in the case of EINTR, which can be spuriously received from
- * debuggers or whatever.
- */
- if (errno == EINTR) {
- return 0;
- } else {
- const char *msg = NULL;
- if (errno == EAGAIN) {
- msg = "Resource temporarily unavailable";
- }
- __redisSetError(c, REDIS_ERR_IO, msg);
- return -1;
- }
- }
- /**
- * We can very well get an EWOULDBLOCK/EAGAIN, however
- */
- if (maybeCheckWant(rssl, err)) {
- return 0;
- } else {
- __redisSetError(c, REDIS_ERR_IO, NULL);
- return -1;
- }
- }
- }
- static ssize_t redisSSLWrite(redisContext *c) {
- redisSSL *rssl = c->privctx;
- size_t len = rssl->lastLen ? rssl->lastLen : hi_sdslen(c->obuf);
- int rv = SSL_write(rssl->ssl, c->obuf, len);
- if (rv > 0) {
- rssl->lastLen = 0;
- } else if (rv < 0) {
- rssl->lastLen = len;
- int err = SSL_get_error(rssl->ssl, rv);
- if ((c->flags & REDIS_BLOCK) == 0 && maybeCheckWant(rssl, err)) {
- return 0;
- } else {
- __redisSetError(c, REDIS_ERR_IO, NULL);
- return -1;
- }
- }
- return rv;
- }
- static void redisSSLAsyncRead(redisAsyncContext *ac) {
- int rv;
- redisSSL *rssl = ac->c.privctx;
- redisContext *c = &ac->c;
- rssl->wantRead = 0;
- if (rssl->pendingWrite) {
- int done;
- /* This is probably just a write event */
- rssl->pendingWrite = 0;
- rv = redisBufferWrite(c, &done);
- if (rv == REDIS_ERR) {
- __redisAsyncDisconnect(ac);
- return;
- } else if (!done) {
- _EL_ADD_WRITE(ac);
- }
- }
- rv = redisBufferRead(c);
- if (rv == REDIS_ERR) {
- __redisAsyncDisconnect(ac);
- } else {
- _EL_ADD_READ(ac);
- redisProcessCallbacks(ac);
- }
- }
- static void redisSSLAsyncWrite(redisAsyncContext *ac) {
- int rv, done = 0;
- redisSSL *rssl = ac->c.privctx;
- redisContext *c = &ac->c;
- rssl->pendingWrite = 0;
- rv = redisBufferWrite(c, &done);
- if (rv == REDIS_ERR) {
- __redisAsyncDisconnect(ac);
- return;
- }
- if (!done) {
- if (rssl->wantRead) {
- /* Need to read-before-write */
- rssl->pendingWrite = 1;
- _EL_DEL_WRITE(ac);
- } else {
- /* No extra reads needed, just need to write more */
- _EL_ADD_WRITE(ac);
- }
- } else {
- /* Already done! */
- _EL_DEL_WRITE(ac);
- }
- /* Always reschedule a read */
- _EL_ADD_READ(ac);
- }
- redisContextFuncs redisContextSSLFuncs = {
- .free_privctx = redisSSLFree,
- .async_read = redisSSLAsyncRead,
- .async_write = redisSSLAsyncWrite,
- .read = redisSSLRead,
- .write = redisSSLWrite
- };
|