Browse Source

authenticated capability (hello) and handshake authentication

cl-refactor
subtly 10 years ago
parent
commit
d137581c7f
  1. 303
      libp2p/Host.cpp
  2. 79
      libp2p/Host.h
  3. 3
      libp2p/Peer.h
  4. 435
      libp2p/RLPxHandshake.cpp
  5. 48
      libp2p/RLPxHandshake.h
  6. 94
      libp2p/Session.cpp
  7. 2
      libp2p/Session.h
  8. 2
      test/rlpx.cpp

303
libp2p/Host.cpp

@ -34,13 +34,12 @@
#include "Common.h"
#include "Capability.h"
#include "UPnP.h"
#include "RLPxHandshake.h"
#include "Host.h"
using namespace std;
using namespace dev;
using namespace dev::p2p;
#include "RLPxHandshake.h"
HostNodeTableHandler::HostNodeTableHandler(Host& _host): m_host(_host) {}
void HostNodeTableHandler::processEvent(NodeId const& _n, NodeTableEventType const& _e)
@ -157,23 +156,63 @@ unsigned Host::protocolVersion() const
return 3;
}
void Host::registerPeer(std::shared_ptr<Session> _s, CapDescs const& _caps)
bool Host::startPeerSession(Public const& _id, RLP const& _rlp, bi::tcp::socket *_socket)
{
/// Get or create Peer
shared_ptr<Peer> p;
p = m_peers[_id];
if (!p)
{
p.reset(new Peer()); // this maybe redundant
p->id = _id;
}
p->m_lastDisconnect = NoDisconnect;
if (p->isOffline())
p->m_lastConnected = std::chrono::system_clock::now();
p->m_failedAttempts = 0;
// TODO: update pendingconns w/session-weak-ptr for graceful shutdown (otherwise this line isn't safe)
p->endpoint.tcp.address(_socket->remote_endpoint().address());
auto protocolVersion = _rlp[1].toInt<unsigned>();
auto clientVersion = _rlp[2].toString();
auto caps = _rlp[3].toVector<CapDesc>();
auto listenPort = _rlp[4].toInt<unsigned short>();
// clang error (previously: ... << hex << caps ...)
// "'operator<<' should be declared prior to the call site or in an associated namespace of one of its arguments"
stringstream capslog;
for (auto cap: caps)
capslog << "(" << cap.first << "," << dec << cap.second << ")";
clog(NetMessageSummary) << "Hello: " << clientVersion << "V[" << protocolVersion << "]" << _id.abridged() << showbase << capslog.str() << dec << listenPort;
// create session so disconnects are managed
auto ps = make_shared<Session>(this, move(*_socket), p, PeerSessionInfo({_id, clientVersion, _socket->remote_endpoint().address().to_string(), listenPort, chrono::steady_clock::duration(), _rlp[3].toSet<CapDesc>(), 0, map<string, string>()}));
if (protocolVersion != this->protocolVersion())
{
ps->disconnect(IncompatibleProtocol);
return false;
}
{
clog(NetNote) << "p2p.host.peer.register" << _s->m_peer->id.abridged();
RecursiveGuard l(x_sessions);
// TODO: temporary loose-coupling; if m_peers already has peer,
// it is same as _s->m_peer. (fixing next PR)
if (!m_peers.count(_s->m_peer->id))
m_peers[_s->m_peer->id] = _s->m_peer;
m_sessions[_s->m_peer->id] = _s;
if (m_sessions.count(_id) && !!m_sessions[_id].lock())
{
// Already connected.
clog(NetWarn) << "Session already exists for peer with id" << _id.abridged();
ps->disconnect(DuplicatePeer);
return false;
}
clog(NetNote) << "p2p.host.peer.register" << _id.abridged();
m_sessions[_id] = ps;
}
ps->start();
unsigned o = (unsigned)UserPacket;
for (auto const& i: _caps)
for (auto const& i: caps)
if (haveCapability(i))
{
_s->m_capabilities[i] = shared_ptr<Capability>(m_capabilities[i]->newPeerCapability(_s.get(), o));
ps->m_capabilities[i] = shared_ptr<Capability>(m_capabilities[i]->newPeerCapability(ps.get(), o));
o += m_capabilities[i]->messageCount();
}
}
@ -340,7 +379,7 @@ void Host::runAcceptor()
{
// doHandshake takes ownersihp of *s via std::move
// incoming connection; we don't yet know nodeid
auto handshake = make_shared<PeerHandshake>(this, s);
auto handshake = make_shared<RLPXHandshake>(this, s);
handshake->start();
success = true;
}
@ -374,244 +413,6 @@ void Host::runAcceptor()
}
}
void PeerHandshake::transition(boost::system::error_code _ech)
{
if (_ech)
{
boost::system::error_code ec;
socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
if (socket->is_open())
socket->close();
return;
}
auto self(shared_from_this());
if (nextState == New)
{
nextState = AckAuth;
clog(NetConnect) << "Authenticating connection for " << socket->remote_endpoint();
if (originated)
{
clog(NetConnect) << "devp2p.connect.egress sending auth";
// egress: tx auth
asserts((bool)remote);
auth.resize(Signature::size + h256::size + Public::size + h256::size + 1);
bytesRef sig(&auth[0], Signature::size);
bytesRef hepubk(&auth[Signature::size], h256::size);
bytesRef pubk(&auth[Signature::size + h256::size], Public::size);
bytesRef nonce(&auth[Signature::size + h256::size + Public::size], h256::size);
// E(remote-pubk, S(ecdhe-random, ecdh-shared-secret^nonce) || H(ecdhe-random-pubk) || pubk || nonce || 0x0)
crypto::ecdh::agree(host->m_alias.sec(), remote, ss);
sign(ecdhe.seckey(), ss ^ this->nonce).ref().copyTo(sig);
sha3(ecdhe.pubkey().ref(), hepubk);
host->m_alias.pub().ref().copyTo(pubk);
this->nonce.ref().copyTo(nonce);
auth[auth.size() - 1] = 0x0;
encryptECIES(remote, &auth, authCipher);
ba::async_write(*socket, ba::buffer(authCipher), [this, self](boost::system::error_code ec, std::size_t)
{
transition(ec);
});
}
else
{
clog(NetConnect) << "devp2p.connect.ingress recving auth";
// ingress: rx auth
authCipher.resize(307);
ba::async_read(*socket, ba::buffer(authCipher, 307), [this, self](boost::system::error_code ec, std::size_t)
{
if (ec)
transition(ec);
else
{
if (!decryptECIES(host->m_alias.sec(), bytesConstRef(&authCipher), auth))
{
clog(NetWarn) << "devp2p.connect.egress recving auth decrypt failed";
nextState = Error;
transition();
return;
}
bytesConstRef sig(&auth[0], Signature::size);
bytesConstRef hepubk(&auth[Signature::size], h256::size);
bytesConstRef pubk(&auth[Signature::size + h256::size], Public::size);
bytesConstRef nonce(&auth[Signature::size + h256::size + Public::size], h256::size);
pubk.copyTo(remote.ref());
nonce.copyTo(remoteNonce.ref());
crypto::ecdh::agree(host->m_alias.sec(), remote, ss);
remoteEphemeral = recover(*(Signature*)sig.data(), ss ^ remoteNonce);
assert(sha3(remoteEphemeral) == *(h256*)hepubk.data());
transition();
}
});
}
}
else if (nextState == AckAuth)
{
nextState = Authenticating;
if (originated)
{
clog(NetConnect) << "devp2p.connect.egress recving ack";
// egress: rx ack
ackCipher.resize(210);
ba::async_read(*socket, ba::buffer(ackCipher, 210), [this, self](boost::system::error_code ec, std::size_t)
{
if (ec)
transition(ec);
else
{
if (!decryptECIES(host->m_alias.sec(), bytesConstRef(&ackCipher), ack))
{
clog(NetWarn) << "devp2p.connect.egress recving ack decrypt failed";
nextState = Error;
transition();
return;
}
bytesConstRef(&ack).cropped(0, Public::size).copyTo(remoteEphemeral.ref());
bytesConstRef(&ack).cropped(Public::size, h256::size).copyTo(remoteNonce.ref());
transition();
}
});
}
else
{
clog(NetConnect) << "devp2p.connect.ingress sending ack";
// ingress: tx ack
ack.resize(Public::size + h256::size + 1);
bytesRef epubk(&ack[0], Public::size);
bytesRef nonce(&ack[Public::size], h256::size);
ecdhe.pubkey().ref().copyTo(epubk);
this->nonce.ref().copyTo(nonce);
ack[ack.size() - 1] = 0x0;
encryptECIES(remote, &ack, ackCipher);
ba::async_write(*socket, ba::buffer(ackCipher), [this, self](boost::system::error_code ec, std::size_t)
{
transition(ec);
});
}
}
else if (nextState == Authenticating)
{
if (originated)
clog(NetConnect) << "devp2p.connect.egress sending magic sequence";
else
clog(NetConnect) << "devp2p.connect.ingress sending magic sequence";
PeerSecrets* k = new PeerSecrets;
bytes keyMaterialBytes(512);
bytesRef keyMaterial(&keyMaterialBytes);
ecdhe.agree(remoteEphemeral, ess);
ess.ref().copyTo(keyMaterial.cropped(0, h256::size));
ss.ref().copyTo(keyMaterial.cropped(h256::size, h256::size));
// auto token = sha3(ssA);
k->encryptK = sha3(keyMaterial);
k->encryptK.ref().copyTo(keyMaterial.cropped(h256::size, h256::size));
k->macK = sha3(keyMaterial);
// Initiator egress-mac: sha3(mac-secret^recipient-nonce || auth-sent-init)
// ingress-mac: sha3(mac-secret^initiator-nonce || auth-recvd-ack)
// Recipient egress-mac: sha3(mac-secret^initiator-nonce || auth-sent-ack)
// ingress-mac: sha3(mac-secret^recipient-nonce || auth-recvd-init)
bytes const& egressCipher = originated ? authCipher : ackCipher;
keyMaterialBytes.resize(h256::size + egressCipher.size());
keyMaterial.retarget(keyMaterialBytes.data(), keyMaterialBytes.size());
(k->macK ^ remoteNonce).ref().copyTo(keyMaterial);
bytesConstRef(&egressCipher).copyTo(keyMaterial.cropped(h256::size, egressCipher.size()));
k->egressMac = sha3(keyMaterial);
bytes const& ingressCipher = originated ? ackCipher : authCipher;
keyMaterialBytes.resize(h256::size + ingressCipher.size());
keyMaterial.retarget(keyMaterialBytes.data(), keyMaterialBytes.size());
(k->macK ^ nonce).ref().copyTo(keyMaterial);
bytesConstRef(&ingressCipher).copyTo(keyMaterial.cropped(h256::size, ingressCipher.size()));
k->ingressMac = sha3(keyMaterial);
// This test will be replaced with protocol-capabilities information (was Hello packet)
// TESTING: send encrypt magic sequence
bytes magic {0x22,0x40,0x08,0x91};
// rlpx encrypt
encryptSymNoAuth(k->encryptK, &magic, k->magicCipherAndMac, h128());
k->magicCipherAndMac.resize(k->magicCipherAndMac.size() + 32);
sha3mac(k->egressMac.ref(), &magic, k->egressMac.ref());
k->egressMac.ref().copyTo(bytesRef(&k->magicCipherAndMac).cropped(k->magicCipherAndMac.size() - 32, 32));
clog(NetConnect) << "devp2p.connect.egress txrx magic sequence";
k->recvdMagicCipherAndMac.resize(k->magicCipherAndMac.size());
ba::async_write(*socket, ba::buffer(k->magicCipherAndMac), [this, self, k, magic](boost::system::error_code ec, std::size_t)
{
if (ec)
{
delete k;
transition(ec);
return;
}
ba::async_read(*socket, ba::buffer(k->recvdMagicCipherAndMac, k->magicCipherAndMac.size()), [this, self, k, magic](boost::system::error_code ec, std::size_t)
{
if (originated)
clog(NetNote) << "devp2p.connect.egress recving magic sequence";
else
clog(NetNote) << "devp2p.connect.ingress recving magic sequence";
if (ec)
{
delete k;
transition(ec);
return;
}
/// capabilities handshake (encrypted magic sequence is placeholder)
bytes decryptedMagic;
decryptSymNoAuth(k->encryptK, h128(), &k->recvdMagicCipherAndMac, decryptedMagic);
if (decryptedMagic[0] == 0x22 && decryptedMagic[1] == 0x40 && decryptedMagic[2] == 0x08 && decryptedMagic[3] == 0x91)
{
shared_ptr<Peer> p;
p = host->m_peers[remote];
if (!p)
{
p.reset(new Peer());
p->id = remote;
}
p->endpoint.tcp.address(socket->remote_endpoint().address());
p->m_lastDisconnect = NoDisconnect;
p->m_lastConnected = std::chrono::system_clock::now();
p->m_failedAttempts = 0;
auto ps = std::make_shared<Session>(host, move(*socket), p);
ps->start();
}
// todo: PeerSession will take ownership of k and use it to encrypt wireline.
delete k;
});
});
}
else
{
clog(NetConnect) << "Disconnecting " << socket->remote_endpoint() << " (Authentication Failed)";
boost::system::error_code ec;
socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
socket->close();
}
}
string Host::pocHost()
{
vector<string> strs;
@ -699,7 +500,7 @@ void Host::connect(std::shared_ptr<Peer> const& _p)
else
{
clog(NetConnect) << "Connected to" << _p->id.abridged() << "@" << _p->peerEndpoint();
auto handshake = make_shared<PeerHandshake>(this, s, _p->id);
auto handshake = make_shared<RLPXHandshake>(this, s, _p->id);
handshake->start();
}
Guard l(x_pendingNodeConns);

79
libp2p/Host.h

@ -77,9 +77,7 @@ private:
class Host: public Worker
{
friend class HostNodeTableHandler;
friend struct PeerHandshake;
friend struct RLPXHandshake;
friend class RLPXHandshake;
friend class Session;
friend class HostCapabilityFace;
@ -110,8 +108,6 @@ public:
CapDescs caps() const { CapDescs ret; for (auto const& i: m_capabilities) ret.push_back(i.first); return ret; }
template <class T> std::shared_ptr<T> cap() const { try { return std::static_pointer_cast<T>(m_capabilities.at(std::make_pair(T::staticName(), T::staticVersion()))); } catch (...) { return nullptr; } }
bool havePeerSession(NodeId _id) { RecursiveGuard l(x_sessions); return m_sessions.count(_id) ? !!m_sessions[_id].lock() : false; }
void addNode(NodeId const& _node, std::string const& _addr, unsigned short _tcpPort, unsigned short _udpPort);
/// Set ideal number of peers.
@ -149,7 +145,8 @@ public:
NodeId id() const { return m_alias.pub(); }
void registerPeer(std::shared_ptr<Session> _s, CapDescs const& _caps);
/// Validates and starts peer session, taking ownership of _socket. Disconnects and returns false upon error.
bool startPeerSession(Public const& _id, RLP const& _hello, bi::tcp::socket *_socket);
protected:
void onNodeTableEvent(NodeId const& _n, NodeTableEventType const& _e);
@ -158,6 +155,8 @@ protected:
void restoreNetwork(bytesConstRef _b);
private:
bool havePeerSession(NodeId _id) { RecursiveGuard l(x_sessions); return m_sessions.count(_id) ? !!m_sessions[_id].lock() : false; }
/// Populate m_peerAddresses with available public addresses.
void determinePublic(std::string const& _publicAddress, bool _upnp);
@ -234,73 +233,5 @@ private:
bool m_accepting = false;
};
/**
* @brief Key material and derived secrets for TCP peer connection.
*/
struct PeerSecrets
{
friend struct PeerHandshake;
friend struct RLPXHandshake;
protected:
Secret encryptK;
Secret macK;
h256 egressMac;
h256 ingressMac;
bytes magicCipherAndMac;
bytes recvdMagicCipherAndMac;
};
struct PeerHandshake: public std::enable_shared_from_this<PeerHandshake>
{
friend class Host;
enum State
{
Error = -1,
New, // New->AckAuth [egress: tx auth, ingress: rx auth]
AckAuth, // AckAuth->Authenticating [egress: rx ack, ingress: tx ack]
Authenticating, // Authenticating [tx caps, rx caps, authenticate]
};
/// Handshake for ingress connection. Takes ownership of socket.
PeerHandshake(Host* _host, bi::tcp::socket* _socket): host(_host), socket(std::move(_socket)), originated(false) { crypto::Nonce::get().ref().copyTo(nonce.ref()); }
/// Handshake for egress connection to _remote. Takes ownership of socket.
PeerHandshake(Host* _host, bi::tcp::socket* _socket, NodeId _remote): host(_host), socket(std::move(_socket)), originated(true), remote(_remote) { crypto::Nonce::get().ref().copyTo(nonce.ref()); }
~PeerHandshake() { delete socket; }
protected:
void start() { transition(); }
private:
void transition(boost::system::error_code _ech = boost::system::error_code());
/// Current state of handshake.
State nextState = New;
Host* host;
/// Node id of remote host for socket.
NodeId remote;
bi::tcp::socket* socket;
bool originated = false;
bytes auth;
bytes authCipher;
bytes ack;
bytes ackCipher;
Secret ss;
Secret ess;
crypto::ECDHE ecdhe;
h256 nonce;
Public remoteEphemeral;
h256 remoteNonce;
};
}
}

