
The functionality is more a part of the provider than the context so it should be defined in it. It also doesn't use any parts of the SSLContext which makes it somewhat more clear that it doesn't belong there.
404 lines
9.7 KiB
C++
404 lines
9.7 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/poll.hh>
|
|
#include <maxscale/service.hh>
|
|
#include <maxscale/routingworker.hh>
|
|
|
|
static RSA* rsa_512 = NULL;
|
|
static RSA* rsa_1024 = NULL;
|
|
|
|
const MXS_ENUM_VALUE ssl_version_values[] =
|
|
{
|
|
{"MAX", 1},
|
|
#ifndef OPENSSL_1_1
|
|
{"TLSv10", 1},
|
|
#endif
|
|
#ifdef OPENSSL_1_0
|
|
{"TLSv11", 1},
|
|
{"TLSv12", 1},
|
|
#endif
|
|
{NULL}
|
|
};
|
|
|
|
static RSA* create_rsa(int bits)
|
|
{
|
|
#ifdef OPENSSL_1_1
|
|
BIGNUM* bn = BN_new();
|
|
BN_set_word(bn, RSA_F4);
|
|
RSA* rsa = RSA_new();
|
|
RSA_generate_key_ex(rsa, bits, bn, NULL);
|
|
BN_free(bn);
|
|
return rsa;
|
|
#else
|
|
return RSA_generate_key(bits, RSA_F4, NULL, NULL);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* The RSA key generation callback function for OpenSSL.
|
|
* @param s SSL structure
|
|
* @param is_export Not used
|
|
* @param keylength Length of the key
|
|
* @return Pointer to RSA structure
|
|
*/
|
|
static RSA* tmp_rsa_callback(SSL* s, int is_export, int keylength)
|
|
{
|
|
RSA* rsa_tmp = NULL;
|
|
|
|
switch (keylength)
|
|
{
|
|
case 512:
|
|
if (rsa_512)
|
|
{
|
|
rsa_tmp = rsa_512;
|
|
}
|
|
else
|
|
{
|
|
/* generate on the fly, should not happen in this example */
|
|
rsa_tmp = create_rsa(keylength);
|
|
rsa_512 = rsa_tmp; /* Remember for later reuse */
|
|
}
|
|
break;
|
|
|
|
case 1024:
|
|
if (rsa_1024)
|
|
{
|
|
rsa_tmp = rsa_1024;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
/* Generating a key on the fly is very costly, so use what is there */
|
|
if (rsa_1024)
|
|
{
|
|
rsa_tmp = rsa_1024;
|
|
}
|
|
else
|
|
{
|
|
rsa_tmp = rsa_512; /* Use at least a shorter key */
|
|
}
|
|
}
|
|
return rsa_tmp;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
// thread-local non-POD types are not supported with older versions of GCC
|
|
static thread_local std::string* ssl_errbuf;
|
|
|
|
static const char* get_ssl_errors()
|
|
{
|
|
if (ssl_errbuf == NULL)
|
|
{
|
|
ssl_errbuf = new std::string;
|
|
}
|
|
|
|
char errbuf[200]; // Enough space according to OpenSSL documentation
|
|
ssl_errbuf->clear();
|
|
|
|
for (int err = ERR_get_error(); err; err = ERR_get_error())
|
|
{
|
|
if (!ssl_errbuf->empty())
|
|
{
|
|
ssl_errbuf->append(", ");
|
|
}
|
|
ssl_errbuf->append(ERR_error_string(err, errbuf));
|
|
}
|
|
|
|
return ssl_errbuf->c_str();
|
|
}
|
|
|
|
namespace maxscale
|
|
{
|
|
|
|
SSLConfig::SSLConfig(const MXS_CONFIG_PARAMETER& params)
|
|
: key(params.get_string(CN_SSL_KEY))
|
|
, cert(params.get_string(CN_SSL_CERT))
|
|
, ca(params.get_string(CN_SSL_CA_CERT))
|
|
, version((ssl_method_type_t)params.get_enum(CN_SSL_VERSION, ssl_version_values))
|
|
, verify_depth(params.get_integer(CN_SSL_CERT_VERIFY_DEPTH))
|
|
, verify_peer(params.get_bool(CN_SSL_VERIFY_PEER_CERTIFICATE))
|
|
{
|
|
}
|
|
|
|
// static
|
|
std::unique_ptr<SSLContext> SSLContext::create(const MXS_CONFIG_PARAMETER& params)
|
|
{
|
|
mxb_assert(access(params.get_string(CN_SSL_CA_CERT).c_str(), F_OK) == 0);
|
|
mxb_assert(params.get_string(CN_SSL_CERT).empty()
|
|
|| access(params.get_string(CN_SSL_CERT).c_str(), F_OK) == 0);
|
|
mxb_assert(params.get_string(CN_SSL_KEY).empty()
|
|
|| access(params.get_string(CN_SSL_KEY).c_str(), F_OK) == 0);
|
|
|
|
std::unique_ptr<SSLContext> ssl(new(std::nothrow) SSLContext(SSLConfig(params)));
|
|
|
|
if (ssl && !ssl->init())
|
|
{
|
|
ssl.reset();
|
|
}
|
|
|
|
return ssl;
|
|
}
|
|
|
|
SSLContext::SSLContext(const SSLConfig& cfg)
|
|
: m_cfg(cfg)
|
|
{
|
|
}
|
|
|
|
bool SSLContext::init()
|
|
{
|
|
bool rval = true;
|
|
|
|
switch (m_cfg.version)
|
|
{
|
|
#ifndef OPENSSL_1_1
|
|
case SERVICE_TLS10:
|
|
m_method = (SSL_METHOD*)TLSv1_method();
|
|
break;
|
|
|
|
#endif
|
|
#ifdef OPENSSL_1_0
|
|
case SERVICE_TLS11:
|
|
m_method = (SSL_METHOD*)TLSv1_1_method();
|
|
break;
|
|
|
|
case SERVICE_TLS12:
|
|
m_method = (SSL_METHOD*)TLSv1_2_method();
|
|
break;
|
|
|
|
#endif
|
|
/** Rest of these use the maximum available SSL/TLS methods */
|
|
case SERVICE_SSL_MAX:
|
|
m_method = (SSL_METHOD*)SSLv23_method();
|
|
break;
|
|
|
|
case SERVICE_TLS_MAX:
|
|
m_method = (SSL_METHOD*)SSLv23_method();
|
|
break;
|
|
|
|
case SERVICE_SSL_TLS_MAX:
|
|
m_method = (SSL_METHOD*)SSLv23_method();
|
|
break;
|
|
|
|
default:
|
|
m_method = (SSL_METHOD*)SSLv23_method();
|
|
break;
|
|
}
|
|
|
|
m_ctx = SSL_CTX_new(m_method);
|
|
|
|
if (m_ctx == NULL)
|
|
{
|
|
MXS_ERROR("SSL context initialization failed: %s", get_ssl_errors());
|
|
return false;
|
|
}
|
|
|
|
SSL_CTX_set_default_read_ahead(m_ctx, 0);
|
|
|
|
/** Enable all OpenSSL bug fixes */
|
|
SSL_CTX_set_options(m_ctx, SSL_OP_ALL);
|
|
|
|
/** Disable SSLv3 */
|
|
SSL_CTX_set_options(m_ctx, SSL_OP_NO_SSLv3);
|
|
|
|
// Disable session cache
|
|
SSL_CTX_set_session_cache_mode(m_ctx, SSL_SESS_CACHE_OFF);
|
|
|
|
//
|
|
// Note: This is not safe if SSL initialization is done concurrently
|
|
//
|
|
/** Generate the 512-bit and 1024-bit RSA keys */
|
|
if (rsa_512 == NULL && (rsa_512 = create_rsa(512)) == NULL)
|
|
{
|
|
MXS_ERROR("512-bit RSA key generation failed.");
|
|
return false;
|
|
}
|
|
else if (rsa_1024 == NULL && (rsa_1024 = create_rsa(1024)) == NULL)
|
|
{
|
|
MXS_ERROR("1024-bit RSA key generation failed.");
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
mxb_assert(rsa_512 && rsa_1024);
|
|
SSL_CTX_set_tmp_rsa_callback(m_ctx, tmp_rsa_callback);
|
|
}
|
|
|
|
mxb_assert(!m_cfg.ca.empty());
|
|
|
|
/* Load the CA certificate into the SSL_CTX structure */
|
|
if (!SSL_CTX_load_verify_locations(m_ctx, m_cfg.ca.c_str(), NULL))
|
|
{
|
|
MXS_ERROR("Failed to set Certificate Authority file");
|
|
return false;
|
|
}
|
|
|
|
if (!m_cfg.cert.empty() && !m_cfg.key.empty())
|
|
{
|
|
/** Load the server certificate */
|
|
if (SSL_CTX_use_certificate_chain_file(m_ctx, m_cfg.cert.c_str()) <= 0)
|
|
{
|
|
MXS_ERROR("Failed to set server SSL certificate: %s", get_ssl_errors());
|
|
return false;
|
|
}
|
|
|
|
/* Load the private-key corresponding to the server certificate */
|
|
if (SSL_CTX_use_PrivateKey_file(m_ctx, m_cfg.key.c_str(), SSL_FILETYPE_PEM) <= 0)
|
|
{
|
|
MXS_ERROR("Failed to set server SSL key: %s", get_ssl_errors());
|
|
return false;
|
|
}
|
|
|
|
/* Check if the server certificate and private-key matches */
|
|
if (!SSL_CTX_check_private_key(m_ctx))
|
|
{
|
|
MXS_ERROR("Server SSL certificate and key do not match: %s", get_ssl_errors());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* Set to require peer (client) certificate verification */
|
|
if (m_cfg.verify_peer)
|
|
{
|
|
SSL_CTX_set_verify(m_ctx, SSL_VERIFY_PEER, NULL);
|
|
}
|
|
|
|
/* Set the verification depth */
|
|
SSL_CTX_set_verify_depth(m_ctx, m_cfg.verify_depth);
|
|
|
|
return true;
|
|
}
|
|
|
|
SSLContext::~SSLContext()
|
|
{
|
|
SSL_CTX_free(m_ctx);
|
|
}
|
|
|
|
SSLProvider::SSLProvider(std::unique_ptr<mxs::SSLContext> context)
|
|
: m_context {std::move(context)}
|
|
{
|
|
}
|
|
|
|
mxs::SSLContext* SSLProvider::context() const
|
|
{
|
|
mxb_assert_message(mxs::RoutingWorker::get_current(), "Must be used on a RoutingWorker");
|
|
return m_context.get();
|
|
}
|
|
|
|
const mxs::SSLConfig& SSLProvider::config() const
|
|
{
|
|
return m_config;
|
|
}
|
|
|
|
void SSLProvider::set_context(std::unique_ptr<mxs::SSLContext> ssl)
|
|
{
|
|
mxb_assert(ssl);
|
|
m_context = std::move(ssl);
|
|
m_config = m_context->config();
|
|
}
|
|
|
|
std::string SSLProvider::to_string() const
|
|
{
|
|
std::ostringstream ss;
|
|
|
|
ss << "\tSSL initialized: yes\n"
|
|
<< "\tSSL method type: " << ssl_method_type_to_string(m_config.version) << "\n"
|
|
<< "\tSSL certificate verification depth: " << m_config.verify_depth << "\n"
|
|
<< "\tSSL peer verification : " << (m_config.verify_peer ? "true" : "false") << "\n"
|
|
<< "\tSSL certificate: " << m_config.cert << "\n"
|
|
<< "\tSSL key: " << m_config.key << "\n"
|
|
<< "\tSSL CA certificate: " << m_config.ca << "\n";
|
|
|
|
return ss.str();
|
|
}
|
|
}
|