Blame keepalived/check/check_ssl.c

Packit c22fc9
/*
Packit c22fc9
 * Soft:        Keepalived is a failover program for the LVS project
Packit c22fc9
 *              <www.linuxvirtualserver.org>. It monitor & manipulate
Packit c22fc9
 *              a loadbalanced server pool using multi-layer checks.
Packit c22fc9
 *
Packit c22fc9
 * Part:        SSL GET CHECK. Perform an ssl get query to a specified
Packit c22fc9
 *              url, compute a MD5 over this result and match it to the
Packit c22fc9
 *              expected value.
Packit c22fc9
 *
Packit c22fc9
 * Authors:     Alexandre Cassen, <acassen@linux-vs.org>
Packit c22fc9
 *              Jan Holmberg, <jan@artech.net>
Packit c22fc9
 *
Packit c22fc9
 *              This program is distributed in the hope that it will be useful,
Packit c22fc9
 *              but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit c22fc9
 *              MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Packit c22fc9
 *              See the GNU General Public License for more details.
Packit c22fc9
 *
Packit c22fc9
 *              This program is free software; you can redistribute it and/or
Packit c22fc9
 *              modify it under the terms of the GNU General Public License
Packit c22fc9
 *              as published by the Free Software Foundation; either version
Packit c22fc9
 *              2 of the License, or (at your option) any later version.
Packit c22fc9
 *
Packit c22fc9
 * Copyright (C) 2001-2017 Alexandre Cassen, <acassen@gmail.com>
Packit c22fc9
 */