3
libp2p/Peer.h

@ -55,8 +55,7 @@ class Peer: public Node
friend class Session; /// Allows Session to update score and rating.
friend class Host; /// For Host: saveNetwork(), restoreNetwork()
friend struct PeerHandshake;
friend struct RLPXHandshake;
friend class RLPXHandshake;
public:
bool isOffline() const { return !m_session.lock(); }

435
libp2p/RLPxHandshake.cpp

@ -28,7 +28,7 @@ using namespace dev;
using namespace dev::p2p;
using namespace CryptoPP;
RLPXFrameIO::RLPXFrameIO(RLPXHandshake& _init): m_macEnc()
RLPXFrameIO::RLPXFrameIO(RLPXHandshake const& _init): m_socket(_init.socket)
{
// we need:
// originated?
@ -36,7 +36,7 @@ RLPXFrameIO::RLPXFrameIO(RLPXHandshake& _init): m_macEnc()
// authCipher
// ackCipher
bytes keyMaterialBytes(512);
bytes keyMaterialBytes(64);
bytesRef keyMaterial(&keyMaterialBytes);
// shared-secret = sha3(ecdhe-shared-secret || sha3(nonce || initiator-nonce))
@ -44,23 +44,24 @@ RLPXFrameIO::RLPXFrameIO(RLPXHandshake& _init): m_macEnc()
_init.ecdhe.agree(_init.remoteEphemeral, ephemeralShared);
ephemeralShared.ref().copyTo(keyMaterial.cropped(0, h256::size));
h512 nonceMaterial;
h256& leftNonce = _init.originated ? _init.remoteNonce : _init.nonce;
h256& rightNonce = _init.originated ? _init.nonce : _init.remoteNonce;
h256 const& leftNonce = _init.originated ? _init.remoteNonce : _init.nonce;
h256 const& rightNonce = _init.originated ? _init.nonce : _init.remoteNonce;
leftNonce.ref().copyTo(nonceMaterial.ref().cropped(0, h256::size));
rightNonce.ref().copyTo(nonceMaterial.ref().cropped(h256::size, h256::size));
auto outRef(keyMaterial.cropped(h256::size, h256::size));
sha3(nonceMaterial.ref(), outRef); // output h(nonces)
sha3(keyMaterial, outRef); // output shared-secret
// token: sha3(outRef)
// token: sha3(outRef, bytesRef(&token)); -> Host (to be saved to disk)
// aes-secret = sha3(ecdhe-shared-secret || shared-secret)
sha3(keyMaterial, outRef); // output aes-secret
m_frameEnc.SetKey(outRef.data(), h256::size);
m_frameEnc.SetKeyWithIV(outRef.data(), h128::size, h128().data());
m_frameDec.SetKeyWithIV(outRef.data(), h128::size, h128().data());
// mac-secret = sha3(ecdhe-shared-secret || aes-secret)
sha3(keyMaterial, outRef); // output mac-secret
m_macEnc.SetKey(outRef.data(), h256::size);
m_macEnc.SetKey(outRef.data(), h128::size);
// Initiator egress-mac: sha3(mac-secret^recipient-nonce || auth-sent-init)
// ingress-mac: sha3(mac-secret^initiator-nonce || auth-recvd-ack)
@ -75,7 +76,7 @@ RLPXFrameIO::RLPXFrameIO(RLPXHandshake& _init): m_macEnc()
m_egressMac.Update(keyMaterial.data(), keyMaterial.size());
// recover mac-secret by re-xoring remoteNonce
(*(h256*)outRef.data() ^ _init.remoteNonce ^ _init.nonce).ref().copyTo(keyMaterial);
(*(h256*)keyMaterial.data() ^ _init.remoteNonce ^ _init.nonce).ref().copyTo(keyMaterial);
bytes const& ingressCipher = _init.originated ? _init.ackCipher : _init.authCipher;
keyMaterialBytes.resize(h256::size + ingressCipher.size());
keyMaterial.retarget(keyMaterialBytes.data(), keyMaterialBytes.size());
@ -83,25 +84,60 @@ RLPXFrameIO::RLPXFrameIO(RLPXHandshake& _init): m_macEnc()
m_ingressMac.Update(keyMaterial.data(), keyMaterial.size());
}
void RLPXFrameIO::writeFullPacketFrame(bytesConstRef _packet)
{
RLPStream header;
}
void RLPXFrameIO::writeHeader(bi::tcp::socket* _socket, h128 const& _header)
void RLPXFrameIO::writeSingleFramePacket(bytesConstRef _packet, bytes& o_bytes)
{
// _packet = type || rlpList()
// current/old packet format: prep(_s).appendList(_args + 1).append((unsigned)_id);
RLPStream header;
header.appendRaw(bytes({byte(_packet.size() >> 16), byte(_packet.size() >> 8), byte(_packet.size())}));
// zeroHeader: []byte{0xC2, 0x80, 0x80}. Should be rlpList(protocolType,seqId,totalPacketSize).
header.appendRaw(bytes({0xc2,0x80,0x80}));
// TODO: SECURITY check that header is <= 16 bytes
bytes headerWithMac;
header.swapOut(headerWithMac);
headerWithMac.resize(32);
m_frameEnc.ProcessData(headerWithMac.data(), headerWithMac.data(), 16);
updateEgressMACWithHeader(bytesConstRef(&headerWithMac).cropped(0, 16));
egressDigest().ref().copyTo(bytesRef(&headerWithMac).cropped(h128::size,h128::size));
auto padding = (16 - (_packet.size() % 16)) % 16;
o_bytes.swap(headerWithMac);
o_bytes.resize(32 + _packet.size() + padding + h128::size);
bytesRef packetRef(o_bytes.data() + 32, _packet.size());
m_frameEnc.ProcessData(packetRef.data(), _packet.data(), _packet.size());
bytesRef paddingRef(o_bytes.data() + 32 + _packet.size(), padding);
if (padding)
m_frameEnc.ProcessData(paddingRef.data(), paddingRef.data(), padding);
bytesRef packetWithPaddingRef(o_bytes.data() + 32, _packet.size() + padding);
updateEgressMACWithEndOfFrame(packetWithPaddingRef);
bytesRef macRef(o_bytes.data() + 32 + _packet.size() + padding, h128::size);
egressDigest().ref().copyTo(macRef);
clog(NetConnect) << "SENT FRAME " << _packet.size() << *(h128*)macRef.data();
clog(NetConnect) << "FRAME TAIL " << *(h128*)(o_bytes.data() + 32 + _packet.size() + padding);
}
void RLPXFrameIO::write(bi::tcp::socket* _socket, bytesConstRef _in, bool _eof)
bool RLPXFrameIO::authAndDecryptHeader(h256& io)
{
updateIngressMACWithHeader(io.ref());
bytesConstRef macRef = io.ref().cropped(h128::size, h128::size);
if (*(h128*)macRef.data() != ingressDigest())
return false;
m_frameDec.ProcessData(io.data(), io.data(), 16);
return true;
}
bool RLPXFrameIO::read(bytesConstRef _in, bytes& o_out)
bool RLPXFrameIO::authAndDecryptFrame(bytesRef io)
{
bytesRef cipherText(io.cropped(0, io.size() - h128::size));
updateIngressMACWithEndOfFrame(cipherText);
bytesConstRef frameMac(io.data() + io.size() - h128::size, h128::size);
if (*(h128*)frameMac.data() != ingressDigest())
return false;
m_frameDec.ProcessData(io.data(), io.data(), io.size() - h128::size);
return true;
}
h128 RLPXFrameIO::egressDigest()
@ -120,9 +156,8 @@ h128 RLPXFrameIO::ingressDigest()
return move(digest);
}
void RLPXFrameIO::updateEgressMACWithHeader(h128 const& _headerCipher)
void RLPXFrameIO::updateEgressMACWithHeader(bytesConstRef _headerCipher)
{
m_egressMac.Update(_headerCipher.data(), h128::size);
updateMAC(m_egressMac, *(h128*)_headerCipher.data());
}
@ -130,11 +165,16 @@ void RLPXFrameIO::updateEgressMACWithEndOfFrame(bytesConstRef _cipher)
{
m_egressMac.Update(_cipher.data(), _cipher.size());
updateMAC(m_egressMac);
{
SHA3_256 prev(m_egressMac);
h128 digest;
prev.TruncatedFinal(digest.data(), h128::size);
clog(NetConnect) << "EGRESS FRAMEMAC " << _cipher.size() << digest;
}
}
void RLPXFrameIO::updateIngressMACWithHeader(bytesConstRef _headerCipher)
{
m_ingressMac.Update(_headerCipher.data(), h128::size);
updateMAC(m_ingressMac, *(h128*)_headerCipher.data());
}
@ -142,6 +182,12 @@ void RLPXFrameIO::updateIngressMACWithEndOfFrame(bytesConstRef _cipher)
{
m_ingressMac.Update(_cipher.data(), _cipher.size());
updateMAC(m_ingressMac);
{
SHA3_256 prev(m_ingressMac);
h128 digest;
prev.TruncatedFinal(digest.data(), h128::size);
clog(NetConnect) << "INGRESS FRAMEMAC " << _cipher.size() << digest;
}
}
void RLPXFrameIO::updateMAC(SHA3_256& _mac, h128 const& _seed)
@ -155,12 +201,13 @@ void RLPXFrameIO::updateMAC(SHA3_256& _mac, h128 const& _seed)
encDigest ^= (!!_seed ? _seed : prevDigestOut);
// update mac for final digest
_mac.Update(encDigest.data(), h256::size);
_mac.Update(encDigest.data(), h128::size);
}
void RLPXHandshake::generateAuth()
void RLPXHandshake::writeAuth()
{
clog(NetConnect) << "p2p.connect.egress sending auth to " << socket->remote_endpoint();
auth.resize(Signature::size + h256::size + Public::size + h256::size + 1);
bytesRef sig(&auth[0], Signature::size);
bytesRef hepubk(&auth[Signature::size], h256::size);
@ -168,211 +215,227 @@ void RLPXHandshake::generateAuth()
bytesRef nonce(&auth[Signature::size + h256::size + Public::size], h256::size);
// E(remote-pubk, S(ecdhe-random, ecdh-shared-secret^nonce) || H(ecdhe-random-pubk) || pubk || nonce || 0x0)
//crypto::ecdh::agree(host->m_alias.sec(), remote, ss);
//sign(ecdhe.seckey(), ss ^ this->nonce).ref().copyTo(sig);
Secret staticShared;
crypto::ecdh::agree(host->m_alias.sec(), remote, staticShared);
sign(ecdhe.seckey(), staticShared ^ this->nonce).ref().copyTo(sig);
sha3(ecdhe.pubkey().ref(), hepubk);
host->m_alias.pub().ref().copyTo(pubk);
this->nonce.ref().copyTo(nonce);
auth[auth.size() - 1] = 0x0;
encryptECIES(remote, &auth, authCipher);
auto self(shared_from_this());
ba::async_write(*socket, ba::buffer(authCipher), [this, self](boost::system::error_code ec, std::size_t)
{
transition(ec);
});
}
void RLPXHandshake::generateAck()
void RLPXHandshake::writeAck()
{
clog(NetConnect) << "p2p.connect.ingress sending ack to " << socket->remote_endpoint();
ack.resize(Public::size + h256::size + 1);
bytesRef epubk(&ack[0], Public::size);
bytesRef nonce(&ack[Public::size], h256::size);
ecdhe.pubkey().ref().copyTo(epubk);
this->nonce.ref().copyTo(nonce);
ack[ack.size() - 1] = 0x0;
encryptECIES(remote, &ack, ackCipher);
auto self(shared_from_this());
ba::async_write(*socket, ba::buffer(ackCipher), [this, self](boost::system::error_code ec, std::size_t)
{
transition(ec);
});
}
bool RLPXHandshake::decodeAuth()
void RLPXHandshake::readAuth()
{
if (!decryptECIES(host->m_alias.sec(), bytesConstRef(&authCipher), auth))
return false;
bytesConstRef sig(&auth[0], Signature::size);
bytesConstRef hepubk(&auth[Signature::size], h256::size);
bytesConstRef pubk(&auth[Signature::size + h256::size], Public::size);
bytesConstRef nonce(&auth[Signature::size + h256::size + Public::size], h256::size);
pubk.copyTo(remote.ref());
nonce.copyTo(remoteNonce.ref());
//crypto::ecdh::agree(host->m_alias.sec(), remote, ss);
//remoteEphemeral = recover(*(Signature*)sig.data(), ss ^ remoteNonce);
assert(sha3(remoteEphemeral) == *(h256*)hepubk.data());
return true;
clog(NetConnect) << "p2p.connect.ingress recving auth from " << socket->remote_endpoint();
authCipher.resize(307);
auto self(shared_from_this());
ba::async_read(*socket, ba::buffer(authCipher, 307), [this, self](boost::system::error_code ec, std::size_t)
{
if (ec)
transition(ec);
else if (decryptECIES(host->m_alias.sec(), bytesConstRef(&authCipher), auth))
{
bytesConstRef sig(&auth[0], Signature::size);
bytesConstRef hepubk(&auth[Signature::size], h256::size);
bytesConstRef pubk(&auth[Signature::size + h256::size], Public::size);
bytesConstRef nonce(&auth[Signature::size + h256::size + Public::size], h256::size);
pubk.copyTo(remote.ref());
nonce.copyTo(remoteNonce.ref());
Secret sharedSecret;
crypto::ecdh::agree(host->m_alias.sec(), remote, sharedSecret);
remoteEphemeral = recover(*(Signature*)sig.data(), sharedSecret ^ remoteNonce);
assert(sha3(remoteEphemeral) == *(h256*)hepubk.data());
transition();
}
else
{
clog(NetWarn) << "p2p.connect.egress recving auth decrypt failed for" << socket->remote_endpoint();
nextState = Error;
transition();
}
});
}
bool RLPXHandshake::decodeAck()
void RLPXHandshake::readAck()
{
clog(NetConnect) << "p2p.connect.egress recving ack from " << socket->remote_endpoint();
ackCipher.resize(210);
auto self(shared_from_this());
ba::async_read(*socket, ba::buffer(ackCipher, 210), [this, self](boost::system::error_code ec, std::size_t)
{
if (ec)
transition(ec);
else if (decryptECIES(host->m_alias.sec(), bytesConstRef(&ackCipher), ack))
{
bytesConstRef(&ack).cropped(0, Public::size).copyTo(remoteEphemeral.ref());
bytesConstRef(&ack).cropped(Public::size, h256::size).copyTo(remoteNonce.ref());
transition();
}
else
{
clog(NetWarn) << "p2p.connect.egress recving ack decrypt failed for " << socket->remote_endpoint();
nextState = Error;
transition();
}
});
}
/// used for protocol handshake
bytes RLPXHandshake::frame(bytesConstRef _packet)
void RLPXHandshake::error()
{
clog(NetConnect) << "Disconnecting " << socket->remote_endpoint() << " (Handshake Failed)";
boost::system::error_code ec;
socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
if (socket->is_open())
socket->close();
}
void RLPXHandshake::transition(boost::system::error_code _ech)
{
if (_ech || nextState == Error)
{
clog(NetConnect) << "Disconnecting " << socket->remote_endpoint() << " (Handshake Failed)";
boost::system::error_code ec;
socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
if (socket->is_open())
socket->close();
return;
}
return error();
auto self(shared_from_this());
if (nextState == New)
{
nextState = AckAuth;
clog(NetConnect) << "Authenticating connection for " << socket->remote_endpoint();
if (originated)
writeAuth();
else
readAuth();
}
else if (nextState == AckAuth)
{
nextState = WriteHello;
if (originated)
readAck();
else
writeAck();
}
else if (nextState == WriteHello)
{
nextState = ReadHello;
if (originated)
{
clog(NetConnect) << "p2p.connect.egress sending auth";
generateAuth();
ba::async_write(*socket, ba::buffer(authCipher), [this, self](boost::system::error_code ec, std::size_t)
{
transition(ec);
});
}
clog(NetConnect) << "p2p.connect.egress sending capabilities handshake";
else
clog(NetConnect) << "p2p.connect.ingress sending capabilities handshake";
io = new RLPXFrameIO(*this);
// old packet format
// 5 arguments, HelloPacket
RLPStream s;
s.appendList(5 + 1).append((unsigned)0)
<< host->protocolVersion()
<< host->m_clientVersion
<< host->caps()
<< host->m_tcpPublic.port()
<< host->id();
bytes packet;
s.swapOut(packet);
io->writeSingleFramePacket(&packet, handshakeOutBuffer);
ba::async_write(*socket, ba::buffer(handshakeOutBuffer), [this, self](boost::system::error_code ec, std::size_t)
{
transition(ec);
});
}
else if (nextState == ReadHello)
{
// Authenticate and decrypt initial hello frame with initial RLPXFrameIO
// and request host to start session.
nextState = StartSession;
// read frame header
handshakeInBuffer.resize(h256::size);
ba::async_read(*socket, boost::asio::buffer(handshakeInBuffer, h256::size), [this,self](boost::system::error_code ec, std::size_t length)
{
clog(NetConnect) << "p2p.connect.ingress recving auth";
authCipher.resize(321);
ba::async_read(*socket, ba::buffer(authCipher, 321), [this, self](boost::system::error_code ec, std::size_t)
if (ec)
transition(ec);
else
{
if (ec)
transition(ec);
else if (decodeAuth())
transition();
else
/// authenticate and decrypt header
if (!io->authAndDecryptHeader(*(h256*)handshakeInBuffer.data()))
{
clog(NetWarn) << "p2p.connect.egress recving auth decrypt failed";
nextState = Error;
transition();
return;
}
});
}
}
else if (nextState == AckAuth)
{
nextState = Authenticating;
if (originated)
{
clog(NetConnect) << "p2p.connect.egress recving ack";
// egress: rx ack
ackCipher.resize(225);
ba::async_read(*socket, ba::buffer(ackCipher, 225), [this, self](boost::system::error_code ec, std::size_t)
{
if (ec)
transition(ec);
else
{
if (!decryptECIES(host->m_alias.sec(), bytesConstRef(&ackCipher), ack))
{
clog(NetWarn) << "p2p.connect.egress recving ack decrypt failed";
nextState = Error;
transition();
return;
}
clog(NetNote) << (originated ? "p2p.connect.egress" : "p2p.connect.ingress") << "recvd hello header";
bytesConstRef(&ack).cropped(0, Public::size).copyTo(remoteEphemeral.ref());
bytesConstRef(&ack).cropped(Public::size, h256::size).copyTo(remoteNonce.ref());
/// check frame size
bytes& header = handshakeInBuffer;
uint32_t frameSize = (uint32_t)(header[2]) | (uint32_t)(header[1])<<8 | (uint32_t)(header[0])<<16;
if (frameSize > 1024)
{
// all future frames: 16777216
clog(NetWarn) << (originated ? "p2p.connect.egress" : "p2p.connect.ingress") << "hello frame is too large";
nextState = Error;
transition();
return;
}
});
}
else
{
clog(NetConnect) << "p2p.connect.ingress sending ack";
// ingress: tx ack
ack.resize(Public::size + h256::size + 1);
bytesRef epubk(&ack[0], Public::size);
bytesRef nonce(&ack[Public::size], h256::size);
ecdhe.pubkey().ref().copyTo(epubk);
this->nonce.ref().copyTo(nonce);
ack[ack.size() - 1] = 0x0;
encryptECIES(remote, &ack, ackCipher);
ba::async_write(*socket, ba::buffer(ackCipher), [this, self](boost::system::error_code ec, std::size_t)
{
transition(ec);
});
}
}
else if (nextState == Authenticating)
{
if (originated)
clog(NetConnect) << "p2p.connect.egress sending magic sequence";
else
clog(NetConnect) << "p2p.connect.ingress sending magic sequence";
RLPXFrameIO* io = new RLPXFrameIO(*this);
// // This test will be replaced with protocol-capabilities information (was Hello packet)
// // TESTING: send encrypt magic sequence
// bytes magic {0x22,0x40,0x08,0x91};
// // rlpx encrypt
// encryptSymNoAuth(k->encryptK, &magic, k->magicCipherAndMac, h128());
// k->magicCipherAndMac.resize(k->magicCipherAndMac.size() + 32);
// sha3mac(k->egressMac.ref(), &magic, k->egressMac.ref());
// k->egressMac.ref().copyTo(bytesRef(&k->magicCipherAndMac).cropped(k->magicCipherAndMac.size() - 32, 32));
//
// clog(NetConnect) << "p2p.connect.egress txrx magic sequence";
// k->recvdMagicCipherAndMac.resize(k->magicCipherAndMac.size());
//
// ba::async_write(*socket, ba::buffer(k->magicCipherAndMac), [this, self, k, magic](boost::system::error_code ec, std::size_t)
// {
// if (ec)
// {
// delete k;
// transition(ec);
// return;
// }
//
// ba::async_read(*socket, ba::buffer(k->recvdMagicCipherAndMac, k->magicCipherAndMac.size()), [this, self, k, magic](boost::system::error_code ec, std::size_t)
// {
// if (originated)
// clog(NetNote) << "p2p.connect.egress recving magic sequence";
// else
// clog(NetNote) << "p2p.connect.ingress recving magic sequence";
//
// if (ec)
// {
// delete k;
// transition(ec);
// return;
// }
//
// /// capabilities handshake (encrypted magic sequence is placeholder)
// bytes decryptedMagic;
// decryptSymNoAuth(k->encryptK, h128(), &k->recvdMagicCipherAndMac, decryptedMagic);
// if (decryptedMagic[0] == 0x22 && decryptedMagic[1] == 0x40 && decryptedMagic[2] == 0x08 && decryptedMagic[3] == 0x91)
// {
shared_ptr<Peer> p;
p = host->m_peers[remote];
if (!p)
/// rlp of header has protocol-type, sequence-id[, total-packet-size]
bytes headerRLP(header.size() - 3 - h128::size);
bytesConstRef(&header).cropped(3).copyTo(&headerRLP);
/// read padded frame and mac
handshakeInBuffer.resize(frameSize + ((16 - (frameSize % 16)) % 16) + h128::size);
ba::async_read(*socket, boost::asio::buffer(handshakeInBuffer, handshakeInBuffer.size()), [this, self, headerRLP](boost::system::error_code ec, std::size_t length)
{
if (ec)
transition(ec);
else
{
p.reset(new Peer());
p->id = remote;
if (!io->authAndDecryptFrame(bytesRef(&handshakeInBuffer)))
{
clog(NetWarn) << (originated ? "p2p.connect.egress" : "p2p.connect.ingress") << "hello frame: decrypt failed";
nextState = Error;
transition();
return;
}
RLP rlp(handshakeInBuffer);
auto packetType = (PacketType)rlp[0].toInt<unsigned>();
if (packetType != 0)
{
clog(NetWarn) << (originated ? "p2p.connect.egress" : "p2p.connect.ingress") << "hello frame: invalid packet type";
nextState = Error;
transition();
return;
}
// todo: handover RLPFrameIO
host->startPeerSession(remote, rlp, socket);
}
p->endpoint.tcp.address(socket->remote_endpoint().address());
p->m_lastDisconnect = NoDisconnect;
p->m_lastConnected = std::chrono::system_clock::now();
p->m_failedAttempts = 0;
auto ps = std::make_shared<Session>(host, move(*socket), p);
ps->start();
// }
// todo: PeerSession will take ownership of k and use it to encrypt wireline.
delete io;
// });
// });
});
}
});
}
}

