diff options
Diffstat (limited to 'fluent-bit/src/tls/openssl.c')
-rw-r--r-- | fluent-bit/src/tls/openssl.c | 616 |
1 files changed, 616 insertions, 0 deletions
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, +}; |