Packit c22fc9
Packit c22fc9
#include "config.h"
Packit c22fc9
Packit c22fc9
#include <fcntl.h>
Packit c22fc9
#include <openssl/err.h>
Packit c22fc9
Packit c22fc9
#include "check_ssl.h"
Packit c22fc9
#include "check_api.h"
Packit c22fc9
#include "check_http.h"
Packit c22fc9
#include "logger.h"
Packit c22fc9
#ifdef THREAD_DUMP
Packit c22fc9
#include "scheduler.h"
Packit c22fc9
#endif
Packit c22fc9
Packit c22fc9
/* SSL primitives */
Packit c22fc9
/* Free an SSL context */
Packit c22fc9
void
Packit c22fc9
clear_ssl(ssl_data_t *ssl)
Packit c22fc9
{
Packit c22fc9
	if (ssl && ssl->ctx) {
Packit c22fc9
		SSL_CTX_free(ssl->ctx);
Packit c22fc9
		ssl->ctx = NULL;
Packit c22fc9
	}
Packit c22fc9
}
Packit c22fc9
Packit c22fc9
/* PEM password callback function */
Packit c22fc9
static int
Packit c22fc9
password_cb(char *buf, int num, __attribute__((unused)) int rwflag, void *userdata)
Packit c22fc9
{
Packit c22fc9
	ssl_data_t *ssl = (ssl_data_t *) userdata;
Packit c22fc9
	size_t plen = strlen(ssl->password);
Packit c22fc9
Packit c22fc9
	if ((unsigned)num < plen + 1)
Packit c22fc9
		return (0);
Packit c22fc9
Packit c22fc9
	strcpy(buf, ssl->password);
Packit c22fc9
	return (int)plen;
Packit c22fc9
}
Packit c22fc9
Packit c22fc9
/* Inititalize global SSL context */
Packit c22fc9
static bool
Packit c22fc9
build_ssl_ctx(void)
Packit c22fc9
{
Packit c22fc9
	ssl_data_t *ssl;
Packit c22fc9
Packit c22fc9
	/* Library initialization */
Packit Service 8fc997
#if HAVE_OPENSSL_INIT_CRYPTO
Packit Service a07680
#ifndef HAVE_OPENSSL_INIT_NO_LOAD_CONFIG_BUG
Packit Service a07680
	/* In OpenSSL v1.1.1 if the following is called, SSL_CTX_new() below fails.
Packit Service a07680
	 * It works in v1.1.0h and v1.1.1b.
Packit Service a07680
	 * It transpires that it works without setting NO_LOAD_CONFIG, but it is
Packit Service a07680
	 * presumably more efficient not to load it. */
Packit c22fc9
	if (!OPENSSL_init_crypto(OPENSSL_INIT_NO_LOAD_CONFIG, NULL))
Packit c22fc9
		log_message(LOG_INFO, "OPENSSL_init_crypto failed");
Packit Service a07680
#endif
Packit c22fc9
#else
Packit c22fc9
	SSL_library_init();
Packit c22fc9
	SSL_load_error_strings();
Packit c22fc9
#endif
Packit c22fc9
Packit c22fc9
	if (!check_data->ssl)
Packit c22fc9
		ssl = (ssl_data_t *) MALLOC(sizeof(ssl_data_t));
Packit c22fc9
	else
Packit c22fc9
		ssl = check_data->ssl;
Packit c22fc9
Packit c22fc9
	/* Initialize SSL context */
Packit Service 8fc997
#if HAVE_TLS_METHOD
Packit c22fc9
	ssl->meth = TLS_method();
Packit c22fc9
#else
Packit c22fc9
	ssl->meth = SSLv23_method();
Packit c22fc9
#endif
Packit c22fc9
	if (!(ssl->ctx = SSL_CTX_new(ssl->meth))) {
Packit c22fc9
		log_message(LOG_INFO, "SSL error: cannot create new SSL context");
Packit c22fc9
		return false;
Packit c22fc9
	}
Packit c22fc9
Packit c22fc9
	/* return for autogen context */
Packit c22fc9
	if (!check_data->ssl) {
Packit c22fc9
		check_data->ssl = ssl;
Packit c22fc9
		goto end;
Packit c22fc9
	}
Packit c22fc9
Packit c22fc9
	/* Load our keys and certificates */
Packit c22fc9
	if (check_data->ssl->certfile)
Packit c22fc9
		if (!
Packit c22fc9
		    (SSL_CTX_use_certificate_chain_file
Packit c22fc9
		     (ssl->ctx, check_data->ssl->certfile))) {
Packit c22fc9
			log_message(LOG_INFO,
Packit c22fc9
			       "SSL error : Cant load certificate file...");
Packit c22fc9
			return false;
Packit c22fc9
		}
Packit c22fc9
Packit c22fc9
	/* Handle password callback using userdata ssl */
Packit c22fc9
	if (check_data->ssl->password) {
Packit c22fc9
		SSL_CTX_set_default_passwd_cb_userdata(ssl->ctx,
Packit c22fc9
						       check_data->ssl);
Packit c22fc9
		SSL_CTX_set_default_passwd_cb(ssl->ctx, password_cb);
Packit c22fc9
	}
Packit c22fc9
Packit c22fc9
	if (check_data->ssl->keyfile)
Packit c22fc9
		if (!
Packit c22fc9
		    (SSL_CTX_use_PrivateKey_file
Packit c22fc9
		     (ssl->ctx, check_data->ssl->keyfile, SSL_FILETYPE_PEM))) {
Packit c22fc9
			log_message(LOG_INFO, "SSL error : Cant load key file...");
Packit c22fc9
			return false;
Packit c22fc9
		}
Packit c22fc9
Packit c22fc9
	/* Load the CAs we trust */
Packit c22fc9
	if (check_data->ssl->cafile)
Packit c22fc9
		if (!
Packit c22fc9
		    (SSL_CTX_load_verify_locations
Packit c22fc9
		     (ssl->ctx, check_data->ssl->cafile, 0))) {
Packit c22fc9
			log_message(LOG_INFO, "SSL error : Cant load CA file...");
Packit c22fc9
			return false;
Packit c22fc9
		}
Packit c22fc9
Packit c22fc9
      end:
Packit c22fc9
#if HAVE_SSL_CTX_SET_VERIFY_DEPTH
Packit c22fc9
	SSL_CTX_set_verify_depth(ssl->ctx, 1);
Packit c22fc9
#endif
Packit c22fc9
Packit c22fc9
	return true;
Packit c22fc9
}
Packit c22fc9
Packit c22fc9
/*
Packit c22fc9
 * Initialize the SSL context, with or without specific
Packit c22fc9
 * configuration files.
Packit c22fc9
 */
