diff --git a/src/ConnectionPool.cpp b/src/ConnectionPool.cpp index 1b87e15ea..22c154f3e 100644 --- a/src/ConnectionPool.cpp +++ b/src/ConnectionPool.cpp @@ -142,7 +142,7 @@ bool ConnectionPool::peerConnected(Peer::pointer peer, const NewcoinAddress& na) void ConnectionPool::peerDisconnected(Peer::pointer peer, const ipPort& ipPeer, const NewcoinAddress& naPeer) { - std::cerr << "ConnectionPool::peerDisconnected: " << peer->mIpPort.first << " " << peer->mIpPort.second << std::endl; + std::cerr << "ConnectionPool::peerDisconnected: " << ipPeer.first << " " << ipPeer.second << std::endl; boost::mutex::scoped_lock sl(mPeerLock); diff --git a/src/NewcoinAddress.cpp b/src/NewcoinAddress.cpp index 366baf852..47b064baf 100644 --- a/src/NewcoinAddress.cpp +++ b/src/NewcoinAddress.cpp @@ -137,10 +137,49 @@ void NewcoinAddress::setNodePublic(const std::vector& vPublic) SetData(VER_NODE_PUBLIC, vPublic); } +bool NewcoinAddress::verifyNodePublic(const uint256& hash, const std::vector& vchSig) const +{ + CKey pubkey = CKey(); + bool bVerified; + + if (!pubkey.SetPubKey(getNodePublic())) + { + // Failed to set public key. + bVerified = false; + } + else + { + bVerified = pubkey.Verify(hash, vchSig); + } + + return bVerified; +} + +bool NewcoinAddress::verifyNodePublic(const uint256& hash, const std::string& strSig) const +{ + std::vector vchSig(strSig.begin(), strSig.end()); + + return verifyNodePublic(hash, vchSig); +} + // // NodePrivate // +const std::vector& NewcoinAddress::getNodePrivateData() const +{ + switch (nVersion) { + case VER_NONE: + throw std::runtime_error("unset source"); + + case VER_NODE_PRIVATE: + return vchData; + + default: + throw std::runtime_error(str(boost::format("bad source: %d") % int(nVersion))); + } +} + uint256 NewcoinAddress::getNodePrivate() const { switch (nVersion) { @@ -183,6 +222,17 @@ void NewcoinAddress::setNodePrivate(uint256 hash256) SetData(VER_NODE_PRIVATE, hash256.begin(), 32); } +void NewcoinAddress::signNodePrivate(const uint256& hash, std::vector& vchSig) const +{ + CKey privkey = CKey(); + + if (!privkey.SetSecret(getNodePrivateData())) + throw std::runtime_error("SetSecret failed."); + + if (!privkey.Sign(hash, vchSig)) + throw std::runtime_error("Signing failed."); +} + // // AccountID // diff --git a/src/NewcoinAddress.h b/src/NewcoinAddress.h index 6b696d093..e07d510f6 100644 --- a/src/NewcoinAddress.h +++ b/src/NewcoinAddress.h @@ -50,10 +50,13 @@ public: bool setNodePublic(const std::string& strPublic); void setNodePublic(const std::vector& vPublic); + bool verifyNodePublic(const uint256& hash, const std::vector& vchSig) const; + bool verifyNodePublic(const uint256& hash, const std::string& strSig) const; // // Node Private // + const std::vector& getNodePrivateData() const; uint256 getNodePrivate() const; std::string humanNodePrivate() const; @@ -61,6 +64,7 @@ public: bool setNodePrivate(const std::string& strPrivate); void setNodePrivate(const std::vector& vPrivate); void setNodePrivate(uint256 hash256); + void signNodePrivate(const uint256& hash, std::vector& vchSig) const; // // Accounts IDs diff --git a/src/Peer.cpp b/src/Peer.cpp index e65dc147b..9d6f612ec 100644 --- a/src/Peer.cpp +++ b/src/Peer.cpp @@ -16,8 +16,12 @@ #include "SerializedTransaction.h" #include "utils.h" +// Node has this long to verify its identity from connection accepted or connection attempt. +#define NODE_VERIFY_SECONDS 15 + Peer::Peer(boost::asio::io_service& io_service, boost::asio::ssl::context& ctx) - : mSocketSsl(io_service, ctx) + : mSocketSsl(io_service, ctx), + mVerifyTimer(io_service) { } @@ -51,6 +55,10 @@ void Peer::handle_write(const boost::system::error_code& error, size_t bytes_tra void Peer::detach() { + boost::system::error_code ecCancel; + + (void) mVerifyTimer.cancel(); + mSendQ.clear(); // mSocketSsl.close(); @@ -60,6 +68,29 @@ void Peer::detach() } } +void Peer::handleVerifyTimer(const boost::system::error_code& ecResult) +{ + if (ecResult == boost::asio::error::operation_aborted) + { + // Timer canceled because deadline no longer needed. + // std::cerr << "Deadline cancelled." << std::endl; + + nothing(); // Aborter is done. + } + else if (ecResult) + { + std::cerr << "Peer verify timer error: " << std::endl; + + // Can't do anything sound. + abort(); + } + else + { + std::cerr << "Peer failed to verify in time." << std::endl; + detach(); + } +} + // Begin trying to connect. We are not connected till we know and accept peer's public key. // Only takes IP addresses (not domains). void Peer::connect(const std::string strIp, int iPort) @@ -78,10 +109,21 @@ void Peer::connect(const std::string strIp, int iPort) if (err || itrEndpoint == boost::asio::ip::tcp::resolver::iterator()) { std::cerr << "Peer::connect: Bad IP" << std::endl; - // Failed to resolve ip. detach(); } else + { + mVerifyTimer.expires_from_now(boost::posix_time::seconds(NODE_VERIFY_SECONDS), err); + mVerifyTimer.async_wait(boost::bind(&Peer::handleVerifyTimer, shared_from_this(), boost::asio::placeholders::error)); + + if (err) + { + std::cerr << "Peer::connect: Failed to set timer." << std::endl; + detach(); + } + } + + if (!err) { std::cerr << "Peer::connect: Connectting: " << mIpPort.first << " " << mIpPort.second << std::endl; @@ -99,7 +141,7 @@ void Peer::connect(const std::string strIp, int iPort) // We have an ecrypted connection to the peer. // Have it say who it is so we know to avoid redundant connections. // Establish that it really who we are talking to by having it sign a connection detail. -// XXX Also need to establish no man in the middle attack is in progress. +// Also need to establish no man in the middle attack is in progress. void Peer::handleStart(const boost::system::error_code& error) { if (error) @@ -130,9 +172,7 @@ void Peer::handleConnect(const boost::system::error_code& error, boost::asio::ip mSocketSsl.set_verify_mode(boost::asio::ssl::verify_none); mSocketSsl.async_handshake(boost::asio::ssl::stream::client, - boost::bind(&Peer::handleStart, - shared_from_this(), - boost::asio::placeholders::error)); + boost::bind(&Peer::handleStart, shared_from_this(), boost::asio::placeholders::error)); } } @@ -172,9 +212,7 @@ void Peer::connected(const boost::system::error_code& error) mSocketSsl.set_verify_mode(boost::asio::ssl::verify_none); mSocketSsl.async_handshake(boost::asio::ssl::stream::server, - boost::bind(&Peer::handleStart, - shared_from_this(), - boost::asio::placeholders::error)); + boost::bind(&Peer::handleStart, shared_from_this(), boost::asio::placeholders::error)); } } @@ -445,14 +483,15 @@ void Peer::recvHello(newcoin::TMHello& packet) #endif bool bDetach = true; - if (mNodePublic.isValid()) - { - std::cerr << "Recv(Hello): Disconnect: Extraneous node public key." << std::endl; - } - else if (!mNodePublic.setNodePublic(packet.nodepublic())) + if (!mNodePublic.setNodePublic(packet.nodepublic())) { std::cerr << "Recv(Hello): Disconnect: Bad node public key." << std::endl; } + else if (!mNodePublic.verifyNodePublic(mCookieHash, packet.nodeproof())) + { + // Unable to verify they have private key for claimed public key. + std::cerr << "Recv(Hello): Disconnect: Failed to verify session." << std::endl; + } else if (!theApp->getConnectionPool().peerConnected(shared_from_this(), mNodePublic)) { // Already connected, self, or some other reason. @@ -461,8 +500,13 @@ void Peer::recvHello(newcoin::TMHello& packet) else { // Successful connection. - // XXX Kill hello timer. + + // Cancel verification timeout. + (void) mVerifyTimer.cancel(); + // XXX Set timer: connection is in grace period to be useful. + // XXX Set timer: connection idle (idle may vary depending on connection type.) + bDetach = false; } @@ -665,36 +709,50 @@ void Peer::recvLedger(newcoin::TMLedgerData& packet) punishPeer(PP_UNWANTED_DATA); } -std::vector Peer::getSessionCookie() +// Get session information we can sign to prevent man in the middle attack. +// (both sides get the same information, neither side controls it) +void Peer::getSessionCookie(std::string& strDst) { - // get session information we can sign - // (both sides get the same information, neither side controls it) SSL* ssl = mSocketSsl.native_handle(); if (!ssl) throw std::runtime_error("No underlying connection"); // Get both finished messages unsigned char s1[1024], s2[1024]; - int l1 = SSL_get_finished(ssl, s1, 1024); - int l2 = SSL_get_finished(ssl, s2, 1024); - if ((l1 < 16) || (l2 < 16)) throw std::runtime_error("Connection setup not complete"); + int l1 = SSL_get_finished(ssl, s1, sizeof(s1)); + int l2 = SSL_get_peer_finished(ssl, s2, sizeof(s2)); + + if ((l1 < 12) || (l2 < 12)) + throw std::runtime_error(str(boost::format("Connection setup not complete: %d %d") % l1 % l2)); // Hash them and XOR the results - unsigned char sha1[32], sha2[32]; + unsigned char sha1[64], sha2[64]; + SHA512(s1, l1, sha1); SHA512(s2, l2, sha2); - for(int i=0; i<32; i++) sha1[i]^=sha2[i]; - return std::vector(sha1, sha1+33); + + for (int i=0; i vchSig; + + getSessionCookie(strCookie); + mCookieHash = Serializer::getSHA512Half(strCookie); + + theApp->getWallet().getNodePrivate().signNodePrivate(mCookieHash, vchSig); + newcoin::TMHello* h=new newcoin::TMHello(); - // set up parameters + h->set_version(theConfig.VERSION); h->set_ledgerindex(theApp->getOPs().getCurrentLedgerID()); h->set_nettime(theApp->getOPs().getNetworkTime()); h->set_nodepublic(theApp->getWallet().getNodePublic().humanNodePublic()); + h->set_nodeproof(&vchSig[0], vchSig.size()); h->set_ipv4port(theConfig.PEER_PORT); Ledger::pointer closingLedger=theApp->getMasterLedger().getClosingLedger(); diff --git a/src/Peer.h b/src/Peer.h index d8ac6e087..1c4240934 100644 --- a/src/Peer.h +++ b/src/Peer.h @@ -28,15 +28,19 @@ public: static const int psbGotHello=0, psbSentHello=1, psbInMap=2, psbTrusted=3; static const int psbNoLedgers=4, psbNoTransactions=5, psbDownLevel=6; - NewcoinAddress mNodePublic; // Node public key of peer. - ipPort mIpPort; - void handleConnect(const boost::system::error_code& error, boost::asio::ip::tcp::resolver::iterator it); private: + NewcoinAddress mNodePublic; // Node public key of peer. + ipPort mIpPort; + uint256 mCookieHash; + boost::asio::ssl::stream mSocketSsl; - void handleStart(const boost::system::error_code& error); + boost::asio::deadline_timer mVerifyTimer; + + void handleStart(const boost::system::error_code& ecResult); + void handleVerifyTimer(const boost::system::error_code& ecResult); protected: @@ -78,7 +82,7 @@ protected: void recvGetLedger(newcoin::TMGetLedger& packet); void recvLedger(newcoin::TMLedgerData& packet); - std::vector getSessionCookie(); + void getSessionCookie(std::string& strDst); public: typedef boost::shared_ptr pointer; diff --git a/src/Wallet.h b/src/Wallet.h index 5e22547ea..8730fa798 100644 --- a/src/Wallet.h +++ b/src/Wallet.h @@ -51,6 +51,7 @@ public: void start(); NewcoinAddress& getNodePublic() { return mNodePublicKey; } + NewcoinAddress& getNodePrivate() { return mNodePrivateKey; } NewcoinAddress addFamily(const std::string& passPhrase, bool lock); NewcoinAddress addFamily(const NewcoinAddress& familySeed, bool lock); diff --git a/src/newcoin.proto b/src/newcoin.proto index 538f0f98f..ab67b2571 100644 --- a/src/newcoin.proto +++ b/src/newcoin.proto @@ -38,9 +38,10 @@ message TMHello { required uint32 version = 1; optional uint32 ledgerIndex = 2; optional uint64 netTime = 3; - optional bytes nodePublic = 4; // node may opt to remain anonymous - optional uint32 ipv4Port = 5; - optional bytes closedLedger = 6; + optional bytes nodePublic = 4; // node may opt to remain anonymous + optional bytes nodeProof = 5; + optional uint32 ipv4Port = 6; + optional bytes closedLedger = 7; } diff --git a/src/utils.cpp b/src/utils.cpp index 9e65a3101..1c454d004 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -19,4 +19,9 @@ boost::posix_time::ptime ptFromSeconds(int iSeconds) : ptEpoch() + boost::posix_time::seconds(iSeconds); } +char charHex(int iDigit) +{ + return iDigit < 10 ? '0' + iDigit : 'A' - 10 + iDigit; +} + // vim:ts=4 diff --git a/src/utils.h b/src/utils.h index 8ec8ffff8..dc6ba8e87 100644 --- a/src/utils.h +++ b/src/utils.h @@ -22,6 +22,30 @@ std::string strJoin(Iterator first, Iterator last, std::string strSeperator) return ossValues.str(); } + +char charHex(int iDigit); + +template +void strHex(std::string& strDst, Iterator first, int iSize) +{ + strDst.resize(iSize*2); + + for (int i = 0; i < iSize; i++) { + unsigned char c = *first++; + + strDst[i*2] = charHex(c >> 4); + strDst[i*2+1] = charHex(c & 15); + } +} + +inline void strHex(std::string& strDst, const std::string& strSrc) { + strHex(strDst, strSrc.begin(), strSrc.size()); +} + +inline void strHex(std::string& strDst, const std::vector vchData) { + strHex(strDst, vchData.begin(), vchData.size()); +} + #endif // vim:ts=4