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