From 2c87739d6c58fba489f165a73e229d229bf37669 Mon Sep 17 00:00:00 2001 From: Nik Bougalis Date: Mon, 21 Nov 2016 17:22:32 -0800 Subject: [PATCH] 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. --- doc/rippled-example.cfg | 13 + src/ripple/basics/impl/make_SSLContext.cpp | 278 +++++++-------------- src/ripple/basics/make_SSLContext.h | 11 +- src/ripple/overlay/impl/OverlayImpl.cpp | 2 +- src/ripple/rpc/impl/ServerHandlerImp.cpp | 6 +- src/ripple/server/Port.h | 2 + src/ripple/server/impl/Port.cpp | 1 + src/test/overlay/short_read_test.cpp | 2 +- 8 files changed, 119 insertions(+), 196 deletions(-) diff --git a/doc/rippled-example.cfg b/doc/rippled-example.cfg index 9420668e74..bc364cb8b3 100644 --- a/doc/rippled-example.cfg +++ b/doc/rippled-example.cfg @@ -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 = +# +# 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] diff --git a/src/ripple/basics/impl/make_SSLContext.cpp b/src/ripple/basics/impl/make_SSLContext.cpp index fffbe5d8c6..fcd4a1541b 100644 --- a/src/ripple/basics/impl/make_SSLContext.cpp +++ b/src/ripple/basics/impl/make_SSLContext.cpp @@ -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 struct custom_delete; @@ -60,15 +68,6 @@ struct custom_delete } }; -template <> -struct custom_delete -{ - void operator() (DH* dh) const - { - DH_free(dh); - } -}; - template using custom_delete_unique_ptr = std::unique_ptr >; @@ -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 set; - - StaticData() - : set (ripple::stopwatch()) - { } -}; - -using dh_ptr = custom_delete_unique_ptr; - -static -dh_ptr -make_DH(std::string const& params) -{ - auto const* p ( - reinterpret_cast (¶ms [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 set; + + StaticData() + : set (ripple::stopwatch()) + { + } + }; static StaticData sd; std::lock_guard 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 -get_context () +get_context (std::string cipherList) { auto c = std::make_shared ( 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 = ¶ms[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 -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 -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; } diff --git a/src/ripple/basics/make_SSLContext.h b/src/ripple/basics/make_SSLContext.h index c43b3be654..c6bfbaa137 100644 --- a/src/ripple/basics/make_SSLContext.h +++ b/src/ripple/basics/make_SSLContext.h @@ -27,12 +27,17 @@ namespace ripple { /** Create a self-signed SSL context that allows anonymous Diffie Hellman. */ std::shared_ptr -make_SSLContext(); +make_SSLContext( + std::string cipherList); /** Create an authenticated SSL context using the specified files. */ std::shared_ptr -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); + } diff --git a/src/ripple/overlay/impl/OverlayImpl.cpp b/src/ripple/overlay/impl/OverlayImpl.cpp index 50913b1c5e..df347c17d5 100644 --- a/src/ripple/overlay/impl/OverlayImpl.cpp +++ b/src/ripple/overlay/impl/OverlayImpl.cpp @@ -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(section, "expire", false); set (setup.ipLimit, "ip_limit", section); diff --git a/src/ripple/rpc/impl/ServerHandlerImp.cpp b/src/ripple/rpc/impl/ServerHandlerImp.cpp index 7f5341ba77..f6af81f6f7 100644 --- a/src/ripple/rpc/impl/ServerHandlerImp.cpp +++ b/src/ripple/rpc/impl/ServerHandlerImp.cpp @@ -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; } diff --git a/src/ripple/server/Port.h b/src/ripple/server/Port.h index 684ab47df1..4f06bdc27f 100644 --- a/src/ripple/server/Port.h +++ b/src/ripple/server/Port.h @@ -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 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 ip; diff --git a/src/ripple/server/impl/Port.cpp b/src/ripple/server/impl/Port.cpp index 8b7181dd4b..457a19d296 100644 --- a/src/ripple/server/impl/Port.cpp +++ b/src/ripple/server/impl/Port.cpp @@ -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 diff --git a/src/test/overlay/short_read_test.cpp b/src/test/overlay/short_read_test.cpp index 6ec7528cdf..92a6cbdcc2 100644 --- a/src/test/overlay/short_read_test.cpp +++ b/src/test/overlay/short_read_test.cpp @@ -555,7 +555,7 @@ public: beast::Thread::setCurrentThreadName("io_service"); this->io_service_.run(); })) - , context_(make_SSLContext()) + , context_(make_SSLContext("")) { }