rippled
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 <ripple/app/ledger/LedgerMaster.h>
21 #include <ripple/app/main/Application.h>
22 #include <ripple/basics/base64.h>
23 #include <ripple/basics/safe_cast.h>
24 #include <ripple/beast/core/LexicalCast.h>
25 #include <ripple/beast/rfc2616.h>
26 #include <ripple/overlay/impl/Handshake.h>
27 #include <ripple/protocol/digest.h>
28 #include <boost/regex.hpp>
29 #include <algorithm>
30 #include <chrono>
31 
32 // VFALCO Shouldn't we have to include the OpenSSL
33 // headers or something for SSL_get_finished?
34 
35 namespace ripple {
36 
39  boost::beast::http::fields const& headers,
40  std::string const& feature)
41 {
42  auto const header = headers.find("X-Protocol-Ctl");
43  if (header == headers.end())
44  return {};
45  boost::smatch match;
46  boost::regex rx(feature + "=([^;\\s]+)");
47  auto const value = header->value().to_string();
48  if (boost::regex_search(value, match, rx))
49  return {match[1]};
50  return {};
51 }
52 
53 bool
55  boost::beast::http::fields const& headers,
56  std::string const& feature,
57  std::string const& value)
58 {
59  if (auto const fvalue = getFeatureValue(headers, feature))
60  return beast::rfc2616::token_in_list(fvalue.value(), value);
61 
62  return false;
63 }
64 
65 bool
67  boost::beast::http::fields const& headers,
68  std::string const& feature)
69 {
70  return isFeatureValue(headers, feature, "1");
71 }
72 
75  bool comprEnabled,
76  bool ledgerReplayEnabled,
77  bool txReduceRelayEnabled,
78  bool vpReduceRelayEnabled)
79 {
81  if (comprEnabled)
82  str << FEATURE_COMPR << "=lz4" << DELIM_FEATURE;
83  if (ledgerReplayEnabled)
84  str << FEATURE_LEDGER_REPLAY << "=1" << DELIM_FEATURE;
85  if (txReduceRelayEnabled)
86  str << FEATURE_TXRR << "=1" << DELIM_FEATURE;
87  if (vpReduceRelayEnabled)
88  str << FEATURE_VPRR << "=1" << DELIM_FEATURE;
89  return str.str();
90 }
91 
94  http_request_type const& headers,
95  bool comprEnabled,
96  bool ledgerReplayEnabled,
97  bool txReduceRelayEnabled,
98  bool vpReduceRelayEnabled)
99 {
100  std::stringstream str;
101  if (comprEnabled && isFeatureValue(headers, FEATURE_COMPR, "lz4"))
102  str << FEATURE_COMPR << "=lz4" << DELIM_FEATURE;
103  if (ledgerReplayEnabled && featureEnabled(headers, FEATURE_LEDGER_REPLAY))
104  str << FEATURE_LEDGER_REPLAY << "=1" << DELIM_FEATURE;
105  if (txReduceRelayEnabled && featureEnabled(headers, FEATURE_TXRR))
106  str << FEATURE_TXRR << "=1" << DELIM_FEATURE;
107  if (vpReduceRelayEnabled && featureEnabled(headers, FEATURE_VPRR))
108  str << FEATURE_VPRR << "=1" << DELIM_FEATURE;
109  return str.str();
110 }
111 
127 hashLastMessage(SSL const* ssl, size_t (*get)(const SSL*, void*, size_t))
128 {
129  constexpr std::size_t sslMinimumFinishedLength = 12;
130 
131  unsigned char buf[1024];
132  size_t len = get(ssl, buf, sizeof(buf));
133 
134  if (len < sslMinimumFinishedLength)
135  return std::nullopt;
136 
137  sha512_hasher h;
138 
139  base_uint<512> cookie;
140  SHA512(buf, len, cookie.data());
141  return cookie;
142 }
143 
146 {
147  auto const cookie1 = hashLastMessage(ssl.native_handle(), SSL_get_finished);
148  if (!cookie1)
149  {
150  JLOG(journal.error()) << "Cookie generation: local setup not complete";
151  return std::nullopt;
152  }
153 
154  auto const cookie2 =
155  hashLastMessage(ssl.native_handle(), SSL_get_peer_finished);
156  if (!cookie2)
157  {
158  JLOG(journal.error()) << "Cookie generation: peer setup not complete";
159  return std::nullopt;
160  }
161 
162  auto const result = (*cookie1 ^ *cookie2);
163 
164  // Both messages hash to the same value and the cookie
165  // is 0. Don't allow this.
166  if (result == beast::zero)
167  {
168  JLOG(journal.error())
169  << "Cookie generation: identical finished messages";
170  return std::nullopt;
171  }
172 
173  return sha512Half(Slice(result.data(), result.size()));
174 }
175 
176 void
178  boost::beast::http::fields& h,
179  ripple::uint256 const& sharedValue,
181  beast::IP::Address public_ip,
182  beast::IP::Address remote_ip,
183  Application& app)
184 {
185  if (networkID)
186  {
187  // The network identifier, if configured, can be used to specify
188  // what network we intend to connect to and detect if the remote
189  // end connects to the same network.
190  h.insert("Network-ID", std::to_string(*networkID));
191  }
192 
193  h.insert(
194  "Network-Time",
195  std::to_string(app.timeKeeper().now().time_since_epoch().count()));
196 
197  h.insert(
198  "Public-Key",
200 
201  {
202  auto const sig = signDigest(
203  app.nodeIdentity().first, app.nodeIdentity().second, sharedValue);
204  h.insert("Session-Signature", base64_encode(sig.data(), sig.size()));
205  }
206 
207  h.insert("Instance-Cookie", std::to_string(app.instanceID()));
208 
209  if (!app.config().SERVER_DOMAIN.empty())
210  h.insert("Server-Domain", app.config().SERVER_DOMAIN);
211 
212  if (beast::IP::is_public(remote_ip))
213  h.insert("Remote-IP", remote_ip.to_string());
214 
215  if (!public_ip.is_unspecified())
216  h.insert("Local-IP", public_ip.to_string());
217 
218  if (auto const cl = app.getLedgerMaster().getClosedLedger())
219  {
220  h.insert("Closed-Ledger", strHex(cl->info().hash));
221  h.insert("Previous-Ledger", strHex(cl->info().parentHash));
222  }
223 }
224 
225 PublicKey
227  boost::beast::http::fields const& headers,
228  ripple::uint256 const& sharedValue,
230  beast::IP::Address public_ip,
231  beast::IP::Address remote,
232  Application& app)
233 {
234  if (auto const iter = headers.find("Server-Domain"); iter != headers.end())
235  {
236  if (!isProperlyFormedTomlDomain(iter->value().to_string()))
237  throw std::runtime_error("Invalid server domain");
238  }
239 
240  if (auto const iter = headers.find("Network-ID"); iter != headers.end())
241  {
242  std::uint32_t nid;
243 
244  if (!beast::lexicalCastChecked(nid, iter->value().to_string()))
245  throw std::runtime_error("Invalid peer network identifier");
246 
247  if (networkID && nid != *networkID)
248  throw std::runtime_error("Peer is on a different network");
249  }
250 
251  if (auto const iter = headers.find("Network-Time"); iter != headers.end())
252  {
253  auto const netTime =
254  [str = iter->value().to_string()]() -> 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 = parseBase58<PublicKey>(
290  TokenType::NodePublic, iter->value().to_string());
291 
292  if (pk)
293  {
294  if (publicKeyType(*pk) != KeyType::secp256k1)
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  if (publicKey == app.nodeIdentity().first)
305  {
306  auto const peerInstanceID = [&headers]() {
307  std::uint64_t iid = 0;
308 
309  if (auto const iter = headers.find("Instance-Cookie");
310  iter != headers.end())
311  {
312  if (!beast::lexicalCastChecked(iid, iter->value().to_string()))
313  throw std::runtime_error("Invalid instance cookie");
314 
315  if (iid == 0)
316  throw std::runtime_error("Invalid instance cookie");
317  }
318 
319  return iid;
320  }();
321 
322  // Attempt to differentiate self-connections as opposed to accidental
323  // node identity reuse caused by accidental misconfiguration. When we
324  // detect this, we stop the process and log an error message.
325  if (peerInstanceID != app.instanceID())
326  {
327  app.signalStop("Remote server is using our node identity");
328  throw std::runtime_error("Node identity reuse detected");
329  }
330 
331  throw std::runtime_error("Self connection");
332  }
333 
334  // This check gets two birds with one stone:
335  //
336  // 1) it verifies that the node we are talking to has access to the
337  // private key corresponding to the public node identity it claims.
338  // 2) it verifies that our SSL session is end-to-end with that node
339  // and not through a proxy that establishes two separate sessions.
340  {
341  auto const iter = headers.find("Session-Signature");
342 
343  if (iter == headers.end())
344  throw std::runtime_error("No session signature specified");
345 
346  auto sig = base64_decode(iter->value().to_string());
347 
348  if (!verifyDigest(publicKey, sharedValue, makeSlice(sig), false))
349  throw std::runtime_error("Failed to verify session");
350  }
351 
352  if (auto const iter = headers.find("Local-IP"); iter != headers.end())
353  {
354  boost::system::error_code ec;
355  auto const local_ip = boost::asio::ip::address::from_string(
356  iter->value().to_string(), ec);
357 
358  if (ec)
359  throw std::runtime_error("Invalid Local-IP");
360 
361  if (beast::IP::is_public(remote) && remote != local_ip)
362  throw std::runtime_error(
363  "Incorrect Local-IP: " + remote.to_string() + " instead of " +
364  local_ip.to_string());
365  }
366 
367  if (auto const iter = headers.find("Remote-IP"); iter != headers.end())
368  {
369  boost::system::error_code ec;
370  auto const remote_ip = boost::asio::ip::address::from_string(
371  iter->value().to_string(), ec);
372 
373  if (ec)
374  throw std::runtime_error("Invalid Remote-IP");
375 
376  if (beast::IP::is_public(remote) &&
377  !beast::IP::is_unspecified(public_ip))
378  {
379  // We know our public IP and peer reports our connection came
380  // from some other IP.
381  if (remote_ip != public_ip)
382  throw std::runtime_error(
383  "Incorrect Remote-IP: " + public_ip.to_string() +
384  " instead of " + remote_ip.to_string());
385  }
386  }
387 
388  return publicKey;
389 }
390 
391 auto
393  bool crawlPublic,
394  bool comprEnabled,
395  bool ledgerReplayEnabled,
396  bool txReduceRelayEnabled,
397  bool vpReduceRelayEnabled) -> request_type
398 {
399  request_type m;
400  m.method(boost::beast::http::verb::get);
401  m.target("/");
402  m.version(11);
403  m.insert("User-Agent", BuildInfo::getFullVersionString());
404  m.insert("Upgrade", supportedProtocolVersions());
405  m.insert("Connection", "Upgrade");
406  m.insert("Connect-As", "Peer");
407  m.insert("Crawl", crawlPublic ? "public" : "private");
408  m.insert(
409  "X-Protocol-Ctl",
411  comprEnabled,
412  ledgerReplayEnabled,
413  txReduceRelayEnabled,
414  vpReduceRelayEnabled));
415  return m;
416 }
417 
420  bool crawlPublic,
421  http_request_type const& req,
422  beast::IP::Address public_ip,
423  beast::IP::Address remote_ip,
424  uint256 const& sharedValue,
427  Application& app)
428 {
429  http_response_type resp;
430  resp.result(boost::beast::http::status::switching_protocols);
431  resp.version(req.version());
432  resp.insert("Connection", "Upgrade");
433  resp.insert("Upgrade", to_string(protocol));
434  resp.insert("Connect-As", "Peer");
435  resp.insert("Server", BuildInfo::getFullVersionString());
436  resp.insert("Crawl", crawlPublic ? "public" : "private");
437  resp.insert(
438  "X-Protocol-Ctl",
440  req,
441  app.config().COMPRESSION,
442  app.config().LEDGER_REPLAY,
445 
446  buildHandshake(resp, sharedValue, networkID, public_ip, remote_ip, app);
447 
448  return resp;
449 }
450 
451 } // namespace ripple
ripple::hashLastMessage
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.
Definition: Handshake.cpp:127
ripple::Application
Definition: Application.h:115
ripple::makeSlice
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:241
std::string
STL class.
ripple::Slice
An immutable linear range of bytes.
Definition: Slice.h:44
ripple::openssl_sha512_hasher
SHA-512 digest.
Definition: digest.h:68
ripple::Config::LEDGER_REPLAY
bool LEDGER_REPLAY
Definition: Config.h:209
std::pair
ripple::Application::instanceID
virtual std::uint64_t instanceID() const =0
Returns a 64-bit instance identifier, generated at startup.
ripple::DELIM_FEATURE
static constexpr char DELIM_FEATURE[]
Definition: Handshake.h:149
ripple::makeFeaturesRequestHeader
std::string makeFeaturesRequestHeader(bool comprEnabled, bool ledgerReplayEnabled, bool txReduceRelayEnabled, bool vpReduceRelayEnabled)
Make request header X-Protocol-Ctl value with supported features.
Definition: Handshake.cpp:74
ripple::base64_encode
std::string base64_encode(std::uint8_t const *data, std::size_t len)
Definition: base64.cpp:236
ripple::FEATURE_LEDGER_REPLAY
static constexpr char FEATURE_LEDGER_REPLAY[]
Definition: Handshake.h:148
ripple::toBase58
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:104
std::stringstream
STL class.
ripple::Application::signalStop
virtual void signalStop(std::string msg="")=0
ripple::makeSharedValue
std::optional< uint256 > makeSharedValue(stream_type &ssl, beast::Journal journal)
Computes a shared value based on the SSL connection state.
Definition: Handshake.cpp:145
ripple::Application::timeKeeper
virtual TimeKeeper & timeKeeper()=0
ripple::FEATURE_VPRR
static constexpr char FEATURE_VPRR[]
Definition: Handshake.h:144
ripple::base_uint::data
pointer data()
Definition: base_uint.h:121
algorithm
ripple::isFeatureValue
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:54
ripple::FEATURE_TXRR
static constexpr char FEATURE_TXRR[]
Definition: Handshake.h:146
ripple::featureEnabled
bool featureEnabled(boost::beast::http::fields const &headers, std::string const &feature)
Check if a feature is enabled.
Definition: Handshake.cpp:66
ripple::publicKeyType
std::optional< KeyType > publicKeyType(Slice const &slice)
Returns the type of public key.
Definition: PublicKey.cpp:207
ripple::base_uint
Integers of any length that is a multiple of 32-bits.
Definition: base_uint.h:81
beast::IP::Address
boost::asio::ip::address Address
Definition: IPAddress.h:41
ripple::Application::getLedgerMaster
virtual LedgerMaster & getLedgerMaster()=0
ripple::PublicKey
A public key.
Definition: PublicKey.h:59
ripple::signDigest
Buffer signDigest(PublicKey const &pk, SecretKey const &sk, uint256 const &digest)
Generate a signature for a message digest.
Definition: SecretKey.cpp:212
chrono
ripple::Application::config
virtual Config & config()=0
ripple::Application::nodeIdentity
virtual std::pair< PublicKey, SecretKey > const & nodeIdentity()=0
std::to_string
T to_string(T... args)
ripple::FEATURE_COMPR
static constexpr char FEATURE_COMPR[]
Definition: Handshake.h:142
ripple::base64_decode
std::string base64_decode(std::string const &data)
Definition: base64.cpp:245
beast::Journal::error
Stream error() const
Definition: Journal.h:333
std::runtime_error
STL class.
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:58
std::uint32_t
beast::IP::is_public
bool is_public(AddressV4 const &addr)
Returns true if the address is a public routable address.
Definition: IPAddressV4.cpp:41
ripple::getFeatureValue
std::optional< std::string > getFeatureValue(boost::beast::http::fields const &headers, std::string const &feature)
Get feature's header value.
Definition: Handshake.cpp:38
ripple::stream_type
boost::beast::ssl_stream< socket_type > stream_type
Definition: Handshake.h:43
ripple::KeyType::secp256k1
@ secp256k1
ripple::Config::VP_REDUCE_RELAY_ENABLE
bool VP_REDUCE_RELAY_ENABLE
Definition: Config.h:234
ripple::BuildInfo::getFullVersionString
std::string const & getFullVersionString()
Full server version string.
Definition: BuildInfo.cpp:78
ripple::supportedProtocolVersions
std::string const & supportedProtocolVersions()
The list of all the protocol versions we support.
Definition: ProtocolVersion.cpp:156
ripple::LedgerMaster::getClosedLedger
std::shared_ptr< Ledger const > getClosedLedger()
Definition: LedgerMaster.h:98
beast::rfc2616::token_in_list
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
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
protocol
Definition: ValidatorList.h:38
ripple::Config::SERVER_DOMAIN
std::string SERVER_DOMAIN
Definition: Config.h:265
beast::lexicalCastChecked
bool lexicalCastChecked(Out &out, In in)
Intelligently convert from one type to another.
Definition: LexicalCast.h:266
ripple::verifyDigest
bool verifyDigest(PublicKey const &publicKey, uint256 const &digest, Slice const &sig, bool mustBeFullyCanonical) noexcept
Verify a secp256k1 signature on the digest of a message.
Definition: PublicKey.cpp:222
ripple::sha512Half
sha512_half_hasher::result_type sha512Half(Args const &... args)
Returns the SHA512-Half of a series of objects.
Definition: digest.h:216
ripple::TimeKeeper::now
virtual time_point now() const override=0
Returns the estimate of wall time, in network time.
ripple::Config::COMPRESSION
bool COMPRESSION
Definition: Config.h:206
std::string::empty
T empty(T... args)
ripple::request_type
boost::beast::http::request< boost::beast::http::empty_body > request_type
Definition: Handshake.h:45
ripple::TokenType::NodePublic
@ NodePublic
std::optional< std::string >
std::stringstream::str
T str(T... args)
std::size_t
ripple::to_string
std::string to_string(Manifest const &m)
Format the specified manifest to a string for debugging purposes.
Definition: app/misc/impl/Manifest.cpp:41
ripple::isProperlyFormedTomlDomain
bool isProperlyFormedTomlDomain(std::string const &domain)
Determines if the given string looks like a TOML-file hosting domain.
ripple::strHex
std::string strHex(FwdIt begin, FwdIt end)
Definition: strHex.h:30
ripple::Config::TX_REDUCE_RELAY_ENABLE
bool TX_REDUCE_RELAY_ENABLE
Definition: Config.h:245
ripple::verifyHandshake
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.
Definition: Handshake.cpp:226
ripple::makeFeaturesResponseHeader
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:93
ripple::makeRequest
auto makeRequest(bool crawlPublic, bool comprEnabled, bool ledgerReplayEnabled, bool txReduceRelayEnabled, bool vpReduceRelayEnabled) -> request_type
Make outbound http request.
Definition: Handshake.cpp:392
ripple::makeResponse
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.
Definition: Handshake.cpp:419
beast::IP::is_unspecified
bool is_unspecified(Address const &addr)
Returns true if the address is unspecified.
Definition: IPAddress.h:59
ripple::http_request_type
boost::beast::http::request< boost::beast::http::dynamic_body > http_request_type
Definition: Handshake.h:47
beast::abstract_clock< NetClock >::time_point
typename NetClock ::time_point time_point
Definition: abstract_clock.h:63
ripple::http_response_type
boost::beast::http::response< boost::beast::http::dynamic_body > http_response_type
Definition: Handshake.h:49
beast::abstract_clock< NetClock >::duration
typename NetClock ::duration duration
Definition: abstract_clock.h:62
ripple::get
T & get(EitherAmount &amt)
Definition: AmountSpec.h:118
ripple::buildHandshake
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.
Definition: Handshake.cpp:177
std::chrono