Harden default TLS configuration (RIPD-1332, RIPD-1333, RIPD-1334):

The existing configuration includes 512 and 1024 bit DH
parameters and supports ciphers such as RC4 and 3DES and
hash algorithms like SHA-1 which are no longer considered
secure.

Going forward, use only 2048-bit DH parameters and define
a new default set of modern ciphers to use:

    HIGH:!aNULL:!MD5:!DSS:!SHA1:!3DES:!RC4:!EXPORT:!DSS

Additionally, allow administrators who wish to have different
settings to configure custom global and per-port ciphers suites
in the configuration file using the `ssl_ciphers` directive.
This commit is contained in:
Nik Bougalis
2016-11-21 17:22:32 -08:00
parent b00b81a861
commit 2c87739d6c
8 changed files with 119 additions and 196 deletions

View File

@@ -260,6 +260,19 @@
# If you need a certificate chain, specify the path to the
# certificate chain here. The chain may include the end certificate.
#
# ssl_ciphers = <cipherlist>
#
# Control the ciphers which the server will support over SSL on the port,
# specified using the OpenSSL "cipher list format".
#
# NOTE If unspecified, rippled will automatically configure a modern
# cipher suite. This default suite should be widely supported.
#
# You should not modify this string unless you have a specific
# reason and cryptographic expertise. Incorrect modification may
# keep rippled from connecting to other instances of rippled or
# prevent RPC and WebSocket clients from connecting.
#
#
#
# [rpc_startup]

View File

