mirror of https://github.com/sudo-project/sudo.git
261 lines
8.1 KiB
C
261 lines
8.1 KiB
C
/*
|
|
* 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);
|
|
|
|
current_cert = X509_STORE_CTX_get_current_cert(ctx);
|
|
|
|
/* if pre-verification of the cert failed, just propagate that result back */
|
|
if (preverify_ok != 1) {
|
|
int err = X509_STORE_CTX_get_error(ctx);
|
|
char current_cert_name[256] = "";
|
|
if (current_cert != NULL)
|
|
X509_NAME_oneline(X509_get_subject_name(current_cert), current_cert_name, sizeof(current_cert_name));
|
|
|
|
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
|
|
"TLS verification failed for cert '%s': '%d:%s'", current_cert_name,
|
|
err, X509_verify_cert_error_string(err));
|
|
debug_return_int(0);
|
|
}
|
|
|
|
/*
|
|
* Since this callback is called for each cert in the chain,
|
|
* check that current cert is the peer's certificate
|
|
*/
|
|
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 */
|