mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
In order to effectively mitigate CVE-2021-3499 even when compiling against versions of OpenSSL prior to 1.1.1k, this commit: 1) requires use of TLS 1.2 or later. Note that both TLS 1.0 and TLS 1.1 have been officially deprecated for over a year. 2) disables renegotiation support for TLS 1.2 connections. Lastly, this commit also changes the default list of ciphers that the server offers, limiting it only to ciphers that are part of TLS 1.2.
423 lines
12 KiB
C++
423 lines
12 KiB
C++
//------------------------------------------------------------------------------
|
|
/*
|
|
This file is part of rippled: https://github.com/ripple/rippled
|
|
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
|
|
|
Permission to use, copy, modify, and/or distribute this software for any
|
|
purpose with or without fee is hereby granted, provided that the above
|
|
copyright notice and this permission notice appear in all copies.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
//==============================================================================
|
|
|
|
#include <ripple/basics/chrono.h>
|
|
#include <ripple/basics/contract.h>
|
|
#include <ripple/basics/make_SSLContext.h>
|
|
#include <ripple/beast/container/aged_unordered_set.h>
|
|
#include <cstdint>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
|
|
namespace ripple {
|
|
namespace openssl {
|
|
namespace detail {
|
|
|
|
/** The default list of ciphers we accept over TLS.
|
|
|
|
Generally we include cipher suites that are part of TLS v1.2, but
|
|
we specifically exclude:
|
|
|
|
- the DSS cipher suites (!DSS);
|
|
- cipher suites using pre-shared keys (!PSK);
|
|
- cipher suites that don't offer encryption (!eNULL); and
|
|
- cipher suites that don't offer authentication (!aNULL).
|
|
|
|
@note Server administrators can override this default list, on either a
|
|
global or per-port basis, using the `ssl_ciphers` directive in the
|
|
config file.
|
|
*/
|
|
std::string const defaultCipherList = "TLSv1.2:!DSS:!PSK:!eNULL:!aNULL";
|
|
|
|
template <class>
|
|
struct custom_delete;
|
|
|
|
template <>
|
|
struct custom_delete<RSA>
|
|
{
|
|
explicit custom_delete() = default;
|
|
|
|
void
|
|
operator()(RSA* rsa) const
|
|
{
|
|
RSA_free(rsa);
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct custom_delete<EVP_PKEY>
|
|
{
|
|
explicit custom_delete() = default;
|
|
|
|
void
|
|
operator()(EVP_PKEY* evp_pkey) const
|
|
{
|
|
EVP_PKEY_free(evp_pkey);
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct custom_delete<X509>
|
|
{
|
|
explicit custom_delete() = default;
|
|
|
|
void
|
|
operator()(X509* x509) const
|
|
{
|
|
X509_free(x509);
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct custom_delete<DH>
|
|
{
|
|
explicit custom_delete() = default;
|
|
|
|
void
|
|
operator()(DH* dh) const
|
|
{
|
|
DH_free(dh);
|
|
}
|
|
};
|
|
|
|
template <class T>
|
|
using custom_delete_unique_ptr = std::unique_ptr<T, custom_delete<T>>;
|
|
|
|
// RSA
|
|
|
|
using rsa_ptr = custom_delete_unique_ptr<RSA>;
|
|
|
|
static rsa_ptr
|
|
rsa_generate_key(int n_bits)
|
|
{
|
|
#if OPENSSL_VERSION_NUMBER >= 0x00908000L
|
|
BIGNUM* bn = BN_new();
|
|
BN_set_word(bn, RSA_F4);
|
|
|
|
RSA* rsa = RSA_new();
|
|
if (RSA_generate_key_ex(rsa, n_bits, bn, nullptr) != 1)
|
|
{
|
|
RSA_free(rsa);
|
|
rsa = nullptr;
|
|
}
|
|
|
|
BN_free(bn);
|
|
#else
|
|
RSA* rsa = RSA_generate_key(n_bits, RSA_F4, nullptr, nullptr);
|
|
#endif
|
|
|
|
if (rsa == nullptr)
|
|
LogicError("RSA_generate_key failed");
|
|
|
|
return rsa_ptr(rsa);
|
|
}
|
|
|
|
// EVP_PKEY
|
|
|
|
using evp_pkey_ptr = custom_delete_unique_ptr<EVP_PKEY>;
|
|
|
|
static evp_pkey_ptr
|
|
evp_pkey_new()
|
|
{
|
|
EVP_PKEY* evp_pkey = EVP_PKEY_new();
|
|
|
|
if (evp_pkey == nullptr)
|
|
LogicError("EVP_PKEY_new failed");
|
|
|
|
return evp_pkey_ptr(evp_pkey);
|
|
}
|
|
|
|
static void
|
|
evp_pkey_assign_rsa(EVP_PKEY* evp_pkey, rsa_ptr rsa)
|
|
{
|
|
if (!EVP_PKEY_assign_RSA(evp_pkey, rsa.get()))
|
|
LogicError("EVP_PKEY_assign_RSA failed");
|
|
|
|
rsa.release();
|
|
}
|
|
|
|
// X509
|
|
|
|
using x509_ptr = custom_delete_unique_ptr<X509>;
|
|
|
|
static x509_ptr
|
|
x509_new()
|
|
{
|
|
X509* x509 = X509_new();
|
|
|
|
if (x509 == nullptr)
|
|
LogicError("X509_new failed");
|
|
|
|
X509_set_version(x509, NID_X509);
|
|
|
|
int const margin = 60 * 60; // 3600, one hour
|
|
int const length = 10 * 365.25 * 24 * 60 * 60; // 315576000, ten years
|
|
|
|
X509_gmtime_adj(X509_get_notBefore(x509), -margin);
|
|
X509_gmtime_adj(X509_get_notAfter(x509), length);
|
|
|
|
return x509_ptr(x509);
|
|
}
|
|
|
|
static void
|
|
x509_set_pubkey(X509* x509, EVP_PKEY* evp_pkey)
|
|
{
|
|
X509_set_pubkey(x509, evp_pkey);
|
|
}
|
|
|
|
static void
|
|
x509_sign(X509* x509, EVP_PKEY* evp_pkey)
|
|
{
|
|
if (!X509_sign(x509, evp_pkey, EVP_sha1()))
|
|
LogicError("X509_sign failed");
|
|
}
|
|
|
|
static void
|
|
ssl_ctx_use_certificate(SSL_CTX* const ctx, x509_ptr cert)
|
|
{
|
|
if (SSL_CTX_use_certificate(ctx, cert.get()) <= 0)
|
|
LogicError("SSL_CTX_use_certificate failed");
|
|
}
|
|
|
|
static void
|
|
ssl_ctx_use_privatekey(SSL_CTX* const ctx, evp_pkey_ptr key)
|
|
{
|
|
if (SSL_CTX_use_PrivateKey(ctx, key.get()) <= 0)
|
|
LogicError("SSL_CTX_use_PrivateKey failed");
|
|
}
|
|
|
|
static std::string
|
|
error_message(std::string const& what, boost::system::error_code const& ec)
|
|
{
|
|
std::stringstream ss;
|
|
ss << what << ": " << ec.message() << " (" << ec.value() << ")";
|
|
return ss.str();
|
|
}
|
|
|
|
static void
|
|
initAnonymous(boost::asio::ssl::context& context)
|
|
{
|
|
using namespace openssl;
|
|
|
|
evp_pkey_ptr pkey = evp_pkey_new();
|
|
evp_pkey_assign_rsa(pkey.get(), rsa_generate_key(2048));
|
|
|
|
x509_ptr cert = x509_new();
|
|
x509_set_pubkey(cert.get(), pkey.get());
|
|
x509_sign(cert.get(), pkey.get());
|
|
|
|
SSL_CTX* const ctx = context.native_handle();
|
|
ssl_ctx_use_certificate(ctx, std::move(cert));
|
|
ssl_ctx_use_privatekey(ctx, std::move(pkey));
|
|
}
|
|
|
|
static void
|
|
initAuthenticated(
|
|
boost::asio::ssl::context& context,
|
|
std::string const& key_file,
|
|
std::string const& cert_file,
|
|
std::string const& chain_file)
|
|
{
|
|
SSL_CTX* const ssl = context.native_handle();
|
|
|
|
bool cert_set = false;
|
|
|
|
if (!cert_file.empty())
|
|
{
|
|
boost::system::error_code ec;
|
|
|
|
context.use_certificate_file(
|
|
cert_file, boost::asio::ssl::context::pem, ec);
|
|
|
|
if (ec)
|
|
{
|
|
LogicError(error_message("Problem with SSL certificate file.", ec)
|
|
.c_str());
|
|
}
|
|
|
|
cert_set = true;
|
|
}
|
|
|
|
if (!chain_file.empty())
|
|
{
|
|
// VFALCO Replace fopen() with RAII
|
|
FILE* f = fopen(chain_file.c_str(), "r");
|
|
|
|
if (!f)
|
|
{
|
|
LogicError(error_message(
|
|
"Problem opening SSL chain file.",
|
|
boost::system::error_code(
|
|
errno, boost::system::generic_category()))
|
|
.c_str());
|
|
}
|
|
|
|
try
|
|
{
|
|
for (;;)
|
|
{
|
|
X509* const x = PEM_read_X509(f, nullptr, nullptr, nullptr);
|
|
|
|
if (x == nullptr)
|
|
break;
|
|
|
|
if (!cert_set)
|
|
{
|
|
if (SSL_CTX_use_certificate(ssl, x) != 1)
|
|
LogicError(
|
|
"Problem retrieving SSL certificate from chain "
|
|
"file.");
|
|
|
|
cert_set = true;
|
|
}
|
|
else if (SSL_CTX_add_extra_chain_cert(ssl, x) != 1)
|
|
{
|
|
X509_free(x);
|
|
LogicError("Problem adding SSL chain certificate.");
|
|
}
|
|
}
|
|
|
|
fclose(f);
|
|
}
|
|
catch (std::exception const&)
|
|
{
|
|
fclose(f);
|
|
LogicError("Reading the SSL chain file generated an exception.");
|
|
}
|
|
}
|
|
|
|
if (!key_file.empty())
|
|
{
|
|
boost::system::error_code ec;
|
|
|
|
context.use_private_key_file(
|
|
key_file, boost::asio::ssl::context::pem, ec);
|
|
|
|
if (ec)
|
|
{
|
|
LogicError(
|
|
error_message("Problem using the SSL private key file.", ec)
|
|
.c_str());
|
|
}
|
|
}
|
|
|
|
if (SSL_CTX_check_private_key(ssl) != 1)
|
|
{
|
|
LogicError("Invalid key in SSL private key file.");
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<boost::asio::ssl::context>
|
|
get_context(std::string const& cipherList)
|
|
{
|
|
auto c = std::make_shared<boost::asio::ssl::context>(
|
|
boost::asio::ssl::context::sslv23);
|
|
|
|
c->set_options(
|
|
boost::asio::ssl::context::default_workarounds |
|
|
boost::asio::ssl::context::no_sslv2 |
|
|
boost::asio::ssl::context::no_sslv3 |
|
|
boost::asio::ssl::context::no_tlsv1 |
|
|
boost::asio::ssl::context::no_tlsv1_1 |
|
|
boost::asio::ssl::context::single_dh_use |
|
|
boost::asio::ssl::context::no_compression);
|
|
|
|
{
|
|
auto const& l = !cipherList.empty() ? cipherList : defaultCipherList;
|
|
auto result = SSL_CTX_set_cipher_list(c->native_handle(), l.c_str());
|
|
if (result != 1)
|
|
LogicError("SSL_CTX_set_cipher_list failed");
|
|
}
|
|
|
|
// These are the raw DH parameters that Ripple Labs has
|
|
// chosen for Ripple, in the binary format needed by
|
|
// d2i_DHparams.
|
|
//
|
|
unsigned char const params[] = {
|
|
0x30, 0x82, 0x01, 0x08, 0x02, 0x82, 0x01, 0x01, 0x00, 0x8f, 0xca, 0x66,
|
|
0x85, 0x33, 0xcb, 0xcf, 0x36, 0x27, 0xb2, 0x4c, 0xb8, 0x50, 0xb8, 0xf9,
|
|
0x53, 0xf8, 0xb9, 0x2d, 0x1c, 0xa2, 0xad, 0x86, 0x58, 0x29, 0x3b, 0x88,
|
|
0x3e, 0xf5, 0x65, 0xb8, 0xda, 0x22, 0xf4, 0x8b, 0x21, 0x12, 0x18, 0xf7,
|
|
0x16, 0xcd, 0x7c, 0xc7, 0x3a, 0x2d, 0x61, 0xb7, 0x11, 0xf6, 0xb0, 0x65,
|
|
0xa0, 0x5b, 0xa4, 0x06, 0x95, 0x28, 0xa4, 0x4f, 0x76, 0xc0, 0xeb, 0xfa,
|
|
0x95, 0xdf, 0xbf, 0x19, 0x90, 0x64, 0x8f, 0x60, 0xd5, 0x36, 0xba, 0xab,
|
|
0x0d, 0x5a, 0x5c, 0x94, 0xd5, 0xf7, 0x32, 0xd6, 0x2a, 0x76, 0x77, 0x83,
|
|
0x10, 0xc4, 0x2f, 0x10, 0x96, 0x3e, 0x37, 0x84, 0x45, 0x9c, 0xef, 0x33,
|
|
0xf6, 0xd0, 0x2a, 0xa7, 0xce, 0x0a, 0xce, 0x0d, 0xa1, 0xa7, 0x44, 0x5d,
|
|
0x18, 0x3f, 0x4f, 0xa4, 0x23, 0x9c, 0x5d, 0x74, 0x4f, 0xee, 0xdf, 0xaa,
|
|
0x0d, 0x0a, 0x52, 0x57, 0x73, 0xb1, 0xe4, 0xc5, 0x72, 0x93, 0x9d, 0x03,
|
|
0xe9, 0xf5, 0x48, 0x8c, 0xd1, 0xe6, 0x7c, 0x21, 0x65, 0x4e, 0x16, 0x51,
|
|
0xa3, 0x16, 0x51, 0x10, 0x75, 0x60, 0x37, 0x93, 0xb8, 0x15, 0xd6, 0x14,
|
|
0x41, 0x4a, 0x61, 0xc9, 0x1a, 0x4e, 0x9f, 0x38, 0xd8, 0x2c, 0xa5, 0x31,
|
|
0xe1, 0x87, 0xda, 0x1f, 0xa4, 0x31, 0xa2, 0xa4, 0x42, 0x1e, 0xe0, 0x30,
|
|
0xea, 0x2f, 0x9b, 0x77, 0x91, 0x59, 0x3e, 0xd5, 0xd0, 0xc5, 0x84, 0x45,
|
|
0x17, 0x19, 0x74, 0x8b, 0x18, 0xb0, 0xc1, 0xe0, 0xfc, 0x1c, 0xaf, 0xe6,
|
|
0x2a, 0xef, 0x4e, 0x0e, 0x8a, 0x5c, 0xc2, 0x91, 0xb9, 0x2b, 0xf8, 0x17,
|
|
0x8d, 0xed, 0x44, 0xaa, 0x47, 0xaa, 0x52, 0xa2, 0xdb, 0xb6, 0xf5, 0xa1,
|
|
0x88, 0x85, 0xa1, 0xd5, 0x87, 0xb8, 0x07, 0xd3, 0x97, 0xbe, 0x37, 0x74,
|
|
0x72, 0xf1, 0xa8, 0x29, 0xf1, 0xa7, 0x7d, 0x19, 0xc3, 0x27, 0x09, 0xcf,
|
|
0x23, 0x02, 0x01, 0x02};
|
|
|
|
unsigned char const* data = ¶ms[0];
|
|
|
|
custom_delete_unique_ptr<DH> const dh{
|
|
d2i_DHparams(nullptr, &data, sizeof(params))};
|
|
if (!dh)
|
|
LogicError("d2i_DHparams returned nullptr.");
|
|
|
|
SSL_CTX_set_tmp_dh(c->native_handle(), dh.get());
|
|
|
|
// Disable all renegotiation support in TLS v1.2. This can help prevent
|
|
// exploitation of the bug described in CVE-2021-3499 (for details see
|
|
// https://www.openssl.org/news/secadv/20210325.txt) when linking against
|
|
// OpenSSL versions prior to 1.1.1k.
|
|
SSL_CTX_set_options(c->native_handle(), SSL_OP_NO_RENEGOTIATION);
|
|
|
|
return c;
|
|
}
|
|
|
|
} // namespace detail
|
|
} // namespace openssl
|
|
|
|
//------------------------------------------------------------------------------
|
|
std::shared_ptr<boost::asio::ssl::context>
|
|
make_SSLContext(std::string const& cipherList)
|
|
{
|
|
auto context = openssl::detail::get_context(cipherList);
|
|
openssl::detail::initAnonymous(*context);
|
|
// VFALCO NOTE, It seems the WebSocket context never has
|
|
// set_verify_mode called, for either setting of WEBSOCKET_SECURE
|
|
context->set_verify_mode(boost::asio::ssl::verify_none);
|
|
return context;
|
|
}
|
|
|
|
std::shared_ptr<boost::asio::ssl::context>
|
|
make_SSLContextAuthed(
|
|
std::string const& keyFile,
|
|
std::string const& certFile,
|
|
std::string const& chainFile,
|
|
std::string const& cipherList)
|
|
{
|
|
auto context = openssl::detail::get_context(cipherList);
|
|
openssl::detail::initAuthenticated(*context, keyFile, certFile, chainFile);
|
|
return context;
|
|
}
|
|
|
|
} // namespace ripple
|