@@ -30,6 +30,14 @@ 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.
char const defaultCipherList[] =
"HIGH:!aNULL:!MD5:!DSS:!SHA1:!3DES:!RC4:!EXPORT:!DSS";
template <class>
struct custom_delete;
@@ -60,15 +68,6 @@ struct custom_delete <X509>
}
};
template <>
struct custom_delete <DH>
{
void operator() (DH* dh) const
{
DH_free(dh);
}
};
template <class T>
using custom_delete_unique_ptr = std::unique_ptr <T, custom_delete <T>>;
@@ -153,159 +152,23 @@ static void ssl_ctx_use_privatekey (SSL_CTX* const ctx, evp_pkey_ptr& key)
LogicError ("SSL_CTX_use_PrivateKey failed");
}
// track when SSL connections have last negotiated
struct StaticData
{
std::mutex lock;
beast::aged_unordered_set <SSL const*> set;
StaticData()
: set (ripple::stopwatch())
{ }
};
using dh_ptr = custom_delete_unique_ptr<DH>;
static
dh_ptr
make_DH(std::string const& params)
{
auto const* p (
reinterpret_cast <std::uint8_t const*>(&params [0]));
DH* const dh = d2i_DHparams (nullptr, &p, params.size ());
if (dh == nullptr)
LogicError ("d2i_DHparams returned nullptr.");
return dh_ptr(dh);
}
/** Retrieve the raw DH parameters for the requested key size.
The result is in the binary format expected by the OpenSSL function
d2i_DHparams and may contain nulls. Use size to determine the actual size.
If the result is empty, the key size is unsupported. As of June 11, 2015,
OpenSSL never passes anything other than 512 or 1024.
*/
static
std::string
getRawDHParams (int keySize)
{
std::string params;
switch (keySize)
{
case 512:
{
// These are the DH parameters that OpenCoin has chosen for Ripple
//
std::uint8_t const raw512 [] = {
0x30, 0x46, 0x02, 0x41, 0x00, 0x98, 0x15, 0xd2, 0xd0, 0x08, 0x32, 0xda,
0xaa, 0xac, 0xc4, 0x71, 0xa3, 0x1b, 0x11, 0xf0, 0x6c, 0x62, 0xb2, 0x35,
0x8a, 0x10, 0x92, 0xc6, 0x0a, 0xa3, 0x84, 0x7e, 0xaf, 0x17, 0x29, 0x0b,
0x70, 0xef, 0x07, 0x4f, 0xfc, 0x9d, 0x6d, 0x87, 0x99, 0x19, 0x09, 0x5b,
0x6e, 0xdb, 0x57, 0x72, 0x4a, 0x7e, 0xcd, 0xaf, 0xbd, 0x3a, 0x97, 0x55,
0x51, 0x77, 0x5a, 0x34, 0x7c, 0xe8, 0xc5, 0x71, 0x63, 0x02, 0x01, 0x02
};
params.resize (sizeof (raw512));
std::copy (raw512, raw512 + sizeof (raw512), params.begin ());
break;
}
case 1024:
{
// These are the DH parameters that Ripple Labs has chosen for Ripple
//
std::uint8_t const raw1024 [] = {
0x30, 0x81, 0x87, 0x02, 0x81, 0x81, 0x00, 0x86, 0xb1, 0x85, 0x36, 0x3d,
0xbc, 0x0b, 0x03, 0xa5, 0xde, 0x53, 0x23, 0x4c, 0x59, 0xd4, 0x2b, 0x2e,
0x88, 0xdf, 0x83, 0x8e, 0xab, 0xe9, 0xc9, 0x0f, 0x20, 0x5c, 0x3e, 0x8d,
0x0e, 0x2c, 0xff, 0xcf, 0x3a, 0xfa, 0x71, 0x67, 0xb2, 0x90, 0xb5, 0x9e,
0x13, 0x9f, 0xa3, 0x70, 0xb2, 0xdf, 0x8d, 0xa4, 0x91, 0xfb, 0x26, 0xe0,
0x95, 0xd2, 0xf9, 0x3b, 0xa5, 0x1f, 0xe4, 0x88, 0x0f, 0x65, 0xfc, 0x8e,
0x58, 0x47, 0x8c, 0x77, 0x93, 0x8c, 0x2d, 0x2a, 0xfa, 0x50, 0xb4, 0xc5,
0x29, 0xba, 0x65, 0xc4, 0x39, 0xeb, 0x8a, 0xc5, 0x93, 0x39, 0xf9, 0x3c,
0x15, 0x1e, 0x95, 0x82, 0x0d, 0x02, 0xff, 0x92, 0x4c, 0xc5, 0x07, 0x76,
0x62, 0xaf, 0xdc, 0xc0, 0x96, 0x95, 0xcf, 0x61, 0x51, 0x17, 0x7c, 0x02,
0x81, 0xdb, 0xc2, 0x6b, 0x07, 0x03, 0x96, 0x39, 0xcc, 0xde, 0xc9, 0xcd,
0x5d, 0x77, 0x3b, 0x02, 0x01, 0x02
};
params.resize (sizeof (raw1024));
std::copy (raw1024, raw1024 + sizeof (raw1024), params.begin ());
break;
}
case 2048:
{
// These are the DH parameters that Ripple Labs has chosen for Ripple
//
std::uint8_t const raw2048 [] = {
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
};
params.resize (sizeof (raw2048));
std::copy (raw2048, raw2048 + sizeof (raw2048), params.begin ());
break;
}
default:
break;
};
return params;
}
static
DH*
getDH (int keyLength)
{
if (keyLength == 512 || keyLength == 1024 || keyLength == 2048)
{
static dh_ptr dh = make_DH(getRawDHParams (keyLength));
return dh.get ();
}
else
{
LogicError ("unsupported key length.");
}
return nullptr;
}
static
DH*
tmp_dh_handler (SSL*, int, int key_length)
{
return DHparams_dup (getDH (key_length));
}
static
bool
disallowRenegotiation (SSL const* ssl, bool isNew)
{
// Do not allow a connection to renegotiate
// more than once every 4 minutes
// 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);
@@ -365,14 +228,8 @@ error_message (std::string const& what,
static
void
initAnonymous (
boost::asio::ssl::context& context, std::string const& cipherList)
boost::asio::ssl::context& context)
{
int const result = SSL_CTX_set_cipher_list (
context.native_handle (),
cipherList.c_str ());
if (result != 1)
LogicError ("SSL_CTX_set_cipher_list failed");
using namespace openssl;
evp_pkey_ptr pkey = evp_pkey_new();
@@ -389,8 +246,11 @@ initAnonymous (
static
void
initAuthenticated (boost::asio::ssl::context& context,
std::string key_file, std::string cert_file, std::string chain_file)
initAuthenticated (
boost::asio::ssl::context& context,
std::string key_file,
std::string cert_file,
std::string chain_file)
{
SSL_CTX* const ssl = context.native_handle ();
@@ -477,7 +337,7 @@ initAuthenticated (boost::asio::ssl::context& context,
}
std::shared_ptr<boost::asio::ssl::context>
get_context ()
get_context (std::string cipherList)
{
auto c = std::make_shared<boost::asio::ssl::context> (
boost::asio::ssl::context::sslv23);
@@ -488,10 +348,54 @@ get_context ()
boost::asio::ssl::context::no_sslv3 |
boost::asio::ssl::context::single_dh_use);
SSL_CTX_set_tmp_dh_callback (
c->native_handle (), tmp_dh_handler);
SSL_CTX_set_info_callback (
c->native_handle (), info_handler);
{
if (cipherList.empty())
cipherList = defaultCipherList;
auto result = SSL_CTX_set_cipher_list (
c->native_handle (), cipherList.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];
DH* const dh = d2i_DHparams (nullptr, &data, sizeof(params));
if (dh == nullptr)
LogicError ("d2i_DHparams returned nullptr.");
SSL_CTX_set_tmp_dh (c->native_handle (), dh);
SSL_CTX_set_info_callback (c->native_handle (), info_handler);
return c;
}
@@ -501,30 +405,26 @@ get_context ()
//------------------------------------------------------------------------------
std::shared_ptr<boost::asio::ssl::context>
make_SSLContext()
make_SSLContext(std::string cipherList)
{
static auto const context =
[]()
{
auto const context = openssl::detail::get_context();
// By default, allow anonymous DH.
openssl::detail::initAnonymous(
*context, "ALL:!LOW:!EXP:!MD5:@STRENGTH");
// 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;
}();
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& key_file,
std::string const& cert_file, std::string const& chain_file)
make_SSLContextAuthed (
std::string keyFile,
std::string certFile,
std::string chainFile,
std::string cipherList)
{
auto const context = openssl::detail::get_context();
auto context = openssl::detail::get_context(cipherList);
openssl::detail::initAuthenticated(*context,
key_file, cert_file, chain_file);
keyFile, certFile, chainFile);
return context;
}

View File

@@ -27,12 +27,17 @@ namespace ripple {
/** Create a self-signed SSL context that allows anonymous Diffie Hellman. */
std::shared_ptr<boost::asio::ssl::context>
make_SSLContext();
make_SSLContext(
std::string cipherList);
/** Create an authenticated SSL context using the specified files. */
std::shared_ptr<boost::asio::ssl::context>
make_SSLContextAuthed (std::string const& key_file,
std::string const& cert_file, std::string const& chain_file);
make_SSLContextAuthed (
std::string keyFile,
std::string certFile,
std::string chainFile,
std::string cipherList);
}

View File

@@ -1074,7 +1074,7 @@ setup_Overlay (BasicConfig const& config)
{
Overlay::Setup setup;
auto const& section = config.section("overlay");
setup.context = make_SSLContext();
setup.context = make_SSLContext("");
setup.expire = get<bool>(section, "expire", false);
set (setup.ipLimit, "ip_limit", section);

View File

@@ -770,10 +770,11 @@ ServerHandler::Setup::makeContexts()
{
if (p.ssl_key.empty() && p.ssl_cert.empty() &&
p.ssl_chain.empty())
p.context = make_SSLContext();
p.context = make_SSLContext(p.ssl_ciphers);
else
p.context = make_SSLContextAuthed (
p.ssl_key, p.ssl_cert, p.ssl_chain);
p.ssl_key, p.ssl_cert, p.ssl_chain,
p.ssl_ciphers);
}
else
{
@@ -828,6 +829,7 @@ to_Port(ParsedPort const& parsed, std::ostream& log)
p.ssl_key = parsed.ssl_key;
p.ssl_cert = parsed.ssl_cert;
p.ssl_chain = parsed.ssl_chain;
p.ssl_ciphers = parsed.ssl_ciphers;
return p;
}

View File

@@ -49,6 +49,7 @@ struct Port
std::string ssl_key;
std::string ssl_cert;
std::string ssl_chain;
std::string ssl_ciphers;
std::shared_ptr<boost::asio::ssl::context> context;
// How many incoming connections are allowed on this
@@ -81,6 +82,7 @@ struct ParsedPort
std::string ssl_key;
std::string ssl_cert;
std::string ssl_chain;
std::string ssl_ciphers;
int limit = 0;
boost::optional<boost::asio::ip::address> ip;

View File

@@ -216,6 +216,7 @@ parse_Port (ParsedPort& port, Section const& section, std::ostream& log)
set(port.ssl_key, "ssl_key", section);
set(port.ssl_cert, "ssl_cert", section);
set(port.ssl_chain, "ssl_chain", section);
set(port.ssl_ciphers, "ssl_ciphers", section);
}
} // ripple

View File

@@ -555,7 +555,7 @@ public:
beast::Thread::setCurrentThreadName("io_service");
this->io_service_.run();
}))
, context_(make_SSLContext())
, context_(make_SSLContext(""))
{
}