summaryrefslogtreecommitdiffstats
path: root/logsrvd/tls_client.c
diff options
context:
space:
mode:
Diffstat (limited to 'logsrvd/tls_client.c')
-rw-r--r--logsrvd/tls_client.c251
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 */