summaryrefslogtreecommitdiffstats
path: root/fluent-bit/src/tls
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 02:57:58 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 02:57:58 +0000
commitbe1c7e50e1e8809ea56f2c9d472eccd8ffd73a97 (patch)
tree9754ff1ca740f6346cf8483ec915d4054bc5da2d /fluent-bit/src/tls
parentInitial commit. (diff)
downloadnetdata-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.c665
-rw-r--r--fluent-bit/src/tls/openssl.c616
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,
+};