319 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			319 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*
 | 
						|
 * Copyright (c) 2016 MariaDB Corporation Ab
 | 
						|
 *
 | 
						|
 * Use of this software is governed by the Business Source License included
 | 
						|
 * in the LICENSE.TXT file and at www.mariadb.com/bsl11.
 | 
						|
 *
 | 
						|
 * Change Date: 2022-01-01
 | 
						|
 *
 | 
						|
 * On the date above, in accordance with the Business Source License, use
 | 
						|
 * of this software will be governed by version 2 or later of the General
 | 
						|
 * Public License.
 | 
						|
 */
 | 
						|
 | 
						|
/**
 | 
						|
 * @file ssl.c  -  SSL generic functions
 | 
						|
 *
 | 
						|
 * SSL is intended to be available in conjunction with a variety of protocols
 | 
						|
 * on either the client or server side.
 | 
						|
 *
 | 
						|
 * @verbatim
 | 
						|
 * Revision History
 | 
						|
 *
 | 
						|
 * Date         Who                     Description
 | 
						|
 * 02/02/16     Martin Brampton         Initial implementation
 | 
						|
 *
 | 
						|
 * @endverbatim
 | 
						|
 */
 | 
						|
#include <stdio.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <string.h>
 | 
						|
#include <stdbool.h>
 | 
						|
#include <sys/ioctl.h>
 | 
						|
#include <maxscale/dcb.hh>
 | 
						|
#include <maxscale/log.h>
 | 
						|
#include <maxscale/poll.hh>
 | 
						|
#include <maxscale/service.hh>
 | 
						|
 | 
						|
/**
 | 
						|
 * @brief Check client's SSL capability and start SSL if appropriate.
 | 
						|
 *
 | 
						|
 * The protocol should determine whether the client is SSL capable and pass
 | 
						|
 * the result as the second parameter. If the listener requires SSL but the
 | 
						|
 * client is not SSL capable, an error message is recorded and failure return
 | 
						|
 * given. If both sides want SSL, and SSL is not already established, the
 | 
						|
 * process is triggered by calling dcb_accept_SSL.
 | 
						|
 *
 | 
						|
 * @param dcb Request handler DCB connected to the client
 | 
						|
 * @param is_capable Indicates if the client can handle SSL
 | 
						|
 * @return 0 if ok, >0 if a problem - see return codes defined in ssl.h
 | 
						|
 */
 | 
						|
