mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-02 17:06:00 +00:00
Improve transport security:
* Add fields for local and remote IP addresses in hello. * Add configuration for known local public IP address * Set fields appropriately * Check the fields * Disallow self connection by key
This commit is contained in:
@@ -359,38 +359,18 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
# [overlay] EXPERIMENTAL
|
# [overlay]
|
||||||
#
|
#
|
||||||
# This section is EXPERIMENTAL, and should not be
|
# Controls settings related to the peer to peer overlay.
|
||||||
# present for production configuration settings.
|
|
||||||
#
|
#
|
||||||
# A set of key/value pair parameters to configure the overlay.
|
# A set of key/value pair parameters to configure the overlay.
|
||||||
#
|
#
|
||||||
# auto_connect = 0 | 1
|
# public_ip = <IP-address>
|
||||||
#
|
|
||||||
# When set, activates the autoconnect feature. This maintains outgoing
|
|
||||||
# connections using PeerFinder's "Outgoing Connection Strategy."
|
|
||||||
#
|
|
||||||
# become_superpeer = 'never' | 'always' | 'auto'
|
|
||||||
#
|
|
||||||
# Controls the selection of peer roles:
|
|
||||||
#
|
|
||||||
# 'never' Always handshake in the leaf role.
|
|
||||||
# 'always' Always handshake in the superpeer role.
|
|
||||||
# 'auto' Start as a leaf, promote to superpeer after
|
|
||||||
# passing capability check (default).
|
|
||||||
#
|
|
||||||
# In the leaf role, a peer does not advertise its IP and port for
|
|
||||||
# the purpose of receiving incoming connections. The peer also does
|
|
||||||
# not forward transactions and validations received from other peers.
|
|
||||||
#
|
|
||||||
# In the superpeer role, a peer advertises its IP and port for
|
|
||||||
# receiving incoming connections after passing an incoming connection
|
|
||||||
# test. Superpeers forward transactions and protocol messages to all
|
|
||||||
# other peers. Superpeers do not forward validations to other superpeers.
|
|
||||||
# Instead, a validation received by a superpeer from a leaf is forwarded
|
|
||||||
# only to other leaf connections.
|
|
||||||
#
|
#
|
||||||
|
# If the server has a known, fixed public IPv4 address,
|
||||||
|
# specify that IP address here in dotted decimal notation.
|
||||||
|
# Peers will use this information to reject attempt to proxy
|
||||||
|
# connections to or from this server.
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
#-------------------------------------------------------------------------------
|
#-------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -67,10 +67,9 @@ public:
|
|||||||
|
|
||||||
struct Setup
|
struct Setup
|
||||||
{
|
{
|
||||||
bool auto_connect = true;
|
|
||||||
Promote promote = Promote::automatic;
|
|
||||||
std::shared_ptr<boost::asio::ssl::context> context;
|
std::shared_ptr<boost::asio::ssl::context> context;
|
||||||
bool expire = false;
|
bool expire = false;
|
||||||
|
beast::IP::Address public_ip;
|
||||||
};
|
};
|
||||||
|
|
||||||
using PeerSequence = std::vector <Peer::ptr>;
|
using PeerSequence = std::vector <Peer::ptr>;
|
||||||
|
|||||||
@@ -218,7 +218,11 @@ ConnectAttempt::onHandshake (error_code ec)
|
|||||||
beast::http::message req = makeRequest(
|
beast::http::message req = makeRequest(
|
||||||
! overlay_.peerFinder().config().peerPrivate,
|
! overlay_.peerFinder().config().peerPrivate,
|
||||||
remote_endpoint_.address());
|
remote_endpoint_.address());
|
||||||
auto const hello = buildHello (sharedValue, app_);
|
auto const hello = buildHello (
|
||||||
|
sharedValue,
|
||||||
|
overlay_.setup().public_ip,
|
||||||
|
beast::IPAddressConversion::from_asio(remote_endpoint_),
|
||||||
|
app_);
|
||||||
appendHello (req, hello);
|
appendHello (req, hello);
|
||||||
|
|
||||||
using beast::http::write;
|
using beast::http::write;
|
||||||
@@ -399,7 +403,10 @@ ConnectAttempt::processResponse (beast::http::message const& m,
|
|||||||
|
|
||||||
RippleAddress publicKey;
|
RippleAddress publicKey;
|
||||||
std::tie(publicKey, success) = verifyHello (hello,
|
std::tie(publicKey, success) = verifyHello (hello,
|
||||||
sharedValue, journal_, app_);
|
sharedValue,
|
||||||
|
overlay_.setup().public_ip,
|
||||||
|
beast::IPAddressConversion::from_asio(remote_endpoint_),
|
||||||
|
journal_, app_);
|
||||||
if(! success)
|
if(! success)
|
||||||
return close(); // verifyHello logs
|
return close(); // verifyHello logs
|
||||||
if(journal_.info) journal_.info <<
|
if(journal_.info) journal_.info <<
|
||||||
|
|||||||
@@ -244,7 +244,10 @@ OverlayImpl::onHandoff (std::unique_ptr <beast::asio::ssl_bundle>&& ssl_bundle,
|
|||||||
|
|
||||||
RippleAddress publicKey;
|
RippleAddress publicKey;
|
||||||
std::tie(publicKey, success) = verifyHello (hello,
|
std::tie(publicKey, success) = verifyHello (hello,
|
||||||
sharedValue, journal, app_);
|
sharedValue,
|
||||||
|
setup_.public_ip,
|
||||||
|
beast::IPAddressConversion::from_asio(
|
||||||
|
remote_endpoint), journal, app_);
|
||||||
if(! success)
|
if(! success)
|
||||||
return handoff;
|
return handoff;
|
||||||
|
|
||||||
@@ -1018,17 +1021,20 @@ setup_Overlay (BasicConfig const& config)
|
|||||||
{
|
{
|
||||||
Overlay::Setup setup;
|
Overlay::Setup setup;
|
||||||
auto const& section = config.section("overlay");
|
auto const& section = config.section("overlay");
|
||||||
set (setup.auto_connect, "auto_connect", section);
|
|
||||||
std::string promote;
|
|
||||||
set (promote, "become_superpeer", section);
|
|
||||||
if (promote == "never")
|
|
||||||
setup.promote = Overlay::Promote::never;
|
|
||||||
else if (promote == "always")
|
|
||||||
setup.promote = Overlay::Promote::always;
|
|
||||||
else
|
|
||||||
setup.promote = Overlay::Promote::automatic;
|
|
||||||
setup.context = make_SSLContext();
|
setup.context = make_SSLContext();
|
||||||
setup.expire = get<bool>(section, "expire", false);
|
setup.expire = get<bool>(section, "expire", false);
|
||||||
|
|
||||||
|
std::string ip;
|
||||||
|
set (ip, "public_ip", section);
|
||||||
|
if (! ip.empty ())
|
||||||
|
{
|
||||||
|
bool valid;
|
||||||
|
std::tie (setup.public_ip, valid) =
|
||||||
|
beast::IP::Address::from_string (ip);
|
||||||
|
if (! valid || ! setup.public_ip.is_v4() ||
|
||||||
|
is_private (setup.public_ip))
|
||||||
|
throw std::runtime_error ("Configured public IP is invalid");
|
||||||
|
}
|
||||||
return setup;
|
return setup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -596,7 +596,7 @@ void PeerImp::doAccept()
|
|||||||
|
|
||||||
auto resp = makeResponse(
|
auto resp = makeResponse(
|
||||||
! overlay_.peerFinder().config().peerPrivate,
|
! overlay_.peerFinder().config().peerPrivate,
|
||||||
http_message_, sharedValue);
|
http_message_, remote_address_, sharedValue);
|
||||||
beast::http::write (write_buffer_, resp);
|
beast::http::write (write_buffer_, resp);
|
||||||
|
|
||||||
auto const protocol = BuildInfo::make_protocol(hello_.protoversion());
|
auto const protocol = BuildInfo::make_protocol(hello_.protoversion());
|
||||||
@@ -636,7 +636,9 @@ void PeerImp::doAccept()
|
|||||||
|
|
||||||
beast::http::message
|
beast::http::message
|
||||||
PeerImp::makeResponse (bool crawl,
|
PeerImp::makeResponse (bool crawl,
|
||||||
beast::http::message const& req, uint256 const& sharedValue)
|
beast::http::message const& req,
|
||||||
|
beast::IP::Endpoint remote,
|
||||||
|
uint256 const& sharedValue)
|
||||||
{
|
{
|
||||||
beast::http::message resp;
|
beast::http::message resp;
|
||||||
resp.request(false);
|
resp.request(false);
|
||||||
@@ -648,7 +650,8 @@ PeerImp::makeResponse (bool crawl,
|
|||||||
resp.headers.append("Connect-AS", "Peer");
|
resp.headers.append("Connect-AS", "Peer");
|
||||||
resp.headers.append("Server", BuildInfo::getFullVersionString());
|
resp.headers.append("Server", BuildInfo::getFullVersionString());
|
||||||
resp.headers.append ("Crawl", crawl ? "public" : "private");
|
resp.headers.append ("Crawl", crawl ? "public" : "private");
|
||||||
protocol::TMHello hello = buildHello(sharedValue, app_);
|
protocol::TMHello hello = buildHello(sharedValue,
|
||||||
|
overlay_.setup().public_ip, remote, app_);
|
||||||
appendHello(resp, hello);
|
appendHello(resp, hello);
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
@@ -1647,22 +1650,6 @@ PeerImp::sendGetPeers ()
|
|||||||
send (packet);
|
send (packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
|
||||||
PeerImp::sendHello()
|
|
||||||
{
|
|
||||||
bool success;
|
|
||||||
std::tie(sharedValue_, success) = makeSharedValue(
|
|
||||||
stream_.native_handle(), journal_);
|
|
||||||
if (! success)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto const hello = buildHello (sharedValue_, app_);
|
|
||||||
auto const m = std::make_shared<Message> (
|
|
||||||
std::move(hello), protocol::mtHELLO);
|
|
||||||
send (m);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
PeerImp::addLedger (uint256 const& hash)
|
PeerImp::addLedger (uint256 const& hash)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -361,6 +361,7 @@ private:
|
|||||||
|
|
||||||
beast::http::message
|
beast::http::message
|
||||||
makeResponse (bool crawl, beast::http::message const& req,
|
makeResponse (bool crawl, beast::http::message const& req,
|
||||||
|
beast::IP::Endpoint remoteAddress,
|
||||||
uint256 const& sharedValue);
|
uint256 const& sharedValue);
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -440,15 +441,6 @@ private:
|
|||||||
void
|
void
|
||||||
sendGetPeers();
|
sendGetPeers();
|
||||||
|
|
||||||
/** Perform a secure handshake with the peer at the other end.
|
|
||||||
If this function returns false then we cannot guarantee that there
|
|
||||||
is no active man-in-the-middle attack taking place and the link
|
|
||||||
MUST be disconnected.
|
|
||||||
@return true if successful, false otherwise.
|
|
||||||
*/
|
|
||||||
bool
|
|
||||||
sendHello();
|
|
||||||
|
|
||||||
void
|
void
|
||||||
addLedger (uint256 const& hash);
|
addLedger (uint256 const& hash);
|
||||||
|
|
||||||
|
|||||||
@@ -106,7 +106,11 @@ makeSharedValue (SSL* ssl, beast::Journal journal)
|
|||||||
}
|
}
|
||||||
|
|
||||||
protocol::TMHello
|
protocol::TMHello
|
||||||
buildHello (uint256 const& sharedValue, Application& app)
|
buildHello (
|
||||||
|
uint256 const& sharedValue,
|
||||||
|
beast::IP::Address public_ip,
|
||||||
|
beast::IP::Endpoint remote,
|
||||||
|
Application& app)
|
||||||
{
|
{
|
||||||
protocol::TMHello h;
|
protocol::TMHello h;
|
||||||
|
|
||||||
@@ -124,6 +128,18 @@ buildHello (uint256 const& sharedValue, Application& app)
|
|||||||
// h.set_ipv4port (portNumber); // ignored now
|
// h.set_ipv4port (portNumber); // ignored now
|
||||||
h.set_testnet (false);
|
h.set_testnet (false);
|
||||||
|
|
||||||
|
if (remote.is_v4())
|
||||||
|
{
|
||||||
|
auto addr = remote.to_v4 ();
|
||||||
|
if (is_public (addr))
|
||||||
|
{
|
||||||
|
// Connection is to a public IP
|
||||||
|
h.set_remote_ip (addr.value);
|
||||||
|
if (public_ip != beast::IP::Address())
|
||||||
|
h.set_local_ip (public_ip.to_v4().value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We always advertise ourselves as private in the HELLO message. This
|
// We always advertise ourselves as private in the HELLO message. This
|
||||||
// suppresses the old peer advertising code and allows PeerFinder to
|
// suppresses the old peer advertising code and allows PeerFinder to
|
||||||
// take over the functionality.
|
// take over the functionality.
|
||||||
@@ -168,6 +184,14 @@ appendHello (beast::http::message& m,
|
|||||||
if (hello.has_ledgerprevious())
|
if (hello.has_ledgerprevious())
|
||||||
h.append ("Previous-Ledger", beast::base64_encode (
|
h.append ("Previous-Ledger", beast::base64_encode (
|
||||||
hello.ledgerprevious()));
|
hello.ledgerprevious()));
|
||||||
|
|
||||||
|
if (hello.has_local_ip())
|
||||||
|
h.append ("Local-IP", beast::IP::to_string (
|
||||||
|
beast::IP::AddressV4(hello.local_ip())));
|
||||||
|
|
||||||
|
if (hello.has_remote_ip())
|
||||||
|
h.append ("Remote-IP", beast::IP::to_string (
|
||||||
|
beast::IP::AddressV4(hello.remote_ip())));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<ProtocolVersion>
|
std::vector<ProtocolVersion>
|
||||||
@@ -292,13 +316,47 @@ parseHello (beast::http::message const& m, beast::Journal journal)
|
|||||||
hello.set_ledgerprevious (beast::base64_decode (iter->second));
|
hello.set_ledgerprevious (beast::base64_decode (iter->second));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto const iter = h.find ("Local-IP");
|
||||||
|
if (iter != h.end())
|
||||||
|
{
|
||||||
|
bool valid;
|
||||||
|
beast::IP::Address address;
|
||||||
|
std::tie (address, valid) =
|
||||||
|
beast::IP::Address::from_string (iter->second);
|
||||||
|
if (!valid)
|
||||||
|
return result;
|
||||||
|
if (address.is_v4())
|
||||||
|
hello.set_local_ip(address.to_v4().value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto const iter = h.find ("Remote-IP");
|
||||||
|
if (iter != h.end())
|
||||||
|
{
|
||||||
|
bool valid;
|
||||||
|
beast::IP::Address address;
|
||||||
|
std::tie (address, valid) =
|
||||||
|
beast::IP::Address::from_string (iter->second);
|
||||||
|
if (!valid)
|
||||||
|
return result;
|
||||||
|
if (address.is_v4())
|
||||||
|
hello.set_remote_ip(address.to_v4().value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
result.second = true;
|
result.second = true;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<RippleAddress, bool>
|
std::pair<RippleAddress, bool>
|
||||||
verifyHello (protocol::TMHello const& h, uint256 const& sharedValue,
|
verifyHello (protocol::TMHello const& h,
|
||||||
beast::Journal journal, Application& app)
|
uint256 const& sharedValue,
|
||||||
|
beast::IP::Address public_ip,
|
||||||
|
beast::IP::Endpoint remote,
|
||||||
|
beast::Journal journal,
|
||||||
|
Application& app)
|
||||||
{
|
{
|
||||||
std::pair<RippleAddress, bool> result = { {}, false };
|
std::pair<RippleAddress, bool> result = { {}, false };
|
||||||
auto const ourTime = app.timeKeeper().now().time_since_epoch().count();
|
auto const ourTime = app.timeKeeper().now().time_since_epoch().count();
|
||||||
@@ -344,6 +402,11 @@ verifyHello (protocol::TMHello const& h, uint256 const& sharedValue,
|
|||||||
journal.info <<
|
journal.info <<
|
||||||
"Hello: Disconnect: Bad node public key.";
|
"Hello: Disconnect: Bad node public key.";
|
||||||
}
|
}
|
||||||
|
else if (result.first == app.getLocalCredentials().getNodePublic())
|
||||||
|
{
|
||||||
|
journal.info <<
|
||||||
|
"Hello: Disconnect: Self connection.";
|
||||||
|
}
|
||||||
else if (! result.first.verifyNodePublic (
|
else if (! result.first.verifyNodePublic (
|
||||||
sharedValue, h.nodeproof (), ECDSA::not_strict))
|
sharedValue, h.nodeproof (), ECDSA::not_strict))
|
||||||
{
|
{
|
||||||
@@ -351,6 +414,31 @@ verifyHello (protocol::TMHello const& h, uint256 const& sharedValue,
|
|||||||
journal.info <<
|
journal.info <<
|
||||||
"Hello: Disconnect: Failed to verify session.";
|
"Hello: Disconnect: Failed to verify session.";
|
||||||
}
|
}
|
||||||
|
else if (h.has_local_ip () &&
|
||||||
|
is_public (remote) &&
|
||||||
|
remote.is_v4 () &&
|
||||||
|
(remote.to_v4().value != h.local_ip ()))
|
||||||
|
{
|
||||||
|
// Remote asked us to confirm connection is from
|
||||||
|
// correct IP
|
||||||
|
journal.info <<
|
||||||
|
"Hello: Disconnect: Peer IP is " <<
|
||||||
|
beast::IP::to_string (remote.to_v4())
|
||||||
|
<< " not " <<
|
||||||
|
beast::IP::to_string (beast::IP::AddressV4 (h.local_ip()));
|
||||||
|
}
|
||||||
|
else if (h.has_remote_ip() && is_public (remote) &&
|
||||||
|
(public_ip != beast::IP::Address()) &&
|
||||||
|
(h.remote_ip() != public_ip.to_v4().value))
|
||||||
|
{
|
||||||
|
// We know our public IP and peer reports connection
|
||||||
|
// from some other IP
|
||||||
|
journal.info <<
|
||||||
|
"Hello: Disconnect: Our IP is " <<
|
||||||
|
beast::IP::to_string (public_ip.to_v4())
|
||||||
|
<< " not " <<
|
||||||
|
beast::IP::to_string (beast::IP::AddressV4 (h.remote_ip()));
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Successful connection.
|
// Successful connection.
|
||||||
|
|||||||
@@ -52,7 +52,9 @@ makeSharedValue (SSL* ssl, beast::Journal journal);
|
|||||||
|
|
||||||
/** Build a TMHello protocol message. */
|
/** Build a TMHello protocol message. */
|
||||||
protocol::TMHello
|
protocol::TMHello
|
||||||
buildHello (uint256 const& sharedValue, Application& app);
|
buildHello (uint256 const& sharedValue,
|
||||||
|
beast::IP::Address public_ip,
|
||||||
|
beast::IP::Endpoint remote, Application& app);
|
||||||
|
|
||||||
/** Insert HTTP headers based on the TMHello protocol message. */
|
/** Insert HTTP headers based on the TMHello protocol message. */
|
||||||
void
|
void
|
||||||
@@ -70,6 +72,8 @@ parseHello (beast::http::message const& m, beast::Journal journal);
|
|||||||
*/
|
*/
|
||||||
std::pair<RippleAddress, bool>
|
std::pair<RippleAddress, bool>
|
||||||
verifyHello (protocol::TMHello const& h, uint256 const& sharedValue,
|
verifyHello (protocol::TMHello const& h, uint256 const& sharedValue,
|
||||||
|
beast::IP::Address public_ip,
|
||||||
|
beast::IP::Endpoint remote,
|
||||||
beast::Journal journal, Application& app);
|
beast::Journal journal, Application& app);
|
||||||
|
|
||||||
/** Parse a set of protocol versions.
|
/** Parse a set of protocol versions.
|
||||||
|
|||||||
@@ -95,6 +95,8 @@ message TMHello
|
|||||||
optional bool nodePrivate = 11; // Request to not forward IP.
|
optional bool nodePrivate = 11; // Request to not forward IP.
|
||||||
optional TMProofWork proofOfWork = 12; // request/provide proof of work
|
optional TMProofWork proofOfWork = 12; // request/provide proof of work
|
||||||
optional bool testNet = 13; // Running as testnet.
|
optional bool testNet = 13; // Running as testnet.
|
||||||
|
optional uint32 local_ip = 14; // our public IP
|
||||||
|
optional uint32 remote_ip = 15; // IP we see connection from
|
||||||
}
|
}
|
||||||
|
|
||||||
// The status of a node in our cluster
|
// The status of a node in our cluster
|
||||||
|
|||||||
Reference in New Issue
Block a user