/* * 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 #include #include #include #include #include #include #include #include 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::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 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 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 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(); } }