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