mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-04 10:45:50 +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/crypto_test_unity.cpp
|
||||||
src/test/unity/json_test_unity.cpp
|
src/test/unity/json_test_unity.cpp
|
||||||
src/test/unity/ledger_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/nodestore_test_unity.cpp
|
||||||
src/test/unity/overlay_test_unity.cpp
|
src/test/unity/overlay_test_unity.cpp
|
||||||
src/test/unity/peerfinder_test_unity.cpp
|
src/test/unity/peerfinder_test_unity.cpp
|
||||||
@@ -2547,6 +2548,11 @@ else ()
|
|||||||
src/test/ledger/SHAMapV2_test.cpp
|
src/test/ledger/SHAMapV2_test.cpp
|
||||||
src/test/ledger/SkipList_test.cpp
|
src/test/ledger/SkipList_test.cpp
|
||||||
src/test/ledger/View_test.cpp
|
src/test/ledger/View_test.cpp
|
||||||
|
#[===============================[
|
||||||
|
nounity, test sources:
|
||||||
|
subdir: net
|
||||||
|
#]===============================]
|
||||||
|
src/test/net/SSLHTTPDownloader_test.cpp
|
||||||
#[===============================[
|
#[===============================[
|
||||||
nounity, test sources:
|
nounity, test sources:
|
||||||
subdir: nodestore
|
subdir: nodestore
|
||||||
|
|||||||
@@ -508,8 +508,7 @@ public:
|
|||||||
*validatorManifests_, *publisherManifests_, *timeKeeper_,
|
*validatorManifests_, *publisherManifests_, *timeKeeper_,
|
||||||
logs_->journal("ValidatorList"), config_->VALIDATION_QUORUM))
|
logs_->journal("ValidatorList"), config_->VALIDATION_QUORUM))
|
||||||
|
|
||||||
, validatorSites_ (std::make_unique<ValidatorSite> (
|
, validatorSites_ (std::make_unique<ValidatorSite> (*this))
|
||||||
get_io_service (), *validators_, logs_->journal("ValidatorSite")))
|
|
||||||
|
|
||||||
, serverHandler_ (make_ServerHandler (*this, *m_networkOPs, get_io_service (),
|
, serverHandler_ (make_ServerHandler (*this, *m_networkOPs, get_io_service (),
|
||||||
*m_jobQueue, *m_networkOPs, *m_resourceManager,
|
*m_jobQueue, *m_networkOPs, *m_resourceManager,
|
||||||
|
|||||||
@@ -22,10 +22,13 @@
|
|||||||
|
|
||||||
#include <ripple/app/misc/ValidatorList.h>
|
#include <ripple/app/misc/ValidatorList.h>
|
||||||
#include <ripple/app/misc/detail/Work.h>
|
#include <ripple/app/misc/detail/Work.h>
|
||||||
|
#include <ripple/app/main/Application.h>
|
||||||
#include <ripple/basics/Log.h>
|
#include <ripple/basics/Log.h>
|
||||||
#include <ripple/basics/StringUtilities.h>
|
#include <ripple/basics/StringUtilities.h>
|
||||||
|
#include <ripple/core/Config.h>
|
||||||
#include <ripple/json/json_value.h>
|
#include <ripple/json/json_value.h>
|
||||||
#include <boost/asio.hpp>
|
#include <boost/asio.hpp>
|
||||||
|
#include <boost/optional.hpp>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
@@ -106,9 +109,9 @@ private:
|
|||||||
boost::optional<Status> lastRefreshStatus;
|
boost::optional<Status> lastRefreshStatus;
|
||||||
};
|
};
|
||||||
|
|
||||||
boost::asio::io_service& ios_;
|
Application& app_;
|
||||||
ValidatorList& validators_;
|
|
||||||
beast::Journal j_;
|
beast::Journal j_;
|
||||||
|
|
||||||
std::mutex mutable sites_mutex_;
|
std::mutex mutable sites_mutex_;
|
||||||
std::mutex mutable state_mutex_;
|
std::mutex mutable state_mutex_;
|
||||||
|
|
||||||
@@ -131,9 +134,8 @@ private:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
ValidatorSite (
|
ValidatorSite (
|
||||||
boost::asio::io_service& ios,
|
Application& app,
|
||||||
ValidatorList& validators,
|
boost::optional<beast::Journal> j = boost::none,
|
||||||
beast::Journal j,
|
|
||||||
std::chrono::seconds timeout = std::chrono::seconds{20});
|
std::chrono::seconds timeout = std::chrono::seconds{20});
|
||||||
~ValidatorSite ();
|
~ValidatorSite ();
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,9 @@
|
|||||||
#define RIPPLE_APP_MISC_DETAIL_WORKSSL_H_INCLUDED
|
#define RIPPLE_APP_MISC_DETAIL_WORKSSL_H_INCLUDED
|
||||||
|
|
||||||
#include <ripple/app/misc/detail/WorkBase.h>
|
#include <ripple/app/misc/detail/WorkBase.h>
|
||||||
#include <ripple/net/RegisterSSLCerts.h>
|
|
||||||
#include <ripple/basics/contract.h>
|
#include <ripple/basics/contract.h>
|
||||||
|
#include <ripple/core/Config.h>
|
||||||
|
#include <ripple/net/HTTPClientSSLContext.h>
|
||||||
#include <boost/asio/ssl.hpp>
|
#include <boost/asio/ssl.hpp>
|
||||||
#include <boost/bind.hpp>
|
#include <boost/bind.hpp>
|
||||||
#include <boost/format.hpp>
|
#include <boost/format.hpp>
|
||||||
@@ -31,24 +32,6 @@ namespace ripple {
|
|||||||
|
|
||||||
namespace detail {
|
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
|
// Work over SSL
|
||||||
class WorkSSL : public WorkBase<WorkSSL>
|
class WorkSSL : public WorkBase<WorkSSL>
|
||||||
, public std::enable_shared_from_this<WorkSSL>
|
, public std::enable_shared_from_this<WorkSSL>
|
||||||
@@ -58,7 +41,7 @@ class WorkSSL : public WorkBase<WorkSSL>
|
|||||||
private:
|
private:
|
||||||
using stream_type = boost::asio::ssl::stream<socket_type&>;
|
using stream_type = boost::asio::ssl::stream<socket_type&>;
|
||||||
|
|
||||||
SSLContext context_;
|
HTTPClientSSLContext context_;
|
||||||
stream_type stream_;
|
stream_type stream_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -68,6 +51,7 @@ public:
|
|||||||
std::string const& port,
|
std::string const& port,
|
||||||
boost::asio::io_service& ios,
|
boost::asio::io_service& ios,
|
||||||
beast::Journal j,
|
beast::Journal j,
|
||||||
|
Config const& config,
|
||||||
callback_type cb);
|
callback_type cb);
|
||||||
~WorkSSL() = default;
|
~WorkSSL() = default;
|
||||||
|
|
||||||
@@ -83,15 +67,6 @@ private:
|
|||||||
|
|
||||||
void
|
void
|
||||||
onHandshake(error_code const& ec);
|
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,
|
std::string const& port,
|
||||||
boost::asio::io_service& ios,
|
boost::asio::io_service& ios,
|
||||||
beast::Journal j,
|
beast::Journal j,
|
||||||
|
Config const& config,
|
||||||
callback_type cb)
|
callback_type cb)
|
||||||
: WorkBase(host, path, port, ios, cb)
|
: WorkBase(host, path, port, ios, cb)
|
||||||
, context_(j)
|
, context_(config, j, boost::asio::ssl::context::tlsv12_client)
|
||||||
, stream_(socket_, context_)
|
, stream_(socket_, context_.context())
|
||||||
{
|
{
|
||||||
// Set SNI hostname
|
auto ec = context_.preConnectVerify(stream_, host_);
|
||||||
SSL_set_tlsext_host_name(stream_.native_handle(), host.c_str());
|
if (ec)
|
||||||
stream_.set_verify_mode (boost::asio::ssl::verify_peer);
|
Throw<std::runtime_error> (
|
||||||
stream_.set_verify_callback( std::bind (
|
boost::str (boost::format ("preConnectVerify: %s") % ec.message ()));
|
||||||
&WorkSSL::rfc2818_verify, host_,
|
|
||||||
std::placeholders::_1, std::placeholders::_2));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
WorkSSL::onConnect(error_code const& ec)
|
WorkSSL::onConnect(error_code const& ec)
|
||||||
{
|
{
|
||||||
if (ec)
|
auto err = ec ? ec : context_.postConnectVerify(stream_, host_);
|
||||||
return fail(ec);
|
if (err)
|
||||||
|
return fail(err);
|
||||||
|
|
||||||
stream_.async_handshake(
|
stream_.async_handshake(
|
||||||
boost::asio::ssl::stream_base::client,
|
boost::asio::ssl::stream_base::client,
|
||||||
|
|||||||
@@ -88,18 +88,16 @@ ValidatorSite::Site::Site (std::string uri)
|
|||||||
}
|
}
|
||||||
|
|
||||||
ValidatorSite::ValidatorSite (
|
ValidatorSite::ValidatorSite (
|
||||||
boost::asio::io_service& ios,
|
Application& app,
|
||||||
ValidatorList& validators,
|
boost::optional<beast::Journal> j,
|
||||||
beast::Journal j,
|
|
||||||
std::chrono::seconds timeout)
|
std::chrono::seconds timeout)
|
||||||
: ios_ (ios)
|
: app_ {app}
|
||||||
, validators_ (validators)
|
, j_ {j ? *j : app_.logs().journal("ValidatorSite") }
|
||||||
, j_ (j)
|
, timer_ {app_.getIOService()}
|
||||||
, timer_ (ios_)
|
, fetching_ {false}
|
||||||
, fetching_ (false)
|
, pending_ {false}
|
||||||
, pending_ (false)
|
, stopping_ {false}
|
||||||
, stopping_ (false)
|
, requestTimeout_ {timeout}
|
||||||
, requestTimeout_ (timeout)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,12 +256,14 @@ ValidatorSite::makeRequest (
|
|||||||
|
|
||||||
if (resource->pUrl.scheme == "https")
|
if (resource->pUrl.scheme == "https")
|
||||||
{
|
{
|
||||||
|
// can throw...
|
||||||
sp = std::make_shared<detail::WorkSSL>(
|
sp = std::make_shared<detail::WorkSSL>(
|
||||||
resource->pUrl.domain,
|
resource->pUrl.domain,
|
||||||
resource->pUrl.path,
|
resource->pUrl.path,
|
||||||
std::to_string(*resource->pUrl.port),
|
std::to_string(*resource->pUrl.port),
|
||||||
ios_,
|
app_.getIOService(),
|
||||||
j_,
|
j_,
|
||||||
|
app_.config(),
|
||||||
onFetch);
|
onFetch);
|
||||||
}
|
}
|
||||||
else if(resource->pUrl.scheme == "http")
|
else if(resource->pUrl.scheme == "http")
|
||||||
@@ -272,7 +272,7 @@ ValidatorSite::makeRequest (
|
|||||||
resource->pUrl.domain,
|
resource->pUrl.domain,
|
||||||
resource->pUrl.path,
|
resource->pUrl.path,
|
||||||
std::to_string(*resource->pUrl.port),
|
std::to_string(*resource->pUrl.port),
|
||||||
ios_,
|
app_.getIOService(),
|
||||||
onFetch);
|
onFetch);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -280,7 +280,7 @@ ValidatorSite::makeRequest (
|
|||||||
BOOST_ASSERT(resource->pUrl.scheme == "file");
|
BOOST_ASSERT(resource->pUrl.scheme == "file");
|
||||||
sp = std::make_shared<detail::WorkFile>(
|
sp = std::make_shared<detail::WorkFile>(
|
||||||
resource->pUrl.path,
|
resource->pUrl.path,
|
||||||
ios_,
|
app_.getIOService(),
|
||||||
onFetchFile);
|
onFetchFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,7 +336,7 @@ ValidatorSite::onTimer (
|
|||||||
sites_[siteIdx].nextRefresh =
|
sites_[siteIdx].nextRefresh =
|
||||||
clock_type::now() + sites_[siteIdx].refreshInterval;
|
clock_type::now() + sites_[siteIdx].refreshInterval;
|
||||||
sites_[siteIdx].redirCount = 0;
|
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);
|
makeRequest(sites_[siteIdx].startingResource, siteIdx, lock);
|
||||||
}
|
}
|
||||||
catch (std::exception &)
|
catch (std::exception &)
|
||||||
@@ -376,7 +376,7 @@ ValidatorSite::parseJsonResponse (
|
|||||||
throw std::runtime_error{"missing fields"};
|
throw std::runtime_error{"missing fields"};
|
||||||
}
|
}
|
||||||
|
|
||||||
auto const disp = validators_.applyList (
|
auto const disp = app_.validators().applyList (
|
||||||
body["manifest"].asString (),
|
body["manifest"].asString (),
|
||||||
body["blob"].asString (),
|
body["blob"].asString (),
|
||||||
body["signature"].asString(),
|
body["signature"].asString(),
|
||||||
|
|||||||
@@ -116,40 +116,6 @@ public:
|
|||||||
return lowest_layer ().cancel (ec);
|
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)
|
void async_handshake (handshake_type type, callback cbFunc)
|
||||||
{
|
{
|
||||||
if ((type == ssl_socket::client) || (mSecure))
|
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/basics/Log.h>
|
||||||
#include <ripple/core/Config.h>
|
#include <ripple/core/Config.h>
|
||||||
|
#include <ripple/net/HTTPClientSSLContext.h>
|
||||||
|
|
||||||
#include <boost/asio/connect.hpp>
|
#include <boost/asio/connect.hpp>
|
||||||
#include <boost/asio/io_service.hpp>
|
#include <boost/asio/io_service.hpp>
|
||||||
@@ -48,10 +49,8 @@ public:
|
|||||||
|
|
||||||
SSLHTTPDownloader(
|
SSLHTTPDownloader(
|
||||||
boost::asio::io_service& io_service,
|
boost::asio::io_service& io_service,
|
||||||
beast::Journal j);
|
beast::Journal j,
|
||||||
|
Config const& config);
|
||||||
bool
|
|
||||||
init(Config const& config);
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
download(
|
download(
|
||||||
@@ -63,12 +62,11 @@ public:
|
|||||||
std::function<void(boost::filesystem::path)> complete);
|
std::function<void(boost::filesystem::path)> complete);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
boost::asio::ssl::context ctx_;
|
HTTPClientSSLContext ssl_ctx_;
|
||||||
boost::asio::io_service::strand strand_;
|
boost::asio::io_service::strand strand_;
|
||||||
boost::optional<
|
boost::optional<
|
||||||
boost::asio::ssl::stream<boost::asio::ip::tcp::socket>> stream_;
|
boost::asio::ssl::stream<boost::asio::ip::tcp::socket>> stream_;
|
||||||
boost::beast::flat_buffer read_buf_;
|
boost::beast::flat_buffer read_buf_;
|
||||||
bool ssl_verify_;
|
|
||||||
beast::Journal j_;
|
beast::Journal j_;
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -21,8 +21,8 @@
|
|||||||
#include <ripple/basics/Log.h>
|
#include <ripple/basics/Log.h>
|
||||||
#include <ripple/basics/StringUtilities.h>
|
#include <ripple/basics/StringUtilities.h>
|
||||||
#include <ripple/net/HTTPClient.h>
|
#include <ripple/net/HTTPClient.h>
|
||||||
|
#include <ripple/net/HTTPClientSSLContext.h>
|
||||||
#include <ripple/net/AutoSocket.h>
|
#include <ripple/net/AutoSocket.h>
|
||||||
#include <ripple/net/RegisterSSLCerts.h>
|
|
||||||
#include <ripple/beast/core/LexicalCast.h>
|
#include <ripple/beast/core/LexicalCast.h>
|
||||||
#include <boost/asio.hpp>
|
#include <boost/asio.hpp>
|
||||||
#include <boost/asio/ssl.hpp>
|
#include <boost/asio/ssl.hpp>
|
||||||
@@ -32,61 +32,6 @@
|
|||||||
|
|
||||||
namespace ripple {
|
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;
|
boost::optional<HTTPClientSSLContext> httpClientSSLContext;
|
||||||
|
|
||||||
void HTTPClient::initializeSSLContext (Config const& config, beast::Journal j)
|
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);
|
httpClientSSLContext.emplace (config, j);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Fetch a web page via http or https.
|
||||||
|
//
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
class HTTPClientImp
|
class HTTPClientImp
|
||||||
@@ -113,8 +62,6 @@ public:
|
|||||||
, mDeadline (io_service)
|
, mDeadline (io_service)
|
||||||
, j_ (j)
|
, j_ (j)
|
||||||
{
|
{
|
||||||
if (!httpClientSSLContext->sslVerify())
|
|
||||||
mSocket.SSLSocket ().set_verify_mode (boost::asio::ssl::verify_none);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
@@ -127,19 +74,24 @@ public:
|
|||||||
osRequest <<
|
osRequest <<
|
||||||
"GET " << strPath << " HTTP/1.0\r\n"
|
"GET " << strPath << " HTTP/1.0\r\n"
|
||||||
"Host: " << strHost << "\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";
|
"Connection: close\r\n\r\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
void request (
|
void
|
||||||
|
request(
|
||||||
bool bSSL,
|
bool bSSL,
|
||||||
std::deque<std::string> deqSites,
|
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::chrono::seconds timeout,
|
||||||
std::function<bool (const boost::system::error_code& ecResult,
|
std::function<bool (
|
||||||
int iStatus, std::string const& strData)> complete)
|
const boost::system::error_code& ecResult,
|
||||||
|
int iStatus,
|
||||||
|
std::string const& strData)> complete)
|
||||||
{
|
{
|
||||||
mSSL = bSSL;
|
mSSL = bSSL;
|
||||||
mDeqSites = deqSites;
|
mDeqSites = deqSites;
|
||||||
@@ -152,23 +104,28 @@ public:
|
|||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
void get (
|
void
|
||||||
bool bSSL,
|
get(bool bSSL,
|
||||||
std::deque<std::string> deqSites,
|
std::deque<std::string> deqSites,
|
||||||
std::string const& strPath,
|
std::string const& strPath,
|
||||||
std::chrono::seconds timeout,
|
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)
|
std::string const& strData)> complete)
|
||||||
{
|
{
|
||||||
|
|
||||||
mComplete = complete;
|
mComplete = complete;
|
||||||
mTimeout = timeout;
|
mTimeout = timeout;
|
||||||
|
|
||||||
request (
|
request (
|
||||||
bSSL,
|
bSSL,
|
||||||
deqSites,
|
deqSites,
|
||||||
std::bind (&HTTPClientImp::makeGet, shared_from_this (), strPath,
|
std::bind(
|
||||||
std::placeholders::_1, std::placeholders::_2),
|
&HTTPClientImp::makeGet,
|
||||||
|
shared_from_this(),
|
||||||
|
strPath,
|
||||||
|
std::placeholders::_1,
|
||||||
|
std::placeholders::_2),
|
||||||
timeout,
|
timeout,
|
||||||
complete);
|
complete);
|
||||||
}
|
}
|
||||||
@@ -225,7 +182,8 @@ public:
|
|||||||
}
|
}
|
||||||
else if (ecResult)
|
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.
|
// Can't do anything sound.
|
||||||
abort ();
|
abort ();
|
||||||
@@ -236,7 +194,9 @@ public:
|
|||||||
|
|
||||||
// Mark us as shutting down.
|
// Mark us as shutting down.
|
||||||
// XXX Use our own error code.
|
// 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.
|
// Cancel any resolving.
|
||||||
mResolver.cancel ();
|
mResolver.cancel ();
|
||||||
@@ -256,7 +216,8 @@ public:
|
|||||||
{
|
{
|
||||||
if (ecResult)
|
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)
|
if (!mShutdown)
|
||||||
mShutdown = ecResult;
|
{
|
||||||
|
mShutdown =
|
||||||
|
ecResult ?
|
||||||
|
ecResult :
|
||||||
|
httpClientSSLContext->preConnectVerify (
|
||||||
|
mSocket.SSLSocket(), mDeqSites[0]);
|
||||||
|
}
|
||||||
|
|
||||||
if (mShutdown)
|
if (mShutdown)
|
||||||
{
|
{
|
||||||
JLOG (j_.trace()) << "Resolve error: " << mDeqSites[0] << ": " << mShutdown.message ();
|
JLOG (j_.trace()) << "Resolve error: "
|
||||||
|
<< mDeqSites[0] << ": " << mShutdown.message ();
|
||||||
|
|
||||||
invokeComplete (mShutdown);
|
invokeComplete (mShutdown);
|
||||||
}
|
}
|
||||||
@@ -278,12 +246,6 @@ public:
|
|||||||
{
|
{
|
||||||
JLOG (j_.trace()) << "Resolve complete.";
|
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 (
|
boost::asio::async_connect (
|
||||||
mSocket.lowest_layer (),
|
mSocket.lowest_layer (),
|
||||||
itrEndpoint,
|
itrEndpoint,
|
||||||
@@ -308,14 +270,13 @@ public:
|
|||||||
{
|
{
|
||||||
JLOG (j_.trace()) << "Connected.";
|
JLOG (j_.trace()) << "Connected.";
|
||||||
|
|
||||||
if (httpClientSSLContext->sslVerify ())
|
mShutdown = httpClientSSLContext->postConnectVerify (
|
||||||
{
|
mSocket.SSLSocket(), mDeqSites[0]);
|
||||||
mShutdown = mSocket.verify (mDeqSites[0]);
|
|
||||||
|
|
||||||
if (mShutdown)
|
if (mShutdown)
|
||||||
{
|
{
|
||||||
JLOG (j_.trace()) << "set_verify_callback: " << mDeqSites[0] << ": " << mShutdown.message ();
|
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)
|
if (!mShutdown)
|
||||||
mShutdown = ecResult;
|
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 << "\"";
|
JLOG (j_.trace()) << "Header: \"" << strHeader << "\"";
|
||||||
|
|
||||||
static boost::regex reStatus ("\\`HTTP/1\\S+ (\\d{3}) .*\\'"); // HTTP/1.1 200 OK
|
static boost::regex reStatus {
|
||||||
static boost::regex reSize ("\\`.*\\r\\nContent-Length:\\s+([0-9]+).*\\'");
|
"\\`HTTP/1\\S+ (\\d{3}) .*\\'"}; // HTTP/1.1 200 OK
|
||||||
static boost::regex reBody ("\\`.*\\r\\n\\r\\n(.*)\\'");
|
static boost::regex reSize {
|
||||||
|
"\\`.*\\r\\nContent-Length:\\s+([0-9]+).*\\'"};
|
||||||
|
static boost::regex reBody {
|
||||||
|
"\\`.*\\r\\n\\r\\n(.*)\\'"};
|
||||||
|
|
||||||
boost::smatch smMatch;
|
boost::smatch smMatch;
|
||||||
|
// Match status code.
|
||||||
bool bMatch = boost::regex_match (strHeader, smMatch, reStatus); // Match status code.
|
if (!boost::regex_match (strHeader, smMatch, reStatus))
|
||||||
|
|
||||||
if (!bMatch)
|
|
||||||
{
|
{
|
||||||
// XXX Use our own error code.
|
// XXX Use our own error code.
|
||||||
JLOG (j_.trace()) << "No status 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,7 +387,8 @@ public:
|
|||||||
mBody = smMatch[1];
|
mBody = smMatch[1];
|
||||||
|
|
||||||
if (boost::regex_match (strHeader, smMatch, reSize))
|
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)
|
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)
|
if (!mShutdown)
|
||||||
mShutdown = ecResult;
|
mShutdown = ecResult;
|
||||||
@@ -460,14 +434,19 @@ public:
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
mResponse.commit (bytes_transferred);
|
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);
|
invokeComplete (ecResult, mStatus, mBody + strBody);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call cancel the deadline timer and invoke the completion routine.
|
// 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;
|
boost::system::error_code ecCancel;
|
||||||
|
|
||||||
@@ -475,10 +454,12 @@ public:
|
|||||||
|
|
||||||
if (ecCancel)
|
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 ())
|
if (!mDeqSites.empty ())
|
||||||
{
|
{
|
||||||
@@ -492,7 +473,8 @@ public:
|
|||||||
// ecResult: !0 = had an error, last entry
|
// ecResult: !0 = had an error, last entry
|
||||||
// iStatus: result, if no error
|
// iStatus: result, if no error
|
||||||
// strData: data, 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)
|
if (!mDeqSites.empty () && bAgain)
|
||||||
@@ -515,8 +497,12 @@ private:
|
|||||||
const unsigned short mPort;
|
const unsigned short mPort;
|
||||||
int mResponseSize;
|
int mResponseSize;
|
||||||
int mStatus;
|
int mStatus;
|
||||||
std::function<void (boost::asio::streambuf& sb, std::string const& strHost)> mBuild;
|
std::function<void (boost::asio::streambuf& sb, std::string const& strHost)>
|
||||||
std::function<bool (const boost::system::error_code& ecResult, int iStatus, std::string const& strData)> mComplete;
|
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;
|
boost::asio::basic_waitable_timer<std::chrono::steady_clock> mDeadline;
|
||||||
|
|
||||||
@@ -566,15 +552,19 @@ void HTTPClient::get (
|
|||||||
client->get (bSSL, deqSites, strPath, timeout, complete);
|
client->get (bSSL, deqSites, strPath, timeout, complete);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HTTPClient::request (
|
void
|
||||||
|
HTTPClient::request(
|
||||||
bool bSSL,
|
bool bSSL,
|
||||||
boost::asio::io_service& io_service,
|
boost::asio::io_service& io_service,
|
||||||
std::string strSite,
|
std::string strSite,
|
||||||
const unsigned short port,
|
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::size_t responseMax,
|
||||||
std::chrono::seconds timeout,
|
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,
|
std::string const& strData)> complete,
|
||||||
beast::Journal& j)
|
beast::Journal& j)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,54 +18,20 @@
|
|||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
||||||
#include <ripple/net/SSLHTTPDownloader.h>
|
#include <ripple/net/SSLHTTPDownloader.h>
|
||||||
#include <ripple/net/RegisterSSLCerts.h>
|
|
||||||
#include <boost/asio/ssl.hpp>
|
#include <boost/asio/ssl.hpp>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
SSLHTTPDownloader::SSLHTTPDownloader(
|
SSLHTTPDownloader::SSLHTTPDownloader(
|
||||||
boost::asio::io_service& io_service,
|
boost::asio::io_service& io_service,
|
||||||
beast::Journal j)
|
beast::Journal j,
|
||||||
: ctx_(boost::asio::ssl::context::tlsv12_client)
|
Config const& config)
|
||||||
|
: ssl_ctx_(config, j, boost::asio::ssl::context::tlsv12_client)
|
||||||
, strand_(io_service)
|
, strand_(io_service)
|
||||||
, j_(j)
|
, 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
|
bool
|
||||||
SSLHTTPDownloader::download(
|
SSLHTTPDownloader::download(
|
||||||
std::string const& host,
|
std::string const& host,
|
||||||
@@ -139,7 +105,7 @@ SSLHTTPDownloader::do_session(
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
stream_.emplace(strand_.context(), ctx_);
|
stream_.emplace(strand_.context(), ssl_ctx_.context());
|
||||||
}
|
}
|
||||||
catch (std::exception const& e)
|
catch (std::exception const& e)
|
||||||
{
|
{
|
||||||
@@ -147,40 +113,18 @@ SSLHTTPDownloader::do_session(
|
|||||||
std::string("exception: ") + e.what());
|
std::string("exception: ") + e.what());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ssl_verify_)
|
ec = ssl_ctx_.preConnectVerify(*stream_, host);
|
||||||
{
|
if (ec)
|
||||||
// If we intend to verify the SSL connection, we need to set the
|
return fail(dstPath, complete, ec, "preConnectVerify");
|
||||||
// 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");
|
|
||||||
}
|
|
||||||
|
|
||||||
boost::asio::async_connect(
|
boost::asio::async_connect(
|
||||||
stream_->next_layer(), results.begin(), results.end(), yield[ec]);
|
stream_->next_layer(), results.begin(), results.end(), yield[ec]);
|
||||||
if (ec)
|
if (ec)
|
||||||
return fail(dstPath, complete, ec, "async_connect");
|
return fail(dstPath, complete, ec, "async_connect");
|
||||||
|
|
||||||
if (ssl_verify_)
|
ec = ssl_ctx_.postConnectVerify(*stream_, host);
|
||||||
{
|
if (ec)
|
||||||
stream_->set_verify_mode(boost::asio::ssl::verify_peer, ec);
|
return fail(dstPath, complete, ec, "postConnectVerify");
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
stream_->async_handshake(ssl::stream_base::client, yield[ec]);
|
stream_->async_handshake(ssl::stream_base::client, yield[ec]);
|
||||||
if (ec)
|
if (ec)
|
||||||
|
|||||||
@@ -113,6 +113,13 @@ ShardArchiveHandler::start()
|
|||||||
|
|
||||||
// Create temp root download directory
|
// Create temp root download directory
|
||||||
create_directory(downloadDir_);
|
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)
|
catch (std::exception const& e)
|
||||||
{
|
{
|
||||||
@@ -121,16 +128,6 @@ ShardArchiveHandler::start()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!downloader_)
|
|
||||||
{
|
|
||||||
downloader_ = std::make_shared<SSLHTTPDownloader>(
|
|
||||||
app_.getIOService(), j_);
|
|
||||||
if (!downloader_->init(app_.config()))
|
|
||||||
{
|
|
||||||
downloader_.reset();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return next(lock);
|
return next(lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,50 +60,6 @@ private:
|
|||||||
|
|
||||||
using Validator = TrustedPublisherServer::Validator;
|
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
|
void
|
||||||
testConfigLoad ()
|
testConfigLoad ()
|
||||||
{
|
{
|
||||||
@@ -113,7 +69,7 @@ private:
|
|||||||
|
|
||||||
Env env (*this);
|
Env env (*this);
|
||||||
auto trustedSites = std::make_unique<ValidatorSite> (
|
auto trustedSites = std::make_unique<ValidatorSite> (
|
||||||
env.app().getIOService(), env.app().validators(), env.journal);
|
env.app(), env.journal);
|
||||||
|
|
||||||
// load should accept empty sites list
|
// load should accept empty sites list
|
||||||
std::vector<std::string> emptyCfgSites;
|
std::vector<std::string> emptyCfgSites;
|
||||||
@@ -171,28 +127,11 @@ private:
|
|||||||
#endif
|
#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
|
struct FetchListConfig
|
||||||
{
|
{
|
||||||
std::string path;
|
std::string path;
|
||||||
std::string msg;
|
std::string msg;
|
||||||
|
bool ssl;
|
||||||
bool failFetch = false;
|
bool failFetch = false;
|
||||||
bool failApply = false;
|
bool failApply = false;
|
||||||
int serverVersion = 1;
|
int serverVersion = 1;
|
||||||
@@ -205,14 +144,15 @@ private:
|
|||||||
testcase << "Fetch list - " <<
|
testcase << "Fetch list - " <<
|
||||||
boost::algorithm::join (paths |
|
boost::algorithm::join (paths |
|
||||||
boost::adaptors::transformed(
|
boost::adaptors::transformed(
|
||||||
[](FetchListConfig const& cfg){ return cfg.path; }),
|
[](FetchListConfig const& cfg){
|
||||||
|
return cfg.path + (cfg.ssl ? " [https]" : " [http]");}),
|
||||||
", ");
|
", ");
|
||||||
using namespace jtx;
|
using namespace jtx;
|
||||||
|
|
||||||
Env env (*this);
|
Env env (*this);
|
||||||
auto& trustedKeys = env.app ().validators ();
|
auto& trustedKeys = env.app ().validators ();
|
||||||
|
|
||||||
TestSink sink;
|
test::StreamSink sink;
|
||||||
beast::Journal journal{sink};
|
beast::Journal journal{sink};
|
||||||
|
|
||||||
PublicKey emptyLocalKey;
|
PublicKey emptyLocalKey;
|
||||||
@@ -228,40 +168,28 @@ private:
|
|||||||
};
|
};
|
||||||
std::vector<publisher> servers;
|
std::vector<publisher> servers;
|
||||||
|
|
||||||
auto const sequence = 1;
|
|
||||||
auto constexpr listSize = 20;
|
auto constexpr listSize = 20;
|
||||||
std::vector<std::string> cfgPublishers;
|
std::vector<std::string> cfgPublishers;
|
||||||
|
|
||||||
for (auto const& cfg : paths)
|
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);
|
servers.push_back(cfg);
|
||||||
auto& item = servers.back();
|
auto& item = servers.back();
|
||||||
item.isRetry = cfg.path == "/bad-resource";
|
item.isRetry = cfg.path == "/bad-resource";
|
||||||
item.list.reserve (listSize);
|
item.list.reserve (listSize);
|
||||||
while (item.list.size () < listSize)
|
while (item.list.size () < listSize)
|
||||||
item.list.push_back (randomValidator());
|
item.list.push_back (TrustedPublisherServer::randomValidator());
|
||||||
|
|
||||||
item.server = std::make_unique<TrustedPublisherServer> (
|
item.server = std::make_unique<TrustedPublisherServer> (
|
||||||
env.app().getIOService(),
|
env.app().getIOService(),
|
||||||
pubSigningKeys,
|
item.list,
|
||||||
manifest,
|
|
||||||
sequence,
|
|
||||||
env.timeKeeper().now() + cfg.expiresFromNow,
|
env.timeKeeper().now() + cfg.expiresFromNow,
|
||||||
cfg.serverVersion,
|
cfg.ssl,
|
||||||
item.list);
|
cfg.serverVersion);
|
||||||
|
cfgPublishers.push_back(strHex(item.server->publisherPublic()));
|
||||||
|
|
||||||
std::stringstream uri;
|
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();
|
item.uri = uri.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,10 +198,7 @@ private:
|
|||||||
|
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
auto sites = std::make_unique<ValidatorSite> (
|
auto sites = std::make_unique<ValidatorSite> (
|
||||||
env.app().getIOService(),
|
env.app(), journal, 2s);
|
||||||
env.app().validators(),
|
|
||||||
journal,
|
|
||||||
2s);
|
|
||||||
|
|
||||||
std::vector<std::string> uris;
|
std::vector<std::string> uris;
|
||||||
for (auto const& u : servers)
|
for (auto const& u : servers)
|
||||||
@@ -300,16 +225,15 @@ private:
|
|||||||
BEAST_EXPECTS(
|
BEAST_EXPECTS(
|
||||||
myStatus[jss::last_refresh_message].asString().empty()
|
myStatus[jss::last_refresh_message].asString().empty()
|
||||||
!= u.cfg.failFetch,
|
!= u.cfg.failFetch,
|
||||||
to_string(myStatus) + "\n" + sink.strm_.str());
|
to_string(myStatus) + "\n" + sink.messages().str());
|
||||||
|
|
||||||
if (! u.cfg.msg.empty())
|
if (! u.cfg.msg.empty())
|
||||||
{
|
{
|
||||||
BEAST_EXPECTS(
|
BEAST_EXPECTS(
|
||||||
sink.strm_.str().find(u.cfg.msg) != std::string::npos,
|
sink.messages().str().find(u.cfg.msg) != std::string::npos,
|
||||||
sink.strm_.str());
|
sink.messages().str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (u.cfg.expectedRefreshMin)
|
if (u.cfg.expectedRefreshMin)
|
||||||
{
|
{
|
||||||
BEAST_EXPECTS(
|
BEAST_EXPECTS(
|
||||||
@@ -347,7 +271,7 @@ private:
|
|||||||
|
|
||||||
Env env (*this);
|
Env env (*this);
|
||||||
|
|
||||||
TestSink sink;
|
test::StreamSink sink;
|
||||||
beast::Journal journal{sink};
|
beast::Journal journal{sink};
|
||||||
|
|
||||||
struct publisher
|
struct publisher
|
||||||
@@ -370,8 +294,7 @@ private:
|
|||||||
item.uri = uri.str();
|
item.uri = uri.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto sites = std::make_unique<ValidatorSite> (
|
auto sites = std::make_unique<ValidatorSite> (env.app(), journal);
|
||||||
env.app().getIOService(), env.app().validators(), journal);
|
|
||||||
|
|
||||||
std::vector<std::string> uris;
|
std::vector<std::string> uris;
|
||||||
for (auto const& u : servers)
|
for (auto const& u : servers)
|
||||||
@@ -393,8 +316,8 @@ private:
|
|||||||
if (u.shouldFail)
|
if (u.shouldFail)
|
||||||
{
|
{
|
||||||
BEAST_EXPECTS(
|
BEAST_EXPECTS(
|
||||||
sink.strm_.str().find(u.expectMsg) != std::string::npos,
|
sink.messages().str().find(u.expectMsg) != std::string::npos,
|
||||||
sink.strm_.str());
|
sink.messages().str());
|
||||||
log << " -- Msg: " <<
|
log << " -- Msg: " <<
|
||||||
myStatus[jss::last_refresh_message].asString() << std::endl;
|
myStatus[jss::last_refresh_message].asString() << std::endl;
|
||||||
}
|
}
|
||||||
@@ -439,71 +362,81 @@ public:
|
|||||||
{
|
{
|
||||||
testConfigLoad ();
|
testConfigLoad ();
|
||||||
|
|
||||||
|
|
||||||
|
for (auto ssl : {true, false})
|
||||||
|
{
|
||||||
// fetch single site
|
// fetch single site
|
||||||
testFetchList ({{"/validators", ""}});
|
testFetchList ({{"/validators", "", ssl}});
|
||||||
// fetch multiple sites
|
// fetch multiple sites
|
||||||
testFetchList ({{"/validators", ""}, {"/validators", ""}});
|
testFetchList ({{"/validators", "", ssl}, {"/validators", "", ssl}});
|
||||||
// fetch single site with single redirects
|
// fetch single site with single redirects
|
||||||
testFetchList ({{"/redirect_once/301", ""}});
|
testFetchList ({{"/redirect_once/301", "", ssl}});
|
||||||
testFetchList ({{"/redirect_once/302", ""}});
|
testFetchList ({{"/redirect_once/302", "", ssl}});
|
||||||
testFetchList ({{"/redirect_once/307", ""}});
|
testFetchList ({{"/redirect_once/307", "", ssl}});
|
||||||
testFetchList ({{"/redirect_once/308", ""}});
|
testFetchList ({{"/redirect_once/308", "", ssl}});
|
||||||
// one redirect, one not
|
// 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)
|
// fetch single site with undending redirect (fails to load)
|
||||||
testFetchList ({
|
testFetchList ({
|
||||||
{"/redirect_forever/301", "Exceeded max redirects", true, true}});
|
{"/redirect_forever/301", "Exceeded max redirects", ssl, true, true}});
|
||||||
// two that redirect forever
|
// two that redirect forever
|
||||||
testFetchList ({
|
testFetchList ({
|
||||||
{"/redirect_forever/307", "Exceeded max redirects", true, true},
|
{"/redirect_forever/307", "Exceeded max redirects", ssl, true, true},
|
||||||
{"/redirect_forever/308", "Exceeded max redirects", true, true}});
|
{"/redirect_forever/308", "Exceeded max redirects", ssl, true, true}});
|
||||||
// one undending redirect, one not
|
// one undending redirect, one not
|
||||||
testFetchList (
|
testFetchList (
|
||||||
{{"/validators", ""},
|
{{"/validators", "", ssl},
|
||||||
{"/redirect_forever/302", "Exceeded max redirects", true, true}});
|
{"/redirect_forever/302", "Exceeded max redirects", ssl, true, true}});
|
||||||
// invalid redir Location
|
// invalid redir Location
|
||||||
testFetchList ({
|
testFetchList ({
|
||||||
{"/redirect_to/ftp://invalid-url/302",
|
{"/redirect_to/ftp://invalid-url/302",
|
||||||
"Invalid redirect location",
|
"Invalid redirect location",
|
||||||
|
ssl,
|
||||||
true,
|
true,
|
||||||
true}});
|
true}});
|
||||||
testFetchList ({
|
testFetchList ({
|
||||||
{"/redirect_to/file://invalid-url/302",
|
{"/redirect_to/file://invalid-url/302",
|
||||||
"Invalid redirect location",
|
"Invalid redirect location",
|
||||||
|
ssl,
|
||||||
true,
|
true,
|
||||||
true}});
|
true}});
|
||||||
// invalid json
|
// invalid json
|
||||||
testFetchList ({
|
testFetchList ({
|
||||||
{"/validators/bad", "Unable to parse JSON response", true, true}});
|
{"/validators/bad", "Unable to parse JSON response", ssl, true, true}});
|
||||||
// error status returned
|
// error status returned
|
||||||
testFetchList ({
|
testFetchList ({
|
||||||
{"/bad-resource", "returned bad status", true, true}});
|
{"/bad-resource", "returned bad status", ssl, true, true}});
|
||||||
// location field missing
|
// location field missing
|
||||||
testFetchList ({
|
testFetchList ({
|
||||||
{"/redirect_nolo/308",
|
{"/redirect_nolo/308",
|
||||||
"returned a redirect with no Location",
|
"returned a redirect with no Location",
|
||||||
|
ssl,
|
||||||
true,
|
true,
|
||||||
true}});
|
true}});
|
||||||
// json fields missing
|
// json fields missing
|
||||||
testFetchList ({
|
testFetchList ({
|
||||||
{"/validators/missing",
|
{"/validators/missing",
|
||||||
"Missing fields in JSON response",
|
"Missing fields in JSON response",
|
||||||
|
ssl,
|
||||||
true,
|
true,
|
||||||
true}});
|
true}});
|
||||||
// timeout
|
// timeout
|
||||||
testFetchList ({
|
testFetchList ({
|
||||||
{"/sleep/3", "took too long", true, true}});
|
{"/sleep/3", "took too long", ssl, true, true}});
|
||||||
// bad manifest version
|
// bad manifest version
|
||||||
testFetchList ({
|
testFetchList ({
|
||||||
{"/validators", "Unsupported version", false, true, 4}});
|
{"/validators", "Unsupported version", ssl, false, true, 4}});
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
// get old validator list
|
// get old validator list
|
||||||
testFetchList ({
|
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
|
// force an out-of-range expiration value
|
||||||
testFetchList ({
|
testFetchList ({
|
||||||
{"/validators",
|
{"/validators",
|
||||||
"Invalid validator list",
|
"Invalid validator list",
|
||||||
|
ssl,
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
1,
|
1,
|
||||||
@@ -512,6 +445,7 @@ public:
|
|||||||
testFetchList ({
|
testFetchList ({
|
||||||
{"/validators/refresh/0",
|
{"/validators/refresh/0",
|
||||||
"",
|
"",
|
||||||
|
ssl,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
1,
|
1,
|
||||||
@@ -520,6 +454,7 @@ public:
|
|||||||
testFetchList ({
|
testFetchList ({
|
||||||
{"/validators/refresh/10",
|
{"/validators/refresh/10",
|
||||||
"",
|
"",
|
||||||
|
ssl,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
1,
|
1,
|
||||||
@@ -528,11 +463,13 @@ public:
|
|||||||
testFetchList ({
|
testFetchList ({
|
||||||
{"/validators/refresh/2000",
|
{"/validators/refresh/2000",
|
||||||
"",
|
"",
|
||||||
|
ssl,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
1,
|
1,
|
||||||
detail::default_expires,
|
detail::default_expires,
|
||||||
60*24}}); // max of 24 hours
|
60*24}}); // max of 24 hours
|
||||||
|
}
|
||||||
testFileURLs();
|
testFileURLs();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,11 +23,16 @@
|
|||||||
#include <ripple/protocol/SecretKey.h>
|
#include <ripple/protocol/SecretKey.h>
|
||||||
#include <ripple/protocol/Sign.h>
|
#include <ripple/protocol/Sign.h>
|
||||||
#include <ripple/basics/base64.h>
|
#include <ripple/basics/base64.h>
|
||||||
|
#include <ripple/basics/random.h>
|
||||||
#include <ripple/basics/strHex.h>
|
#include <ripple/basics/strHex.h>
|
||||||
#include <test/jtx/envconfig.h>
|
#include <test/jtx/envconfig.h>
|
||||||
#include <boost/asio.hpp>
|
|
||||||
#include <boost/algorithm/string/predicate.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/http.hpp>
|
||||||
|
#include <boost/beast/ssl.hpp>
|
||||||
|
#include <boost/beast/version.hpp>
|
||||||
#include <boost/lexical_cast.hpp>
|
#include <boost/lexical_cast.hpp>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
@@ -45,11 +50,47 @@ class TrustedPublisherServer
|
|||||||
using error_code = boost::system::error_code;
|
using error_code = boost::system::error_code;
|
||||||
|
|
||||||
socket_type sock_;
|
socket_type sock_;
|
||||||
|
endpoint_type ep_;
|
||||||
boost::asio::ip::tcp::acceptor acceptor_;
|
boost::asio::ip::tcp::acceptor acceptor_;
|
||||||
std::function<std::string(int)> getList_;
|
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
|
struct Validator
|
||||||
{
|
{
|
||||||
PublicKey masterPublic;
|
PublicKey masterPublic;
|
||||||
@@ -57,19 +98,69 @@ public:
|
|||||||
std::string manifest;
|
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(
|
TrustedPublisherServer(
|
||||||
boost::asio::io_context& ioc,
|
boost::asio::io_context& ioc,
|
||||||
std::pair<PublicKey, SecretKey> keys,
|
std::vector<Validator> const& validators,
|
||||||
std::string const& manifest,
|
|
||||||
int sequence,
|
|
||||||
NetClock::time_point expiration,
|
NetClock::time_point expiration,
|
||||||
int version,
|
bool useSSL = false,
|
||||||
std::vector<Validator> const& validators)
|
int version = 1,
|
||||||
: sock_(ioc), acceptor_(ioc)
|
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 {
|
auto const keys = randomKeyPair(KeyType::secp256k1);
|
||||||
beast::IP::Address::from_string (ripple::test::getEnvLocalhostAddr()),
|
auto const manifest = makeManifestString (
|
||||||
0}; // 0 means let OS pick the port based on what's available
|
publisherPublic_,
|
||||||
|
publisherSecret_,
|
||||||
|
keys.first,
|
||||||
|
keys.second,
|
||||||
|
1);
|
||||||
|
|
||||||
std::string data = "{\"sequence\":" + std::to_string(sequence) +
|
std::string data = "{\"sequence\":" + std::to_string(sequence) +
|
||||||
",\"expiration\":" +
|
",\"expiration\":" +
|
||||||
std::to_string(expiration.time_since_epoch().count()) +
|
std::to_string(expiration.time_since_epoch().count()) +
|
||||||
@@ -94,11 +185,23 @@ public:
|
|||||||
return l.str();
|
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;
|
error_code ec;
|
||||||
|
acceptor_.open(ep_.protocol());
|
||||||
acceptor_.set_option(
|
acceptor_.set_option(
|
||||||
boost::asio::ip::tcp::acceptor::reuse_address(true), ec);
|
boost::asio::ip::tcp::acceptor::reuse_address(true), ec);
|
||||||
acceptor_.bind(ep);
|
acceptor_.bind(ep_);
|
||||||
acceptor_.listen(boost::asio::socket_base::max_connections);
|
acceptor_.listen(boost::asio::socket_base::max_connections);
|
||||||
acceptor_.async_accept(
|
acceptor_.async_accept(
|
||||||
sock_,
|
sock_,
|
||||||
@@ -106,10 +209,17 @@ public:
|
|||||||
&TrustedPublisherServer::on_accept, this, std::placeholders::_1));
|
&TrustedPublisherServer::on_accept, this, std::placeholders::_1));
|
||||||
}
|
}
|
||||||
|
|
||||||
~TrustedPublisherServer()
|
void stop()
|
||||||
{
|
{
|
||||||
error_code ec;
|
error_code ec;
|
||||||
acceptor_.close(ec);
|
acceptor_.close(ec);
|
||||||
|
// TODO consider making this join
|
||||||
|
// any running do_peer threads
|
||||||
|
}
|
||||||
|
|
||||||
|
~TrustedPublisherServer()
|
||||||
|
{
|
||||||
|
stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoint_type
|
endpoint_type
|
||||||
@@ -118,6 +228,186 @@ public:
|
|||||||
return acceptor_.local_endpoint();
|
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:
|
private:
|
||||||
struct lambda
|
struct lambda
|
||||||
{
|
{
|
||||||
@@ -125,19 +415,25 @@ private:
|
|||||||
TrustedPublisherServer& self;
|
TrustedPublisherServer& self;
|
||||||
socket_type sock;
|
socket_type sock;
|
||||||
boost::asio::executor_work_guard<boost::asio::executor> work;
|
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_)
|
: id(id_)
|
||||||
, self(self_)
|
, self(self_)
|
||||||
, sock(std::move(sock_))
|
, sock(std::move(sock_))
|
||||||
, work(sock_.get_executor())
|
, work(sock_.get_executor())
|
||||||
|
, ssl(ssl_)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
operator()()
|
operator()()
|
||||||
{
|
{
|
||||||
self.do_peer(id, std::move(sock));
|
self.do_peer(id, std::move(sock), ssl);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -150,7 +446,7 @@ private:
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
static int id_ = 0;
|
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(
|
acceptor_.async_accept(
|
||||||
sock_,
|
sock_,
|
||||||
std::bind(
|
std::bind(
|
||||||
@@ -158,24 +454,44 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
do_peer(int id, socket_type&& sock0)
|
do_peer(int id, socket_type&& s, bool ssl)
|
||||||
{
|
{
|
||||||
using namespace boost::beast;
|
using namespace boost::beast;
|
||||||
socket_type sock(std::move(sock0));
|
using namespace boost::asio;
|
||||||
multi_buffer sb;
|
socket_type sock(std::move(s));
|
||||||
|
flat_buffer sb;
|
||||||
error_code ec;
|
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 (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
resp_type res;
|
resp_type res;
|
||||||
req_type req;
|
req_type req;
|
||||||
try
|
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)
|
if (ec)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
auto path = req.target().to_string();
|
auto path = req.target().to_string();
|
||||||
res.insert("Server", "TrustedPublisherServer");
|
res.insert("Server", "TrustedPublisherServer");
|
||||||
res.version(req.version());
|
res.version(req.version());
|
||||||
|
res.keep_alive(req.keep_alive());
|
||||||
|
bool prepare = true;
|
||||||
|
|
||||||
if (boost::starts_with(path, "/validators"))
|
if (boost::starts_with(path, "/validators"))
|
||||||
{
|
{
|
||||||
@@ -195,6 +511,25 @@ private:
|
|||||||
res.body() = getList_(refresh);
|
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/"))
|
else if (boost::starts_with(path, "/sleep/"))
|
||||||
{
|
{
|
||||||
auto const sleep_sec =
|
auto const sleep_sec =
|
||||||
@@ -220,7 +555,8 @@ private:
|
|||||||
}
|
}
|
||||||
else if (! boost::starts_with(path, "/redirect_nolo"))
|
else if (! boost::starts_with(path, "/redirect_nolo"))
|
||||||
{
|
{
|
||||||
location << "http://" << local_endpoint() <<
|
location << (ssl ? "https://" : "http://") <<
|
||||||
|
local_endpoint() <<
|
||||||
(boost::starts_with(path, "/redirect_forever/") ?
|
(boost::starts_with(path, "/redirect_forever/") ?
|
||||||
path : "/validators");
|
path : "/validators");
|
||||||
}
|
}
|
||||||
@@ -235,7 +571,8 @@ private:
|
|||||||
res.body() = "The file '" + path + "' was not found";
|
res.body() = "The file '" + path + "' was not found";
|
||||||
}
|
}
|
||||||
|
|
||||||
res.prepare_payload();
|
if (prepare)
|
||||||
|
res.prepare_payload();
|
||||||
}
|
}
|
||||||
catch (std::exception const& e)
|
catch (std::exception const& e)
|
||||||
{
|
{
|
||||||
@@ -247,10 +584,19 @@ private:
|
|||||||
res.body() = std::string{"An internal error occurred"} + e.what();
|
res.body() = std::string{"An internal error occurred"} + e.what();
|
||||||
res.prepare_payload();
|
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;
|
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("admin", getEnvLocalhostAddr());
|
||||||
cfg["port_ws"].set("port", port_ws);
|
cfg["port_ws"].set("port", port_ws);
|
||||||
cfg["port_ws"].set("protocol", "ws");
|
cfg["port_ws"].set("protocol", "ws");
|
||||||
|
cfg.SSL_VERIFY = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace jtx {
|
namespace jtx {
|
||||||
|
|||||||
@@ -30,25 +30,6 @@ namespace ripple {
|
|||||||
|
|
||||||
class Invariants_test : public beast::unit_test::suite
|
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
|
// this is common setup/method for running a failing invariant check. The
|
||||||
// precheck function is used to manipulate the ApplyContext with view
|
// precheck function is used to manipulate the ApplyContext with view
|
||||||
// changes that will cause the check to fail.
|
// 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&){ } };
|
auto tx = STTx {ttACCOUNT_SET, [](STObject&){ } };
|
||||||
txmod(tx);
|
txmod(tx);
|
||||||
OpenView ov {*env.current()};
|
OpenView ov {*env.current()};
|
||||||
TestSink sink;
|
test::StreamSink sink {beast::severities::kWarning};
|
||||||
beast::Journal jlog {sink};
|
beast::Journal jlog {sink};
|
||||||
ApplyContext ac {
|
ApplyContext ac {
|
||||||
env.app(),
|
env.app(),
|
||||||
@@ -112,20 +93,20 @@ class Invariants_test : public beast::unit_test::suite
|
|||||||
? TER {tecINVARIANT_FAILED}
|
? TER {tecINVARIANT_FAILED}
|
||||||
: TER {tefINVARIANT_FAILED}));
|
: TER {tefINVARIANT_FAILED}));
|
||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
boost::starts_with(sink.strm_.str(), "Invariant failed:") ||
|
boost::starts_with(sink.messages().str(), "Invariant failed:") ||
|
||||||
boost::starts_with(sink.strm_.str(),
|
boost::starts_with(sink.messages().str(),
|
||||||
"Transaction caused an exception"));
|
"Transaction caused an exception"));
|
||||||
//uncomment if you want to log the invariant failure message
|
//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)
|
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
|
else
|
||||||
{
|
{
|
||||||
BEAST_EXPECT(tr == tesSUCCESS);
|
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;
|
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:
|
public:
|
||||||
void
|
void
|
||||||
testPrivileges()
|
testPrivileges()
|
||||||
@@ -184,24 +143,29 @@ public:
|
|||||||
return toBase58(TokenType::NodePublic, publicKey);
|
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
|
// 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;
|
std::set<std::string> expectedKeys;
|
||||||
for (auto const& val : validators)
|
for (auto const& val : validators)
|
||||||
expectedKeys.insert(toStr(val.masterPublic));
|
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
|
// Publisher list site unavailable
|
||||||
@@ -216,7 +180,7 @@ public:
|
|||||||
envconfig([&](std::unique_ptr<Config> cfg) {
|
envconfig([&](std::unique_ptr<Config> cfg) {
|
||||||
cfg->section(SECTION_VALIDATOR_LIST_SITES).append(siteURI);
|
cfg->section(SECTION_VALIDATOR_LIST_SITES).append(siteURI);
|
||||||
cfg->section(SECTION_VALIDATOR_LIST_KEYS)
|
cfg->section(SECTION_VALIDATOR_LIST_KEYS)
|
||||||
.append(strHex(publisherPublic));
|
.append(strHex(server.publisherPublic()));
|
||||||
return cfg;
|
return cfg;
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
@@ -253,7 +217,7 @@ public:
|
|||||||
BEAST_EXPECT(!jp.isMember(jss::expiration));
|
BEAST_EXPECT(!jp.isMember(jss::expiration));
|
||||||
BEAST_EXPECT(!jp.isMember(jss::version));
|
BEAST_EXPECT(!jp.isMember(jss::version));
|
||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
jp[jss::pubkey_publisher] == strHex(publisherPublic));
|
jp[jss::pubkey_publisher] == strHex(server.publisherPublic()));
|
||||||
}
|
}
|
||||||
BEAST_EXPECT(jrr[jss::signing_keys].size() == 0);
|
BEAST_EXPECT(jrr[jss::signing_keys].size() == 0);
|
||||||
}
|
}
|
||||||
@@ -272,24 +236,7 @@ public:
|
|||||||
//----------------------------------------------------------------------
|
//----------------------------------------------------------------------
|
||||||
// Publisher list site available
|
// Publisher list site available
|
||||||
{
|
{
|
||||||
using namespace std::chrono_literals;
|
server.start();
|
||||||
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);
|
|
||||||
|
|
||||||
std::stringstream uri;
|
std::stringstream uri;
|
||||||
uri << "http://" << server.local_endpoint() << "/validators";
|
uri << "http://" << server.local_endpoint() << "/validators";
|
||||||
@@ -300,7 +247,7 @@ public:
|
|||||||
envconfig([&](std::unique_ptr<Config> cfg) {
|
envconfig([&](std::unique_ptr<Config> cfg) {
|
||||||
cfg->section(SECTION_VALIDATOR_LIST_SITES).append(siteURI);
|
cfg->section(SECTION_VALIDATOR_LIST_SITES).append(siteURI);
|
||||||
cfg->section(SECTION_VALIDATOR_LIST_KEYS)
|
cfg->section(SECTION_VALIDATOR_LIST_KEYS)
|
||||||
.append(strHex(publisherPublic));
|
.append(strHex(server.publisherPublic()));
|
||||||
return cfg;
|
return cfg;
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
@@ -356,7 +303,7 @@ public:
|
|||||||
}
|
}
|
||||||
BEAST_EXPECT(jp[jss::seq].asUInt() == 1);
|
BEAST_EXPECT(jp[jss::seq].asUInt() == 1);
|
||||||
BEAST_EXPECT(
|
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::expiration] == to_string(expiration));
|
||||||
BEAST_EXPECT(jp[jss::version] == 1);
|
BEAST_EXPECT(jp[jss::version] == 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,18 +111,23 @@ class FileDirGuard : public DirGuard
|
|||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
path const file_;
|
path const file_;
|
||||||
|
bool created_ = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FileDirGuard(beast::unit_test::suite& test,
|
FileDirGuard(beast::unit_test::suite& test,
|
||||||
path subDir, path file, std::string const& contents,
|
path subDir, path file, std::string const& contents,
|
||||||
bool useCounter = true)
|
bool useCounter = true, bool create = true)
|
||||||
: DirGuard(test, subDir, useCounter)
|
: DirGuard(test, subDir, useCounter)
|
||||||
, file_(file.is_absolute() ? file : subdir() / file)
|
, file_(file.is_absolute() ? file : subdir() / file)
|
||||||
{
|
{
|
||||||
if (!exists (file_))
|
if (!exists (file_))
|
||||||
{
|
{
|
||||||
std::ofstream o (file_.string ());
|
if (create)
|
||||||
o << contents;
|
{
|
||||||
|
std::ofstream o (file_.string ());
|
||||||
|
o << contents;
|
||||||
|
created_ = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -137,11 +142,16 @@ public:
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
using namespace boost::filesystem;
|
using namespace boost::filesystem;
|
||||||
if (!exists (file_))
|
if (exists (file_))
|
||||||
test_.log << "Expected " << file_.string ()
|
{
|
||||||
<< " to be an existing file." << std::endl;
|
|
||||||
else
|
|
||||||
remove (file_);
|
remove (file_);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (created_)
|
||||||
|
test_.log << "Expected " << file_.string ()
|
||||||
|
<< " to be an existing file." << std::endl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (std::exception& e)
|
catch (std::exception& e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -94,6 +94,29 @@ public:
|
|||||||
operator beast::Journal&() { return journal_; }
|
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
|
} // test
|
||||||
} // ripple
|
} // 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