mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +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:
@@ -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