3#include <test/jtx/envconfig.h>
5#include <xrpl/basics/Slice.h>
6#include <xrpl/basics/base64.h>
7#include <xrpl/basics/chrono.h>
8#include <xrpl/basics/random.h>
9#include <xrpl/basics/strHex.h>
10#include <xrpl/protocol/HashPrefix.h>
11#include <xrpl/protocol/KeyType.h>
12#include <xrpl/protocol/PublicKey.h>
13#include <xrpl/protocol/SField.h>
14#include <xrpl/protocol/STObject.h>
15#include <xrpl/protocol/SecretKey.h>
16#include <xrpl/protocol/Serializer.h>
17#include <xrpl/protocol/Sign.h>
19#include <boost/asio.hpp>
20#include <boost/asio/ip/tcp.hpp>
21#include <boost/asio/ssl/stream.hpp>
22#include <boost/beast/core/flat_buffer.hpp>
23#include <boost/beast/http.hpp>
24#include <boost/beast/ssl.hpp>
25#include <boost/beast/version.hpp>
26#include <boost/lexical_cast.hpp>
52 using req_type = boost::beast::http::request<boost::beast::http::string_body>;
53 using resp_type = boost::beast::http::response<boost::beast::http::string_body>;
68 boost::asio::ssl::context
sslCtx_{boost::asio::ssl::context::tlsv12};
79 [](
std::size_t, boost::asio::ssl::context_base::password_purpose) {
return "test"; });
82 boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 |
83 boost::asio::ssl::context::single_dh_use);
85 sslCtx_.use_certificate_chain(boost::asio::buffer(
cert().data(),
cert().size()));
88 boost::asio::buffer(
key().data(),
key().size()),
89 boost::asio::ssl::context::file_format::pem);
91 sslCtx_.use_tmp_dh(boost::asio::buffer(
dh().data(),
dh().size()));
123 st[sfSequence] = seq;
124 st[sfPublicKey] = pk;
125 st[sfSigningPubKey] = spk;
146 .masterPublic = masterPublic,
147 .signingPublic = signingKeys.first,
159 boost::asio::io_context& ioc,
165 bool immediateStart =
true,
177 auto const manifest =
181 blobInfo.
reserve(futures.size() + 1);
188 for (
auto const& val : validators)
190 data += R
"({"validation_public_key":")" + strHex(val.masterPublic) +
191 R"(","manifest":")" + val.manifest + "\"},";
200 getList_ = [blob = blob, sig, manifest, version](
int interval) {
203 l << R
"({"blob":")" << blob << "\"" << R
"(,"signature":")" << sig << "\""
204 << R
"(,"manifest":")" << manifest << "\""
205 <<
",\"refresh_interval\": " << interval <<
",\"version\":" << version <<
'}';
208 for (
auto const& future : futures)
211 ",\"effective\":" +
std::to_string(future.first.time_since_epoch().count()) +
212 ",\"expiration\":" +
std::to_string(future.second.time_since_epoch().count()) +
216 for (
auto const& val : validators)
218 data += R
"({"validation_public_key":")" + strHex(val.masterPublic) +
219 R"(","manifest":")" + val.manifest + "\"},";
227 getList2_ = [blobInfo, manifest, version](
int interval) {
232 for (
auto const& info : blobInfo)
234 l << R
"({"blob":")" << info.blob << "\"" << R
"(,"signature":")" << info.signature
240 l <<
"{\"blobs_v2\": [ " << blobs << R
"(],"manifest":")" << manifest << "\""
241 <<
",\"refresh_interval\": " << interval <<
",\"version\":" << (version + 1) <<
'}';
257 acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(
true), ec);
259 acceptor_.listen(boost::asio::socket_base::max_listen_connections);
262 if (
auto p = wp.lock())
361-----BEGIN CERTIFICATE-----
362MIIDczCCAlugAwIBAgIBATANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEL
363MAkGA1UECAwCQ0ExFDASBgNVBAcMC0xvcyBBbmdlbGVzMRswGQYDVQQKDBJyaXBw
364bGVkLXVuaXQtdGVzdHMxFDASBgNVBAMMC2V4YW1wbGUuY29tMB4XDTIyMDIwNTIz
365NDk0M1oXDTQ5MDYyMzIzNDk0M1owazELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNh
366bGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xGzAZBgNVBAoMEnJpcHBs
367ZWQtdW5pdC10ZXN0czESMBAGA1UEAwwJMTI3LjAuMC4xMIIBIjANBgkqhkiG9w0B
368AQEFAAOCAQ8AMIIBCgKCAQEAueZ1hgRxwPgfeVx2AdngUYx7zYcaxcGYXyqi7izJ
369qTuBUcVcTRC/9Ip67RAEhfcgGudRS/a4Sv1ljwiRknSCcD/ZjzOFDLgbqYGSZNEs
370+T/qkwmc/L+Pbzf85HM7RjeGOd6NDQy9+oOBbUtqpTxcSGa4ln+YBFUSeoS1Aa9f
371n9vrxnWX9LgTu5dSWzH5TqFIti+Zs/v0PFjEivBIAOHPslmnzg/wCr99I6z9CAR3
372zVDe7+sxR//ivpeVE7FWjgkGixnUpZAqn69zNkJjMLNXETgOYskZdMIgbVOMr+0q
373S1Uj77mhwxKfpnB6TqUVvWLBvmBDzPjf0m0NcCf9UAjqPwIDAQABoyowKDAmBgNV
374HREEHzAdgglsb2NhbGhvc3SHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQEL
375BQADggEBAJkUFNS0CeEAKvo0ttzooXnCDH3esj2fwmLJQYLUGsAF8DFrFHTqZEcx
376hFRdr0ftEb/VKpV9dVF6xtSoMU56kHOnhbHEWADyqdKUkCDjrGBet5QdWmEwNV2L
377nYrwGQBAybMt/+1XMUV8HeLFJNHnyxfQYcW0fUsrmNGk8W0kzWuuq88qbhfXZAIx
378KiXrzYpLlM0RlpWXRfYQ6mTdSrRrLnEo5MklizVgNB8HYX78lxa06zP08oReQcfT
379GSGO8NEEq8BTVmp69zD1JyfvQcXzsi7WtkAX+/EOFZ7LesnZ6VsyjZ74wECCaQuD
380X1yu/XxHqchM+DOzzVw6wRKaM7Zsk80=
381-----END CERTIFICATE-----
390-----BEGIN RSA PRIVATE KEY-----
391MIIEpAIBAAKCAQEAueZ1hgRxwPgfeVx2AdngUYx7zYcaxcGYXyqi7izJqTuBUcVc
392TRC/9Ip67RAEhfcgGudRS/a4Sv1ljwiRknSCcD/ZjzOFDLgbqYGSZNEs+T/qkwmc
393/L+Pbzf85HM7RjeGOd6NDQy9+oOBbUtqpTxcSGa4ln+YBFUSeoS1Aa9fn9vrxnWX
3949LgTu5dSWzH5TqFIti+Zs/v0PFjEivBIAOHPslmnzg/wCr99I6z9CAR3zVDe7+sx
395R//ivpeVE7FWjgkGixnUpZAqn69zNkJjMLNXETgOYskZdMIgbVOMr+0qS1Uj77mh
396wxKfpnB6TqUVvWLBvmBDzPjf0m0NcCf9UAjqPwIDAQABAoIBAEC9MDpOu+quvg8+
397kt4MKSFdIhQuM7WguNaTe5AkSspDrcJzT7SK275mp259QIYCzMxxuA8TSZTb8A1C
398t6dgKbi7k6FaGMCYMRHzzK6NZfMbPi6cj245q9LYlZpdQswuM/FdPpPH1zUxrNYK
399CIaooZ6ZHzlSD/eaRMgkBQEkONHrZZtEinLIvKedwssPCaXkIISmt7MFQTDOlxkf
400K0Mt1mnRREPYbYSfPEEfIyy/KDIiB5AzgGt+uPOn8Oeb1pSqy69jpYcfhSj+bo4S
401UV6qTuTfBd4qkkNI6d/Z7DcDJFFlfloG/vVgGk/beWNnL2e39vzxiebB3w+MQn4F
402Wyx5mCECgYEA22z1/ihqt9LIAWtP42oSS3S/RxlFzpp5d7QfNqFnEoVgeRhQzleP
403pRJIzVXpMYBxexZYqZA/q8xBSggz+2gmRoYnW20VIzl14DsSH378ye3FRwJB0tLy
404dWU8DC7ZB5XQCTvI9UY3voJNToknODw7RCNO1h3V3T1y6JRLdcLskk8CgYEA2OLy
405aE5bvsUaLBSv7W9NFhSuZ0p9Y0pFmRgHI7g8i/AgRZ0BgiE8u8OZSHmPJPMaNs/h
406YIEIrlsgDci1PzwrUYseRp/aiVE1kyev09/ihqRXTPpLQu6h/d63KRe/06W3t5X3
407Dmfj49hH5zGPBI/0y1ECV/n0fwnRhxSv7fNr3RECgYBEuFpOUAAkNApZj29ErNqv
4088Q9ayAp5yx1RpQLFjEUIoub05e2gwgGF1DUiwc43p59iyjvYVwnp1x13fxwwl4yt
409N6Sp2H7vOja1lCp33MB0yVeohodw7InsxFjLA/0KiBvQWH32exhIPOzTNNcooIx7
410KYeuPUfWc0FCn/cGGZcXtwKBgQC1hp1k99CKBuY05suoanOWe5DNGud/ZvaBgD7Z
411gqYKadxY52QPyknOzZNJuZQ5VM8n+S2lW9osNFDLuKUaW/3Vrh6U9c4vCC1TEPB0
4124PnzvzDiWMsNJjWnCfU7C4meVyFBIt84y3NNjAQCWNRe+S3lzdOsVqRwf4NDD+l/
413uzEYQQKBgQCJczIlwobm1Y6O41hbGZhZL/CGMNS6Z0INi2yasV0WDqYlh7XayHMD
414cK55dMILcbHqeIBq/wR6sIhw6IJcaDBfFfrJiKKDilfij2lHxR2FQrEngtTCCRV+
415ZzARzaWhQPvbDqEtLJDWuXZNXfL8/PTIs5NmuKuQ8F4+gQJpkQgwaw==
416-----END RSA PRIVATE KEY-----
425-----BEGIN CERTIFICATE-----
426MIIDpzCCAo+gAwIBAgIUWc45WqaaNuaSLoFYTMC/Mjfqw/gwDQYJKoZIhvcNAQEL
427BQAwYzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRQwEgYDVQQHDAtMb3MgQW5n
428ZWxlczEbMBkGA1UECgwScmlwcGxlZC11bml0LXRlc3RzMRQwEgYDVQQDDAtleGFt
429cGxlLmNvbTAeFw0yMjAyMDUyMzQ5MDFaFw00OTA2MjMyMzQ5MDFaMGMxCzAJBgNV
430BAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UEBwwLTG9zIEFuZ2VsZXMxGzAZBgNV
431BAoMEnJpcHBsZWQtdW5pdC10ZXN0czEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEi
432MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC0f2JBW2XNW2wT5/ajX2qxmUY+
433aNJGfpV6gZ5CmwdQpbHrPPvJoskxwsCyr3GifzT/GtCpmb1fiu59uUAPxQEYCxiq
434V+HchX4g4Vl27xKJ0P+usxuEED9v7TCteKum9u9eMZ8UDF0fspXcnWGs9fXlyoTj
435uTRP1SBQllk44DPc/KzlrtH+QNXmr9XQnP8XvwWCgJXMx87voxEGiFFOVhkSSAOv
436v+OUGgEuq0NPgwv2LHBlYHSdkoU9F5Z/TmkCAFMShbyoUjldIz2gcWXjN2tespGo
437D6qYvasvPIpmcholBBkc0z8QDt+RNq+Wzrults7epJXy/u+txGK9cHCNlLCpAgMB
438AAGjUzBRMB0GA1UdDgQWBBS1oydh+YyqDNOFKYOvOtVMWKqV4zAfBgNVHSMEGDAW
439gBS1oydh+YyqDNOFKYOvOtVMWKqV4zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
440DQEBCwUAA4IBAQCDPyGKQwQ8Lz0yEgvIl/Uo9BtwAzlvjrLM/39qhStLQqDGSs2Q
441xFIbtjzjuLf5vR3q6OJ62CCvzqXgHkJ+hzVN/tAvyliGTdjJrK+xv1M5a+XipO2f
442c9lb4gRbFL/DyoeoWgb1Rkv3gFf0FlCYH+ZUcYb9ZYCRlGtFgOcxJI2g+T7jSLFp
4438+hSzQ6W5Sp9L6b5iJyCww1vjBvBqzNyZMNeB4gXGtd6z9vMDSvKboTdGD7wcFB+
444mRMyNekaRw+Npy4Hjou5sx272cXHHmPCSF5TjwdaibSaGjx1k0Q50mOf7S9KG5b5
4457X1e3FekJlaD02EBEhtkXURIxogOQALdFncj
446-----END CERTIFICATE-----
455-----BEGIN DH PARAMETERS-----
456MIIBCAKCAQEAp2I2fWEUZ3sCNfitSRC/MdAhJE/bS+NO0O2tWdIdlvmIFE6B5qhC
457sGW9ojrQT8DTxBvGAcbjr/jagmlE3BV4oSnxyhP37G2mDvMOJ29J3NvFD/ZFAW0d
458BvZJ1RNvMu29NmVCyt6/jgzcqrqnami9uD93aK+zaVrlPsPEYM8xB19HXwqsEYCL
459ux2B7sqXm9Ts74HPg/EV+pcVon9phxNWxxgHlOvFc2QjZ3hXH++kzmJ4vs7N/XDB
460xbEQ+TUZ5jbJGSeBqNFKFeuOUQGJ46Io0jBSYd4rSmKUXkvElQwR+n7KF3jy1uAt
461/8hzd8tHn9TyW7Q2/CPkOA6dCXzltpOSowIBAg==
462-----END DH PARAMETERS-----
473 boost::asio::executor_work_guard<boost::asio::executor>
work;
494 static int nextId = 0;
498 if (
auto p = wp.lock())
508 using namespace boost::beast;
509 using namespace boost::asio;
513 std::optional<ssl_stream<ip::tcp::socket&>> sslStream;
520 sslStream->handshake(ssl::stream_base::server, ec);
534 *sslStream, sb, req, ec);
539 http::read(sock, sb, req, ec);
545 std::string_view
const path = req.target();
546 res.insert(
"Server",
"TrustedPublisherServer");
547 res.version(req.version());
548 res.keep_alive(req.keep_alive());
553 res.result(http::status::ok);
554 res.insert(
"Content-Type",
"application/json");
555 if (path ==
"/validators2/bad")
557 res.body() =
"{ 'bad': \"2']";
559 else if (path ==
"/validators2/missing")
561 res.body() =
"{\"version\": 2}";
566 static constexpr char const* kRefreshPrefix =
"/validators2/refresh/";
569 refresh = boost::lexical_cast<unsigned int>(
577 res.result(http::status::ok);
578 res.insert(
"Content-Type",
"application/json");
579 if (path ==
"/validators/bad")
581 res.body() =
"{ 'bad': \"1']";
583 else if (path ==
"/validators/missing")
585 res.body() =
"{\"version\": 1}";
590 static constexpr char const* kRefreshPrefix =
"/validators/refresh/";
593 refresh = boost::lexical_cast<unsigned int>(
602 res.result(http::status::ok);
603 res.insert(
"Content-Type",
"text/example");
605 std::uint64_t
const cl = path.
starts_with(
"/textfile/huge")
608 res.content_length(cl);
609 if (req.method() == http::verb::get)
611 std::stringstream body;
612 for (
auto i = 0; i < 1024; ++i)
615 res.body() = body.
str();
621 auto const sleepSec = boost::lexical_cast<unsigned int>(path.
substr(7));
628 res.result(http::status::moved_permanently);
632 res.result(http::status::found);
636 res.result(http::status::temporary_redirect);
640 res.result(http::status::permanent_redirect);
643 std::stringstream location;
646 location << path.
substr(13);
650 location << (ssl ?
"https://" :
"http://") <<
localEndpoint()
651 << (path.
starts_with(
"/redirect_forever/") ? path :
"/validators");
653 if (!location.
str().empty())
654 res.insert(
"Location", location.
str());
659 res.result(boost::beast::http::status::not_found);
660 res.insert(
"Content-Type",
"text/html");
661 res.body() =
"The file '" + std::string(path) +
667 res.prepare_payload();
669 catch (std::exception
const& e)
672 res.result(boost::beast::http::status::internal_server_error);
673 res.version(req.version());
674 res.insert(
"Server",
"TrustedPublisherServer");
675 res.insert(
"Content-Type",
"text/html");
676 res.body() = std::string{
"An internal error occurred"} + e.
what();
677 res.prepare_payload();
682 write(*sslStream, res, ec);
687 write(sock, res, ec);
690 if (ec || req.need_eof())
696 sslStream->shutdown(ec);
701inline std::shared_ptr<TrustedPublisherServer>
703 boost::asio::io_context& ioc,
709 bool immediateStart =
true,
713 ioc, validators, validUntil, futures, useSSL, version, sequence);
std::chrono::time_point< NetClock > time_point
void add(Serializer &s) const override
std::size_t size() const noexcept
void const * data() const noexcept
boost::asio::ip::address address_type
static std::string const & dh()
static std::string makeManifestString(PublicKey const &pk, SecretKey const &sk, PublicKey const &spk, SecretKey const &ssk, int seq)
PublicKey const & publisherPublic() const
boost::beast::http::request< boost::beast::http::string_body > req_type
boost::asio::ssl::context sslCtx_
static std::string const & caCert()
PublicKey publisherPublic_
static std::string const & key()
boost::asio::ip::tcp::acceptor acceptor_
void onAccept(error_code ec)
~TrustedPublisherServer()
static std::string const & cert()
static Validator randomValidator()
std::function< std::string(int)> getList_
endpoint_type localEndpoint() const
SecretKey publisherSecret_
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)
boost::asio::ip::tcp::endpoint endpoint_type
boost::system::error_code error_code
void doPeer(int id, socket_type &&s, bool ssl)
boost::asio::ip::tcp::socket socket_type
boost::beast::http::response< boost::beast::http::string_body > resp_type
std::function< std::string(int)> getList2_
void loadServerCertificate()
T emplace_back(T... args)
void write(nudb::detail::ostream &os, std::size_t t)
void sign(json::Value &jv, Account const &account, json::Value &sigObject)
Sign automatically into a specific Json field of the jv object.
std::shared_ptr< TrustedPublisherServer > makeTrustedPublisherServer(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)
char const * getEnvLocalhostAddr()
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
std::pair< PublicKey, SecretKey > randomKeyPair(KeyType type)
Create a key pair using secure random numbers.
PublicKey derivePublicKey(KeyType type, SecretKey const &sk)
Derive the public key from a secret key.
std::string strHex(FwdIt begin, FwdIt end)
std::optional< KeyType > publicKeyType(Slice const &slice)
Returns the type of public key.
Slice makeSlice(std::array< T, N > const &a)
SecretKey randomSecretKey()
Create a secret key using secure random numbers.
std::string base64Encode(std::uint8_t const *data, std::size_t len)
T shared_from_this(T... args)
BlobInfo(std::string b, std::string s)
Lambda(int id, TrustedPublisherServer &self, socket_type &&sock, bool ssl)
TrustedPublisherServer & self
boost::asio::executor_work_guard< boost::asio::executor > work
T time_since_epoch(T... args)