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