int ssl_authenticate_client(DCB* dcb, bool is_capable)
 | 
						|
{
 | 
						|
    const char* user = dcb->user ? dcb->user : "";
 | 
						|
    const char* remote = dcb->remote ? dcb->remote : "";
 | 
						|
    const char* service = (dcb->service && dcb->service->name) ? dcb->service->name : "";
 | 
						|
 | 
						|
    if (NULL == dcb->session->listener || NULL == dcb->session->listener->ssl())
 | 
						|
    {
 | 
						|
        /* Not an SSL connection on account of listener configuration */
 | 
						|
        return SSL_AUTH_CHECKS_OK;
 | 
						|
    }
 | 
						|
    /* Now we require an SSL connection */
 | 
						|
    if (!is_capable)
 | 
						|
    {
 | 
						|
        /* Should be SSL, but client is not SSL capable */
 | 
						|
        MXS_INFO("User %s@%s connected to service '%s' without SSL when SSL was required.",
 | 
						|
                 user,
 | 
						|
                 remote,
 | 
						|
                 service);
 | 
						|
        return SSL_ERROR_CLIENT_NOT_SSL;
 | 
						|
    }
 | 
						|
    /* Now we know SSL is required and client is capable */
 | 
						|
    if (dcb->ssl_state != SSL_HANDSHAKE_DONE && dcb->ssl_state != SSL_ESTABLISHED)
 | 
						|
    {
 | 
						|
        int return_code;
 | 
						|
        /** Do the SSL Handshake */
 | 
						|
        if (SSL_HANDSHAKE_UNKNOWN == dcb->ssl_state)
 | 
						|
        {
 | 
						|
            dcb->ssl_state = SSL_HANDSHAKE_REQUIRED;
 | 
						|
        }
 | 
						|
        /**
 | 
						|
         * Note that this will often fail to achieve its result, because further
 | 
						|
         * reading (or possibly writing) of SSL related information is needed.
 | 
						|
         * When that happens, there is a call in poll.c so that an EPOLLIN
 | 
						|
         * event that arrives while the SSL state is SSL_HANDSHAKE_REQUIRED
 | 
						|
         * will trigger dcb_accept_SSL. This situation does not result in a
 | 
						|
         * negative return code - that indicates a real failure.
 | 
						|
         */
 | 
						|
        return_code = dcb_accept_SSL(dcb);
 | 
						|
        if (return_code < 0)
 | 
						|
        {
 | 
						|
            MXS_INFO("User %s@%s failed to connect to service '%s' with SSL.",
 | 
						|
                     user,
 | 
						|
                     remote,
 | 
						|
                     service);
 | 
						|
            return SSL_ERROR_ACCEPT_FAILED;
 | 
						|
        }
 | 
						|
        else if (mxs_log_is_priority_enabled(LOG_INFO))
 | 
						|
        {
 | 
						|
            if (1 == return_code)
 | 
						|
            {
 | 
						|
                MXS_INFO("User %s@%s connected to service '%s' with SSL.",
 | 
						|
                         user,
 | 
						|
                         remote,
 | 
						|
                         service);
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                MXS_INFO("User %s@%s connect to service '%s' with SSL in progress.",
 | 
						|
                         user,
 | 
						|
                         remote,
 | 
						|
                         service);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return SSL_AUTH_CHECKS_OK;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * @brief If an SSL connection is required, check that it has been established.
 | 
						|
 *
 | 
						|
 * This is called at the end of the authentication of a new connection.
 | 
						|
 * If the result is not true, the data packet is abandoned with further
 | 
						|
 * data expected from the client.
 | 
						|
 *
 | 
						|
 * @param dcb Request handler DCB connected to the client
 | 
						|
 * @return Boolean to indicate whether connection is healthy
 | 
						|
 */
 | 
						|
bool ssl_is_connection_healthy(DCB* dcb)
 | 
						|
{
 | 
						|
    /**
 | 
						|
     * If SSL was never expected, or if the connection has state SSL_ESTABLISHED
 | 
						|
     * then everything is as we wish. Otherwise, either there is a problem or
 | 
						|
     * more to be done.
 | 
						|
     */
 | 
						|
    return NULL == dcb->session->listener
 | 
						|
           || NULL == dcb->session->listener->ssl()
 | 
						|
           || dcb->ssl_state == SSL_ESTABLISHED;
 | 
						|
}
 | 
						|
 | 
						|
/* Looks to be redundant - can remove include for ioctl too */
 | 
						|
bool ssl_check_data_to_process(DCB* dcb)
 | 
						|
{
 | 
						|
    /** SSL authentication is still going on, we need to call dcb_accept_SSL
 | 
						|
     * until it return 1 for success or -1 for error */
 | 
						|
    if (dcb->ssl_state == SSL_HANDSHAKE_REQUIRED && 1 == dcb_accept_SSL(dcb))
 | 
						|
    {
 | 
						|
        int b = 0;
 | 
						|
        ioctl(dcb->fd, FIONREAD, &b);
 | 
						|
        if (b != 0)
 | 
						|
        {
 | 
						|
            return true;
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            MXS_DEBUG("[gw_read_client_event] No data in socket after SSL auth");
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return false;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * @brief Check whether a DCB requires SSL.
 | 
						|
 *
 | 
						|
 * This is a very simple test, but is placed in an SSL function so that
 | 
						|
 * the knowledge of the SSL process is removed from the more general
 | 
						|
 * handling of a connection in the protocols.
 | 
						|
 *
 | 
						|
 * @param dcb Request handler DCB connected to the client
 | 
						|
 * @return Boolean indicating whether SSL is required.
 | 
						|
 */
 | 
						|
bool ssl_required_by_dcb(DCB* dcb)
 | 
						|
{
 | 
						|
    return NULL != dcb->session->listener && NULL != dcb->session->listener->ssl();
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * @brief Check whether a DCB requires SSL, but SSL is not yet negotiated.
 | 
						|
 *
 | 
						|
 * This is a very simple test, but is placed in an SSL function so that
 | 
						|
 * the knowledge of the SSL process is removed from the more general
 | 
						|
 * handling of a connection in the protocols.
 | 
						|
 *
 | 
						|
 * @param dcb Request handler DCB connected to the client
 | 
						|
 * @return Boolean indicating whether SSL is required and not negotiated.
 | 
						|
 */
 | 
						|
bool ssl_required_but_not_negotiated(DCB* dcb)
 | 
						|
{
 | 
						|
    return NULL != dcb->session->listener
 | 
						|
           && NULL != dcb->session->listener->ssl()
 | 
						|
           && SSL_HANDSHAKE_UNKNOWN == dcb->ssl_state;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Returns an enum ssl_method_type value as string.
 | 
						|
 *
 | 
						|
 * @param method A method type.
 | 
						|
 * @return The method type expressed as a string.
 | 
						|
 */
 | 
						|
const char* ssl_method_type_to_string(ssl_method_type_t method_type)
 | 
						|
{
 | 
						|
    switch (method_type)
 | 
						|
    {
 | 
						|
#ifndef OPENSSL_1_1
 | 
						|
    case SERVICE_TLS10:
 | 
						|
        return "TLSV10";
 | 
						|
 | 
						|
#endif
 | 
						|
#ifdef OPENSSL_1_0
 | 
						|
    case SERVICE_TLS11:
 | 
						|
        return "TLSV11";
 | 
						|
 | 
						|
    case SERVICE_TLS12:
 | 
						|
        return "TLSV12";
 | 
						|
 | 
						|
#endif
 | 
						|
    case SERVICE_SSL_MAX:
 | 
						|
    case SERVICE_TLS_MAX:
 | 
						|
    case SERVICE_SSL_TLS_MAX:
 | 
						|
        return "MAX";
 | 
						|
 | 
						|
    default:
 | 
						|
        return "Unknown";
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
ssl_method_type_t string_to_ssl_method_type(const char* str)
 | 
						|
{
 | 
						|
    if (strcasecmp("MAX", str) == 0)
 | 
						|
    {
 | 
						|
        return SERVICE_SSL_TLS_MAX;
 | 
						|
    }
 | 
						|
 | 
						|
#ifndef OPENSSL_1_1
 | 
						|
    else if (strcasecmp("TLSV10", str) == 0)
 | 
						|
    {
 | 
						|
        return SERVICE_TLS10;
 | 
						|
    }
 | 
						|
#endif
 | 
						|
#ifdef OPENSSL_1_0
 | 
						|
    else if (strcasecmp("TLSV11", str) == 0)
 | 
						|
    {
 | 
						|
        return SERVICE_TLS11;
 | 
						|
    }
 | 
						|
    else if (strcasecmp("TLSV12", str) == 0)
 | 
						|
    {
 | 
						|
        return SERVICE_TLS12;
 | 
						|
    }
 | 
						|
#endif
 | 
						|
 | 
						|
    return SERVICE_SSL_UNKNOWN;
 | 
						|
}
 | 
						|
 | 
						|
void write_ssl_config(int fd, SSL_LISTENER* ssl)
 | 
						|
{
 | 
						|
    if (ssl)
 | 
						|
    {
 | 
						|
        dprintf(fd, "ssl=required\n");
 | 
						|
 | 
						|
        if (ssl->ssl_cert)
 | 
						|
        {
 | 
						|
            dprintf(fd, "ssl_cert=%s\n", ssl->ssl_cert);
 | 
						|
        }
 | 
						|
 | 
						|
        if (ssl->ssl_key)
 | 
						|
        {
 | 
						|
            dprintf(fd, "ssl_key=%s\n", ssl->ssl_key);
 | 
						|
        }
 | 
						|
 | 
						|
        if (ssl->ssl_ca_cert)
 | 
						|
        {
 | 
						|
            dprintf(fd, "ssl_ca_cert=%s\n", ssl->ssl_ca_cert);
 | 
						|
        }
 | 
						|
        if (ssl->ssl_cert_verify_depth)
 | 
						|
        {
 | 
						|
            dprintf(fd, "ssl_cert_verify_depth=%d\n", ssl->ssl_cert_verify_depth);
 | 
						|
        }
 | 
						|
 | 
						|
        dprintf(fd,
 | 
						|
                "ssl_verify_peer_certificate=%s\n",
 | 
						|
                ssl->ssl_verify_peer_certificate ? "true" : "false");
 | 
						|
 | 
						|
        const char* version = ssl_method_type_to_string(ssl->ssl_method_type);
 | 
						|
        dprintf(fd, "ssl_version=%s\n", version);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
int ssl_authenticate_check_status(DCB* dcb)
 | 
						|
{
 | 
						|
    int rval = MXS_AUTH_FAILED;
 | 
						|
    /**
 | 
						|
     * We record the SSL status before and after ssl authentication. This allows
 | 
						|
     * us to detect if the SSL handshake is immediately completed, which means more
 | 
						|
     * data needs to be read from the socket.
 | 
						|
     */
 | 
						|
    bool health_before = ssl_is_connection_healthy(dcb);
 | 
						|
    int ssl_ret = ssl_authenticate_client(dcb, dcb->authfunc.connectssl(dcb));
 | 
						|
    bool health_after = ssl_is_connection_healthy(dcb);
 | 
						|
 | 
						|
    if (ssl_ret != 0)
 | 
						|
    {
 | 
						|
        rval = (ssl_ret == SSL_ERROR_CLIENT_NOT_SSL) ? MXS_AUTH_FAILED_SSL : MXS_AUTH_FAILED;
 | 
						|
    }
 | 
						|
    else if (!health_after)
 | 
						|
    {
 | 
						|
        rval = MXS_AUTH_SSL_INCOMPLETE;
 | 
						|
    }
 | 
						|
    else if (!health_before && health_after)
 | 
						|
    {
 | 
						|
        rval = MXS_AUTH_SSL_INCOMPLETE;
 | 
						|
        poll_add_epollin_event_to_dcb(dcb, NULL);
 | 
						|
    }
 | 
						|
    else if (health_before && health_after)
 | 
						|
    {
 | 
						|
        rval = MXS_AUTH_SSL_COMPLETE;
 | 
						|
    }
 | 
						|
    return rval;
 | 
						|
}
 |