Files
MaxScale/server/core/ssl.cc
Markus Mäkelä cb72b2a5cc MXS-2483: Move SSL functionality into SSLProvider
The class is intended to be inherited by objects that need an SSL context
and a configuration. In practice this will be servers and listeners.

The SSLContext is stored in a rworker_local shared_ptr that makes it
possible to update safely. As the copying is always done behind a lock the
cached local value always holds a valid SSLContext instance for the
duration of all function calls.

Using the pImpl idiom, the routingworker.hh header is not exposed in the
ssl.hh header. This allows the SSLProvider class to be inherited more
easily.
2019-05-24 15:33:17 +03:00

480 lines
12 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();
}
class SSLProviderImp
{
public:
const mxs::SSLConfig& config() const;
mxs::SSLContext* context() const;
void set_context(std::unique_ptr<mxs::SSLContext> ssl);
SSLProviderImp(std::unique_ptr<mxs::SSLContext>&& context);
private:
mxs::rworker_local<std::shared_ptr<mxs::SSLContext>> m_context; /**< SSL context */
mxs::SSLConfig m_config; /**< SSL configuration */
};
const mxs::SSLConfig& SSLProviderImp::config() const
{
return m_config;
}
mxs::SSLContext* SSLProviderImp::context() const
{
mxb_assert_message(mxs::RoutingWorker::get_current(), "Must be used on a RoutingWorker");
return m_context->get();
}
void SSLProviderImp::set_context(std::unique_ptr<mxs::SSLContext> ssl)
{
mxb_assert_message(mxs::RoutingWorker::get_current()
== mxs::RoutingWorker::get(mxs::RoutingWorker::MAIN),
"Must be only set on the main RoutingWorker");
m_config = ssl ? ssl->config() : mxs::SSLConfig {};
m_context.assign(std::move(ssl));
}
SSLProviderImp::SSLProviderImp(std::unique_ptr<mxs::SSLContext>&& context)
: m_context {std::move(context)}
{
}
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)
{
}
std::string SSLContext::serialize() const
{
std::ostringstream ss;
ss << "ssl=required\n";
if (!m_cfg.cert.empty())
{
ss << "ssl_cert=" << m_cfg.cert << "\n";
}
if (!m_cfg.key.empty())
{
ss << "ssl_key=" << m_cfg.key << "\n";
}
mxb_assert(!m_cfg.ca.empty());
ss << "ssl_ca_cert=" << m_cfg.ca << "\n";
ss << "ssl_version=" << ssl_method_type_to_string(m_cfg.version) << "\n";
ss << "ssl_cert_verify_depth=" << m_cfg.verify_depth << "\n";
ss << "ssl_verify_peer_certificate=" << (m_cfg.verify_peer ? "true" : "false") << "\n";
return ss.str();
}
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;
}
json_t* SSLContext::to_json() const
{
json_t* ssl = json_object();
const char* ssl_method = ssl_method_type_to_string(m_cfg.version);
json_object_set_new(ssl, "ssl_version", json_string(ssl_method));
json_object_set_new(ssl, "ssl_cert", json_string(m_cfg.cert.c_str()));
json_object_set_new(ssl, "ssl_ca_cert", json_string(m_cfg.ca.c_str()));
json_object_set_new(ssl, "ssl_key", json_string(m_cfg.key.c_str()));
return ssl;
}
std::string SSLContext::to_string() const
{
std::ostringstream ss;
ss << "\tSSL initialized: yes\n"
<< "\tSSL method type: " << ssl_method_type_to_string(m_cfg.version) << "\n"
<< "\tSSL certificate verification depth: " << m_cfg.verify_depth << "\n"
<< "\tSSL peer verification : " << (m_cfg.verify_peer ? "true" : "false") << "\n"
<< "\tSSL certificate: " << m_cfg.cert << "\n"
<< "\tSSL key: " << m_cfg.key << "\n"
<< "\tSSL CA certificate: " << m_cfg.ca << "\n";
return ss.str();
}
SSLContext::~SSLContext()
{
SSL_CTX_free(m_ctx);
}
SSLProvider::SSLProvider(std::unique_ptr<mxs::SSLContext>&& ssl)
: m_imp(new SSLProviderImp(std::move(ssl)))
{
}
SSLProvider::~SSLProvider()
{
}
mxs::SSLContext* SSLProvider::context() const
{
return m_imp->context();
}
const mxs::SSLConfig& SSLProvider::config() const
{
return m_imp->config();
}
void SSLProvider::set_context(std::unique_ptr<mxs::SSLContext> ssl)
{
m_imp->set_context(std::move(ssl));
}
}