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:
JoelKatz
2015-09-18 15:09:11 -07:00
committed by Nik Bougalis
parent 8f7ab21423
commit 0c05bd3def
9 changed files with 138 additions and 73 deletions

View File

@@ -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.
# #
# #
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------

View File

@@ -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>;

View File

@@ -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 <<

View File

@@ -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;
} }

View File

@@ -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)
{ {

View File

@@ -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);

View File

@@ -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.

View File

@@ -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.

View File

@@ -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