1 /*
2  * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
3  * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
4  * Copyright (c) 2019, Redis Labs
5  *
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are met:
10  *
11  *   * Redistributions of source code must retain the above copyright notice,
12  *     this list of conditions and the following disclaimer.
13  *   * Redistributions in binary form must reproduce the above copyright
14  *     notice, this list of conditions and the following disclaimer in the
15  *     documentation and/or other materials provided with the distribution.
16  *   * Neither the name of Redis nor the names of its contributors may be used
17  *     to endorse or promote products derived from this software without
18  *     specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 #include "hiredis.h"
34 #include "async.h"
35 
36 #include <assert.h>
37 #include <errno.h>
38 #include <string.h>
39 #ifdef _WIN32
40 #include <windows.h>
41 #else
42 #include <pthread.h>
43 #endif
44 
45 #include <openssl/ssl.h>
46 #include <openssl/err.h>
47 
48 #include "win32.h"
49 #include "async_private.h"
50 #include "hiredis_ssl.h"
51 
52 void __redisSetError(redisContext *c, int type, const char *str);
53 
54 struct redisSSLContext {
55     /* Associated OpenSSL SSL_CTX as created by redisCreateSSLContext() */
56     SSL_CTX *ssl_ctx;
57 
58     /* Requested SNI, or NULL */
59     char *server_name;
60 };
61 
62 /* The SSL connection context is attached to SSL/TLS connections as a privdata. */
63 typedef struct redisSSL {
64     /**
65      * OpenSSL SSL object.
66      */
67     SSL *ssl;
68 
69     /**
70      * SSL_write() requires to be called again with the same arguments it was
71      * previously called with in the event of an SSL_read/SSL_write situation
72      */
73     size_t lastLen;
74 
75     /** Whether the SSL layer requires read (possibly before a write) */
76     int wantRead;
77 
78     /**
79      * Whether a write was requested prior to a read. If set, the write()
80      * should resume whenever a read takes place, if possible
81      */
82     int pendingWrite;
83 } redisSSL;
84 
85 /* Forward declaration */
86 redisContextFuncs redisContextSSLFuncs;
87 
88 /**
89  * OpenSSL global initialization and locking handling callbacks.
90  * Note that this is only required for OpenSSL < 1.1.0.
91  */
92 
93 #if OPENSSL_VERSION_NUMBER < 0x10100000L
94 #define HIREDIS_USE_CRYPTO_LOCKS
95 #endif
96 
97 #ifdef HIREDIS_USE_CRYPTO_LOCKS
98 #ifdef _WIN32
99 typedef CRITICAL_SECTION sslLockType;
sslLockInit(sslLockType * l)100 static void sslLockInit(sslLockType* l) {
101     InitializeCriticalSection(l);
102 }
sslLockAcquire(sslLockType * l)103 static void sslLockAcquire(sslLockType* l) {
104     EnterCriticalSection(l);
105 }
sslLockRelease(sslLockType * l)106 static void sslLockRelease(sslLockType* l) {
107     LeaveCriticalSection(l);
108 }
109 #else
110 typedef pthread_mutex_t sslLockType;
sslLockInit(sslLockType * l)111 static void sslLockInit(sslLockType *l) {
112     pthread_mutex_init(l, NULL);
113 }
sslLockAcquire(sslLockType * l)114 static void sslLockAcquire(sslLockType *l) {
115     pthread_mutex_lock(l);
116 }
sslLockRelease(sslLockType * l)117 static void sslLockRelease(sslLockType *l) {
118     pthread_mutex_unlock(l);
119 }
120 #endif
121 
122 static sslLockType* ossl_locks;
123 
opensslDoLock(int mode,int lkid,const char * f,int line)124 static void opensslDoLock(int mode, int lkid, const char *f, int line) {
125     sslLockType *l = ossl_locks + lkid;
126 
127     if (mode & CRYPTO_LOCK) {
128         sslLockAcquire(l);
129     } else {
130         sslLockRelease(l);
131     }
132 
133     (void)f;
134     (void)line;
135 }
136 
initOpensslLocks(void)137 static int initOpensslLocks(void) {
138     unsigned ii, nlocks;
139     if (CRYPTO_get_locking_callback() != NULL) {
140         /* Someone already set the callback before us. Don't destroy it! */
141         return REDIS_OK;
142     }
143     nlocks = CRYPTO_num_locks();
144     ossl_locks = hi_malloc(sizeof(*ossl_locks) * nlocks);
145     if (ossl_locks == NULL)
146         return REDIS_ERR;
147 
148     for (ii = 0; ii < nlocks; ii++) {
149         sslLockInit(ossl_locks + ii);
150     }
151     CRYPTO_set_locking_callback(opensslDoLock);
152     return REDIS_OK;
153 }
154 #endif /* HIREDIS_USE_CRYPTO_LOCKS */
155 
redisInitOpenSSL(void)156 int redisInitOpenSSL(void)
157 {
158     SSL_library_init();
159 #ifdef HIREDIS_USE_CRYPTO_LOCKS
160     initOpensslLocks();
161 #endif
162 
163     return REDIS_OK;
164 }
165 
166 /**
167  * redisSSLContext helper context destruction.
168  */
169 
redisSSLContextGetError(redisSSLContextError error)170 const char *redisSSLContextGetError(redisSSLContextError error)
171 {
172     switch (error) {
173         case REDIS_SSL_CTX_NONE:
174             return "No Error";
175         case REDIS_SSL_CTX_CREATE_FAILED:
176             return "Failed to create OpenSSL SSL_CTX";
177         case REDIS_SSL_CTX_CERT_KEY_REQUIRED:
178             return "Client cert and key must both be specified or skipped";
179         case REDIS_SSL_CTX_CA_CERT_LOAD_FAILED:
180             return "Failed to load CA Certificate or CA Path";
181         case REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED:
182             return "Failed to load client certificate";
183         case REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED:
184             return "Failed to load private key";
185         default:
186             return "Unknown error code";
187     }
188 }
189 
redisFreeSSLContext(redisSSLContext * ctx)190 void redisFreeSSLContext(redisSSLContext *ctx)
191 {
192     if (!ctx)
193         return;
194 
195     if (ctx->server_name) {
196         hi_free(ctx->server_name);
197         ctx->server_name = NULL;
198     }
199 
200     if (ctx->ssl_ctx) {
201         SSL_CTX_free(ctx->ssl_ctx);
202         ctx->ssl_ctx = NULL;
203     }
204 
205     hi_free(ctx);
206 }
207 
208 
209 /**
210  * redisSSLContext helper context initialization.
211  */
212 
redisCreateSSLContext(const char * cacert_filename,const char * capath,const char * cert_filename,const char * private_key_filename,const char * server_name,redisSSLContextError * error)213 redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *capath,
214         const char *cert_filename, const char *private_key_filename,
215         const char *server_name, redisSSLContextError *error)
216 {
217     redisSSLContext *ctx = hi_calloc(1, sizeof(redisSSLContext));
218     if (ctx == NULL)
219         goto error;
220 
221     ctx->ssl_ctx = SSL_CTX_new(SSLv23_client_method());
222     if (!ctx->ssl_ctx) {
223         if (error) *error = REDIS_SSL_CTX_CREATE_FAILED;
224         goto error;
225     }
226 
227     SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
228     SSL_CTX_set_verify(ctx->ssl_ctx, SSL_VERIFY_PEER, NULL);
229 
230     if ((cert_filename != NULL && private_key_filename == NULL) ||
231             (private_key_filename != NULL && cert_filename == NULL)) {
232         if (error) *error = REDIS_SSL_CTX_CERT_KEY_REQUIRED;
233         goto error;
234     }
235 
236     if (capath || cacert_filename) {
237         if (!SSL_CTX_load_verify_locations(ctx->ssl_ctx, cacert_filename, capath)) {
238             if (error) *error = REDIS_SSL_CTX_CA_CERT_LOAD_FAILED;
239             goto error;
240         }
241     }
242 
243     if (cert_filename) {
244         if (!SSL_CTX_use_certificate_chain_file(ctx->ssl_ctx, cert_filename)) {
245             if (error) *error = REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED;
246             goto error;
247         }
248         if (!SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, private_key_filename, SSL_FILETYPE_PEM)) {
249             if (error) *error = REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED;
250             goto error;
251         }
252     }
253 
254     if (server_name)
255         ctx->server_name = hi_strdup(server_name);
256 
257     return ctx;
258 
259 error:
260     redisFreeSSLContext(ctx);
261     return NULL;
262 }
263 
264 /**
265  * SSL Connection initialization.
266  */
267 
268 
redisSSLConnect(redisContext * c,SSL * ssl)269 static int redisSSLConnect(redisContext *c, SSL *ssl) {
270     if (c->privctx) {
271         __redisSetError(c, REDIS_ERR_OTHER, "redisContext was already associated");
272         return REDIS_ERR;
273     }
274 
275     redisSSL *rssl = hi_calloc(1, sizeof(redisSSL));
276     if (rssl == NULL) {
277         __redisSetError(c, REDIS_ERR_OOM, "Out of memory");
278         return REDIS_ERR;
279     }
280 
281     c->funcs = &redisContextSSLFuncs;
282     rssl->ssl = ssl;
283 
284     SSL_set_mode(rssl->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
285     SSL_set_fd(rssl->ssl, c->fd);
286     SSL_set_connect_state(rssl->ssl);
287 
288     ERR_clear_error();
289     int rv = SSL_connect(rssl->ssl);
290     if (rv == 1) {
291         c->privctx = rssl;
292         return REDIS_OK;
293     }
294 
295     rv = SSL_get_error(rssl->ssl, rv);
296     if (((c->flags & REDIS_BLOCK) == 0) &&
297         (rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE)) {
298         c->privctx = rssl;
299         return REDIS_OK;
300     }
301 
302     if (c->err == 0) {
303         char err[512];
304         if (rv == SSL_ERROR_SYSCALL)
305             snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",strerror(errno));
306         else {
307             unsigned long e = ERR_peek_last_error();
308             snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",
309                     ERR_reason_error_string(e));
310         }
311         __redisSetError(c, REDIS_ERR_IO, err);
312     }
313 
314     hi_free(rssl);
315     return REDIS_ERR;
316 }
317 
318 /**
319  * A wrapper around redisSSLConnect() for users who manage their own context and
320  * create their own SSL object.
321  */
322 
redisInitiateSSL(redisContext * c,SSL * ssl)323 int redisInitiateSSL(redisContext *c, SSL *ssl) {
324     return redisSSLConnect(c, ssl);
325 }
326 
327 /**
328  * A wrapper around redisSSLConnect() for users who use redisSSLContext and don't
329  * manage their own SSL objects.
330  */
331 
redisInitiateSSLWithContext(redisContext * c,redisSSLContext * redis_ssl_ctx)332 int redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx)
333 {
334     if (!c || !redis_ssl_ctx)
335         return REDIS_ERR;
336 
337     /* We want to verify that redisSSLConnect() won't fail on this, as it will
338      * not own the SSL object in that case and we'll end up leaking.
339      */
340     if (c->privctx)
341         return REDIS_ERR;
342 
343     SSL *ssl = SSL_new(redis_ssl_ctx->ssl_ctx);
344     if (!ssl) {
345         __redisSetError(c, REDIS_ERR_OTHER, "Couldn't create new SSL instance");
346         goto error;
347     }
348 
349     if (redis_ssl_ctx->server_name) {
350         if (!SSL_set_tlsext_host_name(ssl, redis_ssl_ctx->server_name)) {
351             __redisSetError(c, REDIS_ERR_OTHER, "Failed to set server_name/SNI");
352             goto error;
353         }
354     }
355 
356     return redisSSLConnect(c, ssl);
357 
358 error:
359     if (ssl)
360         SSL_free(ssl);
361     return REDIS_ERR;
362 }
363 
maybeCheckWant(redisSSL * rssl,int rv)364 static int maybeCheckWant(redisSSL *rssl, int rv) {
365     /**
366      * If the error is WANT_READ or WANT_WRITE, the appropriate flags are set
367      * and true is returned. False is returned otherwise
368      */
369     if (rv == SSL_ERROR_WANT_READ) {
370         rssl->wantRead = 1;
371         return 1;
372     } else if (rv == SSL_ERROR_WANT_WRITE) {
373         rssl->pendingWrite = 1;
374         return 1;
375     } else {
376         return 0;
377     }
378 }
379 
380 /**
381  * Implementation of redisContextFuncs for SSL connections.
382  */
383 
redisSSLFree(void * privctx)384 static void redisSSLFree(void *privctx){
385     redisSSL *rsc = privctx;
386 
387     if (!rsc) return;
388     if (rsc->ssl) {
389         SSL_free(rsc->ssl);
390         rsc->ssl = NULL;
391     }
392     hi_free(rsc);
393 }
394 
redisSSLRead(redisContext * c,char * buf,size_t bufcap)395 static ssize_t redisSSLRead(redisContext *c, char *buf, size_t bufcap) {
396     redisSSL *rssl = c->privctx;
397 
398     int nread = SSL_read(rssl->ssl, buf, bufcap);
399     if (nread > 0) {
400         return nread;
401     } else if (nread == 0) {
402         __redisSetError(c, REDIS_ERR_EOF, "Server closed the connection");
403         return -1;
404     } else {
405         int err = SSL_get_error(rssl->ssl, nread);
406         if (c->flags & REDIS_BLOCK) {
407             /**
408              * In blocking mode, we should never end up in a situation where
409              * we get an error without it being an actual error, except
410              * in the case of EINTR, which can be spuriously received from
411              * debuggers or whatever.
412              */
413             if (errno == EINTR) {
414                 return 0;
415             } else {
416                 const char *msg = NULL;
417                 if (errno == EAGAIN) {
418                     msg = "Resource temporarily unavailable";
419                 }
420                 __redisSetError(c, REDIS_ERR_IO, msg);
421                 return -1;
422             }
423         }
424 
425         /**
426          * We can very well get an EWOULDBLOCK/EAGAIN, however
427          */
428         if (maybeCheckWant(rssl, err)) {
429             return 0;
430         } else {
431             __redisSetError(c, REDIS_ERR_IO, NULL);
432             return -1;
433         }
434     }
435 }
436 
redisSSLWrite(redisContext * c)437 static ssize_t redisSSLWrite(redisContext *c) {
438     redisSSL *rssl = c->privctx;
439 
440     size_t len = rssl->lastLen ? rssl->lastLen : sdslen(c->obuf);
441     int rv = SSL_write(rssl->ssl, c->obuf, len);
442 
443     if (rv > 0) {
444         rssl->lastLen = 0;
445     } else if (rv < 0) {
446         rssl->lastLen = len;
447 
448         int err = SSL_get_error(rssl->ssl, rv);
449         if ((c->flags & REDIS_BLOCK) == 0 && maybeCheckWant(rssl, err)) {
450             return 0;
451         } else {
452             __redisSetError(c, REDIS_ERR_IO, NULL);
453             return -1;
454         }
455     }
456     return rv;
457 }
458 
redisSSLAsyncRead(redisAsyncContext * ac)459 static void redisSSLAsyncRead(redisAsyncContext *ac) {
460     int rv;
461     redisSSL *rssl = ac->c.privctx;
462     redisContext *c = &ac->c;
463 
464     rssl->wantRead = 0;
465 
466     if (rssl->pendingWrite) {
467         int done;
468 
469         /* This is probably just a write event */
470         rssl->pendingWrite = 0;
471         rv = redisBufferWrite(c, &done);
472         if (rv == REDIS_ERR) {
473             __redisAsyncDisconnect(ac);
474             return;
475         } else if (!done) {
476             _EL_ADD_WRITE(ac);
477         }
478     }
479 
480     rv = redisBufferRead(c);
481     if (rv == REDIS_ERR) {
482         __redisAsyncDisconnect(ac);
483     } else {
484         _EL_ADD_READ(ac);
485         redisProcessCallbacks(ac);
486     }
487 }
488 
redisSSLAsyncWrite(redisAsyncContext * ac)489 static void redisSSLAsyncWrite(redisAsyncContext *ac) {
490     int rv, done = 0;
491     redisSSL *rssl = ac->c.privctx;
492     redisContext *c = &ac->c;
493 
494     rssl->pendingWrite = 0;
495     rv = redisBufferWrite(c, &done);
496     if (rv == REDIS_ERR) {
497         __redisAsyncDisconnect(ac);
498         return;
499     }
500 
501     if (!done) {
502         if (rssl->wantRead) {
503             /* Need to read-before-write */
504             rssl->pendingWrite = 1;
505             _EL_DEL_WRITE(ac);
506         } else {
507             /* No extra reads needed, just need to write more */
508             _EL_ADD_WRITE(ac);
509         }
510     } else {
511         /* Already done! */
512         _EL_DEL_WRITE(ac);
513     }
514 
515     /* Always reschedule a read */
516     _EL_ADD_READ(ac);
517 }
518 
519 redisContextFuncs redisContextSSLFuncs = {
520     .free_privctx = redisSSLFree,
521     .async_read = redisSSLAsyncRead,
522     .async_write = redisSSLAsyncWrite,
523     .read = redisSSLRead,
524     .write = redisSSLWrite
525 };
526 
527