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::address::from_string(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::address::from_string(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()
Definition: LedgerMaster.h:79
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
pointer data()
Definition: base_uint.h:125
T empty(T... args)
bool is_public(Address const &addr)
Returns true if the address is a public routable address.
Definition: IPAddress.h:82
bool is_unspecified(Address const &addr)
Returns true if the address is unspecified.
Definition: IPAddress.h:61
boost::asio::ip::address Address
Definition: IPAddress.h:43
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:378
bool lexicalCastChecked(Out &out, In in)
Intelligently convert from one type to another.
Definition: LexicalCast.h:202
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:26
boost::beast::ssl_stream< socket_type > stream_type
Definition: Handshake.h:42
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:114
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.
Definition: PublicKey.cpp:238
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.
Definition: Handshake.cpp:178
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)
Definition: base64.cpp:248
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:392
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.
Definition: Handshake.cpp:146
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.
Definition: PublicKey.cpp:223
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.
Definition: Handshake.cpp:128
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)
Definition: base64.cpp:239
Buffer signDigest(PublicKey const &pk, SecretKey const &sk, uint256 const &digest)
Generate a signature for a message digest.
Definition: SecretKey.cpp:230
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.
Definition: BasicConfig.h:355
auto makeRequest(bool crawlPublic, bool comprEnabled, bool ledgerReplayEnabled, bool txReduceRelayEnabled, bool vpReduceRelayEnabled) -> request_type
Make outbound http request.
Definition: Handshake.cpp:365
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.
Definition: Handshake.cpp:227
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:225
static constexpr char FEATURE_VPRR[]
Definition: Handshake.h:143
constexpr Number abs(Number x) noexcept
Definition: Number.h:332
T str(T... args)
SHA-512 digest.
Definition: digest.h:72
T to_string(T... args)