Files
rippled/src/ripple/basics/impl/make_SSLContext.cpp
2018-04-08 01:52:12 -07:00

470 lines
13 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 {
// We limit the ciphers we request and allow to ensure that weak
// ciphers aren't used. While this isn't strictly necessary for
// the rippled server-server use case, where we only need MITM
// detection/prevention, we also have websocket and rpc scenarios
// and want to ensure weak ciphers can't be used.
std::string const defaultCipherList =
"HIGH:MEDIUM:!aNULL:!MD5:!DSS:!3DES:!RC4:!EXPORT";
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");
}
#ifdef SSL_FLAGS_NO_RENEGOTIATE_CIPHERS
static
bool
disallowRenegotiation (SSL const* ssl, bool isNew)
{
// Track when SSL connections have last negotiated and
// do not allow a connection to renegotiate more than
// once every 4 minutes
struct StaticData
{
std::mutex lock;
beast::aged_unordered_set <SSL const*> set;
StaticData()
: set (ripple::stopwatch())
{
}
};
static StaticData sd;
std::lock_guard <std::mutex> lock (sd.lock);
auto const expired (sd.set.clock().now() - std::chrono::minutes(4));
// Remove expired entries
for (auto iter (sd.set.chronological.begin ());
(iter != sd.set.chronological.end ()) && (iter.when () <= expired);
iter = sd.set.chronological.begin ())
{
sd.set.erase (iter);
}
auto iter = sd.set.find (ssl);
if (iter != sd.set.end ())
{
if (! isNew)
{
// This is a renegotiation and the last negotiation was recent
return true;
}
sd.set.touch (iter);
}
else
{
sd.set.emplace (ssl);
}
return false;
}
static
void
info_handler (SSL const* ssl, int event, int)
{
#if OPENSSL_VERSION_NUMBER < 0x10100000L
if ((ssl->s3) && (event & SSL_CB_HANDSHAKE_START))
{
if (disallowRenegotiation (ssl, SSL_in_before (ssl)))
ssl->s3->flags |= SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS;
}
#else
// empty, flag removed in OpenSSL 1.1
#endif
}
#endif
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::single_dh_use);
{
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 = &params[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());
#ifdef SSL_FLAGS_NO_RENEGOTIATE_CIPHERS
SSL_CTX_set_info_callback (c->native_handle (), info_handler);
#endif
return c;
}
} // detail
} // 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;
}
} // ripple