48
libp2p/RLPxHandshake.h

@ -40,21 +40,21 @@ class RLPXHandshake;
class RLPXFrameIO
{
public:
RLPXFrameIO(RLPXHandshake& _init);
RLPXFrameIO(RLPXHandshake const& _init);
void writeFullPacketFrame(bytesConstRef _packet);
void writeSingleFramePacket(bytesConstRef _packet, bytes& o_bytes);
void writeHeader(bi::tcp::socket* _socket, h128 const& _header);
/// Authenticates and decrypts header in-place.
bool authAndDecryptHeader(h256& io_cipherWithMac);
void write(bi::tcp::socket* _socket, bytesConstRef _in, bool _eof = false);
bool read(bytesConstRef _in, bytes& o_out);
/// Authenticates and decrypts frame in-place.
bool authAndDecryptFrame(bytesRef io_cipherWithMac);
h128 egressDigest();
h128 ingressDigest();
void updateEgressMACWithHeader(h128 const& _headerCipher);
void updateEgressMACWithHeader(bytesConstRef _headerCipher);
void updateEgressMACWithEndOfFrame(bytesConstRef _cipher);
@ -66,21 +66,28 @@ private:
void updateMAC(CryptoPP::SHA3_256& _mac, h128 const& _seed = h128());
CryptoPP::CTR_Mode<CryptoPP::AES>::Encryption m_frameEnc;
CryptoPP::CTR_Mode<CryptoPP::AES>::Encryption m_frameDec;
CryptoPP::ECB_Mode<CryptoPP::AES>::Encryption m_macEnc;
CryptoPP::SHA3_256 m_egressMac;
CryptoPP::SHA3_256 m_ingressMac;
bi::tcp::socket* m_socket;
};
struct RLPXHandshake: public std::enable_shared_from_this<RLPXHandshake>
// TODO: change properties to m_
class RLPXHandshake: public std::enable_shared_from_this<RLPXHandshake>
{
public:
friend class RLPXFrameIO;
friend class Host;
enum State
{
Error = -1,
New, // New->AckAuth [egress: tx auth, ingress: rx auth]
AckAuth, // AckAuth->Authenticating [egress: rx ack, ingress: tx ack]
Authenticating, // Authenticating [tx caps, rx caps, authenticate]
AckAuth, // AckAuth->WriteHello [egress: rx ack, ingress: tx ack]
WriteHello, // WriteHello [tx caps, rx caps, writehello]
ReadHello,
StartSession
};
/// Handshake for ingress connection. Takes ownership of socket.
@ -91,18 +98,16 @@ struct RLPXHandshake: public std::enable_shared_from_this<RLPXHandshake>
~RLPXHandshake() { delete socket; }
protected:
void start() { transition(); }
void generateAuth();
bool decodeAuth();
void generateAck();
bool decodeAck();
protected:
void writeAuth();
void readAuth();
bytes frame(bytesConstRef _packet);
void writeAck();
void readAck();
private:
void error();
void transition(boost::system::error_code _ech = boost::system::error_code());
/// Current state of handshake.
@ -121,12 +126,17 @@ private:
bytes authCipher;
bytes ack;
bytes ackCipher;
bytes handshakeOutBuffer;
bytes handshakeInBuffer;
crypto::ECDHE ecdhe;
h256 nonce;
Public remoteEphemeral;
h256 remoteNonce;
/// Frame IO is used to read frame for last step of handshake authentication.
std::unique_ptr<RLPXFrameIO> io;
};
}

