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 
29 #include <boost/regex.hpp>
30 
31 #include <algorithm>
32 #include <chrono>
33 
34 // VFALCO Shouldn't we have to include the OpenSSL
35 // headers or something for SSL_get_finished?
36 
37 namespace ripple {
38 
41  boost::beast::http::fields const& headers,
42  std::string const& feature)
43 {
44  auto const header = headers.find("X-Protocol-Ctl");
45  if (header == headers.end())
46  return {};
47  boost::smatch match;
48  boost::regex rx(feature + "=([^;\\s]+)");
49  std::string const value = header->value();
50  if (boost::regex_search(value, match, rx))
51  return {match[1]};
52  return {};
53 }
54 
55 bool
57  boost::beast::http::fields const& headers,
58  std::string const& feature,
59  std::string const& value)
60 {
61  if (auto const fvalue = getFeatureValue(headers, feature))
62  return beast::rfc2616::token_in_list(fvalue.value(), value);
63 
64  return false;
65 }
66 
67 bool
69  boost::beast::http::fields const& headers,
70  std::string const& feature)
71 {
72  return isFeatureValue(headers, feature, "1");
73 }
74 
77  bool comprEnabled,
78  bool ledgerReplayEnabled,
79  bool txReduceRelayEnabled,
80  bool vpReduceRelayEnabled)
81 {
83  if (comprEnabled)
84  str << FEATURE_COMPR << "=lz4" << DELIM_FEATURE;
85  if (ledgerReplayEnabled)
86  str << FEATURE_LEDGER_REPLAY << "=1" << DELIM_FEATURE;
87  if (txReduceRelayEnabled)
88  str << FEATURE_TXRR << "=1" << DELIM_FEATURE;
89  if (vpReduceRelayEnabled)
90  str << FEATURE_VPRR << "=1" << DELIM_FEATURE;
91  return str.str();
92 }
93 
96  http_request_type const& headers,
97  bool comprEnabled,
98  bool ledgerReplayEnabled,
99  bool txReduceRelayEnabled,
100  bool vpReduceRelayEnabled)
101 {
102  std::stringstream str;
103  if (comprEnabled && isFeatureValue(headers, FEATURE_COMPR, "lz4"))
104  str << FEATURE_COMPR << "=lz4" << DELIM_FEATURE;
105  if (ledgerReplayEnabled && featureEnabled(headers, FEATURE_LEDGER_REPLAY))
106  str << FEATURE_LEDGER_REPLAY << "=1" << DELIM_FEATURE;
107  if (txReduceRelayEnabled && featureEnabled(headers, FEATURE_TXRR))
108  str << FEATURE_TXRR << "=1" << DELIM_FEATURE;
109  if (vpReduceRelayEnabled && featureEnabled(headers, FEATURE_VPRR))
110  str << FEATURE_VPRR << "=1" << DELIM_FEATURE;
111  return str.str();
112 }
113 
129 hashLastMessage(SSL const* ssl, size_t (*get)(const SSL*, void*, size_t))
130 {
131  constexpr std::size_t sslMinimumFinishedLength = 12;
132 
133  unsigned char buf[1024];
134  size_t len = get(ssl, buf, sizeof(buf));
135 
136  if (len < sslMinimumFinishedLength)
137  return std::nullopt;
138 
139  sha512_hasher h;
140 
141  base_uint<512> cookie;
142  SHA512(buf, len, cookie.data());
143  return cookie;
144 }
145 
148 {
149  auto const cookie1 = hashLastMessage(ssl.native_handle(), SSL_get_finished);
150  if (!cookie1)
151  {
152  JLOG(journal.error()) << "Cookie generation: local setup not complete";
153  return std::nullopt;
154  }
155 
156  auto const cookie2 =
157  hashLastMessage(ssl.native_handle(), SSL_get_peer_finished);
158  if (!cookie2)
159  {
160  JLOG(journal.error()) << "Cookie generation: peer setup not complete";
161  return std::nullopt;
162  }
163 
164  auto const result = (*cookie1 ^ *cookie2);
165 
166  // Both messages hash to the same value and the cookie
167  // is 0. Don't allow this.
168  if (result == beast::zero)
169  {
170  JLOG(journal.error())
171  << "Cookie generation: identical finished messages";
172  return std::nullopt;
173  }
174 
175  return sha512Half(Slice(result.data(), result.size()));
176 }
177 
178 void
180  boost::beast::http::fields& h,
181  ripple::uint256 const& sharedValue,
183  beast::IP::Address public_ip,
184  beast::IP::Address remote_ip,
185  Application& app)
186 {
187  if (networkID)
188  {
189  // The network identifier, if configured, can be used to specify
190  // what network we intend to connect to and detect if the remote
191  // end connects to the same network.
192  h.insert("Network-ID", std::to_string(*networkID));
193  }
194 
195  h.insert(
196  "Network-Time",
197  std::to_string(app.timeKeeper().now().time_since_epoch().count()));
198 
199  h.insert(
200  "Public-Key",
202 
203  {
204  auto const sig = signDigest(
205  app.nodeIdentity().first, app.nodeIdentity().second, sharedValue);
206  h.insert("Session-Signature", base64_encode(sig.data(), sig.size()));
207  }
208 
209  h.insert("Instance-Cookie", std::to_string(app.instanceID()));
210 
211  if (!app.config().SERVER_DOMAIN.empty())
212  h.insert("Server-Domain", app.config().SERVER_DOMAIN);
213 
214  if (beast::IP::is_public(remote_ip))
215  h.insert("Remote-IP", remote_ip.to_string());
216 
217  if (!public_ip.is_unspecified())
218  h.insert("Local-IP", public_ip.to_string());
219 
220  if (auto const cl = app.getLedgerMaster().getClosedLedger())
221  {
222  h.insert("Closed-Ledger", strHex(cl->info().hash));
223  h.insert("Previous-Ledger", strHex(cl->info().parentHash));
224  }
225 }
226 
227 PublicKey
229  boost::beast::http::fields const& headers,
230  ripple::uint256 const& sharedValue,
232  beast::IP::Address public_ip,
233  beast::IP::Address remote,
234  Application& app)
235 {
236  if (auto const iter = headers.find("Server-Domain"); iter != headers.end())
237  {
238  if (!isProperlyFormedTomlDomain(iter->value()))
239  throw std::runtime_error("Invalid server domain");
240  }
241 
242  if (auto const iter = headers.find("Network-ID"); iter != headers.end())
243  {
244  std::uint32_t nid;
245 
246  if (!beast::lexicalCastChecked(nid, std::string(iter->value())))
247  throw std::runtime_error("Invalid peer network identifier");
248 
249  if (networkID && nid != *networkID)
250  throw std::runtime_error("Peer is on a different network");
251  }
252 
253  if (auto const iter = headers.find("Network-Time"); iter != headers.end())
254  {
255  auto const netTime =
256  [str = std::string(iter->value())]() -> TimeKeeper::time_point {
257  TimeKeeper::duration::rep val;
258 
259  if (beast::lexicalCastChecked(val, str))
261 
262  // It's not an error for the header field to not be present but if
263  // it is present and it contains junk data, that is an error.
264  throw std::runtime_error("Invalid peer clock timestamp");
265  }();
266 
267  using namespace std::chrono;
268 
269  auto const ourTime = app.timeKeeper().now();
270  auto const tolerance = 20s;
271 
272  // We can't blindly "return a-b;" because TimeKeeper::time_point
273  // uses an unsigned integer for representing durations, which is
274  // a problem when trying to subtract time points.
275  auto calculateOffset = [](TimeKeeper::time_point a,
277  if (a > b)
278  return duration_cast<std::chrono::seconds>(a - b);
279  return -duration_cast<std::chrono::seconds>(b - a);
280  };
281 
282  auto const offset = calculateOffset(netTime, ourTime);
283 
284  if (abs(offset) > tolerance)
285  throw std::runtime_error("Peer clock is too far off");
286  }
287 
288  PublicKey const publicKey = [&headers] {
289  if (auto const iter = headers.find("Public-Key"); iter != headers.end())
290  {
291  auto pk =
292  parseBase58<PublicKey>(TokenType::NodePublic, iter->value());
293 
294  if (pk)
295  {
296  if (publicKeyType(*pk) != KeyType::secp256k1)
297  throw std::runtime_error("Unsupported public key type");
298 
299  return *pk;
300  }
301  }
302 
303  throw std::runtime_error("Bad node public key");
304  }();
305 
306  // This check gets two birds with one stone:
307  //
308  // 1) it verifies that the node we are talking to has access to the
309  // private key corresponding to the public node identity it claims.
310  // 2) it verifies that our SSL session is end-to-end with that node
311  // and not through a proxy that establishes two separate sessions.
312  {
313  auto const iter = headers.find("Session-Signature");
314 
315  if (iter == headers.end())
316  throw std::runtime_error("No session signature specified");
317 
318  auto sig = base64_decode(iter->value());
319 
320  if (!verifyDigest(publicKey, sharedValue, makeSlice(sig), false))
321  throw std::runtime_error("Failed to verify session");
322  }
323 
324  if (publicKey == app.nodeIdentity().first)
325  throw std::runtime_error("Self connection");
326 
327  if (auto const iter = headers.find("Local-IP"); iter != headers.end())
328  {
329  boost::system::error_code ec;
330  auto const local_ip =
331  boost::asio::ip::address::from_string(iter->value(), ec);
332 
333  if (ec)
334  throw std::runtime_error("Invalid Local-IP");
335 
336  if (beast::IP::is_public(remote) && remote != local_ip)
337  throw std::runtime_error(
338  "Incorrect Local-IP: " + remote.to_string() + " instead of " +
339  local_ip.to_string());
340  }
341 
342  if (auto const iter = headers.find("Remote-IP"); iter != headers.end())
343  {
344  boost::system::error_code ec;
345  auto const remote_ip =
346  boost::asio::ip::address::from_string(iter->value(), ec);
347 
348  if (ec)
349  throw std::runtime_error("Invalid Remote-IP");
350 
351  if (beast::IP::is_public(remote) &&
352  !beast::IP::is_unspecified(public_ip))
353  {
354  // We know our public IP and peer reports our connection came
355  // from some other IP.
356  if (remote_ip != public_ip)
357  throw std::runtime_error(
358  "Incorrect Remote-IP: " + public_ip.to_string() +
359  " instead of " + remote_ip.to_string());
360  }
361  }
362 
363  return publicKey;
364 }
365 
366 auto
368  bool crawlPublic,
369  bool comprEnabled,
370  bool ledgerReplayEnabled,
371  bool txReduceRelayEnabled,
372  bool vpReduceRelayEnabled) -> request_type
373 {
374  request_type m;
375  m.method(boost::beast::http::verb::get);
376  m.target("/");
377  m.version(11);
378  m.insert("User-Agent", BuildInfo::getFullVersionString());
379  m.insert("Upgrade", supportedProtocolVersions());
380  m.insert("Connection", "Upgrade");
381  m.insert("Connect-As", "Peer");
382  m.insert("Crawl", crawlPublic ? "public" : "private");
383  m.insert(
384  "X-Protocol-Ctl",
386  comprEnabled,
387  ledgerReplayEnabled,
388  txReduceRelayEnabled,
389  vpReduceRelayEnabled));
390  return m;
391 }
392 
395  bool crawlPublic,
396  http_request_type const& req,
397  beast::IP::Address public_ip,
398  beast::IP::Address remote_ip,
399  uint256 const& sharedValue,
402  Application& app)
403 {
404  http_response_type resp;
405  resp.result(boost::beast::http::status::switching_protocols);
406  resp.version(req.version());
407  resp.insert("Connection", "Upgrade");
408  resp.insert("Upgrade", to_string(protocol));
409  resp.insert("Connect-As", "Peer");
410  resp.insert("Server", BuildInfo::getFullVersionString());
411  resp.insert("Crawl", crawlPublic ? "public" : "private");
412  resp.insert(
413  "X-Protocol-Ctl",
415  req,
416  app.config().COMPRESSION,
417  app.config().LEDGER_REPLAY,
420 
421  buildHandshake(resp, sharedValue, networkID, public_ip, remote_ip, app);
422 
423  return resp;
424 }
425 
426 } // 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:129
ripple::Application
Definition: Application.h:116
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:233
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:76
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::makeSharedValue
std::optional< uint256 > makeSharedValue(stream_type &ssl, beast::Journal journal)
Computes a shared value based on the SSL connection state.
Definition: Handshake.cpp:147
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:122
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:56
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:68
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:82
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:61
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:332
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:40
ripple::TimeKeeper::now
time_point now() const override
Returns the current time, using the server's clock.
Definition: TimeKeeper.h:63
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:259
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:157
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::abs
constexpr Number abs(Number x) noexcept
Definition: Number.h:327
ripple::Config::SERVER_DOMAIN
std::string SERVER_DOMAIN
Definition: Config.h:290
beast::lexicalCastChecked
bool lexicalCastChecked(Out &out, In in)
Intelligently convert from one type to another.
Definition: LexicalCast.h:164
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::Config::COMPRESSION
bool COMPRESSION
Definition: Config.h:230
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:270
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:228
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:95
ripple::makeRequest
auto makeRequest(bool crawlPublic, bool comprEnabled, bool ledgerReplayEnabled, bool txReduceRelayEnabled, bool vpReduceRelayEnabled) -> request_type
Make outbound http request.
Definition: Handshake.cpp:367
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:394
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:179
std::chrono