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 vpReduceRelayEnabled,
77  bool ledgerReplayEnabled)
78 {
80  if (comprEnabled)
81  str << FEATURE_COMPR << "=lz4" << DELIM_FEATURE;
82  if (vpReduceRelayEnabled)
83  str << FEATURE_VPRR << "=1";
84  if (ledgerReplayEnabled)
85  str << FEATURE_LEDGER_REPLAY << "=1";
86  return str.str();
87 }
88 
91  http_request_type const& headers,
92  bool comprEnabled,
93  bool vpReduceRelayEnabled,
94  bool ledgerReplayEnabled)
95 {
97  if (comprEnabled && isFeatureValue(headers, FEATURE_COMPR, "lz4"))
98  str << FEATURE_COMPR << "=lz4" << DELIM_FEATURE;
99  if (vpReduceRelayEnabled && featureEnabled(headers, FEATURE_VPRR))
100  str << FEATURE_VPRR << "=1";
101  if (ledgerReplayEnabled && featureEnabled(headers, FEATURE_LEDGER_REPLAY))
102  str << FEATURE_LEDGER_REPLAY << "=1";
103  return str.str();
104 }
105 
121 hashLastMessage(SSL const* ssl, size_t (*get)(const SSL*, void*, size_t))
122 {
123  constexpr std::size_t sslMinimumFinishedLength = 12;
124 
125  unsigned char buf[1024];
126  size_t len = get(ssl, buf, sizeof(buf));
127 
128  if (len < sslMinimumFinishedLength)
129  return std::nullopt;
130 
131  sha512_hasher h;
132 
133  base_uint<512> cookie;
134  SHA512(buf, len, cookie.data());
135  return cookie;
136 }
137 
140 {
141  auto const cookie1 = hashLastMessage(ssl.native_handle(), SSL_get_finished);
142  if (!cookie1)
143  {
144  JLOG(journal.error()) << "Cookie generation: local setup not complete";
145  return std::nullopt;
146  }
147 
148  auto const cookie2 =
149  hashLastMessage(ssl.native_handle(), SSL_get_peer_finished);
150  if (!cookie2)
151  {
152  JLOG(journal.error()) << "Cookie generation: peer setup not complete";
153  return std::nullopt;
154  }
155 
156  auto const result = (*cookie1 ^ *cookie2);
157 
158  // Both messages hash to the same value and the cookie
159  // is 0. Don't allow this.
160  if (result == beast::zero)
161  {
162  JLOG(journal.error())
163  << "Cookie generation: identical finished messages";
164  return std::nullopt;
165  }
166 
167  return sha512Half(Slice(result.data(), result.size()));
168 }
169 
170 void
172  boost::beast::http::fields& h,
173  ripple::uint256 const& sharedValue,
175  beast::IP::Address public_ip,
176  beast::IP::Address remote_ip,
177  Application& app)
178 {
179  if (networkID)
180  {
181  // The network identifier, if configured, can be used to specify
182  // what network we intend to connect to and detect if the remote
183  // end connects to the same network.
184  h.insert("Network-ID", std::to_string(*networkID));
185  }
186 
187  h.insert(
188  "Network-Time",
189  std::to_string(app.timeKeeper().now().time_since_epoch().count()));
190 
191  h.insert(
192  "Public-Key",
194 
195  {
196  auto const sig = signDigest(
197  app.nodeIdentity().first, app.nodeIdentity().second, sharedValue);
198  h.insert("Session-Signature", base64_encode(sig.data(), sig.size()));
199  }
200 
201  if (!app.config().SERVER_DOMAIN.empty())
202  h.insert("Server-Domain", app.config().SERVER_DOMAIN);
203 
204  if (beast::IP::is_public(remote_ip))
205  h.insert("Remote-IP", remote_ip.to_string());
206 
207  if (!public_ip.is_unspecified())
208  h.insert("Local-IP", public_ip.to_string());
209 
210  if (auto const cl = app.getLedgerMaster().getClosedLedger())
211  {
212  // TODO: Use hex for these
213  h.insert(
214  "Closed-Ledger",
215  base64_encode(cl->info().hash.begin(), cl->info().hash.size()));
216  h.insert(
217  "Previous-Ledger",
219  cl->info().parentHash.begin(), cl->info().parentHash.size()));
220  }
221 }
222 
223 PublicKey
225  boost::beast::http::fields const& headers,
226  ripple::uint256 const& sharedValue,
228  beast::IP::Address public_ip,
229  beast::IP::Address remote,
230  Application& app)
231 {
232  if (auto const iter = headers.find("Server-Domain"); iter != headers.end())
233  {
234  if (!isProperlyFormedTomlDomain(iter->value().to_string()))
235  throw std::runtime_error("Invalid server domain");
236  }
237 
238  if (auto const iter = headers.find("Network-ID"); iter != headers.end())
239  {
240  std::uint32_t nid;
241 
242  if (!beast::lexicalCastChecked(nid, iter->value().to_string()))
243  throw std::runtime_error("Invalid peer network identifier");
244 
245  if (networkID && nid != *networkID)
246  throw std::runtime_error("Peer is on a different network");
247  }
248 
249  if (auto const iter = headers.find("Network-Time"); iter != headers.end())
250  {
251  auto const netTime =
252  [str = iter->value().to_string()]() -> TimeKeeper::time_point {
253  TimeKeeper::duration::rep val;
254 
255  if (beast::lexicalCastChecked(val, str))
257 
258  // It's not an error for the header field to not be present but if
259  // it is present and it contains junk data, that is an error.
260  throw std::runtime_error("Invalid peer clock timestamp");
261  }();
262 
263  using namespace std::chrono;
264 
265  auto const ourTime = app.timeKeeper().now();
266  auto const tolerance = 20s;
267 
268  // We can't blindly "return a-b;" because TimeKeeper::time_point
269  // uses an unsigned integer for representing durations, which is
270  // a problem when trying to subtract time points.
271  // FIXME: @HowardHinnant, should we migrate to using std::int64_t?
272  auto calculateOffset = [](TimeKeeper::time_point a,
274  if (a > b)
275  return duration_cast<std::chrono::seconds>(a - b);
276  return -duration_cast<std::chrono::seconds>(b - a);
277  };
278 
279  auto const offset = calculateOffset(netTime, ourTime);
280 
281  if (date::abs(offset) > tolerance)
282  throw std::runtime_error("Peer clock is too far off");
283  }
284 
285  PublicKey const publicKey = [&headers] {
286  if (auto const iter = headers.find("Public-Key"); iter != headers.end())
287  {
288  auto pk = parseBase58<PublicKey>(
289  TokenType::NodePublic, iter->value().to_string());
290 
291  if (pk)
292  {
293  if (publicKeyType(*pk) != KeyType::secp256k1)
294  throw std::runtime_error("Unsupported public key type");
295 
296  return *pk;
297  }
298  }
299 
300  throw std::runtime_error("Bad node public key");
301  }();
302 
303  if (publicKey == app.nodeIdentity().first)
304  throw std::runtime_error("Self connection");
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().to_string());
319 
320  if (!verifyDigest(publicKey, sharedValue, makeSlice(sig), false))
321  throw std::runtime_error("Failed to verify session");
322  }
323 
324  if (auto const iter = headers.find("Local-IP"); iter != headers.end())
325  {
326  boost::system::error_code ec;
327  auto const local_ip = boost::asio::ip::address::from_string(
328  iter->value().to_string(), ec);
329 
330  if (ec)
331  throw std::runtime_error("Invalid Local-IP");
332 
333  if (beast::IP::is_public(remote) && remote != local_ip)
334  throw std::runtime_error(
335  "Incorrect Local-IP: " + remote.to_string() + " instead of " +
336  local_ip.to_string());
337  }
338 
339  if (auto const iter = headers.find("Remote-IP"); iter != headers.end())
340  {
341  boost::system::error_code ec;
342  auto const remote_ip = boost::asio::ip::address::from_string(
343  iter->value().to_string(), ec);
344 
345  if (ec)
346  throw std::runtime_error("Invalid Remote-IP");
347 
348  if (beast::IP::is_public(remote) &&
349  !beast::IP::is_unspecified(public_ip))
350  {
351  // We know our public IP and peer reports our connection came
352  // from some other IP.
353  if (remote_ip != public_ip)
354  throw std::runtime_error(
355  "Incorrect Remote-IP: " + public_ip.to_string() +
356  " instead of " + remote_ip.to_string());
357  }
358  }
359 
360  return publicKey;
361 }
362 
363 auto
365  bool crawlPublic,
366  bool comprEnabled,
367  bool vpReduceRelayEnabled,
368  bool ledgerReplayEnabled) -> request_type
369 {
370  request_type m;
371  m.method(boost::beast::http::verb::get);
372  m.target("/");
373  m.version(11);
374  m.insert("User-Agent", BuildInfo::getFullVersionString());
375  m.insert("Upgrade", supportedProtocolVersions());
376  m.insert("Connection", "Upgrade");
377  m.insert("Connect-As", "Peer");
378  m.insert("Crawl", crawlPublic ? "public" : "private");
379  m.insert(
380  "X-Protocol-Ctl",
382  comprEnabled, vpReduceRelayEnabled, ledgerReplayEnabled));
383  return m;
384 }
385 
388  bool crawlPublic,
389  http_request_type const& req,
390  beast::IP::Address public_ip,
391  beast::IP::Address remote_ip,
392  uint256 const& sharedValue,
395  Application& app)
396 {
397  http_response_type resp;
398  resp.result(boost::beast::http::status::switching_protocols);
399  resp.version(req.version());
400  resp.insert("Connection", "Upgrade");
401  resp.insert("Upgrade", to_string(protocol));
402  resp.insert("Connect-As", "Peer");
403  resp.insert("Server", BuildInfo::getFullVersionString());
404  resp.insert("Crawl", crawlPublic ? "public" : "private");
405  resp.insert(
406  "X-Protocol-Ctl",
408  req,
409  app.config().COMPRESSION,
411  app.config().LEDGER_REPLAY));
412 
413  buildHandshake(resp, sharedValue, networkID, public_ip, remote_ip, app);
414 
415  return resp;
416 }
417 
418 } // 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:121
ripple::Application
Definition: Application.h:102
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:240
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:189
std::pair
ripple::DELIM_FEATURE
static constexpr char DELIM_FEATURE[]
Definition: Handshake.h:141
ripple::base64_encode
std::string base64_encode(std::uint8_t const *data, std::size_t len)
Definition: base64.cpp:236
ripple::makeFeaturesRequestHeader
std::string makeFeaturesRequestHeader(bool comprEnabled, bool vpReduceRelayEnabled, bool ledgerReplayEnabled)
Make request header X-Protocol-Ctl value with supported features.
Definition: Handshake.cpp:74
ripple::FEATURE_LEDGER_REPLAY
static constexpr char FEATURE_LEDGER_REPLAY[]
Definition: Handshake.h:139
ripple::toBase58
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:29
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:139
ripple::Application::timeKeeper
virtual TimeKeeper & timeKeeper()=0
ripple::FEATURE_VPRR
static constexpr char FEATURE_VPRR[]
Definition: Handshake.h:137
ripple::base_uint::data
pointer data()
Definition: base_uint.h:114
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::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:203
ripple::base_uint
Integers of any length that is a multiple of 32-bits.
Definition: base_uint.h:74
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:136
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::makeRequest
auto makeRequest(bool crawlPublic, bool comprEnabled, bool vpReduceRelayEnabled, bool ledgerReplayEnabled) -> request_type
Make outbound http request.
Definition: Handshake.cpp:364
ripple::KeyType::secp256k1
@ secp256k1
ripple::Config::VP_REDUCE_RELAY_ENABLE
bool VP_REDUCE_RELAY_ENABLE
Definition: Config.h:205
ripple::BuildInfo::getFullVersionString
std::string const & getFullVersionString()
Full server version string.
Definition: BuildInfo.cpp:74
ripple::supportedProtocolVersions
std::string const & supportedProtocolVersions()
The list of all the protocol versions we support.
Definition: ProtocolVersion.cpp:169
ripple::LedgerMaster::getClosedLedger
std::shared_ptr< Ledger const > getClosedLedger()
Definition: LedgerMaster.h:105
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:221
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:218
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:186
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:38
ripple::isProperlyFormedTomlDomain
bool isProperlyFormedTomlDomain(std::string const &domain)
Determines if the given string looks like a TOML-file hosting domain.
ripple::makeFeaturesResponseHeader
std::string makeFeaturesResponseHeader(http_request_type const &headers, bool comprEnabled, bool vpReduceRelayEnabled, bool ledgerReplayEnabled)
Make response header X-Protocol-Ctl value with supported features.
Definition: Handshake.cpp:90
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:224
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:387
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:171
std::chrono