diff options
| author | Mitja Felicijan <mitja.felicijan@gmail.com> | 2026-01-21 22:52:54 +0100 |
|---|---|---|
| committer | Mitja Felicijan <mitja.felicijan@gmail.com> | 2026-01-21 22:52:54 +0100 |
| commit | dcacc00e3750300617ba6e16eb346713f91a783a (patch) | |
| tree | 38e2d4fb5ed9d119711d4295c6eda4b014af73fd /examples/redis-unstable/src/tls.c | |
| parent | 58dac10aeb8f5a041c46bddbeaf4c7966a99b998 (diff) | |
| download | crep-dcacc00e3750300617ba6e16eb346713f91a783a.tar.gz | |
Remove testing data
Diffstat (limited to 'examples/redis-unstable/src/tls.c')
| -rw-r--r-- | examples/redis-unstable/src/tls.c | 1295 |
1 files changed, 0 insertions, 1295 deletions
diff --git a/examples/redis-unstable/src/tls.c b/examples/redis-unstable/src/tls.c deleted file mode 100644 index 0cae5b9..0000000 --- a/examples/redis-unstable/src/tls.c +++ /dev/null @@ -1,1295 +0,0 @@ -/* - * Copyright (c) 2019-Present, Redis Ltd. - * All rights reserved. - * - * Copyright (c) 2024-present, Valkey contributors. - * All rights reserved. - * - * Licensed under your choice of (a) the Redis Source Available License 2.0 - * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the - * GNU Affero General Public License v3 (AGPLv3). - */ - -#define REDISMODULE_CORE_MODULE /* A module that's part of the redis core, uses server.h too. */ - -#include "server.h" -#include "connhelpers.h" -#include "adlist.h" - -#if (USE_OPENSSL == 1 /* BUILD_YES */ ) || ((USE_OPENSSL == 2 /* BUILD_MODULE */) && (BUILD_TLS_MODULE == 2)) - -#include <openssl/conf.h> -#include <openssl/ssl.h> -#include <openssl/x509.h> -#include <openssl/err.h> -#include <openssl/rand.h> -#include <openssl/pem.h> -#if OPENSSL_VERSION_NUMBER >= 0x30000000L -#include <openssl/decoder.h> -#endif -#include <sys/uio.h> -#include <arpa/inet.h> - -#define REDIS_TLS_PROTO_TLSv1 (1<<0) -#define REDIS_TLS_PROTO_TLSv1_1 (1<<1) -#define REDIS_TLS_PROTO_TLSv1_2 (1<<2) -#define REDIS_TLS_PROTO_TLSv1_3 (1<<3) - -/* Use safe defaults */ -#ifdef TLS1_3_VERSION -#define REDIS_TLS_PROTO_DEFAULT (REDIS_TLS_PROTO_TLSv1_2|REDIS_TLS_PROTO_TLSv1_3) -#else -#define REDIS_TLS_PROTO_DEFAULT (REDIS_TLS_PROTO_TLSv1_2) -#endif - -SSL_CTX *redis_tls_ctx = NULL; -SSL_CTX *redis_tls_client_ctx = NULL; - -static int parseProtocolsConfig(const char *str) { - int i, count = 0; - int protocols = 0; - - if (!str) return REDIS_TLS_PROTO_DEFAULT; - sds *tokens = sdssplitlen(str, strlen(str), " ", 1, &count); - - if (!tokens) { - serverLog(LL_WARNING, "Invalid tls-protocols configuration string"); - return -1; - } - for (i = 0; i < count; i++) { - if (!strcasecmp(tokens[i], "tlsv1")) protocols |= REDIS_TLS_PROTO_TLSv1; - else if (!strcasecmp(tokens[i], "tlsv1.1")) protocols |= REDIS_TLS_PROTO_TLSv1_1; - else if (!strcasecmp(tokens[i], "tlsv1.2")) protocols |= REDIS_TLS_PROTO_TLSv1_2; - else if (!strcasecmp(tokens[i], "tlsv1.3")) { -#ifdef TLS1_3_VERSION - protocols |= REDIS_TLS_PROTO_TLSv1_3; -#else - serverLog(LL_WARNING, "TLSv1.3 is specified in tls-protocols but not supported by OpenSSL."); - protocols = -1; - break; -#endif - } else { - serverLog(LL_WARNING, "Invalid tls-protocols specified. " - "Use a combination of 'TLSv1', 'TLSv1.1', 'TLSv1.2' and 'TLSv1.3'."); - protocols = -1; - break; - } - } - sdsfreesplitres(tokens, count); - - return protocols; -} - -/** - * OpenSSL global initialization and locking handling callbacks. - * Note that this is only required for OpenSSL < 1.1.0. - */ - -#if OPENSSL_VERSION_NUMBER < 0x10100000L -#define USE_CRYPTO_LOCKS -#endif - -#ifdef USE_CRYPTO_LOCKS - -static pthread_mutex_t *openssl_locks; - -static void sslLockingCallback(int mode, int lock_id, const char *f, int line) { - pthread_mutex_t *mt = openssl_locks + lock_id; - - if (mode & CRYPTO_LOCK) { - pthread_mutex_lock(mt); - } else { - pthread_mutex_unlock(mt); - } - - (void)f; - (void)line; -} - -static void initCryptoLocks(void) { - unsigned i, nlocks; - if (CRYPTO_get_locking_callback() != NULL) { - /* Someone already set the callback before us. Don't destroy it! */ - return; - } - nlocks = CRYPTO_num_locks(); - openssl_locks = zmalloc(sizeof(*openssl_locks) * nlocks); - for (i = 0; i < nlocks; i++) { - pthread_mutex_init(openssl_locks + i, NULL); - } - CRYPTO_set_locking_callback(sslLockingCallback); -} -#endif /* USE_CRYPTO_LOCKS */ - -static void tlsInit(void) { - /* Enable configuring OpenSSL using the standard openssl.cnf - * OPENSSL_config()/OPENSSL_init_crypto() should be the first - * call to the OpenSSL* library. - * - OPENSSL_config() should be used for OpenSSL versions < 1.1.0 - * - OPENSSL_init_crypto() should be used for OpenSSL versions >= 1.1.0 - */ - #if OPENSSL_VERSION_NUMBER < 0x10100000L - OPENSSL_config(NULL); - SSL_load_error_strings(); - SSL_library_init(); - #elif OPENSSL_VERSION_NUMBER < 0x10101000L - OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG, NULL); - #else - OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG|OPENSSL_INIT_ATFORK, NULL); - #endif - -#ifdef USE_CRYPTO_LOCKS - initCryptoLocks(); -#endif - - if (!RAND_poll()) { - serverLog(LL_WARNING, "OpenSSL: Failed to seed random number generator."); - } -} - -static void tlsCleanup(void) { - if (redis_tls_ctx) { - SSL_CTX_free(redis_tls_ctx); - redis_tls_ctx = NULL; - } - if (redis_tls_client_ctx) { - SSL_CTX_free(redis_tls_client_ctx); - redis_tls_client_ctx = NULL; - } - - #if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) - // unavailable on LibreSSL - OPENSSL_cleanup(); - #endif -} - -/* Callback for passing a keyfile password stored as an sds to OpenSSL */ -static int tlsPasswordCallback(char *buf, int size, int rwflag, void *u) { - UNUSED(rwflag); - - const char *pass = u; - size_t pass_len; - - if (!pass) return -1; - pass_len = strlen(pass); - if (pass_len > (size_t) size) return -1; - memcpy(buf, pass, pass_len); - - return (int) pass_len; -} - -/* Create a *base* SSL_CTX using the SSL configuration provided. The base context - * includes everything that's common for both client-side and server-side connections. - */ -static SSL_CTX *createSSLContext(redisTLSContextConfig *ctx_config, int protocols, int client) { - const char *cert_file = client ? ctx_config->client_cert_file : ctx_config->cert_file; - const char *key_file = client ? ctx_config->client_key_file : ctx_config->key_file; - const char *key_file_pass = client ? ctx_config->client_key_file_pass : ctx_config->key_file_pass; - char errbuf[256]; - SSL_CTX *ctx = NULL; - - ctx = SSL_CTX_new(SSLv23_method()); - if (!ctx) goto error; - - SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3); - -#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS - SSL_CTX_set_options(ctx, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS); -#endif - - if (!(protocols & REDIS_TLS_PROTO_TLSv1)) - SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1); - if (!(protocols & REDIS_TLS_PROTO_TLSv1_1)) - SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_1); -#ifdef SSL_OP_NO_TLSv1_2 - if (!(protocols & REDIS_TLS_PROTO_TLSv1_2)) - SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_2); -#endif -#ifdef SSL_OP_NO_TLSv1_3 - if (!(protocols & REDIS_TLS_PROTO_TLSv1_3)) - SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_3); -#endif - -#ifdef SSL_OP_NO_COMPRESSION - SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION); -#endif - - SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE|SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); - SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); - - SSL_CTX_set_default_passwd_cb(ctx, tlsPasswordCallback); - SSL_CTX_set_default_passwd_cb_userdata(ctx, (void *) key_file_pass); - - if (SSL_CTX_use_certificate_chain_file(ctx, cert_file) <= 0) { - ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf)); - serverLog(LL_WARNING, "Failed to load certificate: %s: %s", cert_file, errbuf); - goto error; - } - - if (SSL_CTX_use_PrivateKey_file(ctx, key_file, SSL_FILETYPE_PEM) <= 0) { - ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf)); - serverLog(LL_WARNING, "Failed to load private key: %s: %s", key_file, errbuf); - goto error; - } - - if ((ctx_config->ca_cert_file || ctx_config->ca_cert_dir) && - SSL_CTX_load_verify_locations(ctx, ctx_config->ca_cert_file, ctx_config->ca_cert_dir) <= 0) { - ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf)); - serverLog(LL_WARNING, "Failed to configure CA certificate(s) file/directory: %s", errbuf); - goto error; - } - - if (ctx_config->ciphers && !SSL_CTX_set_cipher_list(ctx, ctx_config->ciphers)) { - serverLog(LL_WARNING, "Failed to configure ciphers: %s", ctx_config->ciphers); - goto error; - } - -#ifdef TLS1_3_VERSION - if (ctx_config->ciphersuites && !SSL_CTX_set_ciphersuites(ctx, ctx_config->ciphersuites)) { - serverLog(LL_WARNING, "Failed to configure ciphersuites: %s", ctx_config->ciphersuites); - goto error; - } -#endif - - return ctx; - -error: - if (ctx) SSL_CTX_free(ctx); - return NULL; -} - -/* Attempt to configure/reconfigure TLS. This operation is atomic and will - * leave the SSL_CTX unchanged if fails. - * @priv: config of redisTLSContextConfig. - * @reconfigure: if true, ignore the previous configure; if false, only - * configure from @ctx_config if redis_tls_ctx is NULL. - */ -static int tlsConfigure(void *priv, int reconfigure) { - redisTLSContextConfig *ctx_config = (redisTLSContextConfig *)priv; - char errbuf[256]; - SSL_CTX *ctx = NULL; - SSL_CTX *client_ctx = NULL; - - if (!reconfigure && redis_tls_ctx) { - return C_OK; - } - - if (!ctx_config->cert_file) { - serverLog(LL_WARNING, "No tls-cert-file configured!"); - goto error; - } - - if (!ctx_config->key_file) { - serverLog(LL_WARNING, "No tls-key-file configured!"); - goto error; - } - - if (((server.tls_auth_clients != TLS_CLIENT_AUTH_NO) || server.tls_cluster || server.tls_replication) && - !ctx_config->ca_cert_file && !ctx_config->ca_cert_dir) { - serverLog(LL_WARNING, "Either tls-ca-cert-file or tls-ca-cert-dir must be specified when tls-cluster, tls-replication or tls-auth-clients are enabled!"); - goto error; - } - - int protocols = parseProtocolsConfig(ctx_config->protocols); - if (protocols == -1) goto error; - - /* Create server side/general context */ - ctx = createSSLContext(ctx_config, protocols, 0); - if (!ctx) goto error; - - if (ctx_config->session_caching) { - SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_SERVER); - SSL_CTX_sess_set_cache_size(ctx, ctx_config->session_cache_size); - SSL_CTX_set_timeout(ctx, ctx_config->session_cache_timeout); - SSL_CTX_set_session_id_context(ctx, (void *) "redis", 5); - } else { - SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF); - } - -#ifdef SSL_OP_NO_CLIENT_RENEGOTIATION - SSL_CTX_set_options(ctx, SSL_OP_NO_CLIENT_RENEGOTIATION); -#endif - - if (ctx_config->prefer_server_ciphers) - SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); - -#if ((OPENSSL_VERSION_NUMBER < 0x30000000L) && defined(SSL_CTX_set_ecdh_auto)) - SSL_CTX_set_ecdh_auto(ctx, 1); -#endif - SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE); - - if (ctx_config->dh_params_file) { - FILE *dhfile = fopen(ctx_config->dh_params_file, "r"); - if (!dhfile) { - serverLog(LL_WARNING, "Failed to load %s: %s", ctx_config->dh_params_file, strerror(errno)); - goto error; - } - -#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) - EVP_PKEY *pkey = NULL; - OSSL_DECODER_CTX *dctx = OSSL_DECODER_CTX_new_for_pkey( - &pkey, "PEM", NULL, "DH", OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS, NULL, NULL); - if (!dctx) { - serverLog(LL_WARNING, "No decoder for DH params."); - fclose(dhfile); - goto error; - } - if (!OSSL_DECODER_from_fp(dctx, dhfile)) { - serverLog(LL_WARNING, "%s: failed to read DH params.", ctx_config->dh_params_file); - OSSL_DECODER_CTX_free(dctx); - fclose(dhfile); - goto error; - } - - OSSL_DECODER_CTX_free(dctx); - fclose(dhfile); - - if (SSL_CTX_set0_tmp_dh_pkey(ctx, pkey) <= 0) { - ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf)); - serverLog(LL_WARNING, "Failed to load DH params file: %s: %s", ctx_config->dh_params_file, errbuf); - EVP_PKEY_free(pkey); - goto error; - } - /* Not freeing pkey, it is owned by OpenSSL now */ -#else - DH *dh = PEM_read_DHparams(dhfile, NULL, NULL, NULL); - fclose(dhfile); - if (!dh) { - serverLog(LL_WARNING, "%s: failed to read DH params.", ctx_config->dh_params_file); - goto error; - } - - if (SSL_CTX_set_tmp_dh(ctx, dh) <= 0) { - ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf)); - serverLog(LL_WARNING, "Failed to load DH params file: %s: %s", ctx_config->dh_params_file, errbuf); - DH_free(dh); - goto error; - } - - DH_free(dh); -#endif - } else { -#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) - SSL_CTX_set_dh_auto(ctx, 1); -#endif - } - - /* If a client-side certificate is configured, create an explicit client context */ - if (ctx_config->client_cert_file && ctx_config->client_key_file) { - client_ctx = createSSLContext(ctx_config, protocols, 1); - if (!client_ctx) goto error; - } - - SSL_CTX_free(redis_tls_ctx); - SSL_CTX_free(redis_tls_client_ctx); - redis_tls_ctx = ctx; - redis_tls_client_ctx = client_ctx; - - return C_OK; - -error: - if (ctx) SSL_CTX_free(ctx); - if (client_ctx) SSL_CTX_free(client_ctx); - return C_ERR; -} - -#ifdef TLS_DEBUGGING -#define TLSCONN_DEBUG(fmt, ...) \ - serverLog(LL_DEBUG, "TLSCONN: " fmt, __VA_ARGS__) -#else -#define TLSCONN_DEBUG(fmt, ...) -#endif - -static ConnectionType CT_TLS; - -/* Normal socket connections have a simple events/handler correlation. - * - * With TLS connections we need to handle cases where during a logical read - * or write operation, the SSL library asks to block for the opposite - * socket operation. - * - * When this happens, we need to do two things: - * 1. Make sure we register for the event. - * 2. Make sure we know which handler needs to execute when the - * event fires. That is, if we notify the caller of a write operation - * that it blocks, and SSL asks for a read, we need to trigger the - * write handler again on the next read event. - * - */ - -typedef enum { - WANT_READ = 1, - WANT_WRITE -} WantIOType; - -#define TLS_CONN_FLAG_READ_WANT_WRITE (1<<0) -#define TLS_CONN_FLAG_WRITE_WANT_READ (1<<1) -#define TLS_CONN_FLAG_FD_SET (1<<2) - -typedef struct tls_connection { - connection c; - int flags; - SSL *ssl; - char *ssl_error; - listNode *pending_list_node; -} tls_connection; - -static connection *createTLSConnection(struct aeEventLoop *el, int client_side) { - SSL_CTX *ctx = redis_tls_ctx; - if (client_side && redis_tls_client_ctx) - ctx = redis_tls_client_ctx; - tls_connection *conn = zcalloc(sizeof(tls_connection)); - conn->c.type = &CT_TLS; - conn->c.fd = -1; - conn->c.el = el; - conn->c.iovcnt = IOV_MAX; - conn->ssl = SSL_new(ctx); - return (connection *) conn; -} - -static connection *connCreateTLS(struct aeEventLoop *el) { - return createTLSConnection(el, 1); -} - -/* Fetch the latest OpenSSL error and store it in the connection */ -static void updateTLSError(tls_connection *conn) { - conn->c.last_errno = 0; - if (conn->ssl_error) zfree(conn->ssl_error); - conn->ssl_error = zmalloc(512); - ERR_error_string_n(ERR_get_error(), conn->ssl_error, 512); -} - -/* Create a new TLS connection that is already associated with - * an accepted underlying file descriptor. - * - * The socket is not ready for I/O until connAccept() was called and - * invoked the connection-level accept handler. - * - * Callers should use connGetState() and verify the created connection - * is not in an error state. - */ -static connection *connCreateAcceptedTLS(struct aeEventLoop *el, int fd, void *priv) { - int require_auth = *(int *)priv; - tls_connection *conn = (tls_connection *) createTLSConnection(el, 0); - conn->c.fd = fd; - conn->c.el = el; - conn->c.state = CONN_STATE_ACCEPTING; - - if (!conn->ssl) { - updateTLSError(conn); - conn->c.state = CONN_STATE_ERROR; - return (connection *) conn; - } - - switch (require_auth) { - case TLS_CLIENT_AUTH_NO: - SSL_set_verify(conn->ssl, SSL_VERIFY_NONE, NULL); - break; - case TLS_CLIENT_AUTH_OPTIONAL: - SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, NULL); - break; - default: /* TLS_CLIENT_AUTH_YES, also fall-secure */ - SSL_set_verify(conn->ssl, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); - break; - } - - SSL_set_fd(conn->ssl, conn->c.fd); - SSL_set_accept_state(conn->ssl); - - return (connection *) conn; -} - -static void tlsEventHandler(struct aeEventLoop *el, int fd, void *clientData, int mask); -static void updateSSLEvent(tls_connection *conn); - -/* Process the return code received from OpenSSL> - * Update the want parameter with expected I/O. - * Update the connection's error state if a real error has occurred. - * Returns an SSL error code, or 0 if no further handling is required. - */ -static int handleSSLReturnCode(tls_connection *conn, int ret_value, WantIOType *want) { - if (ret_value <= 0) { - int ssl_err = SSL_get_error(conn->ssl, ret_value); - switch (ssl_err) { - case SSL_ERROR_WANT_WRITE: - *want = WANT_WRITE; - return 0; - case SSL_ERROR_WANT_READ: - *want = WANT_READ; - return 0; - case SSL_ERROR_SYSCALL: - conn->c.last_errno = errno; - if (conn->ssl_error) zfree(conn->ssl_error); - conn->ssl_error = errno ? zstrdup(strerror(errno)) : NULL; - break; - default: - /* Error! */ - updateTLSError(conn); - break; - } - - return ssl_err; - } - - return 0; -} - -/* Handle OpenSSL return code following SSL_write() or SSL_read(): - * - * - Updates conn state and last_errno. - * - If update_event is nonzero, calls updateSSLEvent() when necessary. - * - * Returns ret_value, or -1 on error or dropped connection. - */ -static int updateStateAfterSSLIO(tls_connection *conn, int ret_value, int update_event) { - /* If system call was interrupted, there's no need to go through the full - * OpenSSL error handling and just report this for the caller to retry the - * operation. - */ - if (errno == EINTR) { - conn->c.last_errno = EINTR; - return -1; - } - - if (ret_value <= 0) { - WantIOType want = 0; - int ssl_err; - if (!(ssl_err = handleSSLReturnCode(conn, ret_value, &want))) { - if (want == WANT_READ) conn->flags |= TLS_CONN_FLAG_WRITE_WANT_READ; - if (want == WANT_WRITE) conn->flags |= TLS_CONN_FLAG_READ_WANT_WRITE; - if (update_event) updateSSLEvent(conn); - errno = EAGAIN; - return -1; - } else { - if (ssl_err == SSL_ERROR_ZERO_RETURN || - ((ssl_err == SSL_ERROR_SYSCALL && !errno))) { - conn->c.state = CONN_STATE_CLOSED; - return -1; - } else { - conn->c.state = CONN_STATE_ERROR; - return -1; - } - } - } - - return ret_value; -} - -static void registerSSLEvent(tls_connection *conn, WantIOType want) { - int mask = aeGetFileEvents(conn->c.el, conn->c.fd); - - switch (want) { - case WANT_READ: - if (mask & AE_WRITABLE) aeDeleteFileEvent(conn->c.el, conn->c.fd, AE_WRITABLE); - if (!(mask & AE_READABLE)) aeCreateFileEvent(conn->c.el, conn->c.fd, AE_READABLE, - tlsEventHandler, conn); - break; - case WANT_WRITE: - if (mask & AE_READABLE) aeDeleteFileEvent(conn->c.el, conn->c.fd, AE_READABLE); - if (!(mask & AE_WRITABLE)) aeCreateFileEvent(conn->c.el, conn->c.fd, AE_WRITABLE, - tlsEventHandler, conn); - break; - default: - serverAssert(0); - break; - } -} - -static void updateSSLEvent(tls_connection *conn) { - serverAssert(conn->c.el); - int mask = aeGetFileEvents(conn->c.el, conn->c.fd); - int need_read = conn->c.read_handler || (conn->flags & TLS_CONN_FLAG_WRITE_WANT_READ); - int need_write = conn->c.write_handler || (conn->flags & TLS_CONN_FLAG_READ_WANT_WRITE); - - if (need_read && !(mask & AE_READABLE)) - aeCreateFileEvent(conn->c.el, conn->c.fd, AE_READABLE, tlsEventHandler, conn); - if (!need_read && (mask & AE_READABLE)) - aeDeleteFileEvent(conn->c.el, conn->c.fd, AE_READABLE); - - if (need_write && !(mask & AE_WRITABLE)) - aeCreateFileEvent(conn->c.el, conn->c.fd, AE_WRITABLE, tlsEventHandler, conn); - if (!need_write && (mask & AE_WRITABLE)) - aeDeleteFileEvent(conn->c.el, conn->c.fd, AE_WRITABLE); -} - -/* Add a connection to the list of connections with pending data that has - * already been read from the socket but has not yet been served to the reader. */ -static void tlsPendingAdd(tls_connection *conn) { - if (!conn->c.el->privdata[1]) - conn->c.el->privdata[1] = listCreate(); - - list *pending_list = conn->c.el->privdata[1]; - if (!conn->pending_list_node) { - listAddNodeTail(pending_list, conn); - conn->pending_list_node = listLast(pending_list); - } -} - -/* Removes a connection from the list of connections with pending data. */ -static void tlsPendingRemove(tls_connection *conn) { - if (conn->pending_list_node) { - list *pending_list = conn->c.el->privdata[1]; - listDelNode(pending_list, conn->pending_list_node); - conn->pending_list_node = NULL; - } -} - -static int getCertFieldByName(X509 *cert, const char *field, char *out, size_t outlen) { - if (!cert || !field || !out) return 0; - - int nid = -1; - - if (!strcasecmp(field, "CN")) - nid = NID_commonName; - else if (!strcasecmp(field, "O")) - nid = NID_organizationName; - /* Add more mappings here as needed */ - - if (nid == -1) return 0; - - X509_NAME *subject = X509_get_subject_name(cert); - if (!subject) return 0; - - return X509_NAME_get_text_by_NID(subject, nid, out, outlen) > 0; -} - -sds tlsGetPeerUsername(connection *conn_) { - tls_connection *conn = (tls_connection *)conn_; - if (!conn || !SSL_is_init_finished(conn->ssl)) return NULL; - - /* Find the corresponding field name from the enum mapping */ - const char *field = NULL; - switch (server.tls_ctx_config.client_auth_user) { - case TLS_CLIENT_FIELD_CN: - field = "CN"; - break; - default: - return NULL; - } - - if (!field) return NULL; - - X509 *cert = SSL_get_peer_certificate(conn->ssl); - if (!cert) return NULL; - - char field_value[256]; - sds result = NULL; - - if (getCertFieldByName(cert, field, field_value, sizeof(field_value))) { - result = sdsnew(field_value); - } else { - serverLog(LL_NOTICE, "TLS: Failed to extract field '%s' from certificate", field); - } - - X509_free(cert); - return result; -} - -static void tlsHandleEvent(tls_connection *conn, int mask) { - int ret, conn_error; - - TLSCONN_DEBUG("tlsEventHandler(): fd=%d, state=%d, mask=%d, r=%d, w=%d, flags=%d", - conn->c.fd, conn->c.state, mask, conn->c.read_handler != NULL, conn->c.write_handler != NULL, - conn->flags); - - switch (conn->c.state) { - case CONN_STATE_CONNECTING: - ERR_clear_error(); - conn_error = anetGetError(conn->c.fd); - if (conn_error) { - conn->c.last_errno = conn_error; - conn->c.state = CONN_STATE_ERROR; - } else { - if (!(conn->flags & TLS_CONN_FLAG_FD_SET)) { - SSL_set_fd(conn->ssl, conn->c.fd); - conn->flags |= TLS_CONN_FLAG_FD_SET; - } - ret = SSL_connect(conn->ssl); - if (ret <= 0) { - WantIOType want = 0; - if (!handleSSLReturnCode(conn, ret, &want)) { - registerSSLEvent(conn, want); - - /* Avoid hitting UpdateSSLEvent, which knows nothing - * of what SSL_connect() wants and instead looks at our - * R/W handlers. - */ - return; - } - - /* If not handled, it's an error */ - conn->c.state = CONN_STATE_ERROR; - } else { - conn->c.state = CONN_STATE_CONNECTED; - } - } - - if (!callHandler((connection *) conn, conn->c.conn_handler)) return; - conn->c.conn_handler = NULL; - break; - case CONN_STATE_ACCEPTING: - ERR_clear_error(); - ret = SSL_accept(conn->ssl); - if (ret <= 0) { - WantIOType want = 0; - if (!handleSSLReturnCode(conn, ret, &want)) { - /* Avoid hitting UpdateSSLEvent, which knows nothing - * of what SSL_connect() wants and instead looks at our - * R/W handlers. - */ - registerSSLEvent(conn, want); - return; - } - - /* If not handled, it's an error */ - conn->c.state = CONN_STATE_ERROR; - } else { - conn->c.state = CONN_STATE_CONNECTED; - } - - if (!callHandler((connection *) conn, conn->c.conn_handler)) return; - conn->c.conn_handler = NULL; - break; - case CONN_STATE_CONNECTED: - { - int call_read = ((mask & AE_READABLE) && conn->c.read_handler) || - ((mask & AE_WRITABLE) && (conn->flags & TLS_CONN_FLAG_READ_WANT_WRITE)); - int call_write = ((mask & AE_WRITABLE) && conn->c.write_handler) || - ((mask & AE_READABLE) && (conn->flags & TLS_CONN_FLAG_WRITE_WANT_READ)); - - /* Normally we execute the readable event first, and the writable - * event laster. This is useful as sometimes we may be able - * to serve the reply of a query immediately after processing the - * query. - * - * However if WRITE_BARRIER is set in the mask, our application is - * asking us to do the reverse: never fire the writable event - * after the readable. In such a case, we invert the calls. - * This is useful when, for instance, we want to do things - * in the beforeSleep() hook, like fsynching a file to disk, - * before replying to a client. */ - int invert = conn->c.flags & CONN_FLAG_WRITE_BARRIER; - - if (!invert && call_read) { - conn->flags &= ~TLS_CONN_FLAG_READ_WANT_WRITE; - if (!callHandler((connection *) conn, conn->c.read_handler)) return; - } - - /* Fire the writable event. */ - if (call_write) { - conn->flags &= ~TLS_CONN_FLAG_WRITE_WANT_READ; - if (!callHandler((connection *) conn, conn->c.write_handler)) return; - } - - /* If we have to invert the call, fire the readable event now - * after the writable one. */ - if (invert && call_read) { - conn->flags &= ~TLS_CONN_FLAG_READ_WANT_WRITE; - if (!callHandler((connection *) conn, conn->c.read_handler)) return; - } - - /* If SSL has pending that, already read from the socket, we're at - * risk of not calling the read handler again, make sure to add it - * to a list of pending connection that should be handled anyway. */ - if ((mask & AE_READABLE)) { - if (SSL_pending(conn->ssl) > 0) { - tlsPendingAdd(conn); - } else if (conn->pending_list_node) { - tlsPendingRemove(conn); - } - } - - break; - } - default: - break; - } - - /* The event loop may have been unbound during the event processing above. */ - if (conn->c.el) updateSSLEvent(conn); -} - -static void tlsEventHandler(struct aeEventLoop *el, int fd, void *clientData, int mask) { - UNUSED(el); - UNUSED(fd); - tls_connection *conn = clientData; - tlsHandleEvent(conn, mask); -} - -static void tlsAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) { - int cport, cfd; - int max = server.max_new_tls_conns_per_cycle; - char cip[NET_IP_STR_LEN]; - UNUSED(mask); - UNUSED(privdata); - - while(max--) { - cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport); - if (cfd == ANET_ERR) { - if (anetAcceptFailureNeedsRetry(errno)) - continue; - if (errno != EWOULDBLOCK) - serverLog(LL_WARNING, - "Accepting client connection: %s", server.neterr); - return; - } - serverLog(LL_VERBOSE,"Accepted %s:%d", cip, cport); - acceptCommonHandler(connCreateAcceptedTLS(el,cfd,&server.tls_auth_clients), 0, cip); - } -} - -static int connTLSAddr(connection *conn, char *ip, size_t ip_len, int *port, int remote) { - return anetFdToString(conn->fd, ip, ip_len, port, remote); -} - -static int connTLSIsLocal(connection *conn) { - return connectionTypeTcp()->is_local(conn); -} - -static int connTLSListen(connListener *listener) { - return listenToPort(listener); -} - -static void connTLSShutdown(connection *conn_) { - tls_connection *conn = (tls_connection *) conn_; - - if (conn->ssl) { - if (conn->c.state == CONN_STATE_CONNECTED) - SSL_shutdown(conn->ssl); - SSL_free(conn->ssl); - conn->ssl = NULL; - } - - connectionTypeTcp()->shutdown(conn_); -} - -static void connTLSClose(connection *conn_) { - tls_connection *conn = (tls_connection *) conn_; - - if (conn->ssl) { - if (conn->c.state == CONN_STATE_CONNECTED) - SSL_shutdown(conn->ssl); - SSL_free(conn->ssl); - conn->ssl = NULL; - } - - if (conn->ssl_error) { - zfree(conn->ssl_error); - conn->ssl_error = NULL; - } - - if (conn->pending_list_node) { - list *pending_list = conn->c.el->privdata[1]; - listDelNode(pending_list, conn->pending_list_node); - conn->pending_list_node = NULL; - } - - connectionTypeTcp()->close(conn_); -} - -static int connTLSAccept(connection *_conn, ConnectionCallbackFunc accept_handler) { - tls_connection *conn = (tls_connection *) _conn; - int ret; - - if (conn->c.state != CONN_STATE_ACCEPTING) return C_ERR; - ERR_clear_error(); - - /* Try to accept */ - conn->c.conn_handler = accept_handler; - ret = SSL_accept(conn->ssl); - - if (ret <= 0) { - WantIOType want = 0; - if (!handleSSLReturnCode(conn, ret, &want)) { - registerSSLEvent(conn, want); /* We'll fire back */ - return C_OK; - } else { - conn->c.state = CONN_STATE_ERROR; - return C_ERR; - } - } - - conn->c.state = CONN_STATE_CONNECTED; - if (!callHandler((connection *) conn, conn->c.conn_handler)) return C_OK; - conn->c.conn_handler = NULL; - - return C_OK; -} - -static int connTLSConnect(connection *conn_, const char *addr, int port, const char *src_addr, ConnectionCallbackFunc connect_handler) { - tls_connection *conn = (tls_connection *) conn_; - unsigned char addr_buf[sizeof(struct in6_addr)]; - - if (conn->c.state != CONN_STATE_NONE) return C_ERR; - ERR_clear_error(); - - /* Check whether addr is an IP address, if not, use the value for Server Name Indication */ - if (inet_pton(AF_INET, addr, addr_buf) != 1 && inet_pton(AF_INET6, addr, addr_buf) != 1) { - SSL_set_tlsext_host_name(conn->ssl, addr); - } - - /* Initiate Socket connection first */ - if (connectionTypeTcp()->connect(conn_, addr, port, src_addr, connect_handler) == C_ERR) return C_ERR; - - /* Return now, once the socket is connected we'll initiate - * TLS connection from the event handler. - */ - return C_OK; -} - -static void connTLSUnbindEventLoop(connection *conn_) { - tls_connection *conn = (tls_connection *) conn_; - - /* We need to remove all events from the old event loop. The subsequent - * updateSSLEvent() will add the appropriate events to the new event loop. */ - if (conn->c.el) { - int mask = aeGetFileEvents(conn->c.el, conn->c.fd); - if (mask & AE_READABLE) aeDeleteFileEvent(conn->c.el, conn->c.fd, AE_READABLE); - if (mask & AE_WRITABLE) aeDeleteFileEvent(conn->c.el, conn->c.fd, AE_WRITABLE); - - /* Check if there are pending events and handle accordingly. */ - int has_pending = conn->pending_list_node != NULL; - if (has_pending) tlsPendingRemove(conn); - } -} - -static int connTLSRebindEventLoop(connection *conn_, aeEventLoop *el) { - tls_connection *conn = (tls_connection *) conn_; - serverAssert(!conn->c.el && !conn->c.read_handler && - !conn->c.write_handler && !conn->pending_list_node); - conn->c.el = el; - if (el && SSL_pending(conn->ssl)) tlsPendingAdd(conn); - /* Add the appropriate events to the new event loop. */ - updateSSLEvent((tls_connection *) conn); - return C_OK; -} - -static int connTLSWrite(connection *conn_, const void *data, size_t data_len) { - tls_connection *conn = (tls_connection *) conn_; - int ret; - - if (conn->c.state != CONN_STATE_CONNECTED) return -1; - ERR_clear_error(); - ret = SSL_write(conn->ssl, data, data_len); - return updateStateAfterSSLIO(conn, ret, 1); -} - -static int connTLSWritev(connection *conn_, const struct iovec *iov, int iovcnt) { - if (iovcnt == 1) return connTLSWrite(conn_, iov[0].iov_base, iov[0].iov_len); - - /* Accumulate the amount of bytes of each buffer and check if it exceeds NET_MAX_WRITES_PER_EVENT. */ - size_t iov_bytes_len = 0; - for (int i = 0; i < iovcnt; i++) { - iov_bytes_len += iov[i].iov_len; - if (iov_bytes_len > NET_MAX_WRITES_PER_EVENT) break; - } - - /* The amount of all buffers is greater than NET_MAX_WRITES_PER_EVENT, - * which is not worth doing so much memory copying to reduce system calls, - * therefore, invoke connTLSWrite() multiple times to avoid memory copies. */ - if (iov_bytes_len > NET_MAX_WRITES_PER_EVENT) { - ssize_t tot_sent = 0; - for (int i = 0; i < iovcnt; i++) { - ssize_t sent = connTLSWrite(conn_, iov[i].iov_base, iov[i].iov_len); - if (sent <= 0) return tot_sent > 0 ? tot_sent : sent; - tot_sent += sent; - if ((size_t) sent != iov[i].iov_len) break; - } - return tot_sent; - } - - /* The amount of all buffers is less than NET_MAX_WRITES_PER_EVENT, - * which is worth doing more memory copies in exchange for fewer system calls, - * so concatenate these scattered buffers into a contiguous piece of memory - * and send it away by one call to connTLSWrite(). */ - char buf[iov_bytes_len]; - size_t offset = 0; - for (int i = 0; i < iovcnt; i++) { - memcpy(buf + offset, iov[i].iov_base, iov[i].iov_len); - offset += iov[i].iov_len; - } - return connTLSWrite(conn_, buf, iov_bytes_len); -} - -static int connTLSRead(connection *conn_, void *buf, size_t buf_len) { - tls_connection *conn = (tls_connection *) conn_; - int ret; - - if (conn->c.state != CONN_STATE_CONNECTED) return -1; - ERR_clear_error(); - ret = SSL_read(conn->ssl, buf, buf_len); - return updateStateAfterSSLIO(conn, ret, 1); -} - -static const char *connTLSGetLastError(connection *conn_) { - tls_connection *conn = (tls_connection *) conn_; - - if (conn->ssl_error) return conn->ssl_error; - return NULL; -} - -static int connTLSSetWriteHandler(connection *conn, ConnectionCallbackFunc func, int barrier) { - conn->write_handler = func; - if (barrier) - conn->flags |= CONN_FLAG_WRITE_BARRIER; - else - conn->flags &= ~CONN_FLAG_WRITE_BARRIER; - updateSSLEvent((tls_connection *) conn); - return C_OK; -} - -static int connTLSSetReadHandler(connection *conn, ConnectionCallbackFunc func) { - conn->read_handler = func; - updateSSLEvent((tls_connection *) conn); - return C_OK; -} - -static void setBlockingTimeout(tls_connection *conn, long long timeout) { - anetBlock(NULL, conn->c.fd); - anetSendTimeout(NULL, conn->c.fd, timeout); - anetRecvTimeout(NULL, conn->c.fd, timeout); -} - -static void unsetBlockingTimeout(tls_connection *conn) { - anetNonBlock(NULL, conn->c.fd); - anetSendTimeout(NULL, conn->c.fd, 0); - anetRecvTimeout(NULL, conn->c.fd, 0); -} - -static int connTLSBlockingConnect(connection *conn_, const char *addr, int port, long long timeout) { - tls_connection *conn = (tls_connection *) conn_; - int ret; - - if (conn->c.state != CONN_STATE_NONE) return C_ERR; - - /* Initiate socket blocking connect first */ - if (connectionTypeTcp()->blocking_connect(conn_, addr, port, timeout) == C_ERR) return C_ERR; - - /* Initiate TLS connection now. We set up a send/recv timeout on the socket, - * which means the specified timeout will not be enforced accurately. */ - SSL_set_fd(conn->ssl, conn->c.fd); - setBlockingTimeout(conn, timeout); - ERR_clear_error(); - - if ((ret = SSL_connect(conn->ssl)) <= 0) { - conn->c.state = CONN_STATE_ERROR; - return C_ERR; - } - unsetBlockingTimeout(conn); - - conn->c.state = CONN_STATE_CONNECTED; - return C_OK; -} - -static ssize_t connTLSSyncWrite(connection *conn_, char *ptr, ssize_t size, long long timeout) { - tls_connection *conn = (tls_connection *) conn_; - - setBlockingTimeout(conn, timeout); - SSL_clear_mode(conn->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE); - ERR_clear_error(); - int ret = SSL_write(conn->ssl, ptr, size); - ret = updateStateAfterSSLIO(conn, ret, 0); - SSL_set_mode(conn->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE); - unsetBlockingTimeout(conn); - - return ret; -} - -static ssize_t connTLSSyncRead(connection *conn_, char *ptr, ssize_t size, long long timeout) { - tls_connection *conn = (tls_connection *) conn_; - - setBlockingTimeout(conn, timeout); - ERR_clear_error(); - int ret = SSL_read(conn->ssl, ptr, size); - ret = updateStateAfterSSLIO(conn, ret, 0); - unsetBlockingTimeout(conn); - - return ret; -} - -static ssize_t connTLSSyncReadLine(connection *conn_, char *ptr, ssize_t size, long long timeout) { - tls_connection *conn = (tls_connection *) conn_; - ssize_t nread = 0; - - setBlockingTimeout(conn, timeout); - - size--; - while(size) { - char c; - - ERR_clear_error(); - int ret = SSL_read(conn->ssl, &c, 1); - ret = updateStateAfterSSLIO(conn, ret, 0); - if (ret <= 0) { - nread = -1; - goto exit; - } - if (c == '\n') { - *ptr = '\0'; - if (nread && *(ptr-1) == '\r') *(ptr-1) = '\0'; - goto exit; - } else { - *ptr++ = c; - *ptr = '\0'; - nread++; - } - size--; - } -exit: - unsetBlockingTimeout(conn); - return nread; -} - -static const char *connTLSGetType(connection *conn_) { - (void) conn_; - - return CONN_TYPE_TLS; -} - -static int tlsHasPendingData(struct aeEventLoop *el) { - list *pending_list = el->privdata[1]; - if (!pending_list) - return 0; - return listLength(pending_list) > 0; -} - -static int tlsProcessPendingData(struct aeEventLoop *el) { - listIter li; - listNode *ln; - - list *pending_list = el->privdata[1]; - if (!pending_list) return 0; - int processed = listLength(pending_list); - listRewind(pending_list,&li); - while((ln = listNext(&li))) { - tls_connection *conn = listNodeValue(ln); - tlsHandleEvent(conn, AE_READABLE); - } - return processed; -} - -/* Fetch the peer certificate used for authentication on the specified - * connection and return it as a PEM-encoded sds. - */ -static sds connTLSGetPeerCert(connection *conn_) { - tls_connection *conn = (tls_connection *) conn_; - if ((conn_->type != connectionTypeTls()) || !conn->ssl) return NULL; - - X509 *cert = SSL_get_peer_certificate(conn->ssl); - if (!cert) return NULL; - - BIO *bio = BIO_new(BIO_s_mem()); - if (bio == NULL || !PEM_write_bio_X509(bio, cert)) { - if (bio != NULL) BIO_free(bio); - return NULL; - } - - const char *bio_ptr; - long long bio_len = BIO_get_mem_data(bio, &bio_ptr); - sds cert_pem = sdsnewlen(bio_ptr, bio_len); - BIO_free(bio); - - return cert_pem; -} - -static ConnectionType CT_TLS = { - /* connection type */ - .get_type = connTLSGetType, - - /* connection type initialize & finalize & configure */ - .init = tlsInit, - .cleanup = tlsCleanup, - .configure = tlsConfigure, - - /* ae & accept & listen & error & address handler */ - .ae_handler = tlsEventHandler, - .accept_handler = tlsAcceptHandler, - .addr = connTLSAddr, - .is_local = connTLSIsLocal, - .listen = connTLSListen, - - /* create/shutdown/close connection */ - .conn_create = connCreateTLS, - .conn_create_accepted = connCreateAcceptedTLS, - .shutdown = connTLSShutdown, - .close = connTLSClose, - - /* connect & accept */ - .connect = connTLSConnect, - .blocking_connect = connTLSBlockingConnect, - .accept = connTLSAccept, - - /* event loop */ - .unbind_event_loop = connTLSUnbindEventLoop, - .rebind_event_loop = connTLSRebindEventLoop, - - /* IO */ - .read = connTLSRead, - .write = connTLSWrite, - .writev = connTLSWritev, - .set_write_handler = connTLSSetWriteHandler, - .set_read_handler = connTLSSetReadHandler, - .get_last_error = connTLSGetLastError, - .sync_write = connTLSSyncWrite, - .sync_read = connTLSSyncRead, - .sync_readline = connTLSSyncReadLine, - - /* pending data */ - .has_pending_data = tlsHasPendingData, - .process_pending_data = tlsProcessPendingData, - - /* TLS specified methods */ - .get_peer_cert = connTLSGetPeerCert, - .get_peer_username = tlsGetPeerUsername, -}; - -int RedisRegisterConnectionTypeTLS(void) { - return connTypeRegister(&CT_TLS); -} - -#else /* USE_OPENSSL */ - -int RedisRegisterConnectionTypeTLS(void) { - serverLog(LL_VERBOSE, "Connection type %s not builtin", CONN_TYPE_TLS); - return C_ERR; -} - -#endif - -#if BUILD_TLS_MODULE == 2 /* BUILD_MODULE */ - -#include "release.h" - -int RedisModule_OnLoad(void *ctx, RedisModuleString **argv, int argc) { - UNUSED(argv); - UNUSED(argc); - - /* Connection modules must be part of the same build as redis. */ - if (strcmp(REDIS_BUILD_ID_RAW, redisBuildIdRaw())) { - serverLog(LL_NOTICE, "Connection type %s was not built together with the redis-server used.", CONN_TYPE_TLS); - return REDISMODULE_ERR; - } - - if (RedisModule_Init(ctx,"tls",1,REDISMODULE_APIVER_1) == REDISMODULE_ERR) - return REDISMODULE_ERR; - - /* Connection modules is available only bootup. */ - if ((RedisModule_GetContextFlags(ctx) & REDISMODULE_CTX_FLAGS_SERVER_STARTUP) == 0) { - serverLog(LL_NOTICE, "Connection type %s can be loaded only during bootup", CONN_TYPE_TLS); - return REDISMODULE_ERR; - } - - RedisModule_SetModuleOptions(ctx, REDISMODULE_OPTIONS_HANDLE_REPL_ASYNC_LOAD); - - if(connTypeRegister(&CT_TLS) != C_OK) - return REDISMODULE_ERR; - - return REDISMODULE_OK; -} - -int RedisModule_OnUnload(void *arg) { - UNUSED(arg); - serverLog(LL_NOTICE, "Connection type %s can not be unloaded", CONN_TYPE_TLS); - return REDISMODULE_ERR; -} -#endif |
