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#include <wincrypt.h>
42#else
43#include <pthread.h>
44#endif
45
46#include <openssl/ssl.h>
47#include <openssl/err.h>
48
49#include "win32.h"
50#include "async_private.h"
51#include "hiredis_ssl.h"
52
53void __redisSetError(redisContext *c, int type, const char *str);
54
55struct redisSSLContext {
56 /* Associated OpenSSL SSL_CTX as created by redisCreateSSLContext() */
57 SSL_CTX *ssl_ctx;
58
59 /* Requested SNI, or NULL */
60 char *server_name;
61};
62
63/* The SSL connection context is attached to SSL/TLS connections as a privdata. */
64typedef struct redisSSL {
65 /**
66 * OpenSSL SSL object.
67 */
68 SSL *ssl;
69
70 /**
71 * SSL_write() requires to be called again with the same arguments it was
72 * previously called with in the event of an SSL_read/SSL_write situation
73 */
74 size_t lastLen;
75
76 /** Whether the SSL layer requires read (possibly before a write) */
77 int wantRead;
78
79 /**
80 * Whether a write was requested prior to a read. If set, the write()
81 * should resume whenever a read takes place, if possible
82 */
83 int pendingWrite;
84} redisSSL;
85
86/* Forward declaration */
87redisContextFuncs redisContextSSLFuncs;
88
89/**
90 * OpenSSL global initialization and locking handling callbacks.
91 * Note that this is only required for OpenSSL < 1.1.0.
92 */
93
94#if OPENSSL_VERSION_NUMBER < 0x10100000L
95#define HIREDIS_USE_CRYPTO_LOCKS
96#endif
97
98#ifdef HIREDIS_USE_CRYPTO_LOCKS
99#ifdef _WIN32
100typedef CRITICAL_SECTION sslLockType;
101static void sslLockInit(sslLockType* l) {
102 InitializeCriticalSection(l);
103}
104static void sslLockAcquire(sslLockType* l) {
105 EnterCriticalSection(l);
106}
107static void sslLockRelease(sslLockType* l) {
108 LeaveCriticalSection(l);
109}
110#else
111typedef pthread_mutex_t sslLockType;
112static void sslLockInit(sslLockType *l) {
113 pthread_mutex_init(l, NULL);
114}
115static void sslLockAcquire(sslLockType *l) {
116 pthread_mutex_lock(l);
117}
118static void sslLockRelease(sslLockType *l) {
119 pthread_mutex_unlock(l);
120}
121#endif
122
123static sslLockType* ossl_locks;
124
125static void opensslDoLock(int mode, int lkid, const char *f, int line) {
126 sslLockType *l = ossl_locks + lkid;
127
128 if (mode & CRYPTO_LOCK) {
129 sslLockAcquire(l);
130 } else {
131 sslLockRelease(l);
132 }
133
134 (void)f;
135 (void)line;
136}
137
138static int initOpensslLocks(void) {
139 unsigned ii, nlocks;
140 if (CRYPTO_get_locking_callback() != NULL) {
141 /* Someone already set the callback before us. Don't destroy it! */
142 return REDIS_OK;
143 }
144 nlocks = CRYPTO_num_locks();
145 ossl_locks = hi_malloc(sizeof(*ossl_locks) * nlocks);
146 if (ossl_locks == NULL)
147 return REDIS_ERR;
148
149 for (ii = 0; ii < nlocks; ii++) {
150 sslLockInit(ossl_locks + ii);
151 }
152 CRYPTO_set_locking_callback(opensslDoLock);
153 return REDIS_OK;
154}
155#endif /* HIREDIS_USE_CRYPTO_LOCKS */
156
157int redisInitOpenSSL(void)
158{
159 SSL_library_init();
160#ifdef HIREDIS_USE_CRYPTO_LOCKS
161 initOpensslLocks();
162#endif
163
164 return REDIS_OK;
165}
166
167/**
168 * redisSSLContext helper context destruction.
169 */
170
171const char *redisSSLContextGetError(redisSSLContextError error)
172{
173 switch (error) {
174 case REDIS_SSL_CTX_NONE:
175 return "No Error";
176 case REDIS_SSL_CTX_CREATE_FAILED:
177 return "Failed to create OpenSSL SSL_CTX";
178 case REDIS_SSL_CTX_CERT_KEY_REQUIRED:
179 return "Client cert and key must both be specified or skipped";
180 case REDIS_SSL_CTX_CA_CERT_LOAD_FAILED:
181 return "Failed to load CA Certificate or CA Path";
182 case REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED:
183 return "Failed to load client certificate";
184 case REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED:
185 return "Failed to load private key";
186 case REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED:
187 return "Failed to open system certificate store";
188 case REDIS_SSL_CTX_OS_CERT_ADD_FAILED:
189 return "Failed to add CA certificates obtained from system to the SSL context";
190 default:
191 return "Unknown error code";
192 }
193}
194
195void redisFreeSSLContext(redisSSLContext *ctx)
196{
197 if (!ctx)
198 return;
199
200 if (ctx->server_name) {
201 hi_free(ctx->server_name);
202 ctx->server_name = NULL;
203 }
204
205 if (ctx->ssl_ctx) {
206 SSL_CTX_free(ctx->ssl_ctx);
207 ctx->ssl_ctx = NULL;
208 }
209
210 hi_free(ctx);
211}
212
213
214/**
215 * redisSSLContext helper context initialization.
216 */
217
218redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *capath,
219 const char *cert_filename, const char *private_key_filename,
220 const char *server_name, redisSSLContextError *error)
221{
222#ifdef _WIN32
223 HCERTSTORE win_store = NULL;
224 PCCERT_CONTEXT win_ctx = NULL;
225#endif
226
227 redisSSLContext *ctx = hi_calloc(1, sizeof(redisSSLContext));
228 if (ctx == NULL)
229 goto error;
230
231 ctx->ssl_ctx = SSL_CTX_new(SSLv23_client_method());
232 if (!ctx->ssl_ctx) {
233 if (error) *error = REDIS_SSL_CTX_CREATE_FAILED;
234 goto error;
235 }
236
237 SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
238 SSL_CTX_set_verify(ctx->ssl_ctx, SSL_VERIFY_PEER, NULL);
239
240 if ((cert_filename != NULL && private_key_filename == NULL) ||
241 (private_key_filename != NULL && cert_filename == NULL)) {
242 if (error) *error = REDIS_SSL_CTX_CERT_KEY_REQUIRED;
243 goto error;
244 }
245
246 if (capath || cacert_filename) {
247#ifdef _WIN32
248 if (0 == strcmp(cacert_filename, "wincert")) {
249 win_store = CertOpenSystemStore(NULL, "Root");
250 if (!win_store) {
251 if (error) *error = REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED;
252 goto error;
253 }
254 X509_STORE* store = SSL_CTX_get_cert_store(ctx->ssl_ctx);
255 while (win_ctx = CertEnumCertificatesInStore(win_store, win_ctx)) {
256 X509* x509 = NULL;
257 x509 = d2i_X509(NULL, (const unsigned char**)&win_ctx->pbCertEncoded, win_ctx->cbCertEncoded);
258 if (x509) {
259 if ((1 != X509_STORE_add_cert(store, x509)) ||
260 (1 != SSL_CTX_add_client_CA(ctx->ssl_ctx, x509)))
261 {
262 if (error) *error = REDIS_SSL_CTX_OS_CERT_ADD_FAILED;
263 goto error;
264 }
265 X509_free(x509);
266 }
267 }
268 CertFreeCertificateContext(win_ctx);
269 CertCloseStore(win_store, 0);
270 } else
271#endif
272 if (!SSL_CTX_load_verify_locations(ctx->ssl_ctx, cacert_filename, capath)) {
273 if (error) *error = REDIS_SSL_CTX_CA_CERT_LOAD_FAILED;
274 goto error;
275 }
276 }
277
278 if (cert_filename) {
279 if (!SSL_CTX_use_certificate_chain_file(ctx->ssl_ctx, cert_filename)) {
280 if (error) *error = REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED;
281 goto error;
282 }
283 if (!SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, private_key_filename, SSL_FILETYPE_PEM)) {
284 if (error) *error = REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED;
285 goto error;
286 }
287 }
288
289 if (server_name)
290 ctx->server_name = hi_strdup(server_name);
291
292 return ctx;
293
294error:
295#ifdef _WIN32
296 CertFreeCertificateContext(win_ctx);
297 CertCloseStore(win_store, 0);
298#endif
299 redisFreeSSLContext(ctx);
300 return NULL;
301}
302
303/**
304 * SSL Connection initialization.
305 */
306
307
308static int redisSSLConnect(redisContext *c, SSL *ssl) {
309 if (c->privctx) {
310 __redisSetError(c, REDIS_ERR_OTHER, "redisContext was already associated");
311 return REDIS_ERR;
312 }
313
314 redisSSL *rssl = hi_calloc(1, sizeof(redisSSL));
315 if (rssl == NULL) {
316 __redisSetError(c, REDIS_ERR_OOM, "Out of memory");
317 return REDIS_ERR;
318 }
319
320 c->funcs = &redisContextSSLFuncs;
321 rssl->ssl = ssl;
322
323 SSL_set_mode(rssl->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
324 SSL_set_fd(rssl->ssl, c->fd);
325 SSL_set_connect_state(rssl->ssl);
326
327 ERR_clear_error();
328 int rv = SSL_connect(rssl->ssl);
329 if (rv == 1) {
330 c->privctx = rssl;
331 return REDIS_OK;
332 }
333
334 rv = SSL_get_error(rssl->ssl, rv);
335 if (((c->flags & REDIS_BLOCK) == 0) &&
336 (rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE)) {
337 c->privctx = rssl;
338 return REDIS_OK;
339 }
340
341 if (c->err == 0) {
342 char err[512];
343 if (rv == SSL_ERROR_SYSCALL)
344 snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",strerror(errno));
345 else {
346 unsigned long e = ERR_peek_last_error();
347 snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",
348 ERR_reason_error_string(e));
349 }
350 __redisSetError(c, REDIS_ERR_IO, err);
351 }
352
353 hi_free(rssl);
354 return REDIS_ERR;
355}
356
357/**
358 * A wrapper around redisSSLConnect() for users who manage their own context and
359 * create their own SSL object.
360 */
361
362int redisInitiateSSL(redisContext *c, SSL *ssl) {
363 return redisSSLConnect(c, ssl);
364}
365
366/**
367 * A wrapper around redisSSLConnect() for users who use redisSSLContext and don't
368 * manage their own SSL objects.
369 */
370
371int redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx)
372{
373 if (!c || !redis_ssl_ctx)
374 return REDIS_ERR;
375
376 /* We want to verify that redisSSLConnect() won't fail on this, as it will
377 * not own the SSL object in that case and we'll end up leaking.
378 */
379 if (c->privctx)
380 return REDIS_ERR;
381
382 SSL *ssl = SSL_new(redis_ssl_ctx->ssl_ctx);
383 if (!ssl) {
384 __redisSetError(c, REDIS_ERR_OTHER, "Couldn't create new SSL instance");
385 goto error;
386 }
387
388 if (redis_ssl_ctx->server_name) {
389 if (!SSL_set_tlsext_host_name(ssl, redis_ssl_ctx->server_name)) {
390 __redisSetError(c, REDIS_ERR_OTHER, "Failed to set server_name/SNI");
391 goto error;
392 }
393 }
394
395 if (redisSSLConnect(c, ssl) != REDIS_OK) {
396 goto error;
397 }
398
399 return REDIS_OK;
400
401error:
402 if (ssl)
403 SSL_free(ssl);
404 return REDIS_ERR;
405}
406
407static int maybeCheckWant(redisSSL *rssl, int rv) {
408 /**
409 * If the error is WANT_READ or WANT_WRITE, the appropriate flags are set
410 * and true is returned. False is returned otherwise
411 */
412 if (rv == SSL_ERROR_WANT_READ) {
413 rssl->wantRead = 1;
414 return 1;
415 } else if (rv == SSL_ERROR_WANT_WRITE) {
416 rssl->pendingWrite = 1;
417 return 1;
418 } else {
419 return 0;
420 }
421}
422
423/**
424 * Implementation of redisContextFuncs for SSL connections.
425 */
426
427static void redisSSLFree(void *privctx){
428 redisSSL *rsc = privctx;
429
430 if (!rsc) return;
431 if (rsc->ssl) {
432 SSL_free(rsc->ssl);
433 rsc->ssl = NULL;
434 }
435 hi_free(rsc);
436}
437
438static ssize_t redisSSLRead(redisContext *c, char *buf, size_t bufcap) {
439 redisSSL *rssl = c->privctx;
440
441 int nread = SSL_read(rssl->ssl, buf, bufcap);
442 if (nread > 0) {
443 return nread;
444 } else if (nread == 0) {
445 __redisSetError(c, REDIS_ERR_EOF, "Server closed the connection");
446 return -1;
447 } else {
448 int err = SSL_get_error(rssl->ssl, nread);
449 if (c->flags & REDIS_BLOCK) {
450 /**
451 * In blocking mode, we should never end up in a situation where
452 * we get an error without it being an actual error, except
453 * in the case of EINTR, which can be spuriously received from
454 * debuggers or whatever.
455 */
456 if (errno == EINTR) {
457 return 0;
458 } else {
459 const char *msg = NULL;
460 if (errno == EAGAIN) {
461 msg = "Resource temporarily unavailable";
462 }
463 __redisSetError(c, REDIS_ERR_IO, msg);
464 return -1;
465 }
466 }
467
468 /**
469 * We can very well get an EWOULDBLOCK/EAGAIN, however
470 */
471 if (maybeCheckWant(rssl, err)) {
472 return 0;
473 } else {
474 __redisSetError(c, REDIS_ERR_IO, NULL);
475 return -1;
476 }
477 }
478}
479
480static ssize_t redisSSLWrite(redisContext *c) {
481 redisSSL *rssl = c->privctx;
482
483 size_t len = rssl->lastLen ? rssl->lastLen : hi_sdslen(c->obuf);
484 int rv = SSL_write(rssl->ssl, c->obuf, len);
485
486 if (rv > 0) {
487 rssl->lastLen = 0;
488 } else if (rv < 0) {
489 rssl->lastLen = len;
490
491 int err = SSL_get_error(rssl->ssl, rv);
492 if ((c->flags & REDIS_BLOCK) == 0 && maybeCheckWant(rssl, err)) {
493 return 0;
494 } else {
495 __redisSetError(c, REDIS_ERR_IO, NULL);
496 return -1;
497 }
498 }
499 return rv;
500}
501
502static void redisSSLAsyncRead(redisAsyncContext *ac) {
503 int rv;
504 redisSSL *rssl = ac->c.privctx;
505 redisContext *c = &ac->c;
506
507 rssl->wantRead = 0;
508
509 if (rssl->pendingWrite) {
510 int done;
511
512 /* This is probably just a write event */
513 rssl->pendingWrite = 0;
514 rv = redisBufferWrite(c, &done);
515 if (rv == REDIS_ERR) {
516 __redisAsyncDisconnect(ac);
517 return;
518 } else if (!done) {
519 _EL_ADD_WRITE(ac);
520 }
521 }
522
523 rv = redisBufferRead(c);
524 if (rv == REDIS_ERR) {
525 __redisAsyncDisconnect(ac);
526 } else {
527 _EL_ADD_READ(ac);
528 redisProcessCallbacks(ac);
529 }
530}
531
532static void redisSSLAsyncWrite(redisAsyncContext *ac) {
533 int rv, done = 0;
534 redisSSL *rssl = ac->c.privctx;
535 redisContext *c = &ac->c;
536
537 rssl->pendingWrite = 0;
538 rv = redisBufferWrite(c, &done);
539 if (rv == REDIS_ERR) {
540 __redisAsyncDisconnect(ac);
541 return;
542 }
543
544 if (!done) {
545 if (rssl->wantRead) {
546 /* Need to read-before-write */
547 rssl->pendingWrite = 1;
548 _EL_DEL_WRITE(ac);
549 } else {
550 /* No extra reads needed, just need to write more */
551 _EL_ADD_WRITE(ac);
552 }
553 } else {
554 /* Already done! */
555 _EL_DEL_WRITE(ac);
556 }
557
558 /* Always reschedule a read */
559 _EL_ADD_READ(ac);
560}
561
562redisContextFuncs redisContextSSLFuncs = {
563 .free_privctx = redisSSLFree,
564 .async_read = redisSSLAsyncRead,
565 .async_write = redisSSLAsyncWrite,
566 .read = redisSSLRead,
567 .write = redisSSLWrite
568};
569
570