rippled
Loading...
Searching...
No Matches
Handshake.cpp
1#include <xrpld/app/ledger/LedgerMaster.h>
2#include <xrpld/app/main/Application.h>
3#include <xrpld/overlay/detail/Handshake.h>
4
5#include <xrpl/basics/base64.h>
6#include <xrpl/beast/core/LexicalCast.h>
7#include <xrpl/beast/rfc2616.h>
8#include <xrpl/protocol/digest.h>
9
10#include <boost/regex.hpp>
11
12#include <algorithm>
13
14// VFALCO Shouldn't we have to include the OpenSSL
15// headers or something for SSL_get_finished?
16
17namespace xrpl {
18
20getFeatureValue(boost::beast::http::fields const& headers, std::string const& feature)
21{
22 auto const header = headers.find("X-Protocol-Ctl");
23 if (header == headers.end())
24 return {};
25 boost::smatch match;
26 boost::regex rx(feature + "=([^;\\s]+)");
27 std::string const allFeatures(header->value());
28 if (boost::regex_search(allFeatures, match, rx))
29 return {match[1]};
30 return {};
31}
32
33bool
35 boost::beast::http::fields const& headers,
36 std::string const& feature,
37 std::string const& value)
38{
39 if (auto const fvalue = getFeatureValue(headers, feature))
40 return beast::rfc2616::token_in_list(fvalue.value(), value);
41
42 return false;
43}
44
45bool
46featureEnabled(boost::beast::http::fields const& headers, std::string const& feature)
47{
48 return isFeatureValue(headers, feature, "1");
49}
50
53 bool comprEnabled,
54 bool ledgerReplayEnabled,
55 bool txReduceRelayEnabled,
56 bool vpReduceRelayEnabled)
57{
59 if (comprEnabled)
60 str << FEATURE_COMPR << "=lz4" << DELIM_FEATURE;
61 if (ledgerReplayEnabled)
62 str << FEATURE_LEDGER_REPLAY << "=1" << DELIM_FEATURE;
63 if (txReduceRelayEnabled)
64 str << FEATURE_TXRR << "=1" << DELIM_FEATURE;
65 if (vpReduceRelayEnabled)
66 str << FEATURE_VPRR << "=1" << DELIM_FEATURE;
67 return str.str();
68}
69
72 http_request_type const& headers,
73 bool comprEnabled,
74 bool ledgerReplayEnabled,
75 bool txReduceRelayEnabled,
76 bool vpReduceRelayEnabled)
77{
79 if (comprEnabled && isFeatureValue(headers, FEATURE_COMPR, "lz4"))
80 str << FEATURE_COMPR << "=lz4" << DELIM_FEATURE;
81 if (ledgerReplayEnabled && featureEnabled(headers, FEATURE_LEDGER_REPLAY))
82 str << FEATURE_LEDGER_REPLAY << "=1" << DELIM_FEATURE;
83 if (txReduceRelayEnabled && featureEnabled(headers, FEATURE_TXRR))
84 str << FEATURE_TXRR << "=1" << DELIM_FEATURE;
85 if (vpReduceRelayEnabled && featureEnabled(headers, FEATURE_VPRR))
86 str << FEATURE_VPRR << "=1" << DELIM_FEATURE;
87 return str.str();
88}
89
105hashLastMessage(SSL const* ssl, size_t (*get)(const SSL*, void*, size_t))
106{
107 constexpr std::size_t sslMinimumFinishedLength = 12;
108
109 unsigned char buf[1024];
110 size_t len = get(ssl, buf, sizeof(buf));
111
112 if (len < sslMinimumFinishedLength)
113 return std::nullopt;
114
116
117 base_uint<512> cookie;
118 SHA512(buf, len, cookie.data());
119 return cookie;
120}
121
124{
125 auto const cookie1 = hashLastMessage(ssl.native_handle(), SSL_get_finished);
126 if (!cookie1)
127 {
128 JLOG(journal.error()) << "Cookie generation: local setup not complete";
129 return std::nullopt;
130 }
131
132 auto const cookie2 = hashLastMessage(ssl.native_handle(), SSL_get_peer_finished);
133 if (!cookie2)
134 {
135 JLOG(journal.error()) << "Cookie generation: peer setup not complete";
136 return std::nullopt;
137 }
138
139 auto const result = (*cookie1 ^ *cookie2);
140
141 // Both messages hash to the same value and the cookie
142 // is 0. Don't allow this.
143 if (result == beast::zero)
144 {
145 JLOG(journal.error()) << "Cookie generation: identical finished messages";
146 return std::nullopt;
147 }
148
149 return sha512Half(Slice(result.data(), result.size()));
150}
151
152void
154 boost::beast::http::fields& h,
155 xrpl::uint256 const& sharedValue,
157 beast::IP::Address public_ip,
158 beast::IP::Address remote_ip,
159 Application& app)
160{
161 if (networkID)
162 {
163 // The network identifier, if configured, can be used to specify
164 // what network we intend to connect to and detect if the remote
165 // end connects to the same network.
166 h.insert("Network-ID", std::to_string(*networkID));
167 }
168
169 h.insert("Network-Time", std::to_string(app.timeKeeper().now().time_since_epoch().count()));
170
171 h.insert("Public-Key", toBase58(TokenType::NodePublic, app.nodeIdentity().first));
172
173 {
174 auto const sig =
175 signDigest(app.nodeIdentity().first, app.nodeIdentity().second, sharedValue);
176 h.insert("Session-Signature", base64_encode(sig.data(), sig.size()));
177 }
178
179 h.insert("Instance-Cookie", std::to_string(app.instanceID()));
180
181 if (!app.config().SERVER_DOMAIN.empty())
182 h.insert("Server-Domain", app.config().SERVER_DOMAIN);
183
184 if (beast::IP::is_public(remote_ip))
185 h.insert("Remote-IP", remote_ip.to_string());
186
187 if (!public_ip.is_unspecified())
188 h.insert("Local-IP", public_ip.to_string());
189
190 if (auto const cl = app.getLedgerMaster().getClosedLedger())
191 {
192 h.insert("Closed-Ledger", strHex(cl->header().hash));
193 h.insert("Previous-Ledger", strHex(cl->header().parentHash));
194 }
195}
196
197PublicKey
199 boost::beast::http::fields const& headers,
200 xrpl::uint256 const& sharedValue,
202 beast::IP::Address public_ip,
203 beast::IP::Address remote,
204 Application& app)
205{
206 if (auto const iter = headers.find("Server-Domain"); iter != headers.end())
207 {
208 if (!isProperlyFormedTomlDomain(iter->value()))
209 throw std::runtime_error("Invalid server domain");
210 }
211
212 if (auto const iter = headers.find("Network-ID"); iter != headers.end())
213 {
214 std::uint32_t nid;
215
216 if (!beast::lexicalCastChecked(nid, iter->value()))
217 throw std::runtime_error("Invalid peer network identifier");
218
219 if (networkID && nid != *networkID)
220 throw std::runtime_error("Peer is on a different network");
221 }
222
223 if (auto const iter = headers.find("Network-Time"); iter != headers.end())
224 {
225 auto const netTime = [str = iter->value()]() -> TimeKeeper::time_point {
226 TimeKeeper::duration::rep val;
227
228 if (beast::lexicalCastChecked(val, str))
230
231 // It's not an error for the header field to not be present but if
232 // it is present and it contains junk data, that is an error.
233 throw std::runtime_error("Invalid peer clock timestamp");
234 }();
235
236 using namespace std::chrono;
237
238 auto const ourTime = app.timeKeeper().now();
239 auto const tolerance = 20s;
240
241 // We can't blindly "return a-b;" because TimeKeeper::time_point
242 // uses an unsigned integer for representing durations, which is
243 // a problem when trying to subtract time points.
244 auto calculateOffset = [](TimeKeeper::time_point a, TimeKeeper::time_point b) {
245 if (a > b)
246 return duration_cast<std::chrono::seconds>(a - b);
247 return -duration_cast<std::chrono::seconds>(b - a);
248 };
249
250 auto const offset = calculateOffset(netTime, ourTime);
251
252 if (abs(offset) > tolerance)
253 throw std::runtime_error("Peer clock is too far off");
254 }
255
256 PublicKey const publicKey = [&headers] {
257 if (auto const iter = headers.find("Public-Key"); iter != headers.end())
258 {
259 auto pk = parseBase58<PublicKey>(TokenType::NodePublic, iter->value());
260
261 if (pk)
262 {
264 throw std::runtime_error("Unsupported public key type");
265
266 return *pk;
267 }
268 }
269
270 throw std::runtime_error("Bad node public key");
271 }();
272
273 // This check gets two birds with one stone:
274 //
275 // 1) it verifies that the node we are talking to has access to the
276 // private key corresponding to the public node identity it claims.
277 // 2) it verifies that our SSL session is end-to-end with that node
278 // and not through a proxy that establishes two separate sessions.
279 {
280 auto const iter = headers.find("Session-Signature");
281
282 if (iter == headers.end())
283 throw std::runtime_error("No session signature specified");
284
285 auto sig = base64_decode(iter->value());
286
287 if (!verifyDigest(publicKey, sharedValue, makeSlice(sig), false))
288 throw std::runtime_error("Failed to verify session");
289 }
290
291 if (publicKey == app.nodeIdentity().first)
292 throw std::runtime_error("Self connection");
293
294 if (auto const iter = headers.find("Local-IP"); iter != headers.end())
295 {
296 boost::system::error_code ec;
297 auto const local_ip = boost::asio::ip::make_address(std::string_view(iter->value()), ec);
298
299 if (ec)
300 throw std::runtime_error("Invalid Local-IP");
301
302 if (beast::IP::is_public(remote) && remote != local_ip)
303 throw std::runtime_error(
304 "Incorrect Local-IP: " + remote.to_string() + " instead of " +
305 local_ip.to_string());
306 }
307
308 if (auto const iter = headers.find("Remote-IP"); iter != headers.end())
309 {
310 boost::system::error_code ec;
311 auto const remote_ip = boost::asio::ip::make_address(std::string_view(iter->value()), ec);
312
313 if (ec)
314 throw std::runtime_error("Invalid Remote-IP");
315
316 if (beast::IP::is_public(remote) && !beast::IP::is_unspecified(public_ip))
317 {
318 // We know our public IP and peer reports our connection came
319 // from some other IP.
320 if (remote_ip != public_ip)
321 throw std::runtime_error(
322 "Incorrect Remote-IP: " + public_ip.to_string() + " instead of " +
323 remote_ip.to_string());
324 }
325 }
326
327 return publicKey;
328}
329
330auto
332 bool crawlPublic,
333 bool comprEnabled,
334 bool ledgerReplayEnabled,
335 bool txReduceRelayEnabled,
336 bool vpReduceRelayEnabled) -> request_type
337{
338 request_type m;
339 m.method(boost::beast::http::verb::get);
340 m.target("/");
341 m.version(11);
342 m.insert("User-Agent", BuildInfo::getFullVersionString());
343 m.insert("Upgrade", supportedProtocolVersions());
344 m.insert("Connection", "Upgrade");
345 m.insert("Connect-As", "Peer");
346 m.insert("Crawl", crawlPublic ? "public" : "private");
347 m.insert(
348 "X-Protocol-Ctl",
350 comprEnabled, ledgerReplayEnabled, txReduceRelayEnabled, vpReduceRelayEnabled));
351 return m;
352}
353
356 bool crawlPublic,
357 http_request_type const& req,
358 beast::IP::Address public_ip,
359 beast::IP::Address remote_ip,
360 uint256 const& sharedValue,
363 Application& app)
364{
366 resp.result(boost::beast::http::status::switching_protocols);
367 resp.version(req.version());
368 resp.insert("Connection", "Upgrade");
369 resp.insert("Upgrade", to_string(protocol));
370 resp.insert("Connect-As", "Peer");
371 resp.insert("Server", BuildInfo::getFullVersionString());
372 resp.insert("Crawl", crawlPublic ? "public" : "private");
373 resp.insert(
374 "X-Protocol-Ctl",
376 req,
377 app.config().COMPRESSION,
378 app.config().LEDGER_REPLAY,
381
382 buildHandshake(resp, sharedValue, networkID, public_ip, remote_ip, app);
383
384 return resp;
385}
386
387} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:40
Stream error() const
Definition Journal.h:319
typename Clock::time_point time_point
typename Clock::duration duration
virtual Config & config()=0
virtual std::uint64_t instanceID() const =0
Returns a 64-bit instance identifier, generated at startup.
virtual std::pair< PublicKey, SecretKey > const & nodeIdentity()=0
bool COMPRESSION
Definition Config.h:201
bool TX_REDUCE_RELAY_ENABLE
Definition Config.h:239
std::string SERVER_DOMAIN
Definition Config.h:259
bool VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE
Definition Config.h:229
bool LEDGER_REPLAY
Definition Config.h:204
std::shared_ptr< Ledger const > getClosedLedger()
A public key.
Definition PublicKey.h:42
virtual LedgerMaster & getLedgerMaster()=0
virtual TimeKeeper & timeKeeper()=0
An immutable linear range of bytes.
Definition Slice.h:26
time_point now() const override
Returns the current time, using the server's clock.
Definition TimeKeeper.h:44
Integers of any length that is a multiple of 32-bits.
Definition base_uint.h:66
pointer data()
Definition base_uint.h:101
T empty(T... args)
T is_same_v
bool is_public(Address const &addr)
Returns true if the address is a public routable address.
Definition IPAddress.h:58
bool is_unspecified(Address const &addr)
Returns true if the address is unspecified.
Definition IPAddress.h:37
boost::asio::ip::address Address
Definition IPAddress.h:19
bool token_in_list(boost::string_ref const &value, boost::string_ref const &token)
Returns true if the specified token exists in the list.
Definition rfc2616.h:349
bool lexicalCastChecked(Out &out, In in)
Intelligently convert from one type to another.
std::string const & getFullVersionString()
Full server version string.
Definition BuildInfo.cpp:64
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
boost::beast::http::response< boost::beast::http::dynamic_body > http_response_type
Definition Handoff.h:14
static std::optional< base_uint< 512 > > hashLastMessage(SSL const *ssl, size_t(*get)(const SSL *, void *, size_t))
Hashes the latest finished message from an SSL stream.
bool featureEnabled(boost::beast::http::fields const &headers, std::string const &feature)
Check if a feature is enabled.
Definition Handshake.cpp:46
bool verifyDigest(PublicKey const &publicKey, uint256 const &digest, Slice const &sig, bool mustBeFullyCanonical=true) noexcept
Verify a secp256k1 signature on the digest of a message.
sha512_half_hasher::result_type sha512Half(Args const &... args)
Returns the SHA512-Half of a series of objects.
Definition digest.h:204
std::optional< uint256 > makeSharedValue(stream_type &ssl, beast::Journal journal)
Computes a shared value based on the SSL connection state.
static constexpr char FEATURE_COMPR[]
Definition Handshake.h:118
bool isProperlyFormedTomlDomain(std::string_view domain)
Determines if the given string looks like a TOML-file hosting domain.
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:600
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:10
boost::beast::http::request< boost::beast::http::dynamic_body > http_request_type
Definition Handoff.h:12
std::string base64_decode(std::string_view data)
boost::beast::ssl_stream< socket_type > stream_type
Definition Handshake.h:22
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:92
std::string base64_encode(std::uint8_t const *data, std::size_t len)
auto makeRequest(bool crawlPublic, bool comprEnabled, bool ledgerReplayEnabled, bool txReduceRelayEnabled, bool vpReduceRelayEnabled) -> request_type
Make outbound http request.
std::optional< KeyType > publicKeyType(Slice const &slice)
Returns the type of public key.
boost::beast::http::request< boost::beast::http::empty_body > request_type
Definition Handshake.h:23
std::string makeFeaturesRequestHeader(bool comprEnabled, bool ledgerReplayEnabled, bool txReduceRelayEnabled, bool vpReduceRelayEnabled)
Make request header X-Protocol-Ctl value with supported features.
Definition Handshake.cpp:52
Buffer signDigest(PublicKey const &pk, SecretKey const &sk, uint256 const &digest)
Generate a signature for a message digest.
constexpr Number abs(Number x) noexcept
Definition Number.h:719
static constexpr char DELIM_FEATURE[]
Definition Handshake.h:125
http_response_type makeResponse(bool crawlPublic, http_request_type const &req, beast::IP::Address public_ip, beast::IP::Address remote_ip, uint256 const &sharedValue, std::optional< std::uint32_t > networkID, ProtocolVersion protocol, Application &app)
Make http response.
std::string const & supportedProtocolVersions()
The list of all the protocol versions we support.
std::optional< std::string > getFeatureValue(boost::beast::http::fields const &headers, std::string const &feature)
Get feature's header value.
Definition Handshake.cpp:20
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)
Definition Slice.h:215
static constexpr char FEATURE_LEDGER_REPLAY[]
Definition Handshake.h:124
static constexpr char FEATURE_VPRR[]
Definition Handshake.h:120
std::string makeFeaturesResponseHeader(http_request_type const &headers, bool comprEnabled, bool ledgerReplayEnabled, bool txReduceRelayEnabled, bool vpReduceRelayEnabled)
Make response header X-Protocol-Ctl value with supported features.
Definition Handshake.cpp:71
PublicKey verifyHandshake(boost::beast::http::fields const &headers, xrpl::uint256 const &sharedValue, std::optional< std::uint32_t > networkID, beast::IP::Address public_ip, beast::IP::Address remote, Application &app)
Validate header fields necessary for upgrading the link to the peer protocol.
bool isFeatureValue(boost::beast::http::fields const &headers, std::string const &feature, std::string const &value)
Check if a feature's value is equal to the specified value.
Definition Handshake.cpp:34
static constexpr char FEATURE_TXRR[]
Definition Handshake.h:122
void buildHandshake(boost::beast::http::fields &h, xrpl::uint256 const &sharedValue, std::optional< std::uint32_t > networkID, beast::IP::Address public_ip, beast::IP::Address remote_ip, Application &app)
Insert fields headers necessary for upgrading the link to the peer protocol.
T str(T... args)
SHA-512 digest.
Definition digest.h:51
T to_string(T... args)