diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 02:57:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 02:57:58 +0000 |
commit | be1c7e50e1e8809ea56f2c9d472eccd8ffd73a97 (patch) | |
tree | 9754ff1ca740f6346cf8483ec915d4054bc5da2d /fluent-bit/src/tls | |
parent | Initial commit. (diff) | |
download | netdata-be1c7e50e1e8809ea56f2c9d472eccd8ffd73a97.tar.xz netdata-be1c7e50e1e8809ea56f2c9d472eccd8ffd73a97.zip |
Adding upstream version 1.44.3.upstream/1.44.3upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'fluent-bit/src/tls')
-rw-r--r-- | fluent-bit/src/tls/flb_tls.c | 665 | ||||
-rw-r--r-- | fluent-bit/src/tls/openssl.c | 616 |
2 files changed, 1281 insertions, 0 deletions
diff --git a/fluent-bit/src/tls/flb_tls.c b/fluent-bit/src/tls/flb_tls.c new file mode 100644 index 00000000..8fc711ab --- /dev/null +++ b/fluent-bit/src/tls/flb_tls.c @@ -0,0 +1,665 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2019-2020 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <fluent-bit/flb_info.h> +#include <fluent-bit/flb_time.h> +#include <fluent-bit/flb_socket.h> + +#include "openssl.c" + +/* Config map for Upstream networking setup */ +struct flb_config_map tls_configmap[] = { + { + FLB_CONFIG_MAP_BOOL, "tls", "off", + 0, FLB_FALSE, 0, + "Enable or disable TLS/SSL support", + }, + { + FLB_CONFIG_MAP_BOOL, "tls.verify", "on", + 0, FLB_FALSE, 0, + "Force certificate validation", + }, + { + FLB_CONFIG_MAP_INT, "tls.debug", "1", + 0, FLB_FALSE, 0, + "Set TLS debug verbosity level. It accept the following " + "values: 0 (No debug), 1 (Error), 2 (State change), 3 " + "(Informational) and 4 Verbose" + }, + { + FLB_CONFIG_MAP_STR, "tls.ca_file", NULL, + 0, FLB_FALSE, 0, + "Absolute path to CA certificate file" + }, + { + FLB_CONFIG_MAP_STR, "tls.ca_path", NULL, + 0, FLB_FALSE, 0, + "Absolute path to scan for certificate files" + }, + { + FLB_CONFIG_MAP_STR, "tls.crt_file", NULL, + 0, FLB_FALSE, 0, + "Absolute path to Certificate file" + }, + { + FLB_CONFIG_MAP_STR, "tls.key_file", NULL, + 0, FLB_FALSE, 0, + "Absolute path to private Key file" + }, + { + FLB_CONFIG_MAP_STR, "tls.key_passwd", NULL, + 0, FLB_FALSE, 0, + "Optional password for tls.key_file file" + }, + + { + FLB_CONFIG_MAP_STR, "tls.vhost", NULL, + 0, FLB_FALSE, 0, + "Hostname to be used for TLS SNI extension" + }, + + /* EOF */ + {0} +}; + +struct mk_list *flb_tls_get_config_map(struct flb_config *config) +{ + struct mk_list *config_map; + + config_map = flb_config_map_create(config, tls_configmap); + return config_map; +} + + +static inline void io_tls_backup_event(struct flb_connection *connection, + struct mk_event *backup) +{ + if (connection != NULL && backup != NULL) { + memcpy(backup, &connection->event, sizeof(struct mk_event)); + } +} + +static inline void io_tls_restore_event(struct flb_connection *connection, + struct mk_event *backup) +{ + int result; + + if (connection != NULL && backup != NULL) { + if (MK_EVENT_IS_REGISTERED((&connection->event))) { + result = mk_event_del(connection->evl, &connection->event); + + assert(result == 0); + } + + if (MK_EVENT_IS_REGISTERED(backup)) { + connection->event.priority = backup->priority; + connection->event.handler = backup->handler; + + result = mk_event_add(connection->evl, + connection->fd, + backup->type, + backup->mask, + &connection->event); + + assert(result == 0); + } + } +} + + +static inline int io_tls_event_switch(struct flb_tls_session *session, + int mask) +{ + struct mk_event_loop *event_loop; + struct mk_event *event; + int ret; + + event = &session->connection->event; + event_loop = session->connection->evl; + + if ((event->mask & mask) == 0) { + ret = mk_event_add(event_loop, + event->fd, + FLB_ENGINE_EV_THREAD, + mask, event); + + event->priority = FLB_ENGINE_PRIORITY_CONNECT; + + if (ret == -1) { + flb_error("[io_tls] error changing mask to %i", mask); + + return -1; + } + } + + return 0; +} + +int flb_tls_load_system_certificates(struct flb_tls *tls) +{ + return load_system_certificates(tls->ctx); +} + +struct flb_tls *flb_tls_create(int mode, + int verify, + int debug, + const char *vhost, + const char *ca_path, + const char *ca_file, + const char *crt_file, + const char *key_file, + const char *key_passwd) +{ + void *backend; + struct flb_tls *tls; + + /* Assuming the TLS role based on the connection direction is wrong + * but it's something we'll accept for the moment. + */ + + backend = tls_context_create(verify, debug, mode, + vhost, ca_path, ca_file, + crt_file, key_file, key_passwd); + if (!backend) { + flb_error("[tls] could not create TLS backend"); + return NULL; + } + + tls = flb_calloc(1, sizeof(struct flb_tls)); + if (!tls) { + flb_errno(); + tls_context_destroy(backend); + return NULL; + } + + tls->verify = verify; + tls->debug = debug; + tls->mode = mode; + + if (vhost != NULL) { + tls->vhost = flb_strdup(vhost); + } + tls->ctx = backend; + + tls->api = &tls_openssl; + + return tls; +} + +int flb_tls_init() +{ + return tls_init(); +} + +int flb_tls_destroy(struct flb_tls *tls) +{ + if (tls->ctx) { + tls->api->context_destroy(tls->ctx); + } + + if (tls->vhost != NULL) { + flb_free(tls->vhost); + } + + flb_free(tls); + + return 0; +} + +int flb_tls_net_read(struct flb_tls_session *session, void *buf, size_t len) +{ + time_t timeout_timestamp; + time_t current_timestamp; + struct flb_tls *tls; + int ret; + + tls = session->tls; + + if (session->connection->net->io_timeout > 0) { + timeout_timestamp = time(NULL) + session->connection->net->io_timeout; + } + else { + timeout_timestamp = 0; + } + + retry_read: + ret = tls->api->net_read(session, buf, len); + + current_timestamp = time(NULL); + + if (ret == FLB_TLS_WANT_READ) { + if (timeout_timestamp > 0 && + timeout_timestamp <= current_timestamp) { + return ret; + } + + goto retry_read; + } + else if (ret == FLB_TLS_WANT_WRITE) { + goto retry_read; + } + else if (ret < 0) { + return -1; + } + else if (ret == 0) { + return -1; + } + + return ret; +} + +int flb_tls_net_read_async(struct flb_coro *co, + struct flb_tls_session *session, + void *buf, size_t len) +{ + int event_restore_needed; + struct mk_event event_backup; + struct flb_tls *tls; + int ret; + + tls = session->tls; + + event_restore_needed = FLB_FALSE; + + io_tls_backup_event(session->connection, &event_backup); + + retry_read: + ret = tls->api->net_read(session, buf, len); + + if (ret == FLB_TLS_WANT_READ) { + event_restore_needed = FLB_TRUE; + + session->connection->coroutine = co; + + io_tls_event_switch(session, MK_EVENT_READ); + flb_coro_yield(co, FLB_FALSE); + + goto retry_read; + } + else if (ret == FLB_TLS_WANT_WRITE) { + event_restore_needed = FLB_TRUE; + + session->connection->coroutine = co; + + io_tls_event_switch(session, MK_EVENT_WRITE); + flb_coro_yield(co, FLB_FALSE); + + goto retry_read; + } + else + { + /* We want this field to hold NULL at all times unless we are explicitly + * waiting to be resumed. + */ + session->connection->coroutine = NULL; + + if (ret <= 0) { + ret = -1; + } + } + + if (event_restore_needed) { + /* If we enter here it means we registered this connection + * in the event loop, in which case we need to unregister it + * and restore the original registration if there was one. + * + * We do it conditionally because in those cases in which + * send succeeds on the first try we don't touch the event + * and it wouldn't make sense to unregister and register for + * the same event. + */ + + io_tls_restore_event(session->connection, &event_backup); + } + + return ret; +} + +int flb_tls_net_write(struct flb_tls_session *session, + const void *data, size_t len, size_t *out_len) +{ + size_t total; + int ret; + struct flb_tls *tls; + + total = 0; + tls = session->tls; + +retry_write: + ret = tls->api->net_write(session, + (unsigned char *) data + total, + len - total); + + if (ret == FLB_TLS_WANT_WRITE) { + goto retry_write; + } + else if (ret == FLB_TLS_WANT_READ) { + goto retry_write; + } + else if (ret < 0) { + *out_len = total; + + return -1; + } + + /* Update counter and check if we need to continue writing */ + total += ret; + + if (total < len) { + goto retry_write; + } + + *out_len = total; + + return ret; +} + +int flb_tls_net_write_async(struct flb_coro *co, + struct flb_tls_session *session, + const void *data, size_t len, size_t *out_len) +{ + int event_restore_needed; + struct mk_event event_backup; + size_t total; + int ret; + struct flb_tls *tls; + + total = 0; + tls = session->tls; + + event_restore_needed = FLB_FALSE; + + io_tls_backup_event(session->connection, &event_backup); + +retry_write: + session->connection->coroutine = co; + + ret = tls->api->net_write(session, + (unsigned char *) data + total, + len - total); + + if (ret == FLB_TLS_WANT_WRITE) { + event_restore_needed = FLB_TRUE; + + io_tls_event_switch(session, MK_EVENT_WRITE); + + flb_coro_yield(co, FLB_FALSE); + + goto retry_write; + } + else if (ret == FLB_TLS_WANT_READ) { + event_restore_needed = FLB_TRUE; + + io_tls_event_switch(session, MK_EVENT_READ); + + flb_coro_yield(co, FLB_FALSE); + + goto retry_write; + } + else if (ret < 0) { + /* We want this field to hold NULL at all times unless we are explicitly + * waiting to be resumed. + */ + + session->connection->coroutine = NULL; + *out_len = total; + + io_tls_restore_event(session->connection, &event_backup); + + return -1; + } + + /* Update counter and check if we need to continue writing */ + total += ret; + + if (total < len) { + io_tls_event_switch(session, MK_EVENT_WRITE); + + flb_coro_yield(co, FLB_FALSE); + + goto retry_write; + } + + /* We want this field to hold NULL at all times unless we are explicitly + * waiting to be resumed. + */ + + session->connection->coroutine = NULL; + + *out_len = total; + + if (event_restore_needed) { + /* If we enter here it means we registered this connection + * in the event loop, in which case we need to unregister it + * and restore the original registration if there was one. + * + * We do it conditionally because in those cases in which + * send succeeds on the first try we don't touch the event + * and it wouldn't make sense to unregister and register for + * the same event. + */ + + io_tls_restore_event(session->connection, &event_backup); + } + + return total; +} + +int flb_tls_client_session_create(struct flb_tls *tls, + struct flb_connection *u_conn, + struct flb_coro *co) +{ + return flb_tls_session_create(tls, u_conn, co); +} + +int flb_tls_server_session_create(struct flb_tls *tls, + struct flb_connection *connection, + struct flb_coro *co) +{ + return flb_tls_session_create(tls, connection, co); +} + +/* Create a TLS session (server) */ +int flb_tls_session_create(struct flb_tls *tls, + struct flb_connection *connection, + struct flb_coro *co) +{ + int event_restore_needed; + struct mk_event event_backup; + struct flb_tls_session *session; + int result; + char *vhost; + int flag; + + session = flb_calloc(1, sizeof(struct flb_tls_session)); + + if (session == NULL) { + return -1; + } + + vhost = NULL; + + if (connection->type == FLB_UPSTREAM_CONNECTION) { + if (connection->upstream->proxied_host != NULL) { + vhost = flb_rtrim(connection->upstream->proxied_host, '.'); + } + else { + if (tls->vhost == NULL) { + vhost = flb_rtrim(connection->upstream->tcp_host, '.'); + } + } + } + + /* Create TLS session */ + session->ptr = tls->api->session_create(tls, connection->fd); + + if (session == NULL) { + flb_error("[tls] could not create TLS session for %s", + flb_connection_get_remote_address(connection)); + + return -1; + } + + session->tls = tls; + session->connection = connection; + + result = 0; + + event_restore_needed = FLB_FALSE; + + io_tls_backup_event(session->connection, &event_backup); + + retry_handshake: + result = tls->api->net_handshake(tls, vhost, session->ptr); + + if (result != 0) { + if (result != FLB_TLS_WANT_READ && result != FLB_TLS_WANT_WRITE) { + result = -1; + + goto cleanup; + } + + flag = 0; + + if (result == FLB_TLS_WANT_WRITE) { + flag = MK_EVENT_WRITE; + } + else if (result == FLB_TLS_WANT_READ) { + flag = MK_EVENT_READ; + } + + /* + * If there are no coroutine thread context (th == NULL) it means this + * TLS handshake is happening from a blocking code. Just sleep a bit + * and retry. + * + * In the other case for an async socket 'th' is NOT NULL so the code + * is under a coroutine context and it can yield. + */ + if (co == NULL) { + flb_trace("[io_tls] server handshake connection #%i in process to %s", + connection->fd, + flb_connection_get_remote_address(connection)); + + /* Connect timeout */ + if (connection->net->connect_timeout > 0 && + connection->ts_connect_timeout > 0 && + connection->ts_connect_timeout <= time(NULL)) { + flb_error("[io_tls] handshake connection #%i to %s timed out after " + "%i seconds", + connection->fd, + flb_connection_get_remote_address(connection), + connection->net->connect_timeout); + + result = -1; + + goto cleanup; + } + + flb_time_msleep(500); + + goto retry_handshake; + } + + event_restore_needed = FLB_TRUE; + + /* + * FIXME: if we need multiple reads we are invoking the same + * system call multiple times. + */ + + result = mk_event_add(connection->evl, + connection->fd, + FLB_ENGINE_EV_THREAD, + flag, + &connection->event); + + connection->event.priority = FLB_ENGINE_PRIORITY_CONNECT; + + if (result == -1) { + goto cleanup; + } + + connection->coroutine = co; + + flb_coro_yield(co, FLB_FALSE); + + /* We want this field to hold NULL at all times unless we are explicitly + * waiting to be resumed. + */ + + connection->coroutine = NULL; + + /* This check's purpose is to abort when a timeout is detected. + */ + if (connection->net_error == -1) { + goto retry_handshake; + } + else { + result = -1; + } + } + +cleanup: + if (event_restore_needed) { + /* If we enter here it means we registered this connection + * in the event loop, in which case we need to unregister it + * and restore the original registration if there was one. + * + * We do it conditionally because in those cases in which + * send succeeds on the first try we don't touch the event + * and it wouldn't make sense to unregister and register for + * the same event. + */ + + io_tls_restore_event(session->connection, &event_backup); + } + + if (result != 0) { + flb_tls_session_destroy(session); + } + else { + connection->tls_session = session; + } + + if (vhost != NULL) { + flb_free(vhost); + } + + return result; +} + +int flb_tls_session_destroy(struct flb_tls_session *session) +{ + int ret; + + session->connection->tls_session = NULL; + + if (session->ptr != NULL) { + ret = session->tls->api->session_destroy(session->ptr); + + if (ret == -1) { + return -1; + } + + flb_free(session); + } + + return 0; +} diff --git a/fluent-bit/src/tls/openssl.c b/fluent-bit/src/tls/openssl.c new file mode 100644 index 00000000..0d5d60bb --- /dev/null +++ b/fluent-bit/src/tls/openssl.c @@ -0,0 +1,616 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2022 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <fluent-bit/flb_info.h> +#include <fluent-bit/flb_compat.h> +#include <fluent-bit/tls/flb_tls.h> +#include <fluent-bit/tls/flb_tls_info.h> + +#include <openssl/ssl.h> +#include <openssl/err.h> +#include <openssl/opensslv.h> + +/* + * OPENSSL_VERSION_NUMBER has the following semantics + * + * 0x010100000L M = major F = fix S = status + * MMNNFFPPS N = minor P = patch + */ +#define OPENSSL_1_1_0 0x010100000L + +/* OpenSSL library context */ +struct tls_context { + int debug_level; + SSL_CTX *ctx; + int mode; + pthread_mutex_t mutex; +}; + +struct tls_session { + SSL *ssl; + int fd; + int continuation_flag; + struct tls_context *parent; /* parent struct tls_context ref */ +}; + + +static int tls_init(void) +{ +/* + * Explicit initialization is needed for older versions of + * OpenSSL (before v1.1.0). + * + * https://wiki.openssl.org/index.php/Library_Initialization + */ +#if OPENSSL_VERSION_NUMBER < OPENSSL_1_1_0 + OPENSSL_add_all_algorithms_noconf(); + SSL_load_error_strings(); + SSL_library_init(); +#endif + return 0; +} + +static void tls_info_callback(const SSL *s, int where, int ret) +{ + int w; + int fd; + const char *str; + + fd = SSL_get_fd(s); + w = where & ~SSL_ST_MASK; + if (w & SSL_ST_CONNECT) { + str = "SSL_connect"; + } + else if (w & SSL_ST_ACCEPT) { + str = "SSL_accept"; + } + else { + str = "undefined"; + } + + if (where & SSL_CB_LOOP) { + flb_debug("[tls] connection #%i %s: %s", + fd, str, SSL_state_string_long(s)); + } + else if (where & SSL_CB_ALERT) { + str = (where & SSL_CB_READ) ? "read" : "write"; + flb_debug("[tls] connection #%i SSL3 alert %s:%s:%s", + fd, str, + SSL_alert_type_string_long(ret), + SSL_alert_desc_string_long(ret)); + } + else if (where & SSL_CB_EXIT) { + if (ret == 0) { + flb_error("[tls] connection #%i %s: failed in %s", + fd, str, SSL_state_string_long(s)); + } + else if (ret < 0) { + ret = SSL_get_error(s, ret); + if (ret == SSL_ERROR_WANT_WRITE) { + flb_debug("[tls] connection #%i WANT_WRITE", fd); + } + else if (ret == SSL_ERROR_WANT_READ) { + flb_debug("[tls] connection #%i WANT_READ", fd); + } + else { + flb_error("[tls] connection #%i %s: error in %s", + fd, str, SSL_state_string_long(s)); + } + } + } +} + +static void tls_context_destroy(void *ctx_backend) +{ + struct tls_context *ctx = ctx_backend; + + pthread_mutex_lock(&ctx->mutex); + SSL_CTX_free(ctx->ctx); + pthread_mutex_unlock(&ctx->mutex); + + flb_free(ctx); +} + +#ifdef _MSC_VER +static int windows_load_system_certificates(struct tls_context *ctx) +{ + int ret; + HANDLE win_store; + PCCERT_CONTEXT win_cert = NULL; + const unsigned char *win_cert_data; + X509_STORE *ossl_store = SSL_CTX_get_cert_store(ctx->ctx); + X509 *ossl_cert; + + win_store = CertOpenSystemStoreA(0, "Root"); + if (win_store == NULL) { + flb_error("[tls] Cannot open cert store: %i", GetLastError()); + return -1; + } + + while (win_cert = CertEnumCertificatesInStore(win_store, win_cert)) { + if (win_cert->dwCertEncodingType & X509_ASN_ENCODING) { + /* + * Decode the certificate into X509 struct. + * + * The additional pointer variable is necessary per OpenSSL docs because the + * pointer is incremented by d2i_X509. + */ + win_cert_data = win_cert->pbCertEncoded; + ossl_cert = d2i_X509(NULL, &win_cert_data, win_cert->cbCertEncoded); + if (!ossl_cert) { + flb_debug("[tls] Cannot parse a certificate. skipping..."); + continue; + } + + /* Add X509 struct to the openssl cert store */ + ret = X509_STORE_add_cert(ossl_store, ossl_cert); + if (!ret) { + flb_warn("[tls] Failed to add a certificate to the store: %lu: %s", + ERR_get_error(), ERR_error_string(ERR_get_error(), NULL)); + } + X509_free(ossl_cert); + } + } + + if (!CertCloseStore(win_store, 0)) { + flb_error("[tls] Cannot close cert store: %i", GetLastError()); + return -1; + } + return 0; +} +#endif + +static int load_system_certificates(struct tls_context *ctx) +{ + int ret; + const char *ca_file = FLB_DEFAULT_SEARCH_CA_BUNDLE; + + /* For Windows use specific API to read the certs store */ +#ifdef _MSC_VER + return windows_load_system_certificates(ctx); +#endif + if (access(ca_file, R_OK) != 0) { + ca_file = NULL; + } + + ret = SSL_CTX_load_verify_locations(ctx->ctx, ca_file, FLB_DEFAULT_CA_DIR); + + if (ret != 1) { + ERR_print_errors_fp(stderr); + } + return 0; +} + +static void *tls_context_create(int verify, + int debug, + int mode, + const char *vhost, + const char *ca_path, + const char *ca_file, + const char *crt_file, + const char *key_file, + const char *key_passwd) +{ + int ret; + SSL_CTX *ssl_ctx; + struct tls_context *ctx; + char err_buf[256]; + + /* + * Init library ? based in the documentation on OpenSSL >= 1.1.0 is not longer + * necessary since the library will initialize it self: + * + * https://wiki.openssl.org/index.php/Library_Initialization + */ + + /* Create OpenSSL context */ +#if OPENSSL_VERSION_NUMBER < OPENSSL_1_1_0 + /* + * SSLv23_method() is actually an equivalent of TLS_client_method() + * in OpenSSL v1.0.x. + * + * https://www.openssl.org/docs/man1.0.2/man3/SSLv23_method.html + */ + if (mode == FLB_TLS_SERVER_MODE) { + ssl_ctx = SSL_CTX_new(SSLv23_server_method()); + } + else { + ssl_ctx = SSL_CTX_new(SSLv23_client_method()); + } + +#else + if (mode == FLB_TLS_SERVER_MODE) { + ssl_ctx = SSL_CTX_new(TLS_server_method()); + } + else { + ssl_ctx = SSL_CTX_new(TLS_client_method()); + } +#endif + if (!ssl_ctx) { + flb_error("[openssl] could not create context"); + return NULL; + } + + ctx = flb_calloc(1, sizeof(struct tls_context)); + if (!ctx) { + flb_errno(); + return NULL; + } + ctx->ctx = ssl_ctx; + ctx->mode = mode; + ctx->debug_level = debug; + pthread_mutex_init(&ctx->mutex, NULL); + + /* Verify peer: by default OpenSSL always verify peer */ + if (verify == FLB_FALSE) { + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL); + } + else { + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL); + } + + /* ca_path | ca_file */ + if (ca_path) { + ret = SSL_CTX_load_verify_locations(ctx->ctx, NULL, ca_path); + if (ret != 1) { + ERR_error_string_n(ERR_get_error(), err_buf, sizeof(err_buf)-1); + flb_error("[tls] ca_path '%s' %lu: %s", + ca_path, ERR_get_error(), err_buf); + goto error; + } + } + else if (ca_file) { + ret = SSL_CTX_load_verify_locations(ctx->ctx, ca_file, NULL); + if (ret != 1) { + ERR_error_string_n(ERR_get_error(), err_buf, sizeof(err_buf)-1); + flb_error("[tls] ca_file '%s' %lu: %s", + ca_file, ERR_get_error(), err_buf); + goto error; + } + } + else { + load_system_certificates(ctx); + } + + /* crt_file */ + if (crt_file) { + ret = SSL_CTX_use_certificate_chain_file(ssl_ctx, crt_file); + if (ret != 1) { + ERR_error_string_n(ERR_get_error(), err_buf, sizeof(err_buf)-1); + flb_error("[tls] crt_file '%s' %lu: %s", + crt_file, ERR_get_error(), err_buf); + goto error; + } + } + + /* key_file */ + if (key_file) { + if (key_passwd) { + SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, + (void *) key_passwd); + } + ret = SSL_CTX_use_PrivateKey_file(ssl_ctx, key_file, + SSL_FILETYPE_PEM); + if (ret != 1) { + ERR_error_string_n(ERR_get_error(), err_buf, sizeof(err_buf)-1); + flb_error("[tls] key_file '%s' %lu: %s", + crt_file, ERR_get_error(), err_buf); + } + + /* Make sure the key and certificate file match */ + if (SSL_CTX_check_private_key(ssl_ctx) != 1) { + flb_error("[tls] private_key '%s' and password don't match", + key_file); + goto error; + } + } + + return ctx; + + error: + tls_context_destroy(ctx); + return NULL; +} + +static void *tls_session_create(struct flb_tls *tls, + int fd) +{ + struct tls_session *session; + struct tls_context *ctx = tls->ctx; + SSL *ssl; + + session = flb_calloc(1, sizeof(struct tls_session)); + if (!session) { + flb_errno(); + return NULL; + } + session->parent = ctx; + + pthread_mutex_lock(&ctx->mutex); + ssl = SSL_new(ctx->ctx); + + if (!ssl) { + flb_error("[openssl] could create new SSL context"); + flb_free(session); + pthread_mutex_unlock(&ctx->mutex); + return NULL; + } + + session->continuation_flag = FLB_FALSE; + session->ssl = ssl; + session->fd = fd; + SSL_set_fd(ssl, fd); + + /* + * TLS Debug Levels: + * + * 0: No debug, + * 1: Error + * 2: State change + * 3: Informational + * 4: Verbose + */ + if (tls->debug == 1) { + SSL_set_info_callback(session->ssl, tls_info_callback); + } + pthread_mutex_unlock(&ctx->mutex); + return session; +} + +static int tls_session_destroy(void *session) +{ + struct tls_session *ptr = session; + struct tls_context *ctx; + + if (!ptr) { + return 0; + } + ctx = ptr->parent; + + pthread_mutex_lock(&ctx->mutex); + + if (flb_socket_error(ptr->fd) == 0) { + SSL_shutdown(ptr->ssl); + } + SSL_free(ptr->ssl); + flb_free(ptr); + + pthread_mutex_unlock(&ctx->mutex); + + return 0; +} + +static int tls_net_read(struct flb_tls_session *session, + void *buf, size_t len) +{ + int ret; + char err_buf[256]; + struct tls_context *ctx; + struct tls_session *backend_session; + + if (session->ptr == NULL) { + flb_error("[tls] error: uninitialized backend session"); + + return -1; + } + + backend_session = (struct tls_session *) session->ptr; + + ctx = backend_session->parent; + + pthread_mutex_lock(&ctx->mutex); + + ERR_clear_error(); + + ret = SSL_read(backend_session->ssl, buf, len); + + if (ret <= 0) { + ret = SSL_get_error(backend_session->ssl, ret); + + if (ret == SSL_ERROR_WANT_READ) { + ret = FLB_TLS_WANT_READ; + } + else if (ret == SSL_ERROR_WANT_WRITE) { + ret = FLB_TLS_WANT_WRITE; + } + else if (ret == SSL_ERROR_SYSCALL) { + flb_errno(); + ERR_error_string_n(ret, err_buf, sizeof(err_buf)-1); + flb_error("[tls] syscall error: %s", err_buf); + + /* According to the documentation these are non-recoverable + * errors so we don't need to screen them before saving them + * to the net_error field. + */ + + session->connection->net_error = errno; + + ret = -1; + } + else if (ret < 0) { + ERR_error_string_n(ret, err_buf, sizeof(err_buf)-1); + flb_error("[tls] error: %s", err_buf); + } + else { + ret = -1; + } + } + + pthread_mutex_unlock(&ctx->mutex); + return ret; +} + +static int tls_net_write(struct flb_tls_session *session, + const void *data, size_t len) +{ + int ret; + char err_buf[256]; + size_t total = 0; + struct tls_context *ctx; + struct tls_session *backend_session; + + if (session->ptr == NULL) { + flb_error("[tls] error: uninitialized backend session"); + + return -1; + } + + backend_session = (struct tls_session *) session->ptr; + ctx = backend_session->parent; + + pthread_mutex_lock(&ctx->mutex); + + ERR_clear_error(); + + ret = SSL_write(backend_session->ssl, + (unsigned char *) data + total, + len - total); + + if (ret <= 0) { + ret = SSL_get_error(backend_session->ssl, ret); + + if (ret == SSL_ERROR_WANT_WRITE) { + ret = FLB_TLS_WANT_WRITE; + } + else if (ret == SSL_ERROR_WANT_READ) { + ret = FLB_TLS_WANT_READ; + } + else if (ret == SSL_ERROR_SYSCALL) { + flb_errno(); + ERR_error_string_n(ret, err_buf, sizeof(err_buf)-1); + flb_error("[tls] syscall error: %s", err_buf); + + /* According to the documentation these are non-recoverable + * errors so we don't need to screen them before saving them + * to the net_error field. + */ + + session->connection->net_error = errno; + + ret = -1; + } + else { + ERR_error_string_n(ret, err_buf, sizeof(err_buf)-1); + flb_error("[tls] error: %s", err_buf); + + ret = -1; + } + } + + pthread_mutex_unlock(&ctx->mutex); + + /* Update counter and check if we need to continue writing */ + return ret; +} + +static int tls_net_handshake(struct flb_tls *tls, + char *vhost, + void *ptr_session) +{ + int ret = 0; + char err_buf[256]; + struct tls_session *session = ptr_session; + struct tls_context *ctx; + + ctx = session->parent; + pthread_mutex_lock(&ctx->mutex); + + if (!session->continuation_flag) { + if (tls->mode == FLB_TLS_CLIENT_MODE) { + SSL_set_connect_state(session->ssl); + } + else if (tls->mode == FLB_TLS_SERVER_MODE) { + SSL_set_accept_state(session->ssl); + } + else { + flb_error("[tls] error: invalid tls mode : %d", tls->mode); + pthread_mutex_unlock(&ctx->mutex); + return -1; + } + + if (vhost != NULL) { + SSL_set_tlsext_host_name(session->ssl, vhost); + } + else if (tls->vhost) { + SSL_set_tlsext_host_name(session->ssl, tls->vhost); + } + } + + ERR_clear_error(); + + if (tls->mode == FLB_TLS_CLIENT_MODE) { + ret = SSL_connect(session->ssl); + } + else if (tls->mode == FLB_TLS_SERVER_MODE) { + ret = SSL_accept(session->ssl); + } + + if (ret != 1) { + ret = SSL_get_error(session->ssl, ret); + if (ret != SSL_ERROR_WANT_READ && + ret != SSL_ERROR_WANT_WRITE) { + ret = SSL_get_error(session->ssl, ret); + // The SSL_ERROR_SYSCALL with errno value of 0 indicates unexpected + // EOF from the peer. This is fixed in OpenSSL 3.0. + if (ret == 0) { + flb_error("[tls] error: unexpected EOF"); + } else { + ERR_error_string_n(ret, err_buf, sizeof(err_buf)-1); + flb_error("[tls] error: %s", err_buf); + } + + pthread_mutex_unlock(&ctx->mutex); + + return -1; + } + + if (ret == SSL_ERROR_WANT_WRITE) { + pthread_mutex_unlock(&ctx->mutex); + + session->continuation_flag = FLB_TRUE; + + return FLB_TLS_WANT_WRITE; + } + else if (ret == SSL_ERROR_WANT_READ) { + pthread_mutex_unlock(&ctx->mutex); + + session->continuation_flag = FLB_TRUE; + + return FLB_TLS_WANT_READ; + } + } + + session->continuation_flag = FLB_FALSE; + + pthread_mutex_unlock(&ctx->mutex); + flb_trace("[tls] connection and handshake OK"); + return 0; +} + +/* OpenSSL backend registration */ +static struct flb_tls_backend tls_openssl = { + .name = "openssl", + .context_create = tls_context_create, + .context_destroy = tls_context_destroy, + .session_create = tls_session_create, + .session_destroy = tls_session_destroy, + .net_read = tls_net_read, + .net_write = tls_net_write, + .net_handshake = tls_net_handshake, +}; |