From a4a43a4de9b783a57cb2cbe721217b5a7230fa5b Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Fri, 17 Nov 2017 10:36:00 -0500 Subject: [PATCH] Improve WorkSSL: Support Server Name Indication Ensure windows uses available certificates --- Builds/CMake/CMakeFuncs.cmake | 2 +- Builds/VisualStudio2015/RippleD.vcxproj | 14 ++- .../VisualStudio2015/RippleD.vcxproj.filters | 6 + SConstruct | 1 + src/ripple/app/misc/detail/WorkSSL.h | 36 +++--- src/ripple/app/misc/impl/ValidatorSite.cpp | 1 + src/ripple/core/impl/Config.cpp | 2 +- src/ripple/net/AutoSocket.h | 4 + src/ripple/net/HTTPClient.h | 2 +- src/ripple/net/RegisterSSLCerts.h | 42 +++++++ src/ripple/net/impl/HTTPClient.cpp | 15 ++- src/ripple/net/impl/RegisterSSLCerts.cpp | 109 ++++++++++++++++++ src/ripple/unity/net.cpp | 1 + src/test/jtx/impl/Env.cpp | 2 +- 14 files changed, 210 insertions(+), 27 deletions(-) create mode 100644 src/ripple/net/RegisterSSLCerts.h create mode 100644 src/ripple/net/impl/RegisterSSLCerts.cpp diff --git a/Builds/CMake/CMakeFuncs.cmake b/Builds/CMake/CMakeFuncs.cmake index 87f2ee2675..0fac94ff09 100644 --- a/Builds/CMake/CMakeFuncs.cmake +++ b/Builds/CMake/CMakeFuncs.cmake @@ -761,6 +761,6 @@ macro(link_common_libraries cur_project) $<$,$>:VC/static/libeay32MT>) target_link_libraries(${cur_project} legacy_stdio_definitions.lib Shlwapi kernel32 user32 gdi32 winspool comdlg32 - advapi32 shell32 ole32 oleaut32 uuid odbc32 odbccp32) + advapi32 shell32 ole32 oleaut32 uuid odbc32 odbccp32 crypt32) endif (NOT MSVC) endmacro() diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj index 84f0bf2433..017c67edb6 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj +++ b/Builds/VisualStudio2015/RippleD.vcxproj @@ -110,7 +110,7 @@ /bigobj /FS %(AdditionalOptions) - advapi32.lib;comdlg32.lib;gdi32.lib;kernel32.lib;legacy_stdio_definitions.lib;odbc32.lib;odbccp32.lib;ole32.lib;oleaut32.lib;shell32.lib;Shlwapi.lib;user32.lib;uuid.lib;VC/static/libeay32MTd.lib;VC/static/ssleay32MTd.lib;winspool.lib;%(AdditionalDependencies) + advapi32.lib;comdlg32.lib;crypt32.lib;gdi32.lib;kernel32.lib;legacy_stdio_definitions.lib;odbc32.lib;odbccp32.lib;ole32.lib;oleaut32.lib;shell32.lib;Shlwapi.lib;user32.lib;uuid.lib;VC/static/libeay32MTd.lib;VC/static/ssleay32MTd.lib;winspool.lib;%(AdditionalDependencies) True NoErrorReport Console @@ -147,7 +147,7 @@ /bigobj /FS %(AdditionalOptions) - advapi32.lib;comdlg32.lib;gdi32.lib;kernel32.lib;legacy_stdio_definitions.lib;odbc32.lib;odbccp32.lib;ole32.lib;oleaut32.lib;shell32.lib;Shlwapi.lib;user32.lib;uuid.lib;VC/static/libeay32MTd.lib;VC/static/ssleay32MTd.lib;winspool.lib;%(AdditionalDependencies) + advapi32.lib;comdlg32.lib;crypt32.lib;gdi32.lib;kernel32.lib;legacy_stdio_definitions.lib;odbc32.lib;odbccp32.lib;ole32.lib;oleaut32.lib;shell32.lib;Shlwapi.lib;user32.lib;uuid.lib;VC/static/libeay32MTd.lib;VC/static/ssleay32MTd.lib;winspool.lib;%(AdditionalDependencies) True NoErrorReport Console @@ -182,7 +182,7 @@ /bigobj /FS %(AdditionalOptions) - advapi32.lib;comdlg32.lib;gdi32.lib;kernel32.lib;legacy_stdio_definitions.lib;odbc32.lib;odbccp32.lib;ole32.lib;oleaut32.lib;shell32.lib;Shlwapi.lib;user32.lib;uuid.lib;VC/static/libeay32MT.lib;VC/static/ssleay32MT.lib;winspool.lib;%(AdditionalDependencies) + advapi32.lib;comdlg32.lib;crypt32.lib;gdi32.lib;kernel32.lib;legacy_stdio_definitions.lib;odbc32.lib;odbccp32.lib;ole32.lib;oleaut32.lib;shell32.lib;Shlwapi.lib;user32.lib;uuid.lib;VC/static/libeay32MT.lib;VC/static/ssleay32MT.lib;winspool.lib;%(AdditionalDependencies) True NoErrorReport Console @@ -217,7 +217,7 @@ /bigobj /FS %(AdditionalOptions) - advapi32.lib;comdlg32.lib;gdi32.lib;kernel32.lib;legacy_stdio_definitions.lib;odbc32.lib;odbccp32.lib;ole32.lib;oleaut32.lib;shell32.lib;Shlwapi.lib;user32.lib;uuid.lib;VC/static/libeay32MT.lib;VC/static/ssleay32MT.lib;winspool.lib;%(AdditionalDependencies) + advapi32.lib;comdlg32.lib;crypt32.lib;gdi32.lib;kernel32.lib;legacy_stdio_definitions.lib;odbc32.lib;odbccp32.lib;ole32.lib;oleaut32.lib;shell32.lib;Shlwapi.lib;user32.lib;uuid.lib;VC/static/libeay32MT.lib;VC/static/ssleay32MT.lib;winspool.lib;%(AdditionalDependencies) True NoErrorReport Console @@ -2199,6 +2199,10 @@ True True + + True + True + True True @@ -2213,6 +2217,8 @@ + + diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters index 7b96923343..9fc551f62c 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters @@ -2835,6 +2835,9 @@ ripple\net\impl + + ripple\net\impl + ripple\net\impl @@ -2847,6 +2850,9 @@ ripple\net + + ripple\net + ripple\net diff --git a/SConstruct b/SConstruct index 1d104ec38a..f1409d3301 100644 --- a/SConstruct +++ b/SConstruct @@ -754,6 +754,7 @@ def config_env(toolchain, variant, env): 'uuid.lib', 'odbc32.lib', 'odbccp32.lib', + 'crypt32.lib' ]) env.Append(LINKFLAGS=[ '/DEBUG', diff --git a/src/ripple/app/misc/detail/WorkSSL.h b/src/ripple/app/misc/detail/WorkSSL.h index 363fab0113..dbf52a036d 100644 --- a/src/ripple/app/misc/detail/WorkSSL.h +++ b/src/ripple/app/misc/detail/WorkSSL.h @@ -21,6 +21,7 @@ #define RIPPLE_APP_MISC_DETAIL_WORKSSL_H_INCLUDED #include +#include #include #include #include @@ -33,12 +34,11 @@ namespace detail { class SSLContext : public boost::asio::ssl::context { public: - SSLContext() + SSLContext(beast::Journal j) : boost::asio::ssl::context(boost::asio::ssl::context::sslv23) { boost::system::error_code ec; - set_default_verify_paths (ec); - + registerSSLCerts(*this, ec, j); if (ec) { Throw ( @@ -64,8 +64,11 @@ private: public: WorkSSL( std::string const& host, - std::string const& path, std::string const& port, - boost::asio::io_service& ios, callback_type cb); + std::string const& path, + std::string const& port, + boost::asio::io_service& ios, + beast::Journal j, + callback_type cb); ~WorkSSL() = default; private: @@ -82,13 +85,12 @@ private: onHandshake(error_code const& ec); static bool - rfc2818_verify ( + rfc2818_verify( std::string const& domain, bool preverified, boost::asio::ssl::verify_context& ctx) { - return - boost::asio::ssl::rfc2818_verification (domain) (preverified, ctx); + return boost::asio::ssl::rfc2818_verification(domain)(preverified, ctx); } }; @@ -96,15 +98,19 @@ private: WorkSSL::WorkSSL( std::string const& host, - std::string const& path, std::string const& port, - boost::asio::io_service& ios, callback_type cb) - : WorkBase (host, path, port, ios, cb) - , context_() - , stream_ (socket_, context_) + std::string const& path, + std::string const& port, + boost::asio::io_service& ios, + beast::Journal j, + callback_type cb) + : WorkBase(host, path, port, ios, cb) + , context_(j) + , stream_(socket_, context_) { + // Set SNI hostname + SSL_set_tlsext_host_name(stream_.native_handle(), host.c_str()); stream_.set_verify_mode (boost::asio::ssl::verify_peer); - stream_.set_verify_callback ( - std::bind ( + stream_.set_verify_callback( std::bind ( &WorkSSL::rfc2818_verify, host_, std::placeholders::_1, std::placeholders::_2)); } diff --git a/src/ripple/app/misc/impl/ValidatorSite.cpp b/src/ripple/app/misc/impl/ValidatorSite.cpp index 8f7d2f69c5..82f1d910e2 100644 --- a/src/ripple/app/misc/impl/ValidatorSite.cpp +++ b/src/ripple/app/misc/impl/ValidatorSite.cpp @@ -178,6 +178,7 @@ ValidatorSite::onTimer ( sites_[siteIdx].pUrl.path, std::to_string(*sites_[siteIdx].pUrl.port), ios_, + j_, [this, siteIdx](error_code const& err, detail::response_type&& resp) { onSiteFetch (err, std::move(resp), siteIdx); diff --git a/src/ripple/core/impl/Config.cpp b/src/ripple/core/impl/Config.cpp index 43599a88e0..6c1dff5276 100644 --- a/src/ripple/core/impl/Config.cpp +++ b/src/ripple/core/impl/Config.cpp @@ -255,7 +255,7 @@ void Config::setup (std::string const& strConf, bool bQuiet, legacy("database_path", boost::filesystem::absolute(dataDir).string()); } - HTTPClient::initializeSSLContext(*this); + HTTPClient::initializeSSLContext(*this, j_); if (RUN_STANDALONE) LEDGER_HISTORY = 0; diff --git a/src/ripple/net/AutoSocket.h b/src/ripple/net/AutoSocket.h index bec9ccea47..1dbe4b1d6b 100644 --- a/src/ripple/net/AutoSocket.h +++ b/src/ripple/net/AutoSocket.h @@ -149,6 +149,10 @@ public: return ec; } + void setTLSHostName(std::string const & host) + { + SSL_set_tlsext_host_name(mSocket->native_handle(), host.c_str()); + } /* template BOOST_ASIO_INITFN_RESULT_TYPE(HandshakeHandler, diff --git a/src/ripple/net/HTTPClient.h b/src/ripple/net/HTTPClient.h index 63473be756..2d602b0de0 100644 --- a/src/ripple/net/HTTPClient.h +++ b/src/ripple/net/HTTPClient.h @@ -37,7 +37,7 @@ public: maxClientHeaderBytes = 32 * 1024 }; - static void initializeSSLContext (Config const& config); + static void initializeSSLContext (Config const& config, beast::Journal j); static void get ( bool bSSL, diff --git a/src/ripple/net/RegisterSSLCerts.h b/src/ripple/net/RegisterSSLCerts.h new file mode 100644 index 0000000000..abfbb0c564 --- /dev/null +++ b/src/ripple/net/RegisterSSLCerts.h @@ -0,0 +1,42 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2016 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. +*/ +//============================================================================== + +#ifndef RIPPLE_NET_REGISTER_SSL_CERTS_H_INCLUDED +#define RIPPLE_NET_REGISTER_SSL_CERTS_H_INCLUDED + +#include +#include + +namespace ripple { +/** Register default SSL certificates. + + Register the system default SSL root certificates. On linux/mac, + this just calls asio's `set_default_verify_paths` to look in standard + operating system locations. On windows, it uses the OS certificate + store accessible via CryptoAPI. +*/ +void +registerSSLCerts( + boost::asio::ssl::context&, + boost::system::error_code&, + beast::Journal j); + +} // namespace ripple + +#endif diff --git a/src/ripple/net/impl/HTTPClient.cpp b/src/ripple/net/impl/HTTPClient.cpp index 8adfba5ccc..0524a8ef21 100644 --- a/src/ripple/net/impl/HTTPClient.cpp +++ b/src/ripple/net/impl/HTTPClient.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -40,7 +41,7 @@ class HTTPClientSSLContext { public: explicit - HTTPClientSSLContext (Config const& config) + HTTPClientSSLContext (Config const& config, beast::Journal j) : m_context (boost::asio::ssl::context::sslv23) , verify_ (config.SSL_VERIFY) { @@ -48,7 +49,7 @@ public: if (config.SSL_VERIFY_FILE.empty ()) { - m_context.set_default_verify_paths (ec); + registerSSLCerts(m_context, ec, j); if (ec && config.SSL_VERIFY_DIR.empty ()) Throw ( @@ -89,9 +90,9 @@ private: boost::optional httpClientSSLContext; -void HTTPClient::initializeSSLContext (Config const& config) +void HTTPClient::initializeSSLContext (Config const& config, beast::Journal j) { - httpClientSSLContext.emplace (config); + httpClientSSLContext.emplace (config, j); } //------------------------------------------------------------------------------ @@ -278,6 +279,12 @@ public: { JLOG (j_.trace()) << "Resolve complete."; + // If we intend to verify the SSL connection, we need to + // set the default domain for server name indication *prior* to + // connecting + if (httpClientSSLContext->sslVerify()) + mSocket.setTLSHostName(mDeqSites[0]); + boost::asio::async_connect ( mSocket.lowest_layer (), itrEndpoint, diff --git a/src/ripple/net/impl/RegisterSSLCerts.cpp b/src/ripple/net/impl/RegisterSSLCerts.cpp new file mode 100644 index 0000000000..cc04c338bb --- /dev/null +++ b/src/ripple/net/impl/RegisterSSLCerts.cpp @@ -0,0 +1,109 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2017 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 +#include +#include +#if BOOST_OS_WINDOWS +#include +#include +#include +#include +#include +#include +#include +#endif + +namespace ripple { + +void +registerSSLCerts( + boost::asio::ssl::context& ctx, + boost::system::error_code& ec, + beast::Journal j) +{ +#if BOOST_OS_WINDOWS + auto certStoreDelete = [](void* h) { + if (h != nullptr) + CertCloseStore(h, 0); + }; + std::unique_ptr hStore{ + CertOpenSystemStore(0, "ROOT"), certStoreDelete}; + + if (!hStore) + { + ec = boost::system::error_code( + GetLastError(), boost::system::system_category()); + return; + } + + ERR_clear_error(); + + std::unique_ptr store{ + X509_STORE_new(), X509_STORE_free}; + + if (!store) + { + ec = boost::system::error_code( + static_cast(::ERR_get_error()), + boost::asio::error::get_ssl_category()); + return; + } + + auto warn = [&](std::string const& mesg) { + // Buffer based on asio recommended size + char buf[256]; + ::ERR_error_string_n(ec.value(), buf, sizeof(buf)); + JLOG(j.warn()) << mesg << " " << buf; + ::ERR_clear_error(); + }; + + PCCERT_CONTEXT pContext = NULL; + while ((pContext = CertEnumCertificatesInStore(hStore.get(), pContext)) != + NULL) + { + const unsigned char* pbCertEncoded = pContext->pbCertEncoded; + std::unique_ptr x509{ + d2i_X509(NULL, &pbCertEncoded, pContext->cbCertEncoded), X509_free}; + if (!x509) + { + warn("Error decoding certificate"); + continue; + } + + if (X509_STORE_add_cert(store.get(), x509.get()) != 1) + { + warn("Error adding certificate"); + } + else + { + // Successfully adding to the store took ownership + x509.release(); + } + } + + // This takes ownership of the store + SSL_CTX_set_cert_store(ctx.native_handle(), store.release()); + +#else + + ctx.set_default_verify_paths(ec); +#endif +} + +} // namespace ripple diff --git a/src/ripple/unity/net.cpp b/src/ripple/unity/net.cpp index 7c57f957c4..0fa4a655e5 100644 --- a/src/ripple/unity/net.cpp +++ b/src/ripple/unity/net.cpp @@ -26,3 +26,4 @@ #include #include #include +#include diff --git a/src/test/jtx/impl/Env.cpp b/src/test/jtx/impl/Env.cpp index 753d7046bc..bc3c5b5788 100644 --- a/src/test/jtx/impl/Env.cpp +++ b/src/test/jtx/impl/Env.cpp @@ -89,7 +89,7 @@ Env::AppBundle::AppBundle(beast::unit_test::suite& suite, std::make_unique(); timeKeeper = timeKeeper_.get(); // Hack so we don't have to call Config::setup - HTTPClient::initializeSSLContext(*config); + HTTPClient::initializeSSLContext(*config, debugLog()); owned = make_Application(std::move(config), std::move(logs), std::move(timeKeeper_)); app = owned.get();