94
libp2p/Session.cpp

@ -36,11 +36,11 @@ using namespace dev::p2p;
#endif
#define clogS(X) dev::LogOutputStream<X, true>(false) << "| " << std::setw(2) << m_socket.native_handle() << "] "
Session::Session(Host* _s, bi::tcp::socket _socket, std::shared_ptr<Peer> const& _n):
Session::Session(Host* _s, bi::tcp::socket _socket, std::shared_ptr<Peer> const& _n, PeerSessionInfo _info):
m_server(_s),
m_socket(std::move(_socket)),
m_socket(move(_socket)),
m_peer(_n),
m_info({NodeId(), "?", m_socket.remote_endpoint().address().to_string(), 0, chrono::steady_clock::duration(0), CapDescSet(), 0, map<string, string>()}),
m_info(_info),
m_ping(chrono::steady_clock::time_point::max())
{
m_lastReceived = m_connect = chrono::steady_clock::now();
@ -151,86 +151,6 @@ bool Session::interpret(RLP const& _r)
switch ((PacketType)_r[0].toInt<unsigned>())
{
case HelloPacket:
{
// TODO: P2P first pass, implement signatures. if signature fails, drop connection. if egress, flag node's endpoint as stale.
// Move auth to Host so we consolidate authentication logic and eschew peer deduplication logic.
// Move all node-lifecycle information into Host.
// Finalize peer-lifecycle properties vs node lifecycle.
m_protocolVersion = _r[1].toInt<unsigned>();
auto clientVersion = _r[2].toString();
auto caps = _r[3].toVector<CapDesc>();
auto listenPort = _r[4].toInt<unsigned short>();
auto id = _r[5].toHash<NodeId>();
// clang error (previously: ... << hex << caps ...)
// "'operator<<' should be declared prior to the call site or in an associated namespace of one of its arguments"
stringstream capslog;
for (auto cap: caps)
capslog << "(" << cap.first << "," << dec << cap.second << ")";
clogS(NetMessageSummary) << "Hello: " << clientVersion << "V[" << m_protocolVersion << "]" << id.abridged() << showbase << capslog.str() << dec << listenPort;
if (m_server->id() == id)
{
// Already connected.
clogS(NetWarn) << "Connected to ourself under a false pretext. We were told this peer was id" << id.abridged();
disconnect(LocalIdentity);
return true;
}
// if peer and connection have id, check for UnexpectedIdentity
if (!id)
{
disconnect(NullIdentity);
return true;
}
else if (!m_peer->id)
{
m_peer->id = id;
m_peer->endpoint.tcp.port(listenPort);
}
else if (m_peer->id != id)
{
// TODO p2p: FIXME. Host should catch this and reattempt adding node to table.
m_peer->id = id;
m_peer->m_score = 0;
m_peer->m_rating = 0;
// disconnect(UnexpectedIdentity);
// return true;
}
if (m_server->havePeerSession(id))
{
// Already connected.
clogS(NetWarn) << "Already connected to a peer with id" << id.abridged();
// Possible that two nodes continually connect to each other with exact same timing.
this_thread::sleep_for(chrono::milliseconds(rand() % 100));
disconnect(DuplicatePeer);
return true;
}
if (m_peer->isOffline())
m_peer->m_lastConnected = chrono::system_clock::now();
if (m_protocolVersion != m_server->protocolVersion())
{
disconnect(IncompatibleProtocol);
return true;
}
m_info.clientVersion = clientVersion;
m_info.host = m_socket.remote_endpoint().address().to_string();
m_info.port = listenPort;
m_info.lastPing = std::chrono::steady_clock::duration();
m_info.caps = _r[3].toSet<CapDesc>();
m_info.socket = (unsigned)m_socket.native_handle();
m_info.notes = map<string, string>();
m_server->registerPeer(shared_from_this(), caps);
break;
}
case DisconnectPacket:
{
string reason = "Unspecified";
@ -478,14 +398,6 @@ void Session::disconnect(DisconnectReason _reason)
void Session::start()
{
RLPStream s;
prep(s, HelloPacket, 5)
<< m_server->protocolVersion()
<< m_server->m_clientVersion
<< m_server->caps()
<< m_server->m_tcpPublic.port()
<< m_server->id();
sealAndSend(s);
ping();
doRead();
}

2
libp2p/Session.h

@ -51,7 +51,7 @@ class Session: public std::enable_shared_from_this<Session>
friend class HostCapabilityFace;
public:
Session(Host* _server, bi::tcp::socket _socket, std::shared_ptr<Peer> const& _n);
Session(Host* _server, bi::tcp::socket _socket, std::shared_ptr<Peer> const& _n, PeerSessionInfo _info);
virtual ~Session();
void start();

2
test/rlpx.cpp

@ -141,7 +141,7 @@ BOOST_AUTO_TEST_CASE(test_secrets_cpp_vectors)
bytes macSecret(32);
outRef.copyTo(&macSecret);
BOOST_REQUIRE(macSecret == fromHex("2ec149072353d54437422837c886b0538a9206e6c559f6b4a55f65a866867723"));
m_macEnc.SetKey(outRef.data(), h256::size);
m_macEnc.SetKey(outRef.data(), h128::size);
// Initiator egress-mac: sha3(mac-secret^recipient-nonce || auth-sent-init)
// ingress-mac: sha3(mac-secret^initiator-nonce || auth-recvd-ack)

Loading…
Cancel
Save