20#ifndef RIPPLE_TEST_TRUSTED_PUBLISHER_SERVER_H_INCLUDED
21#define RIPPLE_TEST_TRUSTED_PUBLISHER_SERVER_H_INCLUDED
23#include <test/jtx/envconfig.h>
25#include <xrpl/basics/base64.h>
26#include <xrpl/basics/random.h>
27#include <xrpl/basics/strHex.h>
28#include <xrpl/protocol/PublicKey.h>
29#include <xrpl/protocol/SecretKey.h>
30#include <xrpl/protocol/Sign.h>
32#include <boost/algorithm/string/predicate.hpp>
33#include <boost/asio.hpp>
34#include <boost/asio/ip/tcp.hpp>
35#include <boost/asio/ssl/stream.hpp>
36#include <boost/beast/http.hpp>
37#include <boost/beast/ssl.hpp>
38#include <boost/beast/version.hpp>
39#include <boost/lexical_cast.hpp>
55 boost::beast::http::request<boost::beast::http::string_body>;
57 boost::beast::http::response<boost::beast::http::string_body>;
72 boost::asio::ssl::context
sslCtx_{boost::asio::ssl::context::tlsv12};
83 [](
std::size_t, boost::asio::ssl::context_base::password_purpose) {
88 boost::asio::ssl::context::default_workarounds |
89 boost::asio::ssl::context::no_sslv2 |
90 boost::asio::ssl::context::single_dh_use);
93 boost::asio::buffer(
cert().data(),
cert().size()));
96 boost::asio::buffer(
key().data(),
key().size()),
97 boost::asio::ssl::context::file_format::pem);
99 sslCtx_.use_tmp_dh(boost::asio::buffer(
dh().data(),
dh().size()));
131 st[sfSequence] =
seq;
132 st[sfPublicKey] = pk;
133 st[sfSigningPubKey] = spk;
174 boost::asio::io_context& ioc,
182 bool immediateStart =
true,
199 blobInfo.
reserve(futures.size() + 1);
207 for (
auto const& val : validators)
209 data +=
"{\"validation_public_key\":\"" +
210 strHex(val.masterPublic) +
"\",\"manifest\":\"" +
211 val.manifest +
"\"},";
223 l <<
"{\"blob\":\"" << blob <<
"\"" <<
",\"signature\":\"" <<
sig
224 <<
"\"" <<
",\"manifest\":\"" <<
manifest <<
"\""
225 <<
",\"refresh_interval\": " << interval
226 <<
",\"version\":" << version <<
'}';
229 for (
auto const& future : futures)
239 for (
auto const& val : validators)
241 data +=
"{\"validation_public_key\":\"" +
242 strHex(val.masterPublic) +
"\",\"manifest\":\"" +
243 val.manifest +
"\"},";
257 for (
auto const& info : blobInfo)
259 l <<
"{\"blob\":\"" << info.blob <<
"\"" <<
",\"signature\":\""
260 << info.signature <<
"\"},";
265 l <<
"{\"blobs_v2\": [ " << blobs <<
"],\"manifest\":\"" <<
manifest
266 <<
"\"" <<
",\"refresh_interval\": " << interval
267 <<
",\"version\":" << (version + 1) <<
'}';
284 boost::asio::ip::tcp::acceptor::reuse_address(
true), ec);
286 acceptor_.listen(boost::asio::socket_base::max_connections);
291 if (
auto p = wp.lock())
390-----BEGIN CERTIFICATE-----
391MIIDczCCAlugAwIBAgIBATANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEL
392MAkGA1UECAwCQ0ExFDASBgNVBAcMC0xvcyBBbmdlbGVzMRswGQYDVQQKDBJyaXBw
393bGVkLXVuaXQtdGVzdHMxFDASBgNVBAMMC2V4YW1wbGUuY29tMB4XDTIyMDIwNTIz
394NDk0M1oXDTQ5MDYyMzIzNDk0M1owazELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNh
395bGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xGzAZBgNVBAoMEnJpcHBs
396ZWQtdW5pdC10ZXN0czESMBAGA1UEAwwJMTI3LjAuMC4xMIIBIjANBgkqhkiG9w0B
397AQEFAAOCAQ8AMIIBCgKCAQEAueZ1hgRxwPgfeVx2AdngUYx7zYcaxcGYXyqi7izJ
398qTuBUcVcTRC/9Ip67RAEhfcgGudRS/a4Sv1ljwiRknSCcD/ZjzOFDLgbqYGSZNEs
399+T/qkwmc/L+Pbzf85HM7RjeGOd6NDQy9+oOBbUtqpTxcSGa4ln+YBFUSeoS1Aa9f
400n9vrxnWX9LgTu5dSWzH5TqFIti+Zs/v0PFjEivBIAOHPslmnzg/wCr99I6z9CAR3
401zVDe7+sxR//ivpeVE7FWjgkGixnUpZAqn69zNkJjMLNXETgOYskZdMIgbVOMr+0q
402S1Uj77mhwxKfpnB6TqUVvWLBvmBDzPjf0m0NcCf9UAjqPwIDAQABoyowKDAmBgNV
403HREEHzAdgglsb2NhbGhvc3SHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQEL
404BQADggEBAJkUFNS0CeEAKvo0ttzooXnCDH3esj2fwmLJQYLUGsAF8DFrFHTqZEcx
405hFRdr0ftEb/VKpV9dVF6xtSoMU56kHOnhbHEWADyqdKUkCDjrGBet5QdWmEwNV2L
406nYrwGQBAybMt/+1XMUV8HeLFJNHnyxfQYcW0fUsrmNGk8W0kzWuuq88qbhfXZAIx
407KiXrzYpLlM0RlpWXRfYQ6mTdSrRrLnEo5MklizVgNB8HYX78lxa06zP08oReQcfT
408GSGO8NEEq8BTVmp69zD1JyfvQcXzsi7WtkAX+/EOFZ7LesnZ6VsyjZ74wECCaQuD
409X1yu/XxHqchM+DOzzVw6wRKaM7Zsk80=
410-----END CERTIFICATE-----
419-----BEGIN RSA PRIVATE KEY-----
420MIIEpAIBAAKCAQEAueZ1hgRxwPgfeVx2AdngUYx7zYcaxcGYXyqi7izJqTuBUcVc
421TRC/9Ip67RAEhfcgGudRS/a4Sv1ljwiRknSCcD/ZjzOFDLgbqYGSZNEs+T/qkwmc
422/L+Pbzf85HM7RjeGOd6NDQy9+oOBbUtqpTxcSGa4ln+YBFUSeoS1Aa9fn9vrxnWX
4239LgTu5dSWzH5TqFIti+Zs/v0PFjEivBIAOHPslmnzg/wCr99I6z9CAR3zVDe7+sx
424R//ivpeVE7FWjgkGixnUpZAqn69zNkJjMLNXETgOYskZdMIgbVOMr+0qS1Uj77mh
425wxKfpnB6TqUVvWLBvmBDzPjf0m0NcCf9UAjqPwIDAQABAoIBAEC9MDpOu+quvg8+
426kt4MKSFdIhQuM7WguNaTe5AkSspDrcJzT7SK275mp259QIYCzMxxuA8TSZTb8A1C
427t6dgKbi7k6FaGMCYMRHzzK6NZfMbPi6cj245q9LYlZpdQswuM/FdPpPH1zUxrNYK
428CIaooZ6ZHzlSD/eaRMgkBQEkONHrZZtEinLIvKedwssPCaXkIISmt7MFQTDOlxkf
429K0Mt1mnRREPYbYSfPEEfIyy/KDIiB5AzgGt+uPOn8Oeb1pSqy69jpYcfhSj+bo4S
430UV6qTuTfBd4qkkNI6d/Z7DcDJFFlfloG/vVgGk/beWNnL2e39vzxiebB3w+MQn4F
431Wyx5mCECgYEA22z1/ihqt9LIAWtP42oSS3S/RxlFzpp5d7QfNqFnEoVgeRhQzleP
432pRJIzVXpMYBxexZYqZA/q8xBSggz+2gmRoYnW20VIzl14DsSH378ye3FRwJB0tLy
433dWU8DC7ZB5XQCTvI9UY3voJNToknODw7RCNO1h3V3T1y6JRLdcLskk8CgYEA2OLy
434aE5bvsUaLBSv7W9NFhSuZ0p9Y0pFmRgHI7g8i/AgRZ0BgiE8u8OZSHmPJPMaNs/h
435YIEIrlsgDci1PzwrUYseRp/aiVE1kyev09/ihqRXTPpLQu6h/d63KRe/06W3t5X3
436Dmfj49hH5zGPBI/0y1ECV/n0fwnRhxSv7fNr3RECgYBEuFpOUAAkNApZj29ErNqv
4378Q9ayAp5yx1RpQLFjEUIoub05e2gwgGF1DUiwc43p59iyjvYVwnp1x13fxwwl4yt
438N6Sp2H7vOja1lCp33MB0yVeohodw7InsxFjLA/0KiBvQWH32exhIPOzTNNcooIx7
439KYeuPUfWc0FCn/cGGZcXtwKBgQC1hp1k99CKBuY05suoanOWe5DNGud/ZvaBgD7Z
440gqYKadxY52QPyknOzZNJuZQ5VM8n+S2lW9osNFDLuKUaW/3Vrh6U9c4vCC1TEPB0
4414PnzvzDiWMsNJjWnCfU7C4meVyFBIt84y3NNjAQCWNRe+S3lzdOsVqRwf4NDD+l/
442uzEYQQKBgQCJczIlwobm1Y6O41hbGZhZL/CGMNS6Z0INi2yasV0WDqYlh7XayHMD
443cK55dMILcbHqeIBq/wR6sIhw6IJcaDBfFfrJiKKDilfij2lHxR2FQrEngtTCCRV+
444ZzARzaWhQPvbDqEtLJDWuXZNXfL8/PTIs5NmuKuQ8F4+gQJpkQgwaw==
445-----END RSA PRIVATE KEY-----
454-----BEGIN CERTIFICATE-----
455MIIDpzCCAo+gAwIBAgIUWc45WqaaNuaSLoFYTMC/Mjfqw/gwDQYJKoZIhvcNAQEL
456BQAwYzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRQwEgYDVQQHDAtMb3MgQW5n
457ZWxlczEbMBkGA1UECgwScmlwcGxlZC11bml0LXRlc3RzMRQwEgYDVQQDDAtleGFt
458cGxlLmNvbTAeFw0yMjAyMDUyMzQ5MDFaFw00OTA2MjMyMzQ5MDFaMGMxCzAJBgNV
459BAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UEBwwLTG9zIEFuZ2VsZXMxGzAZBgNV
460BAoMEnJpcHBsZWQtdW5pdC10ZXN0czEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEi
461MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC0f2JBW2XNW2wT5/ajX2qxmUY+
462aNJGfpV6gZ5CmwdQpbHrPPvJoskxwsCyr3GifzT/GtCpmb1fiu59uUAPxQEYCxiq
463V+HchX4g4Vl27xKJ0P+usxuEED9v7TCteKum9u9eMZ8UDF0fspXcnWGs9fXlyoTj
464uTRP1SBQllk44DPc/KzlrtH+QNXmr9XQnP8XvwWCgJXMx87voxEGiFFOVhkSSAOv
465v+OUGgEuq0NPgwv2LHBlYHSdkoU9F5Z/TmkCAFMShbyoUjldIz2gcWXjN2tespGo
466D6qYvasvPIpmcholBBkc0z8QDt+RNq+Wzrults7epJXy/u+txGK9cHCNlLCpAgMB
467AAGjUzBRMB0GA1UdDgQWBBS1oydh+YyqDNOFKYOvOtVMWKqV4zAfBgNVHSMEGDAW
468gBS1oydh+YyqDNOFKYOvOtVMWKqV4zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
469DQEBCwUAA4IBAQCDPyGKQwQ8Lz0yEgvIl/Uo9BtwAzlvjrLM/39qhStLQqDGSs2Q
470xFIbtjzjuLf5vR3q6OJ62CCvzqXgHkJ+hzVN/tAvyliGTdjJrK+xv1M5a+XipO2f
471c9lb4gRbFL/DyoeoWgb1Rkv3gFf0FlCYH+ZUcYb9ZYCRlGtFgOcxJI2g+T7jSLFp
4728+hSzQ6W5Sp9L6b5iJyCww1vjBvBqzNyZMNeB4gXGtd6z9vMDSvKboTdGD7wcFB+
473mRMyNekaRw+Npy4Hjou5sx272cXHHmPCSF5TjwdaibSaGjx1k0Q50mOf7S9KG5b5
4747X1e3FekJlaD02EBEhtkXURIxogOQALdFncj
475-----END CERTIFICATE-----
484-----BEGIN DH PARAMETERS-----
485MIIBCAKCAQEAp2I2fWEUZ3sCNfitSRC/MdAhJE/bS+NO0O2tWdIdlvmIFE6B5qhC
486sGW9ojrQT8DTxBvGAcbjr/jagmlE3BV4oSnxyhP37G2mDvMOJ29J3NvFD/ZFAW0d
487BvZJ1RNvMu29NmVCyt6/jgzcqrqnami9uD93aK+zaVrlPsPEYM8xB19HXwqsEYCL
488ux2B7sqXm9Ts74HPg/EV+pcVon9phxNWxxgHlOvFc2QjZ3hXH++kzmJ4vs7N/XDB
489xbEQ+TUZ5jbJGSeBqNFKFeuOUQGJ46Io0jBSYd4rSmKUXkvElQwR+n7KF3jy1uAt
490/8hzd8tHn9TyW7Q2/CPkOA6dCXzltpOSowIBAg==
491-----END DH PARAMETERS-----
500 TrustedPublisherServer&
self;
502 boost::asio::executor_work_guard<boost::asio::executor>
work;
507 TrustedPublisherServer& self_,
537 if (
auto p = wp.lock())
547 using namespace boost::beast;
559 ssl_stream->handshake(ssl::stream_base::server, ec);
571 http::read(*ssl_stream, sb, req, ec);
573 http::read(sock, sb, req, ec);
579 res.insert(
"Server",
"TrustedPublisherServer");
580 res.version(req.version());
581 res.keep_alive(req.keep_alive());
584 if (boost::starts_with(path,
"/validators2"))
586 res.result(http::status::ok);
587 res.insert(
"Content-Type",
"application/json");
588 if (path ==
"/validators2/bad")
589 res.body() =
"{ 'bad': \"2']";
590 else if (path ==
"/validators2/missing")
591 res.body() =
"{\"version\": 2}";
595 constexpr char const* refreshPrefix =
596 "/validators2/refresh/";
597 if (boost::starts_with(path, refreshPrefix))
598 refresh = boost::lexical_cast<unsigned int>(
599 path.substr(
strlen(refreshPrefix)));
603 else if (boost::starts_with(path,
"/validators"))
605 res.result(http::status::ok);
606 res.insert(
"Content-Type",
"application/json");
607 if (path ==
"/validators/bad")
608 res.body() =
"{ 'bad': \"1']";
609 else if (path ==
"/validators/missing")
610 res.body() =
"{\"version\": 1}";
614 constexpr char const* refreshPrefix =
615 "/validators/refresh/";
616 if (boost::starts_with(path, refreshPrefix))
617 refresh = boost::lexical_cast<unsigned int>(
618 path.substr(
strlen(refreshPrefix)));
622 else if (boost::starts_with(path,
"/textfile"))
625 res.result(http::status::ok);
626 res.insert(
"Content-Type",
"text/example");
629 boost::starts_with(path,
"/textfile/huge")
632 res.content_length(cl);
633 if (req.method() == http::verb::get)
636 for (
auto i = 0; i < 1024; ++i)
637 body <<
static_cast<char>(rand_int<short>(32, 126)),
638 res.body() = body.
str();
641 else if (boost::starts_with(path,
"/sleep/"))
643 auto const sleep_sec =
644 boost::lexical_cast<unsigned int>(path.substr(7));
648 else if (boost::starts_with(path,
"/redirect"))
650 if (boost::ends_with(path,
"/301"))
651 res.result(http::status::moved_permanently);
652 else if (boost::ends_with(path,
"/302"))
653 res.result(http::status::found);
654 else if (boost::ends_with(path,
"/307"))
655 res.result(http::status::temporary_redirect);
656 else if (boost::ends_with(path,
"/308"))
657 res.result(http::status::permanent_redirect);
660 if (boost::starts_with(path,
"/redirect_to/"))
662 location << path.substr(13);
664 else if (!boost::starts_with(path,
"/redirect_nolo"))
667 << (ssl ?
"https://" :
"http://")
669 << (boost::starts_with(path,
"/redirect_forever/")
673 if (!location.
str().empty())
674 res.insert(
"Location", location.
str());
679 res.result(boost::beast::http::status::not_found);
680 res.insert(
"Content-Type",
"text/html");
687 res.prepare_payload();
692 res.result(boost::beast::http::status::internal_server_error);
693 res.version(req.version());
694 res.
insert(
"Server",
"TrustedPublisherServer");
695 res.insert(
"Content-Type",
"text/html");
698 res.prepare_payload();
702 write(*ssl_stream, res, ec);
704 write(sock, res, ec);
706 if (ec || req.need_eof())
712 ssl_stream->shutdown(ec);
718 boost::asio::io_context& ioc,
725 bool immediateStart =
true,
728 auto const r = std::make_shared<TrustedPublisherServer>(
729 ioc, validators, validUntil, futures, useSSL, version, sequence);
void add(Serializer &s) const override
std::size_t size() const noexcept
void const * data() const noexcept
boost::beast::http::request< boost::beast::http::string_body > req_type
boost::asio::ip::address address_type
static std::string const & key()
void do_peer(int id, socket_type &&s, bool ssl)
static std::string const & cert()
TrustedPublisherServer(boost::asio::io_context &ioc, std::vector< Validator > const &validators, NetClock::time_point validUntil, std::vector< std::pair< NetClock::time_point, NetClock::time_point > > const &futures, bool useSSL=false, int version=1, bool immediateStart=true, int sequence=1)
PublicKey publisherPublic_
boost::asio::ip::tcp::socket socket_type
void load_server_certificate()
boost::beast::http::response< boost::beast::http::string_body > resp_type
void on_accept(error_code ec)
static std::string const & ca_cert()
std::function< std::string(int)> getList2_
PublicKey const & publisherPublic() const
~TrustedPublisherServer()
endpoint_type local_endpoint() const
static std::string const & dh()
boost::asio::ip::tcp::acceptor acceptor_
boost::system::error_code error_code
std::function< std::string(int)> getList_
boost::asio::ssl::context sslCtx_
SecretKey publisherSecret_
boost::asio::ip::tcp::endpoint endpoint_type
static Validator randomValidator()
static std::string makeManifestString(PublicKey const &pk, SecretKey const &sk, PublicKey const &spk, SecretKey const &ssk, int seq)
Set the regular signature on a JTx.
T emplace_back(T... args)
void write(nudb::detail::ostream &os, std::size_t t)
void sign(Json::Value &jv, Account const &account)
Sign automatically.
const char * getEnvLocalhostAddr()
std::shared_ptr< TrustedPublisherServer > make_TrustedPublisherServer(boost::asio::io_context &ioc, std::vector< TrustedPublisherServer::Validator > const &validators, NetClock::time_point validUntil, std::vector< std::pair< NetClock::time_point, NetClock::time_point > > const &futures, bool useSSL=false, int version=1, bool immediateStart=true, int sequence=1)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
PublicKey derivePublicKey(KeyType type, SecretKey const &sk)
Derive the public key from a secret key.
std::optional< KeyType > publicKeyType(Slice const &slice)
Returns the type of public key.
bool from_string(RangeSet< T > &rs, std::string const &s)
Convert the given styled string to a RangeSet.
std::string strHex(FwdIt begin, FwdIt end)
std::enable_if_t< std::is_same< T, char >::value||std::is_same< T, unsigned char >::value, Slice > makeSlice(std::array< T, N > const &a)
SecretKey randomSecretKey()
Create a secret key using secure random numbers.
std::string base64_encode(std::uint8_t const *data, std::size_t len)
std::pair< PublicKey, SecretKey > randomKeyPair(KeyType type)
Create a key pair using secure random numbers.
T shared_from_this(T... args)
BlobInfo(std::string b, std::string s)
TrustedPublisherServer & self
boost::asio::executor_work_guard< boost::asio::executor > work
lambda(int id_, TrustedPublisherServer &self_, socket_type &&sock_, bool ssl_)
Set the sequence number on a JTx.
T time_since_epoch(T... args)