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