Packit c22fc9
bool
Packit c22fc9
init_ssl_ctx(void)
Packit c22fc9
{
Packit c22fc9
	ssl_data_t *ssl = check_data->ssl;
Packit c22fc9
Packit c22fc9
	if (!build_ssl_ctx()) {
Packit c22fc9
		log_message(LOG_INFO, "Error Initialize SSL, ctx Instance");
Packit c22fc9
		log_message(LOG_INFO, "  SSL  keyfile:%s", ssl->keyfile);
Packit c22fc9
		log_message(LOG_INFO, "  SSL password:%s", ssl->password);
Packit c22fc9
		log_message(LOG_INFO, "  SSL   cafile:%s", ssl->cafile);
Packit c22fc9
		log_message(LOG_INFO, "Terminate...");
Packit c22fc9
		clear_ssl(ssl);
Packit c22fc9
		return false;
Packit c22fc9
	}
Packit c22fc9
	return true;
Packit c22fc9
}
Packit c22fc9
Packit c22fc9
/* Display SSL error to readable string */
Packit c22fc9
int
Packit c22fc9
ssl_printerr(int err)
Packit c22fc9
{
Packit c22fc9
	switch (err) {
Packit c22fc9
	case SSL_ERROR_ZERO_RETURN:
Packit c22fc9
		log_message(LOG_INFO, "  SSL error: (zero return)");
Packit c22fc9
		break;
Packit c22fc9
	case SSL_ERROR_WANT_READ:
Packit c22fc9
		log_message(LOG_INFO, "  SSL error: (read error)");
Packit c22fc9
		break;
Packit c22fc9
	case SSL_ERROR_WANT_WRITE:
Packit c22fc9
		log_message(LOG_INFO, "  SSL error: (write error)");
Packit c22fc9
		break;
Packit c22fc9
	case SSL_ERROR_WANT_CONNECT:
Packit c22fc9
		log_message(LOG_INFO, "  SSL error: (connect error)");
Packit c22fc9
		break;
Packit c22fc9
	case SSL_ERROR_WANT_X509_LOOKUP:
Packit c22fc9
		log_message(LOG_INFO, "  SSL error: (X509 lookup error)");
Packit c22fc9
		break;
Packit c22fc9
	case SSL_ERROR_SYSCALL:
Packit c22fc9
		log_message(LOG_INFO, "  SSL error: (syscall error)");
Packit c22fc9
		break;
Packit c22fc9
	case SSL_ERROR_SSL:
Packit c22fc9
		/* Note: the following is not thread safe. Use MALLOC(256) and ERR_error_string_n if need thread safety */
Packit c22fc9
		log_message(LOG_INFO, "  SSL error: (%s)", ERR_error_string(ERR_get_error(), NULL));
Packit c22fc9
		break;
Packit c22fc9
	}
Packit c22fc9
	return 0;
Packit c22fc9
}
Packit c22fc9
Packit c22fc9
int
Packit c22fc9
ssl_connect(thread_t * thread, int new_req)
Packit c22fc9
{
Packit c22fc9
	checker_t *checker = THREAD_ARG(thread);
Packit c22fc9
	http_checker_t *http_get_check = CHECKER_ARG(checker);
Packit c22fc9
	request_t *req = http_get_check->req;
Packit c22fc9
#ifdef _HAVE_SSL_SET_TLSEXT_HOST_NAME_
Packit c22fc9
	url_t *url = list_element(http_get_check->url, http_get_check->url_it);
Packit c22fc9
	char* vhost = NULL;
Packit c22fc9
#endif
Packit c22fc9
	int ret = 0;
Packit c22fc9
Packit c22fc9
	/* First round, create SSL context */
Packit c22fc9
	if (new_req) {
Packit c22fc9
		int bio_fd;
Packit c22fc9
Packit c22fc9
		if (!(req->ssl = SSL_new(check_data->ssl->ctx))) {
Packit c22fc9
			log_message(LOG_INFO, "Unable to establish ssl connection - SSL_new() failed");
Packit c22fc9
			return 0;
Packit c22fc9
		}
Packit c22fc9
Packit c22fc9
		if (!(req->bio = BIO_new_socket(thread->u.fd, BIO_NOCLOSE))) {
Packit c22fc9
			log_message(LOG_INFO, "Unable to establish ssl connection - BIO_new_socket() failed");
Packit c22fc9
			return 0;
Packit c22fc9
		}
Packit c22fc9
Packit c22fc9
		BIO_get_fd(req->bio, &bio_fd);
Packit c22fc9
		fcntl(bio_fd, F_SETFD, fcntl(bio_fd, F_GETFD) | FD_CLOEXEC);
Packit Service 8fc997
#if HAVE_SSL_SET0_RBIO
Packit c22fc9
		BIO_up_ref(req->bio);
Packit c22fc9
		SSL_set0_rbio(req->ssl, req->bio);
Packit c22fc9
		SSL_set0_wbio(req->ssl, req->bio);
Packit c22fc9
#else
Packit c22fc9
		SSL_set_bio(req->ssl, req->bio, req->bio);
Packit c22fc9
#endif
Packit c22fc9
#ifdef _HAVE_SSL_SET_TLSEXT_HOST_NAME_
Packit c22fc9
		if (http_get_check->enable_sni) {
Packit c22fc9
			if (url && url->virtualhost)
Packit c22fc9
				vhost = url->virtualhost;
Packit c22fc9
			else if (http_get_check->virtualhost)
Packit c22fc9
				vhost = http_get_check->virtualhost;
Packit c22fc9
			else if (checker->vs->virtualhost)
Packit c22fc9
				vhost = checker->vs->virtualhost;
Packit c22fc9
			if (vhost)
Packit c22fc9
				SSL_set_tlsext_host_name(req->ssl, vhost);
Packit c22fc9
		}
Packit c22fc9
#endif
Packit c22fc9
	}
Packit c22fc9
Packit c22fc9
	ret = SSL_connect(req->ssl);
Packit c22fc9
Packit c22fc9
	return ret;
Packit c22fc9
}
Packit c22fc9
Packit c22fc9
bool
Packit c22fc9
ssl_send_request(SSL * ssl, char *str_request, int request_len)
Packit c22fc9
{
Packit c22fc9
	int err, r = 0;
Packit c22fc9
Packit c22fc9
	while (true) {
Packit c22fc9
		err = 1;
Packit c22fc9
		r = SSL_write(ssl, str_request, request_len);
Packit c22fc9
		if (SSL_ERROR_NONE != SSL_get_error(ssl, r))
Packit c22fc9
			break;
Packit c22fc9
		err++;
Packit c22fc9
		if (request_len != r)
Packit c22fc9
			break;
Packit c22fc9
		err++;
Packit c22fc9
		break;
Packit c22fc9
	}
Packit c22fc9
Packit c22fc9
	return (err == 3);
Packit c22fc9
}
Packit c22fc9
Packit c22fc9
/* Asynchronous SSL stream reader */
Packit c22fc9
int
Packit c22fc9
ssl_read_thread(thread_t * thread)
Packit c22fc9
{
Packit c22fc9
	checker_t *checker = THREAD_ARG(thread);
Packit c22fc9
	http_checker_t *http_get_check = CHECKER_ARG(checker);
Packit c22fc9
	request_t *req = http_get_check->req;
Packit c22fc9
	url_t *url = list_element(http_get_check->url, http_get_check->url_it);
Packit c22fc9
	unsigned timeout = checker->co->connection_to;
Packit c22fc9
	unsigned char digest[MD5_DIGEST_LENGTH];
Packit c22fc9
	int r = 0;
Packit c22fc9
Packit c22fc9
	/* Handle read timeout */
Packit c22fc9
	if (thread->type == THREAD_READ_TIMEOUT && !req->extracted)
Packit c22fc9
		return timeout_epilog(thread, "Timeout SSL read");
Packit c22fc9
Packit c22fc9
	/* read the SSL stream */
Packit c22fc9
	r = SSL_read(req->ssl, req->buffer + req->len, (int)(MAX_BUFFER_LENGTH - req->len));
Packit c22fc9
Packit c22fc9
	req->error = SSL_get_error(req->ssl, r);
Packit c22fc9
Packit c22fc9
	if (req->error == SSL_ERROR_WANT_READ) {
Packit c22fc9
		 /* async read unfinished */
Packit c22fc9
		thread_add_read(thread->master, ssl_read_thread, checker,
Packit c22fc9
				thread->u.fd, timeout);
Packit c22fc9
	} else if (r > 0 && req->error == 0) {
Packit c22fc9
		/* Handle response stream */
Packit c22fc9
		http_process_response(req, (size_t)r, url);
Packit c22fc9
Packit c22fc9
		/*
Packit c22fc9
		 * Register next ssl stream reader.
Packit c22fc9
		 * Register itself to not perturbe global I/O multiplexer.
Packit c22fc9
		 */
Packit c22fc9
		thread_add_read(thread->master, ssl_read_thread, checker,
Packit c22fc9
				thread->u.fd, timeout);
Packit c22fc9
	} else if (req->error) {
Packit c22fc9
Packit c22fc9
		/* All the SSL streal has been parsed */
Packit c22fc9
		if (url->digest)
Packit c22fc9
			MD5_Final(digest, &req->context);
Packit c22fc9
		SSL_set_quiet_shutdown(req->ssl, 1);
Packit c22fc9
Packit c22fc9
		r = (req->error == SSL_ERROR_ZERO_RETURN) ? SSL_shutdown(req->ssl) : 0;
Packit c22fc9
Packit c22fc9
		if (r && !req->extracted) {
Packit c22fc9
			return timeout_epilog(thread, "SSL read error from");
Packit c22fc9
		}
Packit c22fc9
Packit c22fc9
		/* Handle response stream */
Packit c22fc9
		http_handle_response(thread, digest, !req->extracted);
Packit c22fc9
Packit c22fc9
	}
Packit c22fc9
Packit c22fc9
	return 0;
Packit c22fc9
}
Packit c22fc9
Packit c22fc9
#ifdef THREAD_DUMP
Packit c22fc9
void
Packit c22fc9
register_check_ssl_addresses(void)
Packit c22fc9
{
Packit c22fc9
	register_thread_address("ssl_read_thread", ssl_read_thread);
Packit c22fc9
}
Packit c22fc9
#endif