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