mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-04 11:15:56 +00:00
Honor SSL config settings for ValidatorSites:
FIXES: #2990 * refactor common SSL client setup * enable SSL in unit-test http server * add tests for SSLHTTPDownloader * misc test refactoring
This commit is contained in:
@@ -2041,6 +2041,7 @@ if (unity)
|
||||
src/test/unity/crypto_test_unity.cpp
|
||||
src/test/unity/json_test_unity.cpp
|
||||
src/test/unity/ledger_test_unity.cpp
|
||||
src/test/unity/net_test_unity.cpp
|
||||
src/test/unity/nodestore_test_unity.cpp
|
||||
src/test/unity/overlay_test_unity.cpp
|
||||
src/test/unity/peerfinder_test_unity.cpp
|
||||
@@ -2547,6 +2548,11 @@ else ()
|
||||
src/test/ledger/SHAMapV2_test.cpp
|
||||
src/test/ledger/SkipList_test.cpp
|
||||
src/test/ledger/View_test.cpp
|
||||
#[===============================[
|
||||
nounity, test sources:
|
||||
subdir: net
|
||||
#]===============================]
|
||||
src/test/net/SSLHTTPDownloader_test.cpp
|
||||
#[===============================[
|
||||
nounity, test sources:
|
||||
subdir: nodestore
|
||||
|
||||
@@ -508,8 +508,7 @@ public:
|
||||
*validatorManifests_, *publisherManifests_, *timeKeeper_,
|
||||
logs_->journal("ValidatorList"), config_->VALIDATION_QUORUM))
|
||||
|
||||
, validatorSites_ (std::make_unique<ValidatorSite> (
|
||||
get_io_service (), *validators_, logs_->journal("ValidatorSite")))
|
||||
, validatorSites_ (std::make_unique<ValidatorSite> (*this))
|
||||
|
||||
, serverHandler_ (make_ServerHandler (*this, *m_networkOPs, get_io_service (),
|
||||
*m_jobQueue, *m_networkOPs, *m_resourceManager,
|
||||
|
||||
@@ -22,10 +22,13 @@
|
||||
|
||||
#include <ripple/app/misc/ValidatorList.h>
|
||||
#include <ripple/app/misc/detail/Work.h>
|
||||
#include <ripple/app/main/Application.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/basics/StringUtilities.h>
|
||||
#include <ripple/core/Config.h>
|
||||
#include <ripple/json/json_value.h>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
|
||||
@@ -106,9 +109,9 @@ private:
|
||||
boost::optional<Status> lastRefreshStatus;
|
||||
};
|
||||
|
||||
boost::asio::io_service& ios_;
|
||||
ValidatorList& validators_;
|
||||
Application& app_;
|
||||
beast::Journal j_;
|
||||
|
||||
std::mutex mutable sites_mutex_;
|
||||
std::mutex mutable state_mutex_;
|
||||
|
||||
@@ -131,9 +134,8 @@ private:
|
||||
|
||||
public:
|
||||
ValidatorSite (
|
||||
boost::asio::io_service& ios,
|
||||
ValidatorList& validators,
|
||||
beast::Journal j,
|
||||
Application& app,
|
||||
boost::optional<beast::Journal> j = boost::none,
|
||||
std::chrono::seconds timeout = std::chrono::seconds{20});
|
||||
~ValidatorSite ();
|
||||
|
||||
|
||||
@@ -21,8 +21,9 @@
|
||||
#define RIPPLE_APP_MISC_DETAIL_WORKSSL_H_INCLUDED
|
||||
|
||||
#include <ripple/app/misc/detail/WorkBase.h>
|
||||
#include <ripple/net/RegisterSSLCerts.h>
|
||||
#include <ripple/basics/contract.h>
|
||||
#include <ripple/core/Config.h>
|
||||
#include <ripple/net/HTTPClientSSLContext.h>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/format.hpp>
|
||||
@@ -31,24 +32,6 @@ namespace ripple {
|
||||
|
||||
namespace detail {
|
||||
|
||||
class SSLContext : public boost::asio::ssl::context
|
||||
{
|
||||
public:
|
||||
SSLContext(beast::Journal j)
|
||||
: boost::asio::ssl::context(boost::asio::ssl::context::sslv23)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
registerSSLCerts(*this, ec, j);
|
||||
if (ec)
|
||||
{
|
||||
Throw<std::runtime_error> (
|
||||
boost::str (boost::format (
|
||||
"Failed to set_default_verify_paths: %s") %
|
||||
ec.message ()));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Work over SSL
|
||||
class WorkSSL : public WorkBase<WorkSSL>
|
||||
, public std::enable_shared_from_this<WorkSSL>
|
||||
@@ -58,7 +41,7 @@ class WorkSSL : public WorkBase<WorkSSL>
|
||||
private:
|
||||
using stream_type = boost::asio::ssl::stream<socket_type&>;
|
||||
|
||||
SSLContext context_;
|
||||
HTTPClientSSLContext context_;
|
||||
stream_type stream_;
|
||||
|
||||
public:
|
||||
@@ -68,6 +51,7 @@ public:
|
||||
std::string const& port,
|
||||
boost::asio::io_service& ios,
|
||||
beast::Journal j,
|
||||
Config const& config,
|
||||
callback_type cb);
|
||||
~WorkSSL() = default;
|
||||
|
||||
@@ -83,15 +67,6 @@ private:
|
||||
|
||||
void
|
||||
onHandshake(error_code const& ec);
|
||||
|
||||
static bool
|
||||
rfc2818_verify(
|
||||
std::string const& domain,
|
||||
bool preverified,
|
||||
boost::asio::ssl::verify_context& ctx)
|
||||
{
|
||||
return boost::asio::ssl::rfc2818_verification(domain)(preverified, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -102,24 +77,24 @@ WorkSSL::WorkSSL(
|
||||
std::string const& port,
|
||||
boost::asio::io_service& ios,
|
||||
beast::Journal j,
|
||||
Config const& config,
|
||||
callback_type cb)
|
||||
: WorkBase(host, path, port, ios, cb)
|
||||
, context_(j)
|
||||
, stream_(socket_, context_)
|
||||
, context_(config, j, boost::asio::ssl::context::tlsv12_client)
|
||||
, stream_(socket_, context_.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 (
|
||||
&WorkSSL::rfc2818_verify, host_,
|
||||
std::placeholders::_1, std::placeholders::_2));
|
||||
auto ec = context_.preConnectVerify(stream_, host_);
|
||||
if (ec)
|
||||
Throw<std::runtime_error> (
|
||||
boost::str (boost::format ("preConnectVerify: %s") % ec.message ()));
|
||||
}
|
||||
|
||||
void
|
||||
WorkSSL::onConnect(error_code const& ec)
|
||||
{
|
||||
if (ec)
|
||||
return fail(ec);
|
||||
auto err = ec ? ec : context_.postConnectVerify(stream_, host_);
|
||||
if (err)
|
||||
return fail(err);
|
||||
|
||||
stream_.async_handshake(
|
||||
boost::asio::ssl::stream_base::client,
|
||||
|
||||
@@ -88,18 +88,16 @@ ValidatorSite::Site::Site (std::string uri)
|
||||
}
|
||||
|
||||
ValidatorSite::ValidatorSite (
|
||||
boost::asio::io_service& ios,
|
||||
ValidatorList& validators,
|
||||
beast::Journal j,
|
||||
Application& app,
|
||||
boost::optional<beast::Journal> j,
|
||||
std::chrono::seconds timeout)
|
||||
: ios_ (ios)
|
||||
, validators_ (validators)
|
||||
, j_ (j)
|
||||
, timer_ (ios_)
|
||||
, fetching_ (false)
|
||||
, pending_ (false)
|
||||
, stopping_ (false)
|
||||
, requestTimeout_ (timeout)
|
||||
: app_ {app}
|
||||
, j_ {j ? *j : app_.logs().journal("ValidatorSite") }
|
||||
, timer_ {app_.getIOService()}
|
||||
, fetching_ {false}
|
||||
, pending_ {false}
|
||||
, stopping_ {false}
|
||||
, requestTimeout_ {timeout}
|
||||
{
|
||||
}
|
||||
|
||||
@@ -258,12 +256,14 @@ ValidatorSite::makeRequest (
|
||||
|
||||
if (resource->pUrl.scheme == "https")
|
||||
{
|
||||
// can throw...
|
||||
sp = std::make_shared<detail::WorkSSL>(
|
||||
resource->pUrl.domain,
|
||||
resource->pUrl.path,
|
||||
std::to_string(*resource->pUrl.port),
|
||||
ios_,
|
||||
app_.getIOService(),
|
||||
j_,
|
||||
app_.config(),
|
||||
onFetch);
|
||||
}
|
||||
else if(resource->pUrl.scheme == "http")
|
||||
@@ -272,7 +272,7 @@ ValidatorSite::makeRequest (
|
||||
resource->pUrl.domain,
|
||||
resource->pUrl.path,
|
||||
std::to_string(*resource->pUrl.port),
|
||||
ios_,
|
||||
app_.getIOService(),
|
||||
onFetch);
|
||||
}
|
||||
else
|
||||
@@ -280,7 +280,7 @@ ValidatorSite::makeRequest (
|
||||
BOOST_ASSERT(resource->pUrl.scheme == "file");
|
||||
sp = std::make_shared<detail::WorkFile>(
|
||||
resource->pUrl.path,
|
||||
ios_,
|
||||
app_.getIOService(),
|
||||
onFetchFile);
|
||||
}
|
||||
|
||||
@@ -336,7 +336,7 @@ ValidatorSite::onTimer (
|
||||
sites_[siteIdx].nextRefresh =
|
||||
clock_type::now() + sites_[siteIdx].refreshInterval;
|
||||
sites_[siteIdx].redirCount = 0;
|
||||
// the WorkSSL client can throw if SSL init fails
|
||||
// the WorkSSL client ctor can throw if SSL init fails
|
||||
makeRequest(sites_[siteIdx].startingResource, siteIdx, lock);
|
||||
}
|
||||
catch (std::exception &)
|
||||
@@ -376,7 +376,7 @@ ValidatorSite::parseJsonResponse (
|
||||
throw std::runtime_error{"missing fields"};
|
||||
}
|
||||
|
||||
auto const disp = validators_.applyList (
|
||||
auto const disp = app_.validators().applyList (
|
||||
body["manifest"].asString (),
|
||||
body["blob"].asString (),
|
||||
body["signature"].asString(),
|
||||
|
||||
@@ -116,40 +116,6 @@ public:
|
||||
return lowest_layer ().cancel (ec);
|
||||
}
|
||||
|
||||
|
||||
static bool rfc2818_verify (std::string const& domain, bool preverified,
|
||||
boost::asio::ssl::verify_context& ctx, beast::Journal j)
|
||||
{
|
||||
using namespace ripple;
|
||||
|
||||
if (boost::asio::ssl::rfc2818_verification (domain) (preverified, ctx))
|
||||
return true;
|
||||
|
||||
JLOG (j.warn()) <<
|
||||
"Outbound SSL connection to " << domain <<
|
||||
" fails certificate verification";
|
||||
return false;
|
||||
}
|
||||
|
||||
boost::system::error_code verify (std::string const& strDomain)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
|
||||
mSocket->set_verify_mode (boost::asio::ssl::verify_peer);
|
||||
|
||||
// XXX Verify semantics of RFC 2818 are what we want.
|
||||
mSocket->set_verify_callback (
|
||||
std::bind (&rfc2818_verify, strDomain,
|
||||
std::placeholders::_1, std::placeholders::_2, j_), ec);
|
||||
|
||||
return ec;
|
||||
}
|
||||
|
||||
void setTLSHostName(std::string const & host)
|
||||
{
|
||||
SSL_set_tlsext_host_name(mSocket->native_handle(), host.c_str());
|
||||
}
|
||||
|
||||
void async_handshake (handshake_type type, callback cbFunc)
|
||||
{
|
||||
if ((type == ssl_socket::client) || (mSecure))
|
||||
|
||||
191
src/ripple/net/HTTPClientSSLContext.h
Normal file
191
src/ripple/net/HTTPClientSSLContext.h
Normal file
@@ -0,0 +1,191 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2019 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_HTTPCLIENTSSLCONTEXT_H_INCLUDED
|
||||
#define RIPPLE_NET_HTTPCLIENTSSLCONTEXT_H_INCLUDED
|
||||
|
||||
#include <ripple/basics/contract.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/core/Config.h>
|
||||
#include <ripple/net/RegisterSSLCerts.h>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
#include <boost/format.hpp>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class HTTPClientSSLContext
|
||||
{
|
||||
public:
|
||||
|
||||
explicit
|
||||
HTTPClientSSLContext (
|
||||
Config const& config,
|
||||
beast::Journal j,
|
||||
boost::asio::ssl::context_base::method method =
|
||||
boost::asio::ssl::context::sslv23)
|
||||
: ssl_context_ {method}
|
||||
, j_(j)
|
||||
, verify_ {config.SSL_VERIFY}
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
|
||||
if (config.SSL_VERIFY_FILE.empty ())
|
||||
{
|
||||
registerSSLCerts(ssl_context_, ec, j_);
|
||||
|
||||
if (ec && config.SSL_VERIFY_DIR.empty ())
|
||||
Throw<std::runtime_error> (
|
||||
boost::str (boost::format (
|
||||
"Failed to set_default_verify_paths: %s") %
|
||||
ec.message ()));
|
||||
}
|
||||
else
|
||||
{
|
||||
ssl_context_.load_verify_file (config.SSL_VERIFY_FILE);
|
||||
}
|
||||
|
||||
if (! config.SSL_VERIFY_DIR.empty ())
|
||||
{
|
||||
ssl_context_.add_verify_path (config.SSL_VERIFY_DIR, ec);
|
||||
|
||||
if (ec)
|
||||
Throw<std::runtime_error> (
|
||||
boost::str (boost::format (
|
||||
"Failed to add verify path: %s") % ec.message ()));
|
||||
}
|
||||
}
|
||||
|
||||
boost::asio::ssl::context& context()
|
||||
{
|
||||
return ssl_context_;
|
||||
}
|
||||
|
||||
bool sslVerify() const
|
||||
{
|
||||
return verify_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief invoked before connect/async_connect on an ssl stream
|
||||
* to setup name verification.
|
||||
*
|
||||
* If we intend to verify the SSL connection, we need to set the
|
||||
* default domain for server name indication *prior* to connecting
|
||||
*
|
||||
* @param strm asio ssl stream
|
||||
* @param host hostname to verify
|
||||
*
|
||||
* @return error_code indicating failures, if any
|
||||
*/
|
||||
template<class T,
|
||||
class = std::enable_if_t<
|
||||
std::is_same<T, boost::asio::ssl::stream<boost::asio::ip::tcp::socket>>::value ||
|
||||
std::is_same<T, boost::asio::ssl::stream<boost::asio::ip::tcp::socket&>>::value
|
||||
>
|
||||
>
|
||||
boost::system::error_code
|
||||
preConnectVerify (
|
||||
T& strm,
|
||||
std::string const& host)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
if (!SSL_set_tlsext_host_name(strm.native_handle(), host.c_str()))
|
||||
{
|
||||
ec.assign(static_cast<int>(
|
||||
::ERR_get_error()), boost::asio::error::get_ssl_category());
|
||||
}
|
||||
else if (!sslVerify())
|
||||
{
|
||||
strm.set_verify_mode(boost::asio::ssl::verify_none, ec);
|
||||
}
|
||||
return ec;
|
||||
|
||||
}
|
||||
|
||||
template<class T,
|
||||
class = std::enable_if_t<
|
||||
std::is_same<T, boost::asio::ssl::stream<boost::asio::ip::tcp::socket>>::value ||
|
||||
std::is_same<T, boost::asio::ssl::stream<boost::asio::ip::tcp::socket&>>::value
|
||||
>
|
||||
>
|
||||
/**
|
||||
* @brief invoked after connect/async_connect but before sending data
|
||||
* on an ssl stream - to setup name verification.
|
||||
*
|
||||
* @param strm asio ssl stream
|
||||
* @param host hostname to verify
|
||||
*/
|
||||
boost::system::error_code
|
||||
postConnectVerify (
|
||||
T& strm,
|
||||
std::string const& host)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
|
||||
if (sslVerify())
|
||||
{
|
||||
strm.set_verify_mode (boost::asio::ssl::verify_peer, ec);
|
||||
if (!ec)
|
||||
{
|
||||
strm.set_verify_callback (
|
||||
std::bind (&rfc2818_verify, host,
|
||||
std::placeholders::_1, std::placeholders::_2, j_), ec);
|
||||
}
|
||||
}
|
||||
|
||||
return ec;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief callback invoked for name verification - just passes through
|
||||
* to the asio rfc2818 implementation.
|
||||
*
|
||||
* @param domain hostname expected
|
||||
* @param preverified passed by implementation
|
||||
* @param ctx passed by implementation
|
||||
* @param j journal for logging
|
||||
*/
|
||||
static
|
||||
bool
|
||||
rfc2818_verify (
|
||||
std::string const& domain,
|
||||
bool preverified,
|
||||
boost::asio::ssl::verify_context& ctx,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (boost::asio::ssl::rfc2818_verification (domain) (preverified, ctx))
|
||||
return true;
|
||||
|
||||
JLOG (j.warn()) <<
|
||||
"Outbound SSL connection to " << domain <<
|
||||
" fails certificate verification";
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
boost::asio::ssl::context ssl_context_;
|
||||
beast::Journal j_;
|
||||
const bool verify_;
|
||||
};
|
||||
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/core/Config.h>
|
||||
#include <ripple/net/HTTPClientSSLContext.h>
|
||||
|
||||
#include <boost/asio/connect.hpp>
|
||||
#include <boost/asio/io_service.hpp>
|
||||
@@ -48,10 +49,8 @@ public:
|
||||
|
||||
SSLHTTPDownloader(
|
||||
boost::asio::io_service& io_service,
|
||||
beast::Journal j);
|
||||
|
||||
bool
|
||||
init(Config const& config);
|
||||
beast::Journal j,
|
||||
Config const& config);
|
||||
|
||||
bool
|
||||
download(
|
||||
@@ -63,12 +62,11 @@ public:
|
||||
std::function<void(boost::filesystem::path)> complete);
|
||||
|
||||
private:
|
||||
boost::asio::ssl::context ctx_;
|
||||
HTTPClientSSLContext ssl_ctx_;
|
||||
boost::asio::io_service::strand strand_;
|
||||
boost::optional<
|
||||
boost::asio::ssl::stream<boost::asio::ip::tcp::socket>> stream_;
|
||||
boost::beast::flat_buffer read_buf_;
|
||||
bool ssl_verify_;
|
||||
beast::Journal j_;
|
||||
|
||||
void
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/basics/StringUtilities.h>
|
||||
#include <ripple/net/HTTPClient.h>
|
||||
#include <ripple/net/HTTPClientSSLContext.h>
|
||||
#include <ripple/net/AutoSocket.h>
|
||||
#include <ripple/net/RegisterSSLCerts.h>
|
||||
#include <ripple/beast/core/LexicalCast.h>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
@@ -32,61 +32,6 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
//
|
||||
// Fetch a web page via http or https.
|
||||
//
|
||||
|
||||
class HTTPClientSSLContext
|
||||
{
|
||||
public:
|
||||
explicit
|
||||
HTTPClientSSLContext (Config const& config, beast::Journal j)
|
||||
: m_context (boost::asio::ssl::context::sslv23)
|
||||
, verify_ (config.SSL_VERIFY)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
|
||||
if (config.SSL_VERIFY_FILE.empty ())
|
||||
{
|
||||
registerSSLCerts(m_context, ec, j);
|
||||
|
||||
if (ec && config.SSL_VERIFY_DIR.empty ())
|
||||
Throw<std::runtime_error> (
|
||||
boost::str (boost::format (
|
||||
"Failed to set_default_verify_paths: %s") %
|
||||
ec.message ()));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_context.load_verify_file (config.SSL_VERIFY_FILE);
|
||||
}
|
||||
|
||||
if (! config.SSL_VERIFY_DIR.empty ())
|
||||
{
|
||||
m_context.add_verify_path (config.SSL_VERIFY_DIR, ec);
|
||||
|
||||
if (ec)
|
||||
Throw<std::runtime_error> (
|
||||
boost::str (boost::format (
|
||||
"Failed to add verify path: %s") % ec.message ()));
|
||||
}
|
||||
}
|
||||
|
||||
boost::asio::ssl::context& context()
|
||||
{
|
||||
return m_context;
|
||||
}
|
||||
|
||||
bool sslVerify() const
|
||||
{
|
||||
return verify_;
|
||||
}
|
||||
|
||||
private:
|
||||
boost::asio::ssl::context m_context;
|
||||
bool verify_;
|
||||
};
|
||||
|
||||
boost::optional<HTTPClientSSLContext> httpClientSSLContext;
|
||||
|
||||
void HTTPClient::initializeSSLContext (Config const& config, beast::Journal j)
|
||||
@@ -94,6 +39,10 @@ void HTTPClient::initializeSSLContext (Config const& config, beast::Journal j)
|
||||
httpClientSSLContext.emplace (config, j);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Fetch a web page via http or https.
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class HTTPClientImp
|
||||
@@ -113,8 +62,6 @@ public:
|
||||
, mDeadline (io_service)
|
||||
, j_ (j)
|
||||
{
|
||||
if (!httpClientSSLContext->sslVerify())
|
||||
mSocket.SSLSocket ().set_verify_mode (boost::asio::ssl::verify_none);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -127,19 +74,24 @@ public:
|
||||
osRequest <<
|
||||
"GET " << strPath << " HTTP/1.0\r\n"
|
||||
"Host: " << strHost << "\r\n"
|
||||
"Accept: */*\r\n" // YYY Do we need this line?
|
||||
"Accept: */*\r\n" // YYY Do we need this line?
|
||||
"Connection: close\r\n\r\n";
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
void request (
|
||||
void
|
||||
request(
|
||||
bool bSSL,
|
||||
std::deque<std::string> deqSites,
|
||||
std::function<void (boost::asio::streambuf& sb, std::string const& strHost)> build,
|
||||
std::function<void (
|
||||
boost::asio::streambuf& sb,
|
||||
std::string const& strHost)> build,
|
||||
std::chrono::seconds timeout,
|
||||
std::function<bool (const boost::system::error_code& ecResult,
|
||||
int iStatus, std::string const& strData)> complete)
|
||||
std::function<bool (
|
||||
const boost::system::error_code& ecResult,
|
||||
int iStatus,
|
||||
std::string const& strData)> complete)
|
||||
{
|
||||
mSSL = bSSL;
|
||||
mDeqSites = deqSites;
|
||||
@@ -152,23 +104,28 @@ public:
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
void get (
|
||||
bool bSSL,
|
||||
void
|
||||
get(bool bSSL,
|
||||
std::deque<std::string> deqSites,
|
||||
std::string const& strPath,
|
||||
std::chrono::seconds timeout,
|
||||
std::function<bool (const boost::system::error_code& ecResult, int iStatus,
|
||||
std::function<bool (
|
||||
const boost::system::error_code& ecResult,
|
||||
int iStatus,
|
||||
std::string const& strData)> complete)
|
||||
{
|
||||
|
||||
mComplete = complete;
|
||||
mTimeout = timeout;
|
||||
|
||||
request (
|
||||
bSSL,
|
||||
deqSites,
|
||||
std::bind (&HTTPClientImp::makeGet, shared_from_this (), strPath,
|
||||
std::placeholders::_1, std::placeholders::_2),
|
||||
std::bind(
|
||||
&HTTPClientImp::makeGet,
|
||||
shared_from_this(),
|
||||
strPath,
|
||||
std::placeholders::_1,
|
||||
std::placeholders::_2),
|
||||
timeout,
|
||||
complete);
|
||||
}
|
||||
@@ -225,7 +182,8 @@ public:
|
||||
}
|
||||
else if (ecResult)
|
||||
{
|
||||
JLOG (j_.trace()) << "Deadline error: " << mDeqSites[0] << ": " << ecResult.message ();
|
||||
JLOG (j_.trace()) << "Deadline error: "
|
||||
<< mDeqSites[0] << ": " << ecResult.message ();
|
||||
|
||||
// Can't do anything sound.
|
||||
abort ();
|
||||
@@ -236,7 +194,9 @@ public:
|
||||
|
||||
// Mark us as shutting down.
|
||||
// XXX Use our own error code.
|
||||
mShutdown = boost::system::error_code (boost::system::errc::bad_address, boost::system::system_category ());
|
||||
mShutdown = boost::system::error_code {
|
||||
boost::system::errc::bad_address,
|
||||
boost::system::system_category ()};
|
||||
|
||||
// Cancel any resolving.
|
||||
mResolver.cancel ();
|
||||
@@ -256,7 +216,8 @@ public:
|
||||
{
|
||||
if (ecResult)
|
||||
{
|
||||
JLOG (j_.trace()) << "Shutdown error: " << mDeqSites[0] << ": " << ecResult.message ();
|
||||
JLOG (j_.trace()) << "Shutdown error: "
|
||||
<< mDeqSites[0] << ": " << ecResult.message ();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,11 +227,18 @@ public:
|
||||
)
|
||||
{
|
||||
if (!mShutdown)
|
||||
mShutdown = ecResult;
|
||||
{
|
||||
mShutdown =
|
||||
ecResult ?
|
||||
ecResult :
|
||||
httpClientSSLContext->preConnectVerify (
|
||||
mSocket.SSLSocket(), mDeqSites[0]);
|
||||
}
|
||||
|
||||
if (mShutdown)
|
||||
{
|
||||
JLOG (j_.trace()) << "Resolve error: " << mDeqSites[0] << ": " << mShutdown.message ();
|
||||
JLOG (j_.trace()) << "Resolve error: "
|
||||
<< mDeqSites[0] << ": " << mShutdown.message ();
|
||||
|
||||
invokeComplete (mShutdown);
|
||||
}
|
||||
@@ -278,12 +246,6 @@ 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,
|
||||
@@ -308,14 +270,13 @@ public:
|
||||
{
|
||||
JLOG (j_.trace()) << "Connected.";
|
||||
|
||||
if (httpClientSSLContext->sslVerify ())
|
||||
{
|
||||
mShutdown = mSocket.verify (mDeqSites[0]);
|
||||
mShutdown = httpClientSSLContext->postConnectVerify (
|
||||
mSocket.SSLSocket(), mDeqSites[0]);
|
||||
|
||||
if (mShutdown)
|
||||
{
|
||||
JLOG (j_.trace()) << "set_verify_callback: " << mDeqSites[0] << ": " << mShutdown.message ();
|
||||
}
|
||||
if (mShutdown)
|
||||
{
|
||||
JLOG (j_.trace()) << "postConnectVerify: "
|
||||
<< mDeqSites[0] << ": " << mShutdown.message ();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,7 +325,9 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void handleWrite (const boost::system::error_code& ecResult, std::size_t bytes_transferred)
|
||||
void handleWrite (
|
||||
const boost::system::error_code& ecResult,
|
||||
std::size_t bytes_transferred)
|
||||
{
|
||||
if (!mShutdown)
|
||||
mShutdown = ecResult;
|
||||
@@ -389,24 +352,32 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void handleHeader (const boost::system::error_code& ecResult, std::size_t bytes_transferred)
|
||||
void handleHeader (
|
||||
const boost::system::error_code& ecResult,
|
||||
std::size_t bytes_transferred)
|
||||
{
|
||||
std::string strHeader ((std::istreambuf_iterator<char> (&mHeader)), std::istreambuf_iterator<char> ());
|
||||
std::string strHeader {
|
||||
{std::istreambuf_iterator<char> (&mHeader)},
|
||||
std::istreambuf_iterator<char> ()};
|
||||
JLOG (j_.trace()) << "Header: \"" << strHeader << "\"";
|
||||
|
||||
static boost::regex reStatus ("\\`HTTP/1\\S+ (\\d{3}) .*\\'"); // HTTP/1.1 200 OK
|
||||
static boost::regex reSize ("\\`.*\\r\\nContent-Length:\\s+([0-9]+).*\\'");
|
||||
static boost::regex reBody ("\\`.*\\r\\n\\r\\n(.*)\\'");
|
||||
static boost::regex reStatus {
|
||||
"\\`HTTP/1\\S+ (\\d{3}) .*\\'"}; // HTTP/1.1 200 OK
|
||||
static boost::regex reSize {
|
||||
"\\`.*\\r\\nContent-Length:\\s+([0-9]+).*\\'"};
|
||||
static boost::regex reBody {
|
||||
"\\`.*\\r\\n\\r\\n(.*)\\'"};
|
||||
|
||||
boost::smatch smMatch;
|
||||
|
||||
bool bMatch = boost::regex_match (strHeader, smMatch, reStatus); // Match status code.
|
||||
|
||||
if (!bMatch)
|
||||
// Match status code.
|
||||
if (!boost::regex_match (strHeader, smMatch, reStatus))
|
||||
{
|
||||
// XXX Use our own error code.
|
||||
JLOG (j_.trace()) << "No status code";
|
||||
invokeComplete (boost::system::error_code (boost::system::errc::bad_address, boost::system::system_category ()));
|
||||
invokeComplete (
|
||||
boost::system::error_code {
|
||||
boost::system::errc::bad_address,
|
||||
boost::system::system_category ()});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -416,7 +387,8 @@ public:
|
||||
mBody = smMatch[1];
|
||||
|
||||
if (boost::regex_match (strHeader, smMatch, reSize))
|
||||
mResponseSize = beast::lexicalCastThrow <int> (std::string(smMatch[1]));
|
||||
mResponseSize =
|
||||
beast::lexicalCastThrow <int> (std::string(smMatch[1]));
|
||||
|
||||
if (mResponseSize == 0)
|
||||
{
|
||||
@@ -440,7 +412,9 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void handleData (const boost::system::error_code& ecResult, std::size_t bytes_transferred)
|
||||
void handleData (
|
||||
const boost::system::error_code& ecResult,
|
||||
std::size_t bytes_transferred)
|
||||
{
|
||||
if (!mShutdown)
|
||||
mShutdown = ecResult;
|
||||
@@ -460,14 +434,19 @@ public:
|
||||
else
|
||||
{
|
||||
mResponse.commit (bytes_transferred);
|
||||
std::string strBody ((std::istreambuf_iterator<char> (&mResponse)), std::istreambuf_iterator<char> ());
|
||||
std::string strBody {
|
||||
{std::istreambuf_iterator<char> (&mResponse)},
|
||||
std::istreambuf_iterator<char> ()};
|
||||
invokeComplete (ecResult, mStatus, mBody + strBody);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call cancel the deadline timer and invoke the completion routine.
|
||||
void invokeComplete (const boost::system::error_code& ecResult, int iStatus = 0, std::string const& strData = "")
|
||||
void invokeComplete (
|
||||
const boost::system::error_code& ecResult,
|
||||
int iStatus = 0,
|
||||
std::string const& strData = "")
|
||||
{
|
||||
boost::system::error_code ecCancel;
|
||||
|
||||
@@ -475,10 +454,12 @@ public:
|
||||
|
||||
if (ecCancel)
|
||||
{
|
||||
JLOG (j_.trace()) << "invokeComplete: Deadline cancel error: " << ecCancel.message ();
|
||||
JLOG (j_.trace()) << "invokeComplete: Deadline cancel error: "
|
||||
<< ecCancel.message ();
|
||||
}
|
||||
|
||||
JLOG (j_.debug()) << "invokeComplete: Deadline popping: " << mDeqSites.size ();
|
||||
JLOG (j_.debug()) << "invokeComplete: Deadline popping: "
|
||||
<< mDeqSites.size ();
|
||||
|
||||
if (!mDeqSites.empty ())
|
||||
{
|
||||
@@ -492,7 +473,8 @@ public:
|
||||
// ecResult: !0 = had an error, last entry
|
||||
// iStatus: result, if no error
|
||||
// strData: data, if no error
|
||||
bAgain = mComplete && mComplete (ecResult ? ecResult : ecCancel, iStatus, strData);
|
||||
bAgain = mComplete &&
|
||||
mComplete (ecResult ? ecResult : ecCancel, iStatus, strData);
|
||||
}
|
||||
|
||||
if (!mDeqSites.empty () && bAgain)
|
||||
@@ -515,8 +497,12 @@ private:
|
||||
const unsigned short mPort;
|
||||
int mResponseSize;
|
||||
int mStatus;
|
||||
std::function<void (boost::asio::streambuf& sb, std::string const& strHost)> mBuild;
|
||||
std::function<bool (const boost::system::error_code& ecResult, int iStatus, std::string const& strData)> mComplete;
|
||||
std::function<void (boost::asio::streambuf& sb, std::string const& strHost)>
|
||||
mBuild;
|
||||
std::function<bool (
|
||||
const boost::system::error_code& ecResult,
|
||||
int iStatus,
|
||||
std::string const& strData)> mComplete;
|
||||
|
||||
boost::asio::basic_waitable_timer<std::chrono::steady_clock> mDeadline;
|
||||
|
||||
@@ -566,15 +552,19 @@ void HTTPClient::get (
|
||||
client->get (bSSL, deqSites, strPath, timeout, complete);
|
||||
}
|
||||
|
||||
void HTTPClient::request (
|
||||
void
|
||||
HTTPClient::request(
|
||||
bool bSSL,
|
||||
boost::asio::io_service& io_service,
|
||||
std::string strSite,
|
||||
const unsigned short port,
|
||||
std::function<void (boost::asio::streambuf& sb, std::string const& strHost)> setRequest,
|
||||
std::function<void (boost::asio::streambuf& sb, std::string const& strHost)>
|
||||
setRequest,
|
||||
std::size_t responseMax,
|
||||
std::chrono::seconds timeout,
|
||||
std::function<bool (const boost::system::error_code& ecResult, int iStatus,
|
||||
std::function<bool (
|
||||
const boost::system::error_code& ecResult,
|
||||
int iStatus,
|
||||
std::string const& strData)> complete,
|
||||
beast::Journal& j)
|
||||
{
|
||||
|
||||
@@ -18,54 +18,20 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/net/SSLHTTPDownloader.h>
|
||||
#include <ripple/net/RegisterSSLCerts.h>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
SSLHTTPDownloader::SSLHTTPDownloader(
|
||||
boost::asio::io_service& io_service,
|
||||
beast::Journal j)
|
||||
: ctx_(boost::asio::ssl::context::tlsv12_client)
|
||||
beast::Journal j,
|
||||
Config const& config)
|
||||
: ssl_ctx_(config, j, boost::asio::ssl::context::tlsv12_client)
|
||||
, strand_(io_service)
|
||||
, j_(j)
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
SSLHTTPDownloader::init(Config const& config)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
if (config.SSL_VERIFY_FILE.empty())
|
||||
{
|
||||
registerSSLCerts(ctx_, ec, j_);
|
||||
if (ec && config.SSL_VERIFY_DIR.empty())
|
||||
{
|
||||
JLOG(j_.error()) <<
|
||||
"Failed to set_default_verify_paths: " <<
|
||||
ec.message();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
ctx_.load_verify_file(config.SSL_VERIFY_FILE);
|
||||
|
||||
if (!config.SSL_VERIFY_DIR.empty())
|
||||
{
|
||||
ctx_.add_verify_path(config.SSL_VERIFY_DIR, ec);
|
||||
if (ec)
|
||||
{
|
||||
JLOG(j_.error()) <<
|
||||
"Failed to add verify path: " <<
|
||||
ec.message();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ssl_verify_ = config.SSL_VERIFY;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SSLHTTPDownloader::download(
|
||||
std::string const& host,
|
||||
@@ -139,7 +105,7 @@ SSLHTTPDownloader::do_session(
|
||||
|
||||
try
|
||||
{
|
||||
stream_.emplace(strand_.context(), ctx_);
|
||||
stream_.emplace(strand_.context(), ssl_ctx_.context());
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
@@ -147,40 +113,18 @@ SSLHTTPDownloader::do_session(
|
||||
std::string("exception: ") + e.what());
|
||||
}
|
||||
|
||||
if (ssl_verify_)
|
||||
{
|
||||
// If we intend to verify the SSL connection, we need to set the
|
||||
// default domain for server name indication *prior* to connecting
|
||||
if (!SSL_set_tlsext_host_name(stream_->native_handle(), host.c_str()))
|
||||
{
|
||||
ec.assign(static_cast<int>(
|
||||
::ERR_get_error()), boost::asio::error::get_ssl_category());
|
||||
return fail(dstPath, complete, ec, "SSL_set_tlsext_host_name");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stream_->set_verify_mode(boost::asio::ssl::verify_none, ec);
|
||||
if (ec)
|
||||
return fail(dstPath, complete, ec, "set_verify_mode");
|
||||
}
|
||||
ec = ssl_ctx_.preConnectVerify(*stream_, host);
|
||||
if (ec)
|
||||
return fail(dstPath, complete, ec, "preConnectVerify");
|
||||
|
||||
boost::asio::async_connect(
|
||||
stream_->next_layer(), results.begin(), results.end(), yield[ec]);
|
||||
if (ec)
|
||||
return fail(dstPath, complete, ec, "async_connect");
|
||||
|
||||
if (ssl_verify_)
|
||||
{
|
||||
stream_->set_verify_mode(boost::asio::ssl::verify_peer, ec);
|
||||
if (ec)
|
||||
return fail(dstPath, complete, ec, "set_verify_mode");
|
||||
|
||||
stream_->set_verify_callback(
|
||||
boost::asio::ssl::rfc2818_verification(host.c_str()), ec);
|
||||
if (ec)
|
||||
return fail(dstPath, complete, ec, "set_verify_callback");
|
||||
}
|
||||
ec = ssl_ctx_.postConnectVerify(*stream_, host);
|
||||
if (ec)
|
||||
return fail(dstPath, complete, ec, "postConnectVerify");
|
||||
|
||||
stream_->async_handshake(ssl::stream_base::client, yield[ec]);
|
||||
if (ec)
|
||||
|
||||
@@ -113,6 +113,13 @@ ShardArchiveHandler::start()
|
||||
|
||||
// Create temp root download directory
|
||||
create_directory(downloadDir_);
|
||||
|
||||
if (!downloader_)
|
||||
{
|
||||
// will throw if can't initialize ssl context
|
||||
downloader_ = std::make_shared<SSLHTTPDownloader>(
|
||||
app_.getIOService(), j_, app_.config());
|
||||
}
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
@@ -121,16 +128,6 @@ ShardArchiveHandler::start()
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!downloader_)
|
||||
{
|
||||
downloader_ = std::make_shared<SSLHTTPDownloader>(
|
||||
app_.getIOService(), j_);
|
||||
if (!downloader_->init(app_.config()))
|
||||
{
|
||||
downloader_.reset();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return next(lock);
|
||||
}
|
||||
|
||||
|
||||
@@ -60,50 +60,6 @@ private:
|
||||
|
||||
using Validator = TrustedPublisherServer::Validator;
|
||||
|
||||
static
|
||||
PublicKey
|
||||
randomNode ()
|
||||
{
|
||||
return derivePublicKey (KeyType::secp256k1, randomSecretKey());
|
||||
}
|
||||
|
||||
static
|
||||
std::string
|
||||
makeManifestString (
|
||||
PublicKey const& pk,
|
||||
SecretKey const& sk,
|
||||
PublicKey const& spk,
|
||||
SecretKey const& ssk,
|
||||
int seq)
|
||||
{
|
||||
STObject st(sfGeneric);
|
||||
st[sfSequence] = seq;
|
||||
st[sfPublicKey] = pk;
|
||||
st[sfSigningPubKey] = spk;
|
||||
|
||||
sign(st, HashPrefix::manifest, *publicKeyType(spk), ssk);
|
||||
sign(st, HashPrefix::manifest, *publicKeyType(pk), sk,
|
||||
sfMasterSignature);
|
||||
|
||||
Serializer s;
|
||||
st.add(s);
|
||||
|
||||
return base64_encode (std::string(
|
||||
static_cast<char const*> (s.data()), s.size()));
|
||||
}
|
||||
|
||||
static
|
||||
Validator
|
||||
randomValidator ()
|
||||
{
|
||||
auto const secret = randomSecretKey();
|
||||
auto const masterPublic =
|
||||
derivePublicKey(KeyType::ed25519, secret);
|
||||
auto const signingKeys = randomKeyPair(KeyType::secp256k1);
|
||||
return { masterPublic, signingKeys.first, makeManifestString (
|
||||
masterPublic, secret, signingKeys.first, signingKeys.second, 1) };
|
||||
}
|
||||
|
||||
void
|
||||
testConfigLoad ()
|
||||
{
|
||||
@@ -113,7 +69,7 @@ private:
|
||||
|
||||
Env env (*this);
|
||||
auto trustedSites = std::make_unique<ValidatorSite> (
|
||||
env.app().getIOService(), env.app().validators(), env.journal);
|
||||
env.app(), env.journal);
|
||||
|
||||
// load should accept empty sites list
|
||||
std::vector<std::string> emptyCfgSites;
|
||||
@@ -171,28 +127,11 @@ private:
|
||||
#endif
|
||||
}
|
||||
|
||||
class TestSink : public beast::Journal::Sink
|
||||
{
|
||||
public:
|
||||
std::stringstream strm_;
|
||||
|
||||
TestSink () : Sink (beast::severities::kDebug, false) { }
|
||||
|
||||
void
|
||||
write (beast::severities::Severity level,
|
||||
std::string const& text) override
|
||||
{
|
||||
if (level < threshold())
|
||||
return;
|
||||
|
||||
strm_ << text << std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
struct FetchListConfig
|
||||
{
|
||||
std::string path;
|
||||
std::string msg;
|
||||
bool ssl;
|
||||
bool failFetch = false;
|
||||
bool failApply = false;
|
||||
int serverVersion = 1;
|
||||
@@ -205,14 +144,15 @@ private:
|
||||
testcase << "Fetch list - " <<
|
||||
boost::algorithm::join (paths |
|
||||
boost::adaptors::transformed(
|
||||
[](FetchListConfig const& cfg){ return cfg.path; }),
|
||||
[](FetchListConfig const& cfg){
|
||||
return cfg.path + (cfg.ssl ? " [https]" : " [http]");}),
|
||||
", ");
|
||||
using namespace jtx;
|
||||
|
||||
Env env (*this);
|
||||
auto& trustedKeys = env.app ().validators ();
|
||||
|
||||
TestSink sink;
|
||||
test::StreamSink sink;
|
||||
beast::Journal journal{sink};
|
||||
|
||||
PublicKey emptyLocalKey;
|
||||
@@ -228,40 +168,28 @@ private:
|
||||
};
|
||||
std::vector<publisher> servers;
|
||||
|
||||
auto const sequence = 1;
|
||||
auto constexpr listSize = 20;
|
||||
std::vector<std::string> cfgPublishers;
|
||||
|
||||
for (auto const& cfg : paths)
|
||||
{
|
||||
auto const publisherSecret = randomSecretKey();
|
||||
auto const publisherPublic =
|
||||
derivePublicKey(KeyType::ed25519, publisherSecret);
|
||||
auto const pubSigningKeys = randomKeyPair(KeyType::secp256k1);
|
||||
cfgPublishers.push_back(strHex(publisherPublic));
|
||||
|
||||
auto const manifest = makeManifestString (
|
||||
publisherPublic, publisherSecret,
|
||||
pubSigningKeys.first, pubSigningKeys.second, 1);
|
||||
|
||||
servers.push_back(cfg);
|
||||
auto& item = servers.back();
|
||||
item.isRetry = cfg.path == "/bad-resource";
|
||||
item.list.reserve (listSize);
|
||||
while (item.list.size () < listSize)
|
||||
item.list.push_back (randomValidator());
|
||||
item.list.push_back (TrustedPublisherServer::randomValidator());
|
||||
|
||||
item.server = std::make_unique<TrustedPublisherServer> (
|
||||
env.app().getIOService(),
|
||||
pubSigningKeys,
|
||||
manifest,
|
||||
sequence,
|
||||
item.list,
|
||||
env.timeKeeper().now() + cfg.expiresFromNow,
|
||||
cfg.serverVersion,
|
||||
item.list);
|
||||
cfg.ssl,
|
||||
cfg.serverVersion);
|
||||
cfgPublishers.push_back(strHex(item.server->publisherPublic()));
|
||||
|
||||
std::stringstream uri;
|
||||
uri << "http://" << item.server->local_endpoint() << cfg.path;
|
||||
uri << (cfg.ssl ? "https://" : "http://") << item.server->local_endpoint() << cfg.path;
|
||||
item.uri = uri.str();
|
||||
}
|
||||
|
||||
@@ -270,10 +198,7 @@ private:
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
auto sites = std::make_unique<ValidatorSite> (
|
||||
env.app().getIOService(),
|
||||
env.app().validators(),
|
||||
journal,
|
||||
2s);
|
||||
env.app(), journal, 2s);
|
||||
|
||||
std::vector<std::string> uris;
|
||||
for (auto const& u : servers)
|
||||
@@ -300,16 +225,15 @@ private:
|
||||
BEAST_EXPECTS(
|
||||
myStatus[jss::last_refresh_message].asString().empty()
|
||||
!= u.cfg.failFetch,
|
||||
to_string(myStatus) + "\n" + sink.strm_.str());
|
||||
to_string(myStatus) + "\n" + sink.messages().str());
|
||||
|
||||
if (! u.cfg.msg.empty())
|
||||
{
|
||||
BEAST_EXPECTS(
|
||||
sink.strm_.str().find(u.cfg.msg) != std::string::npos,
|
||||
sink.strm_.str());
|
||||
sink.messages().str().find(u.cfg.msg) != std::string::npos,
|
||||
sink.messages().str());
|
||||
}
|
||||
|
||||
|
||||
if (u.cfg.expectedRefreshMin)
|
||||
{
|
||||
BEAST_EXPECTS(
|
||||
@@ -347,7 +271,7 @@ private:
|
||||
|
||||
Env env (*this);
|
||||
|
||||
TestSink sink;
|
||||
test::StreamSink sink;
|
||||
beast::Journal journal{sink};
|
||||
|
||||
struct publisher
|
||||
@@ -370,8 +294,7 @@ private:
|
||||
item.uri = uri.str();
|
||||
}
|
||||
|
||||
auto sites = std::make_unique<ValidatorSite> (
|
||||
env.app().getIOService(), env.app().validators(), journal);
|
||||
auto sites = std::make_unique<ValidatorSite> (env.app(), journal);
|
||||
|
||||
std::vector<std::string> uris;
|
||||
for (auto const& u : servers)
|
||||
@@ -393,8 +316,8 @@ private:
|
||||
if (u.shouldFail)
|
||||
{
|
||||
BEAST_EXPECTS(
|
||||
sink.strm_.str().find(u.expectMsg) != std::string::npos,
|
||||
sink.strm_.str());
|
||||
sink.messages().str().find(u.expectMsg) != std::string::npos,
|
||||
sink.messages().str());
|
||||
log << " -- Msg: " <<
|
||||
myStatus[jss::last_refresh_message].asString() << std::endl;
|
||||
}
|
||||
@@ -439,71 +362,81 @@ public:
|
||||
{
|
||||
testConfigLoad ();
|
||||
|
||||
|
||||
for (auto ssl : {true, false})
|
||||
{
|
||||
// fetch single site
|
||||
testFetchList ({{"/validators", ""}});
|
||||
testFetchList ({{"/validators", "", ssl}});
|
||||
// fetch multiple sites
|
||||
testFetchList ({{"/validators", ""}, {"/validators", ""}});
|
||||
testFetchList ({{"/validators", "", ssl}, {"/validators", "", ssl}});
|
||||
// fetch single site with single redirects
|
||||
testFetchList ({{"/redirect_once/301", ""}});
|
||||
testFetchList ({{"/redirect_once/302", ""}});
|
||||
testFetchList ({{"/redirect_once/307", ""}});
|
||||
testFetchList ({{"/redirect_once/308", ""}});
|
||||
testFetchList ({{"/redirect_once/301", "", ssl}});
|
||||
testFetchList ({{"/redirect_once/302", "", ssl}});
|
||||
testFetchList ({{"/redirect_once/307", "", ssl}});
|
||||
testFetchList ({{"/redirect_once/308", "", ssl}});
|
||||
// one redirect, one not
|
||||
testFetchList ({{"/validators", ""}, {"/redirect_once/302", ""}});
|
||||
testFetchList ({
|
||||
{"/validators", "", ssl},
|
||||
{"/redirect_once/302", "", ssl}});
|
||||
// fetch single site with undending redirect (fails to load)
|
||||
testFetchList ({
|
||||
{"/redirect_forever/301", "Exceeded max redirects", true, true}});
|
||||
{"/redirect_forever/301", "Exceeded max redirects", ssl, true, true}});
|
||||
// two that redirect forever
|
||||
testFetchList ({
|
||||
{"/redirect_forever/307", "Exceeded max redirects", true, true},
|
||||
{"/redirect_forever/308", "Exceeded max redirects", true, true}});
|
||||
{"/redirect_forever/307", "Exceeded max redirects", ssl, true, true},
|
||||
{"/redirect_forever/308", "Exceeded max redirects", ssl, true, true}});
|
||||
// one undending redirect, one not
|
||||
testFetchList (
|
||||
{{"/validators", ""},
|
||||
{"/redirect_forever/302", "Exceeded max redirects", true, true}});
|
||||
{{"/validators", "", ssl},
|
||||
{"/redirect_forever/302", "Exceeded max redirects", ssl, true, true}});
|
||||
// invalid redir Location
|
||||
testFetchList ({
|
||||
{"/redirect_to/ftp://invalid-url/302",
|
||||
"Invalid redirect location",
|
||||
ssl,
|
||||
true,
|
||||
true}});
|
||||
testFetchList ({
|
||||
{"/redirect_to/file://invalid-url/302",
|
||||
"Invalid redirect location",
|
||||
ssl,
|
||||
true,
|
||||
true}});
|
||||
// invalid json
|
||||
testFetchList ({
|
||||
{"/validators/bad", "Unable to parse JSON response", true, true}});
|
||||
{"/validators/bad", "Unable to parse JSON response", ssl, true, true}});
|
||||
// error status returned
|
||||
testFetchList ({
|
||||
{"/bad-resource", "returned bad status", true, true}});
|
||||
{"/bad-resource", "returned bad status", ssl, true, true}});
|
||||
// location field missing
|
||||
testFetchList ({
|
||||
{"/redirect_nolo/308",
|
||||
"returned a redirect with no Location",
|
||||
ssl,
|
||||
true,
|
||||
true}});
|
||||
// json fields missing
|
||||
testFetchList ({
|
||||
{"/validators/missing",
|
||||
"Missing fields in JSON response",
|
||||
ssl,
|
||||
true,
|
||||
true}});
|
||||
// timeout
|
||||
testFetchList ({
|
||||
{"/sleep/3", "took too long", true, true}});
|
||||
{"/sleep/3", "took too long", ssl, true, true}});
|
||||
// bad manifest version
|
||||
testFetchList ({
|
||||
{"/validators", "Unsupported version", false, true, 4}});
|
||||
{"/validators", "Unsupported version", ssl, false, true, 4}});
|
||||
using namespace std::chrono_literals;
|
||||
// get old validator list
|
||||
testFetchList ({
|
||||
{"/validators", "Stale validator list", false, true, 1, 0s}});
|
||||
{"/validators", "Stale validator list", ssl, false, true, 1, 0s}});
|
||||
// force an out-of-range expiration value
|
||||
testFetchList ({
|
||||
{"/validators",
|
||||
"Invalid validator list",
|
||||
ssl,
|
||||
false,
|
||||
true,
|
||||
1,
|
||||
@@ -512,6 +445,7 @@ public:
|
||||
testFetchList ({
|
||||
{"/validators/refresh/0",
|
||||
"",
|
||||
ssl,
|
||||
false,
|
||||
false,
|
||||
1,
|
||||
@@ -520,6 +454,7 @@ public:
|
||||
testFetchList ({
|
||||
{"/validators/refresh/10",
|
||||
"",
|
||||
ssl,
|
||||
false,
|
||||
false,
|
||||
1,
|
||||
@@ -528,11 +463,13 @@ public:
|
||||
testFetchList ({
|
||||
{"/validators/refresh/2000",
|
||||
"",
|
||||
ssl,
|
||||
false,
|
||||
false,
|
||||
1,
|
||||
detail::default_expires,
|
||||
60*24}}); // max of 24 hours
|
||||
}
|
||||
testFileURLs();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -23,11 +23,16 @@
|
||||
#include <ripple/protocol/SecretKey.h>
|
||||
#include <ripple/protocol/Sign.h>
|
||||
#include <ripple/basics/base64.h>
|
||||
#include <ripple/basics/random.h>
|
||||
#include <ripple/basics/strHex.h>
|
||||
#include <test/jtx/envconfig.h>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/ssl/stream.hpp>
|
||||
#include <boost/beast/http.hpp>
|
||||
#include <boost/beast/ssl.hpp>
|
||||
#include <boost/beast/version.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <thread>
|
||||
|
||||
@@ -45,11 +50,47 @@ class TrustedPublisherServer
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
socket_type sock_;
|
||||
endpoint_type ep_;
|
||||
boost::asio::ip::tcp::acceptor acceptor_;
|
||||
std::function<std::string(int)> getList_;
|
||||
|
||||
public:
|
||||
// The SSL context is required, and holds certificates
|
||||
bool useSSL_;
|
||||
boost::asio::ssl::context sslCtx_{boost::asio::ssl::context::tlsv12};
|
||||
|
||||
SecretKey publisherSecret_;
|
||||
PublicKey publisherPublic_;
|
||||
|
||||
// Load a signed certificate into the ssl context, and configure
|
||||
// the context for use with a server.
|
||||
inline
|
||||
void
|
||||
load_server_certificate()
|
||||
{
|
||||
sslCtx_.set_password_callback(
|
||||
[](std::size_t,
|
||||
boost::asio::ssl::context_base::password_purpose)
|
||||
{
|
||||
return "test";
|
||||
});
|
||||
|
||||
sslCtx_.set_options(
|
||||
boost::asio::ssl::context::default_workarounds |
|
||||
boost::asio::ssl::context::no_sslv2 |
|
||||
boost::asio::ssl::context::single_dh_use);
|
||||
|
||||
sslCtx_.use_certificate_chain(
|
||||
boost::asio::buffer(cert().data(), cert().size()));
|
||||
|
||||
sslCtx_.use_private_key(
|
||||
boost::asio::buffer(key().data(), key().size()),
|
||||
boost::asio::ssl::context::file_format::pem);
|
||||
|
||||
sslCtx_.use_tmp_dh(
|
||||
boost::asio::buffer(dh().data(), dh().size()));
|
||||
}
|
||||
|
||||
public:
|
||||
struct Validator
|
||||
{
|
||||
PublicKey masterPublic;
|
||||
@@ -57,19 +98,69 @@ public:
|
||||
std::string manifest;
|
||||
};
|
||||
|
||||
static
|
||||
std::string
|
||||
makeManifestString (
|
||||
PublicKey const& pk,
|
||||
SecretKey const& sk,
|
||||
PublicKey const& spk,
|
||||
SecretKey const& ssk,
|
||||
int seq)
|
||||
{
|
||||
STObject st(sfGeneric);
|
||||
st[sfSequence] = seq;
|
||||
st[sfPublicKey] = pk;
|
||||
st[sfSigningPubKey] = spk;
|
||||
|
||||
sign(st, HashPrefix::manifest, *publicKeyType(spk), ssk);
|
||||
sign(st, HashPrefix::manifest, *publicKeyType(pk), sk,
|
||||
sfMasterSignature);
|
||||
|
||||
Serializer s;
|
||||
st.add(s);
|
||||
|
||||
return base64_encode (std::string(
|
||||
static_cast<char const*> (s.data()), s.size()));
|
||||
}
|
||||
|
||||
static
|
||||
Validator
|
||||
randomValidator ()
|
||||
{
|
||||
auto const secret = randomSecretKey();
|
||||
auto const masterPublic =
|
||||
derivePublicKey(KeyType::ed25519, secret);
|
||||
auto const signingKeys = randomKeyPair(KeyType::secp256k1);
|
||||
return { masterPublic, signingKeys.first, makeManifestString (
|
||||
masterPublic, secret, signingKeys.first, signingKeys.second, 1) };
|
||||
}
|
||||
|
||||
TrustedPublisherServer(
|
||||
boost::asio::io_context& ioc,
|
||||
std::pair<PublicKey, SecretKey> keys,
|
||||
std::string const& manifest,
|
||||
int sequence,
|
||||
std::vector<Validator> const& validators,
|
||||
NetClock::time_point expiration,
|
||||
int version,
|
||||
std::vector<Validator> const& validators)
|
||||
: sock_(ioc), acceptor_(ioc)
|
||||
bool useSSL = false,
|
||||
int version = 1,
|
||||
bool immediateStart = true,
|
||||
int sequence = 1)
|
||||
: sock_{ioc}
|
||||
, ep_{beast::IP::Address::from_string(
|
||||
ripple::test::getEnvLocalhostAddr()),
|
||||
// 0 means let OS pick the port based on what's available
|
||||
0}
|
||||
, acceptor_{ioc}
|
||||
, useSSL_{useSSL}
|
||||
, publisherSecret_{randomSecretKey()}
|
||||
, publisherPublic_{derivePublicKey(KeyType::ed25519, publisherSecret_)}
|
||||
{
|
||||
endpoint_type const& ep {
|
||||
beast::IP::Address::from_string (ripple::test::getEnvLocalhostAddr()),
|
||||
0}; // 0 means let OS pick the port based on what's available
|
||||
auto const keys = randomKeyPair(KeyType::secp256k1);
|
||||
auto const manifest = makeManifestString (
|
||||
publisherPublic_,
|
||||
publisherSecret_,
|
||||
keys.first,
|
||||
keys.second,
|
||||
1);
|
||||
|
||||
std::string data = "{\"sequence\":" + std::to_string(sequence) +
|
||||
",\"expiration\":" +
|
||||
std::to_string(expiration.time_since_epoch().count()) +
|
||||
@@ -94,11 +185,23 @@ public:
|
||||
return l.str();
|
||||
};
|
||||
|
||||
acceptor_.open(ep.protocol());
|
||||
if (useSSL_)
|
||||
{
|
||||
// This holds the self-signed certificate used by the server
|
||||
load_server_certificate();
|
||||
}
|
||||
|
||||
if (immediateStart)
|
||||
start();
|
||||
}
|
||||
|
||||
void start()
|
||||
{
|
||||
error_code ec;
|
||||
acceptor_.open(ep_.protocol());
|
||||
acceptor_.set_option(
|
||||
boost::asio::ip::tcp::acceptor::reuse_address(true), ec);
|
||||
acceptor_.bind(ep);
|
||||
acceptor_.bind(ep_);
|
||||
acceptor_.listen(boost::asio::socket_base::max_connections);
|
||||
acceptor_.async_accept(
|
||||
sock_,
|
||||
@@ -106,10 +209,17 @@ public:
|
||||
&TrustedPublisherServer::on_accept, this, std::placeholders::_1));
|
||||
}
|
||||
|
||||
~TrustedPublisherServer()
|
||||
void stop()
|
||||
{
|
||||
error_code ec;
|
||||
acceptor_.close(ec);
|
||||
// TODO consider making this join
|
||||
// any running do_peer threads
|
||||
}
|
||||
|
||||
~TrustedPublisherServer()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
endpoint_type
|
||||
@@ -118,6 +228,186 @@ public:
|
||||
return acceptor_.local_endpoint();
|
||||
}
|
||||
|
||||
PublicKey const&
|
||||
publisherPublic() const
|
||||
{
|
||||
return publisherPublic_;
|
||||
}
|
||||
|
||||
/* CA/self-signed certs :
|
||||
*
|
||||
* The following three methods return certs/keys used by
|
||||
* server and/or client to do the SSL handshake. These strings
|
||||
* were generated using the script below. The server key and cert
|
||||
* are used to configure the server (see load_server_certificate
|
||||
* above). The ca.crt should be used to configure the client
|
||||
* when ssl verification is enabled.
|
||||
*
|
||||
* note:
|
||||
* cert() ==> server.crt
|
||||
* key() ==> server.key
|
||||
* ca_cert() ==> ca.crt
|
||||
* dh() ==> dh.pem
|
||||
```
|
||||
#!/usr/bin/env bash
|
||||
|
||||
mkdir -p /tmp/__certs__
|
||||
pushd /tmp/__certs__
|
||||
rm *.crt *.key *.pem
|
||||
|
||||
# generate CA
|
||||
openssl genrsa -out ca.key 2048
|
||||
openssl req -new -x509 -nodes -days 10000 -key ca.key -out ca.crt \
|
||||
-subj "/C=US/ST=CA/L=Los Angeles/O=rippled-unit-tests/CN=example.com"
|
||||
# generate private cert
|
||||
openssl genrsa -out server.key 2048
|
||||
# Generate certificate signing request
|
||||
# since our unit tests can run in either ipv4 or ipv6 mode,
|
||||
# we need to use extensions (subjectAltName) so that we can
|
||||
# associate both ipv4 and ipv6 localhost addresses with this cert
|
||||
cat >"extras.cnf" <<EOF
|
||||
[req]
|
||||
req_extensions = v3_req
|
||||
distinguished_name = req_distinguished_name
|
||||
|
||||
[req_distinguished_name]
|
||||
|
||||
[v3_req]
|
||||
subjectAltName = @alt_names
|
||||
|
||||
[alt_names]
|
||||
DNS.1 = localhost
|
||||
IP.1 = ::1
|
||||
EOF
|
||||
openssl req -new -key server.key -out server.csr \
|
||||
-config extras.cnf \
|
||||
-subj "/C=US/ST=California/L=San Francisco/O=rippled-unit-tests/CN=127.0.0.1" \
|
||||
|
||||
# Create public certificate by signing with our CA
|
||||
openssl x509 -req -days 10000 -in server.csr -CA ca.crt -CAkey ca.key -out server.crt \
|
||||
-extfile extras.cnf -set_serial 01 -extensions v3_req
|
||||
|
||||
# generate DH params for server
|
||||
openssl dhparam -out dh.pem 2048
|
||||
# verify certs
|
||||
openssl verify -CAfile ca.crt server.crt
|
||||
openssl x509 -in server.crt -text -noout
|
||||
popd
|
||||
```
|
||||
*/
|
||||
static
|
||||
std::string const&
|
||||
cert()
|
||||
{
|
||||
static std::string const cert {R"cert(
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDczCCAlugAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEL
|
||||
MAkGA1UECAwCQ0ExFDASBgNVBAcMC0xvcyBBbmdlbGVzMRswGQYDVQQKDBJyaXBw
|
||||
bGVkLXVuaXQtdGVzdHMxFDASBgNVBAMMC2V4YW1wbGUuY29tMB4XDTE5MDgwNzE3
|
||||
MzM1OFoXDTQ2MTIyMzE3MzM1OFowazELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNh
|
||||
bGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xGzAZBgNVBAoMEnJpcHBs
|
||||
ZWQtdW5pdC10ZXN0czESMBAGA1UEAwwJMTI3LjAuMC4xMIIBIjANBgkqhkiG9w0B
|
||||
AQEFAAOCAQ8AMIIBCgKCAQEA5Ky0UE9K+gFOznfwBvq2HfnQOOPGtVf4G9m63b5V
|
||||
QNJYCSNiYxkGZW72ESM3XA8BledlkV9pwIm17+7ucB1Ed3efQjQDq2RSk5LDYDaa
|
||||
r0Qzzy0EC3b9+AKA6mAoVY6s1Qws/YvM4esz0H+SVvtVcFqA46kRWhJN7M5ic1lu
|
||||
d58fAq04BHqi5zOEOzfHJYPGUgQOxRTHluYkkkBrL2xioHHnOROshW+PIYFiAc/h
|
||||
WPzuihPHnKaziPRw+O6O8ysnCxycQHgqtvx73T52eJdLxtr3ToRWaY/8VF/Cog5c
|
||||
uvWEtg6EucGOszIH8O7eJWaJpVpAfZIX+c62MQWLpOLi/QIDAQABoyowKDAmBgNV
|
||||
HREEHzAdgglsb2NhbGhvc3SHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQEF
|
||||
BQADggEBAOhLAO/e0lGi9TZ2HiVi4sJ7KVQaBQHGhfsysILoQNHrNqDypPc/ZrSa
|
||||
WQ2OqyUeltMnUdN5S1h3MKRZlbAeBQlwkPdjTzlzWkCMWB5BsfIGy5ovqmNQ7zPa
|
||||
Khg5oxq3mU8ZLiJP4HngyU+hOOCt5tttex2S8ubjFT+3C3cydLKEOXCUPspaVkKn
|
||||
Eq8WSBoYTvyUVmSi6+m6HGiowWsM5Qgj93IRW6JCbkgfPeKXC/5ykAPQcFHwNaKT
|
||||
rpWokcavZyMbVjRsbzCQcc7n2j7tbLOu2svSLy6oXwG6n/bEagl5WpN2/TzQuwe7
|
||||
f5ktutc4DDJSV7fuYYCuGumrHAjcELE=
|
||||
-----END CERTIFICATE-----
|
||||
)cert"};
|
||||
return cert;
|
||||
}
|
||||
|
||||
static
|
||||
std::string const&
|
||||
key()
|
||||
{
|
||||
static std::string const key {R"pkey(
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEA5Ky0UE9K+gFOznfwBvq2HfnQOOPGtVf4G9m63b5VQNJYCSNi
|
||||
YxkGZW72ESM3XA8BledlkV9pwIm17+7ucB1Ed3efQjQDq2RSk5LDYDaar0Qzzy0E
|
||||
C3b9+AKA6mAoVY6s1Qws/YvM4esz0H+SVvtVcFqA46kRWhJN7M5ic1lud58fAq04
|
||||
BHqi5zOEOzfHJYPGUgQOxRTHluYkkkBrL2xioHHnOROshW+PIYFiAc/hWPzuihPH
|
||||
nKaziPRw+O6O8ysnCxycQHgqtvx73T52eJdLxtr3ToRWaY/8VF/Cog5cuvWEtg6E
|
||||
ucGOszIH8O7eJWaJpVpAfZIX+c62MQWLpOLi/QIDAQABAoIBACf8mzs/4lh9Sg6I
|
||||
ooxV4uqy+Fo6WlDzpQsZs7d6xOWk4ogWi+nQQnISSS0N/2w1o41W/UfCa3ejnRDr
|
||||
sv4f4A0T+eFVvx6FWHs9urRkWAA16OldccufbyGjLm/NiMANRuOqUWO0woru2gyn
|
||||
git7n6EZ8lfdBI+/i6jRHh4VkV+ROt5Zbp9zuJsj0yMqJH7J6Ebtl1jAF6PemLBL
|
||||
yxdiYqR8LKTunTGGP/L+4K5a389oPDcJ1+YX805NEopmfrIhPr+BQYdz8905aVFk
|
||||
FSS4TJy23EhFLzKf3+iSept6Giim+2yy2rv1RPCKgjOXbJ+4LD48xumDol6XWgYr
|
||||
1CBzQIECgYEA/jBEGOjV02a9A3C5RJxZMawlGwGrvvALG2UrKvwQc595uxwrUw9S
|
||||
Mn3ZQBEGnEWwEf44jSpWzp8TtejMxEvrU5243eWgwif1kqr1Mcj54DR7Qm15/hsj
|
||||
M3nA2WscVG2OHBs4AwzMCHE2vfEAkbz71s6xonhg6zvsC26Zy3hYPqkCgYEA5k3k
|
||||
OuCeG5FXW1/GzhvVFuhl6msNKzuUnLmJg6500XPny5Xo7W3RMvjtTM2XLt1USU6D
|
||||
arMCCQ1A8ku1SoFdSw5RC6Fl8ZoUFBz9FPPwT6usQssGyFxiiqdHLvTlk12NNCk3
|
||||
vJYsdQ+v/dKuZ8T4U3GTgQSwPTj6J0kJUf5y2jUCgYEA+hi/R8r/aArz+kiU4T78
|
||||
O3Vm5NWWCD3ij8fQ23A7N6g3e7RRpF20wF02vmSCHowqmumI9swrsQyvthIiNxmD
|
||||
pzfORvXCYIY0h2SR77QQt1qr1EYm+6/zyJgI+WL78s4APwNA7y9OKRhLhkN0DfDl
|
||||
0Qp5mKPcqFbC/tSJmbsFCFECgYEAwlLC2rMgdV5jeWQNGWf+mv+ozu1ZBTuWn88l
|
||||
qwiO5RSJZwysp3nb5MiJYh6vDAoQznIDDQrSEtUuEcOzypPxJh2EYO3kWMGLY5U6
|
||||
Lm3OPUs7ZHhu1qytMRUISSS2eWucc4C72NJV3MhJ1T/pjQF0DuRsc5aDJoVm/bLw
|
||||
vFCYlGkCgYEAgBDIIqdo1th1HE95SQfpP2wV/jA6CPamIciNwS3bpyhDBqs9oLUc
|
||||
qzXidOpXAVYg1wl/BqpaCQcmmhCrnSLJYdOMpudVyLCCfYmBJ0bs2DCAe5ibGbL7
|
||||
VruAOjS4yBepkXJU9xwKHxDmgTo/oQ5smq7SNOUWDSElVI/CyZ0x7qA=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
)pkey"};
|
||||
return key;
|
||||
}
|
||||
|
||||
static
|
||||
std::string const&
|
||||
ca_cert()
|
||||
{
|
||||
static std::string const cert {R"cert(
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDQjCCAioCCQDxKQafEvp+VTANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJV
|
||||
UzELMAkGA1UECAwCQ0ExFDASBgNVBAcMC0xvcyBBbmdlbGVzMRswGQYDVQQKDBJy
|
||||
aXBwbGVkLXVuaXQtdGVzdHMxFDASBgNVBAMMC2V4YW1wbGUuY29tMB4XDTE5MDgw
|
||||
NzE3MzM1OFoXDTQ2MTIyMzE3MzM1OFowYzELMAkGA1UEBhMCVVMxCzAJBgNVBAgM
|
||||
AkNBMRQwEgYDVQQHDAtMb3MgQW5nZWxlczEbMBkGA1UECgwScmlwcGxlZC11bml0
|
||||
LXRlc3RzMRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD
|
||||
ggEPADCCAQoCggEBAO9oqh72ttM7hjPnbMcJw0EuyULocEn2hlg4HE4YtzaxlRIz
|
||||
dHm8nMkG/9yGmHBCuue/Gzssm/CzlduGezae01p8eaFUuEJsjxdrXe89Wk2QH+dm
|
||||
Fn+SRbGcHaaTV/cyJrvusG7pOu95HL2eebuwiZ+tX5JP01R732iQt8Beeygh/W4P
|
||||
n2f//fAxbdAIWzx2DH6cmSNe6lpoQe/MN15o8V3whutcC3fkis6wcA7BKZcdVdL2
|
||||
daFWA6mt4SPWldOfWQVAIX4vRvheWPy34OLCgx+wZWg691Lwd1F+paarKombatUt
|
||||
vKMTeolFYl3zkZZMYvR0Oyrt5NXUhRfmG7xR3bkCAwEAATANBgkqhkiG9w0BAQsF
|
||||
AAOCAQEAggKO5WdtU67QPcAdo1Uar0SFouvVLwxJvoKlQ5rqF3idd0HnFVy7iojW
|
||||
G2sZq7z8SNDMkUXZLbcbYNRyrZI0PdjfI0kyNpaa3pEcPcR8aOcTEOtW6V67FrPG
|
||||
8aNYpr6a8PPq12aHzPSNjlUGot/qffGIQ0H2OqdWMOUXMMFnmH2KnnWi46Aq3gaF
|
||||
uyHGrEczjJAK7NTzP8A7fbrmT00Sn6ft1FriQyhvDkUgPXBGWKpOFO84V27oo0ZL
|
||||
xXQHDWcpX+8yNKynjafkXLx6qXwcySF2bKcTIRsxlN6WNRqZ+wqpNStkjuoFkYR/
|
||||
IfW9PBfO/gCtNJQ+lqpoTd3kLBCAng==
|
||||
-----END CERTIFICATE-----
|
||||
)cert"};
|
||||
return cert;
|
||||
}
|
||||
|
||||
static
|
||||
std::string const&
|
||||
dh()
|
||||
{
|
||||
static std::string const dh {R"dh(
|
||||
-----BEGIN DH PARAMETERS-----
|
||||
MIIBCAKCAQEAnJaaKu3U2a7ZVBvIC+NVNHXo9q6hNCazze+4pwXAKBVXH0ozInEw
|
||||
WKozYxVJLW7dvDHdjdFOSuTLQDqaPW9zVMQKM0BKu81+JyfJi7C3HYKUw7ECVHp4
|
||||
DLvhDe6N5eBj/t1FUwcfS2VNIx4QcJiw6FH3CwNNee1fIi5VTRJp2GLUuMCHkT/I
|
||||
FTODJ+Anw12cJqLdgQfV74UV/Y7JCQl3/DOIy+2YkmX8vWVHX1h6EI5Gw4a3jgqF
|
||||
gVyCOWoVCfgu37H5e7ERyoAxigiP8hMqoGpmJUYJghVKWoFgNUqXw+guVJ56eIuH
|
||||
0wVs/LXflOZ42PJAiwv4LTNOtpG2pWGjOwIBAg==
|
||||
-----END DH PARAMETERS-----
|
||||
)dh"};
|
||||
return dh;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
struct lambda
|
||||
{
|
||||
@@ -125,19 +415,25 @@ private:
|
||||
TrustedPublisherServer& self;
|
||||
socket_type sock;
|
||||
boost::asio::executor_work_guard<boost::asio::executor> work;
|
||||
bool ssl;
|
||||
|
||||
lambda(int id_, TrustedPublisherServer& self_, socket_type&& sock_)
|
||||
lambda(
|
||||
int id_,
|
||||
TrustedPublisherServer& self_,
|
||||
socket_type&& sock_,
|
||||
bool ssl_)
|
||||
: id(id_)
|
||||
, self(self_)
|
||||
, sock(std::move(sock_))
|
||||
, work(sock_.get_executor())
|
||||
, ssl(ssl_)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()()
|
||||
{
|
||||
self.do_peer(id, std::move(sock));
|
||||
self.do_peer(id, std::move(sock), ssl);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -150,7 +446,7 @@ private:
|
||||
return;
|
||||
|
||||
static int id_ = 0;
|
||||
std::thread{lambda{++id_, *this, std::move(sock_)}}.detach();
|
||||
std::thread{lambda{++id_, *this, std::move(sock_), useSSL_}}.detach();
|
||||
acceptor_.async_accept(
|
||||
sock_,
|
||||
std::bind(
|
||||
@@ -158,24 +454,44 @@ private:
|
||||
}
|
||||
|
||||
void
|
||||
do_peer(int id, socket_type&& sock0)
|
||||
do_peer(int id, socket_type&& s, bool ssl)
|
||||
{
|
||||
using namespace boost::beast;
|
||||
socket_type sock(std::move(sock0));
|
||||
multi_buffer sb;
|
||||
using namespace boost::asio;
|
||||
socket_type sock(std::move(s));
|
||||
flat_buffer sb;
|
||||
error_code ec;
|
||||
boost::optional<ssl_stream<ip::tcp::socket&>> ssl_stream;
|
||||
|
||||
if (ssl)
|
||||
{
|
||||
// Construct the stream around the socket
|
||||
ssl_stream.emplace(sock, sslCtx_);
|
||||
// Perform the SSL handshake
|
||||
ssl_stream->handshake(ssl::stream_base::server, ec);
|
||||
if(ec)
|
||||
return;
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
resp_type res;
|
||||
req_type req;
|
||||
try
|
||||
{
|
||||
http::read(sock, sb, req, ec);
|
||||
if (ssl)
|
||||
http::read(*ssl_stream, sb, req, ec);
|
||||
else
|
||||
http::read(sock, sb, req, ec);
|
||||
|
||||
if (ec)
|
||||
break;
|
||||
|
||||
auto path = req.target().to_string();
|
||||
res.insert("Server", "TrustedPublisherServer");
|
||||
res.version(req.version());
|
||||
res.keep_alive(req.keep_alive());
|
||||
bool prepare = true;
|
||||
|
||||
if (boost::starts_with(path, "/validators"))
|
||||
{
|
||||
@@ -195,6 +511,25 @@ private:
|
||||
res.body() = getList_(refresh);
|
||||
}
|
||||
}
|
||||
else if (boost::starts_with(path, "/textfile"))
|
||||
{
|
||||
prepare = false;
|
||||
res.result(http::status::ok);
|
||||
res.insert("Content-Type", "text/example");
|
||||
// if huge was requested, lie about content length
|
||||
std::uint64_t cl =
|
||||
boost::starts_with(path, "/textfile/huge") ?
|
||||
std::numeric_limits<uint64_t>::max() :
|
||||
1024;
|
||||
res.content_length(cl);
|
||||
if (req.method() == http::verb::get)
|
||||
{
|
||||
std::stringstream body;
|
||||
for (auto i=0; i<1024; ++i)
|
||||
body << static_cast<char>(rand_int<short>(32, 126)),
|
||||
res.body() = body.str();
|
||||
}
|
||||
}
|
||||
else if (boost::starts_with(path, "/sleep/"))
|
||||
{
|
||||
auto const sleep_sec =
|
||||
@@ -220,7 +555,8 @@ private:
|
||||
}
|
||||
else if (! boost::starts_with(path, "/redirect_nolo"))
|
||||
{
|
||||
location << "http://" << local_endpoint() <<
|
||||
location << (ssl ? "https://" : "http://") <<
|
||||
local_endpoint() <<
|
||||
(boost::starts_with(path, "/redirect_forever/") ?
|
||||
path : "/validators");
|
||||
}
|
||||
@@ -235,7 +571,8 @@ private:
|
||||
res.body() = "The file '" + path + "' was not found";
|
||||
}
|
||||
|
||||
res.prepare_payload();
|
||||
if (prepare)
|
||||
res.prepare_payload();
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
@@ -247,10 +584,19 @@ private:
|
||||
res.body() = std::string{"An internal error occurred"} + e.what();
|
||||
res.prepare_payload();
|
||||
}
|
||||
write(sock, res, ec);
|
||||
if (ec)
|
||||
|
||||
if (ssl)
|
||||
write(*ssl_stream, res, ec);
|
||||
else
|
||||
write(sock, res, ec);
|
||||
|
||||
if (ec || req.need_eof())
|
||||
break;
|
||||
}
|
||||
|
||||
// Perform the SSL shutdown
|
||||
if (ssl)
|
||||
ssl_stream->shutdown(ec);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ setupConfigForUnitTests (Config& cfg)
|
||||
cfg["port_ws"].set("admin", getEnvLocalhostAddr());
|
||||
cfg["port_ws"].set("port", port_ws);
|
||||
cfg["port_ws"].set("protocol", "ws");
|
||||
cfg.SSL_VERIFY = false;
|
||||
}
|
||||
|
||||
namespace jtx {
|
||||
|
||||
@@ -30,25 +30,6 @@ namespace ripple {
|
||||
|
||||
class Invariants_test : public beast::unit_test::suite
|
||||
{
|
||||
|
||||
class TestSink : public beast::Journal::Sink
|
||||
{
|
||||
public:
|
||||
std::stringstream strm_;
|
||||
|
||||
TestSink () : Sink (beast::severities::kWarning, false) { }
|
||||
|
||||
void
|
||||
write (beast::severities::Severity level,
|
||||
std::string const& text) override
|
||||
{
|
||||
if (level < threshold())
|
||||
return;
|
||||
|
||||
strm_ << text << std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
// this is common setup/method for running a failing invariant check. The
|
||||
// precheck function is used to manipulate the ApplyContext with view
|
||||
// changes that will cause the check to fail.
|
||||
@@ -87,7 +68,7 @@ class Invariants_test : public beast::unit_test::suite
|
||||
auto tx = STTx {ttACCOUNT_SET, [](STObject&){ } };
|
||||
txmod(tx);
|
||||
OpenView ov {*env.current()};
|
||||
TestSink sink;
|
||||
test::StreamSink sink {beast::severities::kWarning};
|
||||
beast::Journal jlog {sink};
|
||||
ApplyContext ac {
|
||||
env.app(),
|
||||
@@ -112,20 +93,20 @@ class Invariants_test : public beast::unit_test::suite
|
||||
? TER {tecINVARIANT_FAILED}
|
||||
: TER {tefINVARIANT_FAILED}));
|
||||
BEAST_EXPECT(
|
||||
boost::starts_with(sink.strm_.str(), "Invariant failed:") ||
|
||||
boost::starts_with(sink.strm_.str(),
|
||||
boost::starts_with(sink.messages().str(), "Invariant failed:") ||
|
||||
boost::starts_with(sink.messages().str(),
|
||||
"Transaction caused an exception"));
|
||||
//uncomment if you want to log the invariant failure message
|
||||
//log << " --> " << sink.strm_.str() << std::endl;
|
||||
//log << " --> " << sink.messages().str() << std::endl;
|
||||
for (auto const& m : expect_logs)
|
||||
{
|
||||
BEAST_EXPECT(sink.strm_.str().find(m) != std::string::npos);
|
||||
BEAST_EXPECT(sink.messages().str().find(m) != std::string::npos);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(tr == tesSUCCESS);
|
||||
BEAST_EXPECT(sink.strm_.str().empty());
|
||||
BEAST_EXPECT(sink.messages().str().empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
267
src/test/net/SSLHTTPDownloader_test.cpp
Normal file
267
src/test/net/SSLHTTPDownloader_test.cpp
Normal file
@@ -0,0 +1,267 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright 2019 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/net/SSLHTTPDownloader.h>
|
||||
#include <test/jtx.h>
|
||||
#include <test/jtx/TrustedPublisherServer.h>
|
||||
#include <test/unit_test/FileDirGuard.h>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
class SSLHTTPDownloader_test : public beast::unit_test::suite
|
||||
{
|
||||
TrustedPublisherServer createServer(
|
||||
jtx::Env& env,
|
||||
bool ssl = true)
|
||||
{
|
||||
std::vector<TrustedPublisherServer::Validator> list;
|
||||
list.push_back (TrustedPublisherServer::randomValidator());
|
||||
return TrustedPublisherServer {
|
||||
env.app().getIOService(),
|
||||
list,
|
||||
env.timeKeeper().now() + std::chrono::seconds{3600},
|
||||
ssl};
|
||||
}
|
||||
|
||||
struct DownloadCompleter
|
||||
{
|
||||
std::mutex m;
|
||||
std::condition_variable cv;
|
||||
bool called = false;
|
||||
boost::filesystem::path dest;
|
||||
|
||||
void operator ()(boost::filesystem::path dst)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
called = true;
|
||||
dest = std::move(dst);
|
||||
cv.notify_one();
|
||||
};
|
||||
|
||||
bool waitComplete()
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
using namespace std::chrono_literals;
|
||||
#if BOOST_OS_WINDOWS
|
||||
auto constexpr timeout = 4s;
|
||||
#else
|
||||
auto constexpr timeout = 2s;
|
||||
#endif
|
||||
auto stat = cv.wait_for(lk, timeout, [this]{return called;});
|
||||
called = false;
|
||||
return stat;
|
||||
};
|
||||
};
|
||||
DownloadCompleter cb;
|
||||
|
||||
struct Downloader
|
||||
{
|
||||
test::StreamSink sink_;
|
||||
beast::Journal journal_;
|
||||
// The SSLHTTPDownloader must be created as shared_ptr
|
||||
// because it uses shared_from_this
|
||||
std::shared_ptr<SSLHTTPDownloader> ptr_;
|
||||
|
||||
Downloader(jtx::Env& env)
|
||||
: journal_ {sink_}
|
||||
, ptr_ {std::make_shared<SSLHTTPDownloader>(
|
||||
env.app().getIOService(), journal_, env.app().config())}
|
||||
{}
|
||||
|
||||
SSLHTTPDownloader* operator->()
|
||||
{
|
||||
return ptr_.get();
|
||||
}
|
||||
};
|
||||
|
||||
void
|
||||
testDownload(bool verify)
|
||||
{
|
||||
testcase <<
|
||||
std::string("Basic download - SSL ") +
|
||||
(verify ? "Verify" : "No Verify");
|
||||
|
||||
using namespace jtx;
|
||||
ripple::test::detail::FileDirGuard cert {
|
||||
*this, "_cert", "ca.pem", TrustedPublisherServer::ca_cert()};
|
||||
|
||||
Env env {*this, envconfig([&cert, &verify](std::unique_ptr<Config> cfg)
|
||||
{
|
||||
if ((cfg->SSL_VERIFY = verify)) //yes, this is assignment
|
||||
cfg->SSL_VERIFY_FILE = cert.file().string();
|
||||
return cfg;
|
||||
})};
|
||||
|
||||
Downloader downloader {env};
|
||||
|
||||
// create a TrustedPublisherServer as a simple HTTP
|
||||
// server to request from. Use the /textfile endpoint
|
||||
// to get a simple text file sent as response.
|
||||
auto server = createServer(env);
|
||||
|
||||
ripple::test::detail::FileDirGuard const data {
|
||||
*this, "downloads", "data", "", false, false};
|
||||
// initiate the download and wait for the callback
|
||||
// to be invoked
|
||||
auto stat = downloader->download(
|
||||
server.local_endpoint().address().to_string(),
|
||||
std::to_string(server.local_endpoint().port()),
|
||||
"/textfile",
|
||||
11,
|
||||
data.file(),
|
||||
std::function<void(boost::filesystem::path)> {std::ref(cb)});
|
||||
if (!BEAST_EXPECT(stat))
|
||||
{
|
||||
log << "Failed. LOGS:\n" + downloader.sink_.messages().str();
|
||||
return;
|
||||
}
|
||||
if (!BEAST_EXPECT(cb.waitComplete()))
|
||||
{
|
||||
log << "Failed. LOGS:\n" + downloader.sink_.messages().str();
|
||||
return;
|
||||
}
|
||||
BEAST_EXPECT(cb.dest == data.file());
|
||||
if (!BEAST_EXPECT(boost::filesystem::exists(data.file())))
|
||||
return;
|
||||
BEAST_EXPECT(boost::filesystem::file_size(data.file()) > 0);
|
||||
}
|
||||
|
||||
void
|
||||
testFailures()
|
||||
{
|
||||
testcase("Error conditions");
|
||||
using namespace jtx;
|
||||
Env env {*this};
|
||||
|
||||
{
|
||||
// file exists
|
||||
Downloader dl {env};
|
||||
ripple::test::detail::FileDirGuard const datafile {
|
||||
*this, "downloads", "data", "file contents"};
|
||||
BEAST_EXPECT(!dl->download(
|
||||
"localhost",
|
||||
"443",
|
||||
"",
|
||||
11,
|
||||
datafile.file(),
|
||||
std::function<void(boost::filesystem::path)> {std::ref(cb)}));
|
||||
}
|
||||
{
|
||||
// bad hostname
|
||||
Downloader dl {env};
|
||||
ripple::test::detail::FileDirGuard const datafile {
|
||||
*this, "downloads", "data", "", false, false};
|
||||
BEAST_EXPECT(dl->download(
|
||||
"badhostname",
|
||||
"443",
|
||||
"",
|
||||
11,
|
||||
datafile.file(),
|
||||
std::function<void(boost::filesystem::path)> {std::ref(cb)}));
|
||||
BEAST_EXPECT(cb.waitComplete());
|
||||
BEAST_EXPECT(!boost::filesystem::exists(datafile.file()));
|
||||
BEAST_EXPECTS(
|
||||
dl.sink_.messages().str().find("async_resolve")
|
||||
!= std::string::npos,
|
||||
dl.sink_.messages().str());
|
||||
}
|
||||
{
|
||||
// can't connect
|
||||
Downloader dl {env};
|
||||
ripple::test::detail::FileDirGuard const datafile {
|
||||
*this, "downloads", "data", "", false, false};
|
||||
auto server = createServer(env);
|
||||
auto host = server.local_endpoint().address().to_string();
|
||||
auto port = std::to_string(server.local_endpoint().port());
|
||||
server.stop();
|
||||
BEAST_EXPECT(dl->download(
|
||||
host,
|
||||
port,
|
||||
"",
|
||||
11,
|
||||
datafile.file(),
|
||||
std::function<void(boost::filesystem::path)> {std::ref(cb)}));
|
||||
BEAST_EXPECT(cb.waitComplete());
|
||||
BEAST_EXPECT(!boost::filesystem::exists(datafile.file()));
|
||||
BEAST_EXPECTS(
|
||||
dl.sink_.messages().str().find("async_connect")
|
||||
!= std::string::npos,
|
||||
dl.sink_.messages().str());
|
||||
}
|
||||
{
|
||||
// not ssl (failed handlshake)
|
||||
Downloader dl {env};
|
||||
ripple::test::detail::FileDirGuard const datafile {
|
||||
*this, "downloads", "data", "", false, false};
|
||||
auto server = createServer(env, false);
|
||||
BEAST_EXPECT(dl->download(
|
||||
server.local_endpoint().address().to_string(),
|
||||
std::to_string(server.local_endpoint().port()),
|
||||
"",
|
||||
11,
|
||||
datafile.file(),
|
||||
std::function<void(boost::filesystem::path)> {std::ref(cb)}));
|
||||
BEAST_EXPECT(cb.waitComplete());
|
||||
BEAST_EXPECT(!boost::filesystem::exists(datafile.file()));
|
||||
BEAST_EXPECTS(
|
||||
dl.sink_.messages().str().find("async_handshake")
|
||||
!= std::string::npos,
|
||||
dl.sink_.messages().str());
|
||||
}
|
||||
{
|
||||
// huge file (content length)
|
||||
Downloader dl {env};
|
||||
ripple::test::detail::FileDirGuard const datafile {
|
||||
*this, "downloads", "data", "", false, false};
|
||||
auto server = createServer(env);
|
||||
BEAST_EXPECT(dl->download(
|
||||
server.local_endpoint().address().to_string(),
|
||||
std::to_string(server.local_endpoint().port()),
|
||||
"/textfile/huge",
|
||||
11,
|
||||
datafile.file(),
|
||||
std::function<void(boost::filesystem::path)> {std::ref(cb)}));
|
||||
BEAST_EXPECT(cb.waitComplete());
|
||||
BEAST_EXPECT(!boost::filesystem::exists(datafile.file()));
|
||||
BEAST_EXPECTS(
|
||||
dl.sink_.messages().str().find("Insufficient disk space")
|
||||
!= std::string::npos,
|
||||
dl.sink_.messages().str());
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testDownload(true);
|
||||
testDownload(false);
|
||||
testFailures();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(SSLHTTPDownloader, net, ripple);
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -38,47 +38,6 @@ class ValidatorRPC_test : public beast::unit_test::suite
|
||||
{
|
||||
using Validator = TrustedPublisherServer::Validator;
|
||||
|
||||
static
|
||||
Validator
|
||||
randomValidator ()
|
||||
{
|
||||
auto const secret = randomSecretKey();
|
||||
auto const masterPublic =
|
||||
derivePublicKey(KeyType::ed25519, secret);
|
||||
auto const signingKeys = randomKeyPair(KeyType::secp256k1);
|
||||
return { masterPublic, signingKeys.first, makeManifestString (
|
||||
masterPublic, secret, signingKeys.first, signingKeys.second, 1) };
|
||||
}
|
||||
|
||||
static
|
||||
std::string
|
||||
makeManifestString(
|
||||
PublicKey const& pk,
|
||||
SecretKey const& sk,
|
||||
PublicKey const& spk,
|
||||
SecretKey const& ssk,
|
||||
int seq)
|
||||
{
|
||||
STObject st(sfGeneric);
|
||||
st[sfSequence] = seq;
|
||||
st[sfPublicKey] = pk;
|
||||
st[sfSigningPubKey] = spk;
|
||||
|
||||
sign(st, HashPrefix::manifest, *publicKeyType(spk), ssk);
|
||||
sign(
|
||||
st,
|
||||
HashPrefix::manifest,
|
||||
*publicKeyType(pk),
|
||||
sk,
|
||||
sfMasterSignature);
|
||||
|
||||
Serializer s;
|
||||
st.add(s);
|
||||
|
||||
return base64_encode(
|
||||
std::string(static_cast<char const*>(s.data()), s.size()));
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
testPrivileges()
|
||||
@@ -184,24 +143,29 @@ public:
|
||||
return toBase58(TokenType::NodePublic, publicKey);
|
||||
};
|
||||
|
||||
// Publisher manifest/signing keys
|
||||
auto const publisherSecret = randomSecretKey();
|
||||
auto const publisherPublic =
|
||||
derivePublicKey(KeyType::ed25519, publisherSecret);
|
||||
auto const publisherSigningKeys = randomKeyPair(KeyType::secp256k1);
|
||||
auto const manifest = makeManifestString(
|
||||
publisherPublic,
|
||||
publisherSecret,
|
||||
publisherSigningKeys.first,
|
||||
publisherSigningKeys.second,
|
||||
1);
|
||||
|
||||
// Validator keys that will be in the published list
|
||||
std::vector<Validator> validators = {randomValidator(), randomValidator()};
|
||||
std::vector<Validator> validators = {
|
||||
TrustedPublisherServer::randomValidator(),
|
||||
TrustedPublisherServer::randomValidator()};
|
||||
std::set<std::string> expectedKeys;
|
||||
for (auto const& val : validators)
|
||||
expectedKeys.insert(toStr(val.masterPublic));
|
||||
|
||||
// Manage single thread io_service for server
|
||||
struct Worker : BasicApp
|
||||
{
|
||||
Worker() : BasicApp(1) {}
|
||||
};
|
||||
Worker w;
|
||||
using namespace std::chrono_literals;
|
||||
NetClock::time_point const expiration{3600s};
|
||||
TrustedPublisherServer server{
|
||||
w.get_io_service(),
|
||||
validators,
|
||||
expiration,
|
||||
false,
|
||||
1,
|
||||
false};
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Publisher list site unavailable
|
||||
@@ -216,7 +180,7 @@ public:
|
||||
envconfig([&](std::unique_ptr<Config> cfg) {
|
||||
cfg->section(SECTION_VALIDATOR_LIST_SITES).append(siteURI);
|
||||
cfg->section(SECTION_VALIDATOR_LIST_KEYS)
|
||||
.append(strHex(publisherPublic));
|
||||
.append(strHex(server.publisherPublic()));
|
||||
return cfg;
|
||||
}),
|
||||
};
|
||||
@@ -253,7 +217,7 @@ public:
|
||||
BEAST_EXPECT(!jp.isMember(jss::expiration));
|
||||
BEAST_EXPECT(!jp.isMember(jss::version));
|
||||
BEAST_EXPECT(
|
||||
jp[jss::pubkey_publisher] == strHex(publisherPublic));
|
||||
jp[jss::pubkey_publisher] == strHex(server.publisherPublic()));
|
||||
}
|
||||
BEAST_EXPECT(jrr[jss::signing_keys].size() == 0);
|
||||
}
|
||||
@@ -272,24 +236,7 @@ public:
|
||||
//----------------------------------------------------------------------
|
||||
// Publisher list site available
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
NetClock::time_point const expiration{3600s};
|
||||
|
||||
// Manage single thread io_service for server
|
||||
struct Worker : BasicApp
|
||||
{
|
||||
Worker() : BasicApp(1) {}
|
||||
};
|
||||
Worker w;
|
||||
|
||||
TrustedPublisherServer server(
|
||||
w.get_io_service(),
|
||||
publisherSigningKeys,
|
||||
manifest,
|
||||
1,
|
||||
expiration,
|
||||
1,
|
||||
validators);
|
||||
server.start();
|
||||
|
||||
std::stringstream uri;
|
||||
uri << "http://" << server.local_endpoint() << "/validators";
|
||||
@@ -300,7 +247,7 @@ public:
|
||||
envconfig([&](std::unique_ptr<Config> cfg) {
|
||||
cfg->section(SECTION_VALIDATOR_LIST_SITES).append(siteURI);
|
||||
cfg->section(SECTION_VALIDATOR_LIST_KEYS)
|
||||
.append(strHex(publisherPublic));
|
||||
.append(strHex(server.publisherPublic()));
|
||||
return cfg;
|
||||
}),
|
||||
};
|
||||
@@ -356,7 +303,7 @@ public:
|
||||
}
|
||||
BEAST_EXPECT(jp[jss::seq].asUInt() == 1);
|
||||
BEAST_EXPECT(
|
||||
jp[jss::pubkey_publisher] == strHex(publisherPublic));
|
||||
jp[jss::pubkey_publisher] == strHex(server.publisherPublic()));
|
||||
BEAST_EXPECT(jp[jss::expiration] == to_string(expiration));
|
||||
BEAST_EXPECT(jp[jss::version] == 1);
|
||||
}
|
||||
|
||||
@@ -111,18 +111,23 @@ class FileDirGuard : public DirGuard
|
||||
{
|
||||
protected:
|
||||
path const file_;
|
||||
bool created_ = false;
|
||||
|
||||
public:
|
||||
FileDirGuard(beast::unit_test::suite& test,
|
||||
path subDir, path file, std::string const& contents,
|
||||
bool useCounter = true)
|
||||
bool useCounter = true, bool create = true)
|
||||
: DirGuard(test, subDir, useCounter)
|
||||
, file_(file.is_absolute() ? file : subdir() / file)
|
||||
{
|
||||
if (!exists (file_))
|
||||
{
|
||||
std::ofstream o (file_.string ());
|
||||
o << contents;
|
||||
if (create)
|
||||
{
|
||||
std::ofstream o (file_.string ());
|
||||
o << contents;
|
||||
created_ = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -137,11 +142,16 @@ public:
|
||||
try
|
||||
{
|
||||
using namespace boost::filesystem;
|
||||
if (!exists (file_))
|
||||
test_.log << "Expected " << file_.string ()
|
||||
<< " to be an existing file." << std::endl;
|
||||
else
|
||||
if (exists (file_))
|
||||
{
|
||||
remove (file_);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (created_)
|
||||
test_.log << "Expected " << file_.string ()
|
||||
<< " to be an existing file." << std::endl;
|
||||
}
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
|
||||
@@ -94,6 +94,29 @@ public:
|
||||
operator beast::Journal&() { return journal_; }
|
||||
};
|
||||
|
||||
// this sink can be used to create a custom journal
|
||||
// whose log messages will be captured to a stringstream
|
||||
// that can be later inspected.
|
||||
class StreamSink : public beast::Journal::Sink
|
||||
{
|
||||
std::stringstream strm_;
|
||||
public:
|
||||
StreamSink (
|
||||
beast::severities::Severity threshold = beast::severities::kDebug)
|
||||
: Sink (threshold, false) { }
|
||||
|
||||
void
|
||||
write (beast::severities::Severity level,
|
||||
std::string const& text) override
|
||||
{
|
||||
if (level < threshold())
|
||||
return;
|
||||
|
||||
strm_ << text << std::endl;
|
||||
}
|
||||
std::stringstream const& messages() const { return strm_ ; }
|
||||
};
|
||||
|
||||
} // test
|
||||
} // ripple
|
||||
|
||||
|
||||
21
src/test/unity/net_test_unity.cpp
Normal file
21
src/test/unity/net_test_unity.cpp
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2019 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 <test/net/SSLHTTPDownloader_test.cpp>
|
||||
Reference in New Issue
Block a user