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#include <xrpl/basics/base64.h>
24#include <xrpl/beast/core/LexicalCast.h>
25#include <xrpl/beast/rfc2616.h>
26#include <xrpl/protocol/digest.h>
27
28#include <boost/regex.hpp>
29
30#include <algorithm>
31
32// VFALCO Shouldn't we have to include the OpenSSL
33// headers or something for SSL_get_finished?
34
35namespace 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 std::string const allFeatures(header->value());
48 if (boost::regex_search(allFeatures, match, rx))
49 return {match[1]};
50 return {};
51}
52
53bool
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
65bool
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{
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
127hashLastMessage(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
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
176void
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
225PublicKey
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()))
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()))
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 = [str = iter->value()]() -> TimeKeeper::time_point {
254 TimeKeeper::duration::rep val;
255
256 if (beast::lexicalCastChecked(val, str))
258
259 // It's not an error for the header field to not be present but if
260 // it is present and it contains junk data, that is an error.
261 throw std::runtime_error("Invalid peer clock timestamp");
262 }();
263
264 using namespace std::chrono;
265
266 auto const ourTime = app.timeKeeper().now();
267 auto const tolerance = 20s;
268
269 // We can't blindly "return a-b;" because TimeKeeper::time_point
270 // uses an unsigned integer for representing durations, which is
271 // a problem when trying to subtract time points.
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 (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 =
289 parseBase58<PublicKey>(TokenType::NodePublic, iter->value());
290
291 if (pk)
292 {
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 // This check gets two birds with one stone:
304 //
305 // 1) it verifies that the node we are talking to has access to the
306 // private key corresponding to the public node identity it claims.
307 // 2) it verifies that our SSL session is end-to-end with that node
308 // and not through a proxy that establishes two separate sessions.
309 {
310 auto const iter = headers.find("Session-Signature");
311
312 if (iter == headers.end())
313 throw std::runtime_error("No session signature specified");
314
315 auto sig = base64_decode(iter->value());
316
317 if (!verifyDigest(publicKey, sharedValue, makeSlice(sig), false))
318 throw std::runtime_error("Failed to verify session");
319 }
320
321 if (publicKey == app.nodeIdentity().first)
322 throw std::runtime_error("Self connection");
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 =
328 boost::asio::ip::address::from_string(iter->value(), 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 =
343 boost::asio::ip::address::from_string(iter->value(), 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
363auto
365 bool crawlPublic,
366 bool comprEnabled,
367 bool ledgerReplayEnabled,
368 bool txReduceRelayEnabled,
369 bool vpReduceRelayEnabled) -> request_type
370{
371 request_type m;
372 m.method(boost::beast::http::verb::get);
373 m.target("/");
374 m.version(11);
375 m.insert("User-Agent", BuildInfo::getFullVersionString());
376 m.insert("Upgrade", supportedProtocolVersions());
377 m.insert("Connection", "Upgrade");
378 m.insert("Connect-As", "Peer");
379 m.insert("Crawl", crawlPublic ? "public" : "private");
380 m.insert(
381 "X-Protocol-Ctl",
383 comprEnabled,
384 ledgerReplayEnabled,
385 txReduceRelayEnabled,
386 vpReduceRelayEnabled));
387 return m;
388}
389
392 bool crawlPublic,
393 http_request_type const& req,
394 beast::IP::Address public_ip,
395 beast::IP::Address remote_ip,
396 uint256 const& sharedValue,
399 Application& app)
400{
402 resp.result(boost::beast::http::status::switching_protocols);
403 resp.version(req.version());
404 resp.insert("Connection", "Upgrade");
405 resp.insert("Upgrade", to_string(protocol));
406 resp.insert("Connect-As", "Peer");
407 resp.insert("Server", BuildInfo::getFullVersionString());
408 resp.insert("Crawl", crawlPublic ? "public" : "private");
409 resp.insert(
410 "X-Protocol-Ctl",
412 req,
413 app.config().COMPRESSION,
414 app.config().LEDGER_REPLAY,
417
418 buildHandshake(resp, sharedValue, networkID, public_ip, remote_ip, app);
419
420 return resp;
421}
422
423} // 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_ENABLE
Definition: Config.h:248
bool LEDGER_REPLAY
Definition: Config.h:223
bool TX_REDUCE_RELAY_ENABLE
Definition: Config.h:259
std::string SERVER_DOMAIN
Definition: Config.h:279
bool COMPRESSION
Definition: Config.h:220
std::shared_ptr< Ledger const > getClosedLedger()
Definition: LedgerMaster.h:78
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:41
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:93
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:237
static constexpr char DELIM_FEATURE[]
Definition: Handshake.h:147
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:177
static constexpr char FEATURE_COMPR[]
Definition: Handshake.h:140
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
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:391
static constexpr char FEATURE_LEDGER_REPLAY[]
Definition: Handshake.h:146
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
std::optional< uint256 > makeSharedValue(stream_type &ssl, beast::Journal journal)
Computes a shared value based on the SSL connection state.
Definition: Handshake.cpp:145
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:222
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:127
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:229
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:66
std::optional< std::string > getFeatureValue(boost::beast::http::fields const &headers, std::string const &feature)
Get feature's header value.
Definition: Handshake.cpp:38
static constexpr char FEATURE_TXRR[]
Definition: Handshake.h:144
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:364
boost::beast::http::request< boost::beast::http::empty_body > request_type
Definition: Handshake.h:43
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
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:142
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)