/* * SPDX-License-Identifier: ISC * * Copyright (c) 2019-2022 Todd C. Miller * * 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 #include #ifdef HAVE_STDBOOL_H # include #else # include "compat/stdbool.h" #endif /* HAVE_STDBOOL_H */ #if defined(HAVE_STDINT_H) # include #elif defined(HAVE_INTTYPES_H) # include #endif #include #include #include #include #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 */