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