xrpld
Loading...
Searching...
No Matches
Handshake.cpp
1#include <xrpld/overlay/detail/Handshake.h>
2
3#include <xrpld/app/ledger/LedgerMaster.h>
4#include <xrpld/app/main/Application.h>
5#include <xrpld/overlay/detail/ProtocolVersion.h>
6
7#include <xrpl/basics/Log.h>
8#include <xrpl/basics/Slice.h>
9#include <xrpl/basics/StringUtilities.h>
10#include <xrpl/basics/base64.h>
11#include <xrpl/basics/base_uint.h>
12#include <xrpl/basics/strHex.h>
13#include <xrpl/beast/core/LexicalCast.h>
14#include <xrpl/beast/net/IPAddress.h>
15#include <xrpl/beast/rfc2616.h>
16#include <xrpl/beast/utility/Journal.h>
17#include <xrpl/beast/utility/Zero.h>
18#include <xrpl/protocol/BuildInfo.h>
19#include <xrpl/protocol/KeyType.h>
20#include <xrpl/protocol/PublicKey.h>
21#include <xrpl/protocol/SecretKey.h>
22#include <xrpl/protocol/digest.h>
23#include <xrpl/protocol/tokens.h>
24
25#include <boost/asio/ip/address.hpp>
26#include <boost/beast/http/status.hpp>
27#include <boost/beast/http/verb.hpp>
28#include <boost/regex/v5/regex.hpp>
29#include <boost/regex/v5/regex_search.hpp>
30#include <boost/system/detail/error_code.hpp>
31
32#include <openssl/crypto.h>
33#include <openssl/sha.h>
34#include <openssl/ssl.h>
35
36#include <chrono>
37#include <cstddef>
38#include <cstdint>
39#include <optional>
40#include <sstream>
41#include <stdexcept>
42#include <string>
43#include <string_view>
44
45// VFALCO Shouldn't we have to include the OpenSSL
46// headers or something for SSL_get_finished?
47
48namespace xrpl {
49
50std::optional<std::string>
51getFeatureValue(boost::beast::http::fields const& headers, std::string const& feature)
52{
53 auto const header = headers.find("X-Protocol-Ctl");
54 if (header == headers.end())
55 return {};
56 boost::smatch match;
57 boost::regex const rx(feature + "=([^;\\s]+)");
58 std::string const allFeatures(header->value());
59 if (boost::regex_search(allFeatures, match, rx))
60 return {match[1]};
61 return {};
62}
63
64bool
66 boost::beast::http::fields const& headers,
67 std::string const& feature,
68 std::string const& value)
69{
70 if (auto const fvalue = getFeatureValue(headers, feature))
71 return beast::rfc2616::tokenInList(fvalue.value(), value);
72
73 return false;
74}
75
76bool
77featureEnabled(boost::beast::http::fields const& headers, std::string const& feature)
78{
79 return isFeatureValue(headers, feature, "1");
80}
81
84 bool comprEnabled,
85 bool ledgerReplayEnabled,
86 bool txReduceRelayEnabled,
87 bool vpReduceRelayEnabled)
88{
90 if (comprEnabled)
91 str << kFeatureCompr << "=lz4" << kDelimFeature;
92 if (ledgerReplayEnabled)
93 str << kFeatureLedgerReplay << "=1" << kDelimFeature;
94 if (txReduceRelayEnabled)
95 str << kFeatureTxrr << "=1" << kDelimFeature;
96 if (vpReduceRelayEnabled)
97 str << kFeatureVprr << "=1" << kDelimFeature;
98 return str.str();
99}
100
103 http_request_type const& headers,
104 bool comprEnabled,
105 bool ledgerReplayEnabled,
106 bool txReduceRelayEnabled,
107 bool vpReduceRelayEnabled)
108{
110 if (comprEnabled && isFeatureValue(headers, kFeatureCompr, "lz4"))
111 str << kFeatureCompr << "=lz4" << kDelimFeature;
112 if (ledgerReplayEnabled && featureEnabled(headers, kFeatureLedgerReplay))
113 str << kFeatureLedgerReplay << "=1" << kDelimFeature;
114 if (txReduceRelayEnabled && featureEnabled(headers, kFeatureTxrr))
115 str << kFeatureTxrr << "=1" << kDelimFeature;
116 if (vpReduceRelayEnabled && featureEnabled(headers, kFeatureVprr))
117 str << kFeatureVprr << "=1" << kDelimFeature;
118 return str.str();
119}
120
136hashLastMessage(SSL const* ssl, size_t (*get)(const SSL*, void*, size_t))
137{
138 static constexpr std::size_t kSslMinimumFinishedLength = 12;
139
140 unsigned char buf[1024];
141 size_t const len = get(ssl, buf, sizeof(buf));
142
143 if (len < kSslMinimumFinishedLength)
144 return std::nullopt;
145
146 sha512_hasher const h;
147
148 BaseUInt<512> cookie;
149 SHA512(buf, len, cookie.data());
150 return cookie;
151}
152
155{
156 auto const cookie1 = hashLastMessage(ssl.native_handle(), SSL_get_finished);
157 if (!cookie1)
158 {
159 JLOG(journal.error()) << "Cookie generation: local setup not complete";
160 return std::nullopt;
161 }
162
163 auto const cookie2 = hashLastMessage(ssl.native_handle(), SSL_get_peer_finished);
164 if (!cookie2)
165 {
166 JLOG(journal.error()) << "Cookie generation: peer setup not complete";
167 return std::nullopt;
168 }
169
170 auto const result = (*cookie1 ^ *cookie2);
171
172 // Both messages hash to the same value and the cookie
173 // is 0. Don't allow this.
174 if (result == beast::kZero)
175 {
176 JLOG(journal.error()) << "Cookie generation: identical finished messages";
177 return std::nullopt;
178 }
179
180 return sha512Half(Slice(result.data(), result.size()));
181}
182
183void
185 boost::beast::http::fields& h,
186 xrpl::uint256 const& sharedValue,
188 beast::IP::Address publicIp,
189 beast::IP::Address remoteIp,
190 Application& app)
191{
192 if (networkID)
193 {
194 // The network identifier, if configured, can be used to specify
195 // what network we intend to connect to and detect if the remote
196 // end connects to the same network.
197 h.insert("Network-ID", std::to_string(*networkID));
198 }
199
200 h.insert("Network-Time", std::to_string(app.getTimeKeeper().now().time_since_epoch().count()));
201
202 h.insert("Public-Key", toBase58(TokenType::NodePublic, app.nodeIdentity().first));
203
204 {
205 auto const sig =
206 signDigest(app.nodeIdentity().first, app.nodeIdentity().second, sharedValue);
207 h.insert("Session-Signature", base64Encode(sig.data(), sig.size()));
208 }
209
210 h.insert("Instance-Cookie", std::to_string(app.instanceID()));
211
212 if (!app.config().serverDomain.empty())
213 h.insert("Server-Domain", app.config().serverDomain);
214
215 if (beast::IP::isPublic(remoteIp))
216 h.insert("Remote-IP", remoteIp.to_string());
217
218 if (!publicIp.is_unspecified())
219 h.insert("Local-IP", publicIp.to_string());
220
221 if (auto const cl = app.getLedgerMaster().getClosedLedger())
222 {
223 h.insert("Closed-Ledger", strHex(cl->header().hash));
224 h.insert("Previous-Ledger", strHex(cl->header().parentHash));
225 }
226}
227
228PublicKey
230 boost::beast::http::fields const& headers,
231 xrpl::uint256 const& sharedValue,
233 beast::IP::Address publicIp,
234 beast::IP::Address remote,
235 Application& app)
236{
237 if (auto const iter = headers.find("Server-Domain"); iter != headers.end())
238 {
239 if (!isProperlyFormedTomlDomain(iter->value()))
240 throw std::runtime_error("Invalid server domain");
241 }
242
243 if (auto const iter = headers.find("Network-ID"); iter != headers.end())
244 {
245 std::uint32_t nid = 0;
246
247 if (!beast::lexicalCastChecked(nid, iter->value()))
248 throw std::runtime_error("Invalid peer network identifier");
249
250 if (networkID && nid != *networkID)
251 throw std::runtime_error("Peer is on a different network");
252 }
253
254 if (auto const iter = headers.find("Network-Time"); iter != headers.end())
255 {
256 auto const netTime = [str = iter->value()]() -> TimeKeeper::time_point {
257 TimeKeeper::duration::rep val = 0;
258
259 if (beast::lexicalCastChecked(val, str))
261
262 // It's not an error for the header field to not be present but if
263 // it is present and it contains junk data, that is an error.
264 throw std::runtime_error("Invalid peer clock timestamp");
265 }();
266
267 using namespace std::chrono;
268
269 auto const ourTime = app.getTimeKeeper().now();
270 auto const tolerance = 20s;
271
272 // We can't blindly "return a-b;" because TimeKeeper::time_point
273 // uses an unsigned integer for representing durations, which is
274 // a problem when trying to subtract time points.
275 auto calculateOffset = [](TimeKeeper::time_point a, TimeKeeper::time_point b) {
276 if (a > b)
279 };
280
281 auto const offset = calculateOffset(netTime, ourTime);
282
283 if (abs(offset) > tolerance)
284 throw std::runtime_error("Peer clock is too far off");
285 }
286
287 PublicKey const publicKey = [&headers] {
288 if (auto const iter = headers.find("Public-Key"); iter != headers.end())
289 {
290 auto pk = 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 = base64Decode(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 localIp = boost::asio::ip::make_address(std::string_view(iter->value()), ec);
329
330 if (ec)
331 throw std::runtime_error("Invalid Local-IP");
332
333 if (beast::IP::isPublic(remote) && remote != localIp)
334 {
335 throw std::runtime_error(
336 "Incorrect Local-IP: " + remote.to_string() + " instead of " + localIp.to_string());
337 }
338 }
339
340 if (auto const iter = headers.find("Remote-IP"); iter != headers.end())
341 {
342 boost::system::error_code ec;
343 auto const remoteIp = boost::asio::ip::make_address(std::string_view(iter->value()), ec);
344
345 if (ec)
346 throw std::runtime_error("Invalid Remote-IP");
347
348 if (beast::IP::isPublic(remote) && !beast::IP::isUnspecified(publicIp))
349 {
350 // We know our public IP and peer reports our connection came
351 // from some other IP.
352 if (remoteIp != publicIp)
353 {
354 throw std::runtime_error(
355 "Incorrect Remote-IP: " + publicIp.to_string() + " instead of " +
356 remoteIp.to_string());
357 }
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, ledgerReplayEnabled, txReduceRelayEnabled, vpReduceRelayEnabled));
385 return m;
386}
387
390 bool crawlPublic,
391 http_request_type const& req,
392 beast::IP::Address publicIp,
393 beast::IP::Address remoteIp,
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().ledgerReplay,
415
416 buildHandshake(resp, sharedValue, networkID, publicIp, remoteIp, app);
417
418 return resp;
419}
420
421} // namespace xrpl
NetClock::time_point time_point
A generic endpoint for log messages.
Definition Journal.h:38
Stream error() const
Definition Journal.h:315
virtual Config & config()=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
Integers of any length that is a multiple of 32-bits.
Definition base_uint.h:71
pointer data()
Definition base_uint.h:106
bool vpReduceRelayBaseSquelchEnable
Definition Config.h:233
bool compression
Definition Config.h:205
bool ledgerReplay
Definition Config.h:208
bool txReduceRelayEnable
Definition Config.h:243
std::string serverDomain
Definition Config.h:263
std::shared_ptr< Ledger const > getClosedLedger()
A public key.
Definition PublicKey.h:42
virtual LedgerMaster & getLedgerMaster()=0
virtual TimeKeeper & getTimeKeeper()=0
An immutable linear range of bytes.
Definition Slice.h:26
time_point now() const override
Returns the current time, using the server's clock.
Definition TimeKeeper.h:44
T duration_cast(T... args)
T empty(T... args)
bool isPublic(Address const &addr)
Returns true if the address is a public routable address.
Definition IPAddress.h:58
bool isUnspecified(Address const &addr)
Returns true if the address is unspecified.
Definition IPAddress.h:37
boost::asio::ip::address Address
Definition IPAddress.h:19
bool tokenInList(boost::string_ref const &value, boost::string_ref const &token)
Returns true if the specified token exists in the list.
Definition rfc2616.h:349
bool lexicalCastChecked(Out &out, In in)
Intelligently convert from one type to another.
std::string const & getFullVersionString()
Full server version string.
Definition BuildInfo.cpp:82
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::string base64Decode(std::string_view data)
static constexpr char kDelimFeature[]
Definition Handshake.h:125
bool featureEnabled(boost::beast::http::fields const &headers, std::string const &feature)
Check if a feature is enabled.
Definition Handshake.cpp:77
static std::optional< BaseUInt< 512 > > hashLastMessage(SSL const *ssl, size_t(*get)(const SSL *, void *, size_t))
Hashes the latest finished message from an SSL stream.
PublicKey verifyHandshake(boost::beast::http::fields const &headers, xrpl::uint256 const &sharedValue, std::optional< std::uint32_t > networkID, beast::IP::Address publicIp, beast::IP::Address remote, Application &app)
Validate header fields necessary for upgrading the link to the peer protocol.
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.
sha512_half_hasher::result_type sha512Half(Args const &... args)
Returns the SHA512-Half of a series of objects.
Definition digest.h:204
std::optional< uint256 > makeSharedValue(stream_type &ssl, beast::Journal journal)
Computes a shared value based on the SSL connection state.
std::optional< AccountID > parseBase58(std::string const &s)
Parse AccountID from checked, base58 string.
bool isProperlyFormedTomlDomain(std::string_view domain)
Determines if the given string looks like a TOML-file hosting domain.
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:10
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:93
static constexpr char kFeatureLedgerReplay[]
Definition Handshake.h:124
boost::beast::http::request< boost::beast::http::empty_body > request_type
Definition Handshake.h:23
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:633
auto makeRequest(bool crawlPublic, bool comprEnabled, bool ledgerReplayEnabled, bool txReduceRelayEnabled, bool vpReduceRelayEnabled) -> request_type
Make outbound http request.
std::optional< KeyType > publicKeyType(Slice const &slice)
Returns the type of public key.
static constexpr char kFeatureTxrr[]
Definition Handshake.h:122
http_response_type makeResponse(bool crawlPublic, http_request_type const &req, beast::IP::Address publicIp, beast::IP::Address remoteIp, uint256 const &sharedValue, std::optional< std::uint32_t > networkID, ProtocolVersion protocol, Application &app)
Make http response.
void buildHandshake(boost::beast::http::fields &h, xrpl::uint256 const &sharedValue, std::optional< std::uint32_t > networkID, beast::IP::Address publicIp, beast::IP::Address remoteIp, Application &app)
Insert fields headers necessary for upgrading the link to the peer protocol.
std::string base64Encode(std::uint8_t const *data, std::size_t len)
std::string makeFeaturesRequestHeader(bool comprEnabled, bool ledgerReplayEnabled, bool txReduceRelayEnabled, bool vpReduceRelayEnabled)
Make request header X-Protocol-Ctl value with supported features.
Definition Handshake.cpp:83
Buffer signDigest(PublicKey const &pk, SecretKey const &sk, uint256 const &digest)
Generate a signature for a message digest.
OpensslSha512Hasher sha512_hasher
Definition digest.h:96
constexpr Number abs(Number x) noexcept
Definition Number.h:823
std::string const & supportedProtocolVersions()
The list of all the protocol versions we support.
std::optional< std::string > getFeatureValue(boost::beast::http::fields const &headers, std::string const &feature)
Get feature's header value.
Definition Handshake.cpp:51
std::pair< std::uint16_t, std::uint16_t > ProtocolVersion
Represents a particular version of the peer-to-peer protocol.
boost::beast::ssl_stream< socket_type > stream_type
Definition Handshake.h:22
boost::beast::http::request< boost::beast::http::dynamic_body > http_request_type
Definition Handoff.h:12
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.
static constexpr char kFeatureCompr[]
Definition Handshake.h:118
BaseUInt< 256 > uint256
Definition base_uint.h:562
static constexpr char kFeatureVprr[]
Definition Handshake.h:120
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:65
std::enable_if_t< std::is_same_v< T, char >||std::is_same_v< T, unsigned char >, Slice > makeSlice(std::array< T, N > const &a)
Definition Slice.h:215
boost::beast::http::response< boost::beast::http::dynamic_body > http_response_type
Definition Handoff.h:14
T str(T... args)
T to_string(T... args)