diff options
Diffstat (limited to 'logsrvd/tls_client.c')
-rw-r--r-- | logsrvd/tls_client.c | 251 |
1 files changed, 251 insertions, 0 deletions
diff --git a/logsrvd/tls_client.c b/logsrvd/tls_client.c new file mode 100644 index 0000000..dde6a60 --- /dev/null +++ b/logsrvd/tls_client.c @@ -0,0 +1,251 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2019-2022 Todd C. Miller <Todd.Miller@sudo.ws> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include <config.h> + +#include <errno.h> +#ifdef HAVE_STDBOOL_H +# include <stdbool.h> +#else +# include "compat/stdbool.h" +#endif /* HAVE_STDBOOL_H */ +#if defined(HAVE_STDINT_H) +# include <stdint.h> +#elif defined(HAVE_INTTYPES_H) +# include <inttypes.h> +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "sudo_compat.h" +#include "sudo_debug.h" +#include "sudo_event.h" +#include "sudo_fatal.h" +#include "sudo_gettext.h" +#include "sudo_util.h" + +#include "logsrv_util.h" +#include "tls_common.h" +#include "hostcheck.h" + +#if defined(HAVE_OPENSSL) + +/* + * Check that the server's certificate is valid that it contains the + * server name or IP address. + * Returns 0 if the cert is invalid, else 1. + */ +static int +verify_peer_identity(int preverify_ok, X509_STORE_CTX *ctx) +{ + HostnameValidationResult result; + struct peer_info *peer_info; + SSL *ssl; + X509 *current_cert; + X509 *peer_cert; + debug_decl(verify_peer_identity, SUDO_DEBUG_UTIL); + + /* if pre-verification of the cert failed, just propagate that result back */ + if (preverify_ok != 1) { + debug_return_int(0); + } + + /* + * Since this callback is called for each cert in the chain, + * check that current cert is the peer's certificate + */ + current_cert = X509_STORE_CTX_get_current_cert(ctx); + peer_cert = X509_STORE_CTX_get0_cert(ctx); + if (current_cert != peer_cert) { + debug_return_int(1); + } + + /* Fetch the attached peer_info from the ssl connection object. */ + ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); + peer_info = SSL_get_ex_data(ssl, 1); + + /* + * Validate the cert based on the host name and IP address. + * If host name is not known, validate_hostname() can resolve it. + */ + result = validate_hostname(peer_cert, + peer_info->name ? peer_info->name : peer_info->ipaddr, + peer_info->ipaddr, peer_info->name ? 0 : 1); + + debug_return_int(result == MatchFound); +} + +void +tls_connect_cb(int sock, int what, void *v) +{ + struct tls_client_closure *tls_client = v; + struct sudo_event_base *evbase = tls_client->evbase; + const struct timespec *timeout = &tls_client->connect_timeout; + const char *errstr; + int con_stat; + debug_decl(tls_connect_cb, SUDO_DEBUG_UTIL); + + if (what == SUDO_EV_TIMEOUT) { + sudo_warnx("%s", U_("TLS handshake timeout occurred")); + goto bad; + } + + con_stat = SSL_connect(tls_client->ssl); + + if (con_stat == 1) { + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "SSL_connect successful"); + tls_client->tls_connect_state = true; + } else { + switch (SSL_get_error(tls_client->ssl, con_stat)) { + /* TLS handshake is not finished, reschedule event */ + case SSL_ERROR_WANT_READ: + sudo_debug_printf(SUDO_DEBUG_NOTICE|SUDO_DEBUG_LINENO, + "SSL_connect returns SSL_ERROR_WANT_READ"); + if (what != SUDO_EV_READ) { + if (sudo_ev_set(tls_client->tls_connect_ev, + SSL_get_fd(tls_client->ssl), SUDO_EV_READ, + tls_connect_cb, tls_client) == -1) { + sudo_warnx("%s", U_("unable to set event")); + goto bad; + } + } + if (sudo_ev_add(evbase, tls_client->tls_connect_ev, timeout, false) == -1) { + sudo_warnx("%s", U_("unable to add event to queue")); + goto bad; + } + break; + case SSL_ERROR_WANT_WRITE: + sudo_debug_printf(SUDO_DEBUG_NOTICE|SUDO_DEBUG_LINENO, + "SSL_connect returns SSL_ERROR_WANT_WRITE"); + if (what != SUDO_EV_WRITE) { + if (sudo_ev_set(tls_client->tls_connect_ev, + SSL_get_fd(tls_client->ssl), SUDO_EV_WRITE, + tls_connect_cb, tls_client) == -1) { + sudo_warnx("%s", U_("unable to set event")); + goto bad; + } + } + if (sudo_ev_add(evbase, tls_client->tls_connect_ev, timeout, false) == -1) { + sudo_warnx("%s", U_("unable to add event to queue")); + goto bad; + } + break; + case SSL_ERROR_SYSCALL: + sudo_warnx(U_("TLS connection failed: %s"), strerror(errno)); + goto bad; + default: + errstr = ERR_reason_error_string(ERR_get_error()); + sudo_warnx(U_("TLS connection failed: %s"), + errstr ? errstr : strerror(errno)); + goto bad; + } + } + + if (tls_client->tls_connect_state) { + sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO, + "TLS version: %s, negotiated cipher suite: %s", + SSL_get_version(tls_client->ssl), SSL_get_cipher(tls_client->ssl)); + + /* Done with TLS connect, send ClientHello */ + sudo_ev_free(tls_client->tls_connect_ev); + tls_client->tls_connect_ev = NULL; + if (!tls_client->start_fn(tls_client)) + goto bad; + } + + debug_return; + +bad: + sudo_ev_loopbreak(evbase); + debug_return; +} + +bool +tls_ctx_client_setup(SSL_CTX *ssl_ctx, int sock, + struct tls_client_closure *closure) +{ + const char *errstr; + bool ret = false; + debug_decl(tls_ctx_client_setup, SUDO_DEBUG_UTIL); + + if ((closure->ssl = SSL_new(ssl_ctx)) == NULL) { + errstr = ERR_reason_error_string(ERR_get_error()); + sudo_warnx(U_("unable to allocate ssl object: %s"), + errstr ? errstr : strerror(errno)); + goto done; + } + + if (SSL_set_ex_data(closure->ssl, 1, closure->peer_name) <= 0) { + errstr = ERR_reason_error_string(ERR_get_error()); + sudo_warnx(U_("Unable to attach user data to the ssl object: %s"), + errstr ? errstr : strerror(errno)); + goto done; + } + + if (SSL_set_fd(closure->ssl, sock) <= 0) { + errstr = ERR_reason_error_string(ERR_get_error()); + sudo_warnx(U_("Unable to attach socket to the ssl object: %s"), + errstr ? errstr : strerror(errno)); + goto done; + } + + if (sudo_ev_add(closure->evbase, closure->tls_connect_ev, NULL, false) == -1) { + sudo_warnx("%s", U_("unable to add event to queue")); + goto done; + } + + ret = true; + +done: + debug_return_bool(ret); +} + +bool +tls_client_setup(int sock, const char *ca_bundle_file, const char *cert_file, + const char *key_file, const char *dhparam_file, const char *ciphers_v12, + const char *ciphers_v13, bool verify_server, bool check_peer, + struct tls_client_closure *closure) +{ + SSL_CTX *ssl_ctx; + debug_decl(tls_client_setup, SUDO_DEBUG_UTIL); + + ssl_ctx = init_tls_context(ca_bundle_file, cert_file, key_file, + dhparam_file, ciphers_v12, ciphers_v13, verify_server); + if (ssl_ctx == NULL) { + sudo_warnx("%s", U_("unable to initialize TLS context")); + debug_return_bool(false); + } + + if (check_peer) { + /* Verify server cert during the handshake. */ + SSL_CTX_set_verify(ssl_ctx, + SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + verify_peer_identity); + } + + debug_return_bool(tls_ctx_client_setup(ssl_ctx, sock, closure)); +} +#endif /* HAVE_OPENSSL */ |