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