diff --git a/libdevcore/vector_ref.h b/libdevcore/vector_ref.h index 2c5f07e51..4ecc1cbd1 100644 --- a/libdevcore/vector_ref.h +++ b/libdevcore/vector_ref.h @@ -40,7 +40,7 @@ public: bool empty() const { return !m_count; } vector_ref<_T> next() const { return vector_ref<_T>(m_data + m_count, m_count); } vector_ref<_T> cropped(size_t _begin, size_t _count = ~size_t(0)) const { if (m_data && _begin + std::max(size_t(0), _count) <= m_count) return vector_ref<_T>(m_data + _begin, _count == ~size_t(0) ? m_count - _begin : _count); else return vector_ref<_T>(); } - void retarget(_T const* _d, size_t _s) { m_data = _d; m_count = _s; } + void retarget(_T* _d, size_t _s) { m_data = _d; m_count = _s; } void retarget(std::vector<_T> const& _t) { m_data = _t.data(); m_count = _t.size(); } void copyTo(vector_ref::type> _t) const { memcpy(_t.data(), m_data, std::min(_t.size(), m_count) * sizeof(_T)); } void populate(vector_ref::type> _t) const { copyTo(_t); memset(_t.data() + m_count, 0, std::max(_t.size(), m_count) - m_count); } diff --git a/libdevcrypto/Common.cpp b/libdevcrypto/Common.cpp index 048134de8..e108b230f 100644 --- a/libdevcrypto/Common.cpp +++ b/libdevcrypto/Common.cpp @@ -15,8 +15,8 @@ along with cpp-ethereum. If not, see . */ /** @file Common.cpp - * @author Gav Wood * @author Alex Leverington + * @author Gav Wood * @date 2014 */ @@ -82,6 +82,22 @@ bool dev::decrypt(Secret const& _k, bytesConstRef _cipher, bytes& o_plaintext) return true; } +void dev::encryptECIES(Public const& _k, bytesConstRef _plain, bytes& o_cipher) +{ + bytes io = _plain.toBytes(); + s_secp256k1.encryptECIES(_k, io); + o_cipher = std::move(io); +} + +bool dev::decryptECIES(Secret const& _k, bytesConstRef _cipher, bytes& o_plaintext) +{ + bytes io = _cipher.toBytes(); + if (!s_secp256k1.decryptECIES(_k, io)) + return false; + o_plaintext = std::move(io); + return true; +} + void dev::encryptSym(Secret const& _k, bytesConstRef _plain, bytes& o_cipher) { // TOOD: @alex @subtly do this properly. @@ -94,6 +110,54 @@ bool dev::decryptSym(Secret const& _k, bytesConstRef _cipher, bytes& o_plain) return decrypt(_k, _cipher, o_plain); } +h128 dev::encryptSymNoAuth(Secret const& _k, bytesConstRef _plain, bytes& o_cipher) +{ + h128 iv(Nonce::get()); + return encryptSymNoAuth(_k, _plain, o_cipher, iv); +} + +h128 dev::encryptSymNoAuth(Secret const& _k, bytesConstRef _plain, bytes& o_cipher, h128 const& _iv) +{ + o_cipher.resize(_plain.size()); + + const int c_aesKeyLen = 16; + SecByteBlock key(_k.data(), c_aesKeyLen); + try + { + CTR_Mode::Encryption e; + e.SetKeyWithIV(key, key.size(), _iv.data()); + e.ProcessData(o_cipher.data(), _plain.data(), _plain.size()); + return _iv; + } + catch (CryptoPP::Exception& _e) + { + cerr << _e.what() << endl; + o_cipher.resize(0); + return h128(); + } +} + +bool dev::decryptSymNoAuth(Secret const& _k, h128 const& _iv, bytesConstRef _cipher, bytes& o_plaintext) +{ + o_plaintext.resize(_cipher.size()); + + const size_t c_aesKeyLen = 16; + SecByteBlock key(_k.data(), c_aesKeyLen); + try + { + CTR_Mode::Decryption d; + d.SetKeyWithIV(key, key.size(), _iv.data()); + d.ProcessData(o_plaintext.data(), _cipher.data(), _cipher.size()); + return true; + } + catch (CryptoPP::Exception& _e) + { + cerr << _e.what() << endl; + o_plaintext.resize(0); + return false; + } +} + Public dev::recover(Signature const& _sig, h256 const& _message) { return s_secp256k1.recover(_sig, _message.ref()); diff --git a/libdevcrypto/Common.h b/libdevcrypto/Common.h index ccbd0953b..38e5649fb 100644 --- a/libdevcrypto/Common.h +++ b/libdevcrypto/Common.h @@ -15,8 +15,8 @@ along with cpp-ethereum. If not, see . */ /** @file Common.h - * @author Gav Wood * @author Alex Leverington + * @author Gav Wood * @date 2014 * * Ethereum-specific data structures & algorithms. @@ -96,6 +96,21 @@ void encryptSym(Secret const& _k, bytesConstRef _plain, bytes& o_cipher); /// Symmetric decryption. bool decryptSym(Secret const& _k, bytesConstRef _cipher, bytes& o_plaintext); +/// Encrypt payload using ECIES standard with AES128-CTR. +void encryptECIES(Public const& _k, bytesConstRef _plain, bytes& o_cipher); + +/// Decrypt payload using ECIES standard with AES128-CTR. +bool decryptECIES(Secret const& _k, bytesConstRef _cipher, bytes& o_plaintext); + +/// Encrypts payload with random IV/ctr using AES128-CTR. +h128 encryptSymNoAuth(Secret const& _k, bytesConstRef _plain, bytes& o_cipher); + +/// Encrypts payload with specified IV/ctr using AES128-CTR. +h128 encryptSymNoAuth(Secret const& _k, bytesConstRef _plain, bytes& o_cipher, h128 const& _iv); + +/// Decrypts payload with specified IV/ctr. +bool decryptSymNoAuth(Secret const& _k, h128 const& _iv, bytesConstRef _cipher, bytes& o_plaintext); + /// Recovers Public key from signed message hash. Public recover(Signature const& _sig, h256 const& _hash); diff --git a/libdevcrypto/CryptoPP.cpp b/libdevcrypto/CryptoPP.cpp index 43993e0c5..11e4c1472 100644 --- a/libdevcrypto/CryptoPP.cpp +++ b/libdevcrypto/CryptoPP.cpp @@ -19,8 +19,9 @@ * @date 2014 */ -#include "CryptoPP.h" #include +#include "ECDHE.h" +#include "CryptoPP.h" using namespace std; using namespace dev; @@ -31,6 +32,119 @@ static_assert(dev::Secret::size == 32, "Secret key must be 32 bytes."); static_assert(dev::Public::size == 64, "Public key must be 64 bytes."); static_assert(dev::Signature::size == 65, "Signature must be 65 bytes."); +bytes Secp256k1::eciesKDF(Secret _z, bytes _s1, unsigned kdByteLen) +{ + // interop w/go ecies implementation + + // for sha3, blocksize is 136 bytes + // for sha256, blocksize is 64 bytes + auto reps = ((kdByteLen + 7) * 8) / (64 * 8); + bytes ctr({0, 0, 0, 1}); + bytes k; + CryptoPP::SHA256 ctx; + for (unsigned i = 0; i <= reps; i++) + { + ctx.Update(ctr.data(), ctr.size()); + ctx.Update(_z.data(), Secret::size); + ctx.Update(_s1.data(), _s1.size()); + // append hash to k + bytes digest(32); + ctx.Final(digest.data()); + ctx.Restart(); + + k.reserve(k.size() + h256::size); + move(digest.begin(), digest.end(), back_inserter(k)); + + if (++ctr[3] || ++ctr[2] || ++ctr[1] || ++ctr[0]) + continue; + } + + k.resize(kdByteLen); + return move(k); +} + +void Secp256k1::encryptECIES(Public const& _k, bytes& io_cipher) +{ + // interop w/go ecies implementation + auto r = KeyPair::create(); + h256 z; + ecdh::agree(r.sec(), _k, z); + auto key = eciesKDF(z, bytes(), 32); + bytesConstRef eKey = bytesConstRef(&key).cropped(0, 16); + bytesRef mKeyMaterial = bytesRef(&key).cropped(16, 16); + CryptoPP::SHA256 ctx; + ctx.Update(mKeyMaterial.data(), mKeyMaterial.size()); + bytes mKey(32); + ctx.Final(mKey.data()); + + bytes cipherText; + encryptSymNoAuth(*(Secret*)eKey.data(), bytesConstRef(&io_cipher), cipherText, h128()); + if (cipherText.empty()) + return; + + bytes msg(1 + Public::size + h128::size + cipherText.size() + 32); + msg[0] = 0x04; + r.pub().ref().copyTo(bytesRef(&msg).cropped(1, Public::size)); + bytesRef msgCipherRef = bytesRef(&msg).cropped(1 + Public::size + h128::size, cipherText.size()); + bytesConstRef(&cipherText).copyTo(msgCipherRef); + + // tag message + CryptoPP::HMAC hmacctx(mKey.data(), mKey.size()); + bytesConstRef cipherWithIV = bytesRef(&msg).cropped(1 + Public::size, h128::size + cipherText.size()); + hmacctx.Update(cipherWithIV.data(), cipherWithIV.size()); + hmacctx.Final(msg.data() + 1 + Public::size + cipherWithIV.size()); + + io_cipher.resize(msg.size()); + io_cipher.swap(msg); +} + +bool Secp256k1::decryptECIES(Secret const& _k, bytes& io_text) +{ + // interop w/go ecies implementation + + // io_cipher[0] must be 2, 3, or 4, else invalidpublickey + if (io_text[0] < 2 || io_text[0] > 4) + // invalid message: publickey + return false; + + if (io_text.size() < (1 + Public::size + h128::size + 1 + h256::size)) + // invalid message: length + return false; + + h256 z; + ecdh::agree(_k, *(Public*)(io_text.data()+1), z); + auto key = eciesKDF(z, bytes(), 64); + bytesConstRef eKey = bytesConstRef(&key).cropped(0, 16); + bytesRef mKeyMaterial = bytesRef(&key).cropped(16, 16); + bytes mKey(32); + CryptoPP::SHA256 ctx; + ctx.Update(mKeyMaterial.data(), mKeyMaterial.size()); + ctx.Final(mKey.data()); + + bytes plain; + size_t cipherLen = io_text.size() - 1 - Public::size - h128::size - h256::size; + bytesConstRef cipherWithIV(io_text.data() + 1 + Public::size, h128::size + cipherLen); + bytesConstRef cipherIV = cipherWithIV.cropped(0, h128::size); + bytesConstRef cipherNoIV = cipherWithIV.cropped(h128::size, cipherLen); + bytesConstRef msgMac(cipherNoIV.data() + cipherLen, h256::size); + h128 iv(cipherIV.toBytes()); + + // verify tag + CryptoPP::HMAC hmacctx(mKey.data(), mKey.size()); + hmacctx.Update(cipherWithIV.data(), cipherWithIV.size()); + h256 mac; + hmacctx.Final(mac.data()); + for (unsigned i = 0; i < h256::size; i++) + if (mac[i] != msgMac[i]) + return false; + + decryptSymNoAuth(*(Secret*)eKey.data(), iv, cipherNoIV, plain); + io_text.resize(plain.size()); + io_text.swap(plain); + + return true; +} + void Secp256k1::encrypt(Public const& _k, bytes& io_cipher) { ECIES::Encryptor e; @@ -199,13 +313,14 @@ bool Secp256k1::verifySecret(Secret const& _s, Public& _p) void Secp256k1::agree(Secret const& _s, Public const& _r, h256& o_s) { - (void)o_s; - (void)_s; - ECDH::Domain d(m_oid); + // TODO: mutex ASN1::secp256k1() singleton + // Creating Domain is non-const for m_oid and m_oid is not thread-safe + ECDH::Domain d(ASN1::secp256k1()); assert(d.AgreedValueLength() == sizeof(o_s)); byte remote[65] = {0x04}; memcpy(&remote[1], _r.data(), 64); - assert(d.Agree(o_s.data(), _s.data(), remote)); + bool result = d.Agree(o_s.data(), _s.data(), remote); + assert(result); } void Secp256k1::exportPublicKey(CryptoPP::DL_PublicKey_EC const& _k, Public& o_p) diff --git a/libdevcrypto/CryptoPP.h b/libdevcrypto/CryptoPP.h index fa9d92aa1..4991e3713 100644 --- a/libdevcrypto/CryptoPP.h +++ b/libdevcrypto/CryptoPP.h @@ -65,6 +65,7 @@ inline Integer secretToExponent(Secret const& _s) { return std::move(Integer(_s. /** * CryptoPP secp256k1 algorithms. + * @todo Collect ECIES methods into class. */ class Secp256k1 { @@ -75,12 +76,21 @@ public: void toPublic(Secret const& _s, Public& o_public) { exponentToPublic(Integer(_s.data(), sizeof(_s)), o_public); } - /// Encrypts text (replace input). + /// Encrypts text (replace input). (ECIES w/XOR-SHA1) void encrypt(Public const& _k, bytes& io_cipher); - /// Decrypts text (replace input). + /// Decrypts text (replace input). (ECIES w/XOR-SHA1) void decrypt(Secret const& _k, bytes& io_text); + /// Encrypts text (replace input). (ECIES w/AES128-CTR-SHA256) + void encryptECIES(Public const& _k, bytes& io_cipher); + + /// Decrypts text (replace input). (ECIES w/AES128-CTR-SHA256) + bool decryptECIES(Secret const& _k, bytes& io_text); + + /// Key derivation function used by encryptECIES and decryptECIES. + bytes eciesKDF(Secret _z, bytes _s1, unsigned kdBitLen = 256); + /// @returns siganture of message. Signature sign(Secret const& _k, bytesConstRef _message); diff --git a/libdevcrypto/ECDHE.cpp b/libdevcrypto/ECDHE.cpp index deae3bc6d..a00a92872 100644 --- a/libdevcrypto/ECDHE.cpp +++ b/libdevcrypto/ECDHE.cpp @@ -29,7 +29,12 @@ using namespace dev::crypto; static Secp256k1 s_secp256k1; -void ECDHE::agree(Public const& _remote, Secret& o_sharedSecret) +void dev::crypto::ecdh::agree(Secret const& _s, Public const& _r, h256& o_s) +{ + s_secp256k1.agree(_s, _r, o_s); +} + +void ECDHE::agree(Public const& _remote, Secret& o_sharedSecret) const { if (m_remoteEphemeral) // agreement can only occur once diff --git a/libdevcrypto/ECDHE.h b/libdevcrypto/ECDHE.h index 4450aec4b..d3c9ae325 100644 --- a/libdevcrypto/ECDHE.h +++ b/libdevcrypto/ECDHE.h @@ -48,6 +48,11 @@ private: std::map m_sessions; Secret m_secret; }; + +namespace ecdh +{ +void agree(Secret const& _s, Public const& _r, h256& o_s); +} /** * @brief Derive DH shared secret from EC keypairs. @@ -62,12 +67,14 @@ public: /// Public key sent to remote. Public pubkey() { return m_ephemeral.pub(); } + Secret seckey() { return m_ephemeral.sec(); } + /// Input public key for dh agreement, output generated shared secret. - void agree(Public const& _remoteEphemeral, Secret& o_sharedSecret); + void agree(Public const& _remoteEphemeral, Secret& o_sharedSecret) const; protected: - KeyPair m_ephemeral; ///< Ephemeral keypair; generated. - Public m_remoteEphemeral; ///< Public key of remote; parameter. + KeyPair m_ephemeral; ///< Ephemeral keypair; generated. + mutable Public m_remoteEphemeral; ///< Public key of remote; parameter. Set once when agree is called, otherwise immutable. }; /** @@ -80,10 +87,10 @@ class ECDHEKeyExchange: private ECDHE { public: /// Exchange with unknown remote (pass public key for ingress exchange) - ECDHEKeyExchange(Alias& _k): m_alias(_k) {}; + ECDHEKeyExchange(Alias& _k): m_alias(_k) {} /// Exchange with known remote - ECDHEKeyExchange(Alias& _k, AliasSession _known): m_alias(_k), m_known(_known) {}; + ECDHEKeyExchange(Alias& _k, AliasSession _known): m_alias(_k), m_known(_known) {} /// Provide public key for dh agreement to generate shared secret. void agree(Public const& _remoteEphemeral); diff --git a/libethereum/EthereumPeer.cpp b/libethereum/EthereumPeer.cpp index 95e1aadda..58cc5ca72 100644 --- a/libethereum/EthereumPeer.cpp +++ b/libethereum/EthereumPeer.cpp @@ -297,13 +297,13 @@ bool EthereumPeer::interpret(unsigned _id, RLP const& _r) { case StatusPacket: { - m_protocolVersion = _r[1].toInt(); - m_networkId = _r[2].toInt(); + m_protocolVersion = _r[0].toInt(); + m_networkId = _r[1].toInt(); // a bit dirty as we're misusing these to communicate the values to transition, but harmless. - m_totalDifficulty = _r[3].toInt(); - m_latestHash = _r[4].toHash(); - auto genesisHash = _r[5].toHash(); + m_totalDifficulty = _r[2].toInt(); + m_latestHash = _r[3].toHash(); + auto genesisHash = _r[4].toHash(); clogS(NetMessageSummary) << "Status:" << m_protocolVersion << "/" << m_networkId << "/" << genesisHash.abridged() << ", TD:" << m_totalDifficulty << "=" << m_latestHash.abridged(); @@ -324,10 +324,10 @@ bool EthereumPeer::interpret(unsigned _id, RLP const& _r) case GetTransactionsPacket: break; // DEPRECATED. case TransactionsPacket: { - clogS(NetMessageSummary) << "Transactions (" << dec << (_r.itemCount() - 1) << "entries)"; - addRating(_r.itemCount() - 1); + clogS(NetMessageSummary) << "Transactions (" << dec << _r.itemCount() << "entries)"; + addRating(_r.itemCount()); Guard l(x_knownTransactions); - for (unsigned i = 1; i < _r.itemCount(); ++i) + for (unsigned i = 0; i < _r.itemCount(); ++i) { auto h = sha3(_r[i].data()); m_knownTransactions.insert(h); @@ -339,8 +339,8 @@ bool EthereumPeer::interpret(unsigned _id, RLP const& _r) } case GetBlockHashesPacket: { - h256 later = _r[1].toHash(); - unsigned limit = _r[2].toInt(); + h256 later = _r[0].toHash(); + unsigned limit = _r[1].toInt(); clogS(NetMessageSummary) << "GetBlockHashes (" << limit << "entries," << later.abridged() << ")"; unsigned c = min(host()->m_chain.number(later), limit); @@ -355,19 +355,19 @@ bool EthereumPeer::interpret(unsigned _id, RLP const& _r) } case BlockHashesPacket: { - clogS(NetMessageSummary) << "BlockHashes (" << dec << (_r.itemCount() - 1) << "entries)" << (_r.itemCount() - 1 ? "" : ": NoMoreHashes"); + clogS(NetMessageSummary) << "BlockHashes (" << dec << _r.itemCount() << "entries)" << (_r.itemCount() ? "" : ": NoMoreHashes"); if (m_asking != Asking::Hashes) { cwarn << "Peer giving us hashes when we didn't ask for them."; break; } - if (_r.itemCount() == 1) + if (_r.itemCount() == 0) { transition(Asking::Blocks); return true; } - for (unsigned i = 1; i < _r.itemCount(); ++i) + for (unsigned i = 0; i < _r.itemCount(); ++i) { auto h = _r[i].toHash(); if (host()->m_chain.isKnown(h)) @@ -384,11 +384,11 @@ bool EthereumPeer::interpret(unsigned _id, RLP const& _r) } case GetBlocksPacket: { - clogS(NetMessageSummary) << "GetBlocks (" << dec << (_r.itemCount() - 1) << "entries)"; + clogS(NetMessageSummary) << "GetBlocks (" << dec << _r.itemCount() << "entries)"; // return the requested blocks. bytes rlp; unsigned n = 0; - for (unsigned i = 1; i < _r.itemCount() && i <= c_maxBlocks; ++i) + for (unsigned i = 0; i < _r.itemCount() && i <= c_maxBlocks; ++i) { auto b = host()->m_chain.block(_r[i].toHash()); if (b.size()) @@ -404,12 +404,12 @@ bool EthereumPeer::interpret(unsigned _id, RLP const& _r) } case BlocksPacket: { - clogS(NetMessageSummary) << "Blocks (" << dec << (_r.itemCount() - 1) << "entries)" << (_r.itemCount() - 1 ? "" : ": NoMoreBlocks"); + clogS(NetMessageSummary) << "Blocks (" << dec << _r.itemCount() << "entries)" << (_r.itemCount() ? "" : ": NoMoreBlocks"); if (m_asking != Asking::Blocks) clogS(NetWarn) << "Unexpected Blocks received!"; - if (_r.itemCount() == 1) + if (_r.itemCount() == 0) { // Got to this peer's latest block - just give up. transition(Asking::Nothing); @@ -422,7 +422,7 @@ bool EthereumPeer::interpret(unsigned _id, RLP const& _r) unsigned got = 0; unsigned repeated = 0; - for (unsigned i = 1; i < _r.itemCount(); ++i) + for (unsigned i = 0; i < _r.itemCount(); ++i) { auto h = BlockInfo::headerHash(_r[i].data()); if (m_sub.noteBlock(h)) @@ -467,14 +467,14 @@ bool EthereumPeer::interpret(unsigned _id, RLP const& _r) } case NewBlockPacket: { - auto h = BlockInfo::headerHash(_r[1].data()); + auto h = BlockInfo::headerHash(_r[0].data()); clogS(NetMessageSummary) << "NewBlock: " << h.abridged(); - if (_r.itemCount() != 3) + if (_r.itemCount() != 2) disable("NewBlock without 2 data fields."); else { - switch (host()->m_bq.import(_r[1].data(), host()->m_chain)) + switch (host()->m_bq.import(_r[0].data(), host()->m_chain)) { case ImportResult::Success: addRating(100); @@ -493,7 +493,7 @@ bool EthereumPeer::interpret(unsigned _id, RLP const& _r) case ImportResult::UnknownParent: clogS(NetMessageSummary) << "Received block with no known parent. Resyncing..."; - setNeedsSyncing(h, _r[2].toInt()); + setNeedsSyncing(h, _r[1].toInt()); break; } Guard l(x_knownBlocks); diff --git a/libp2p/Capability.cpp b/libp2p/Capability.cpp index 5ef70bbc3..dccc130cd 100644 --- a/libp2p/Capability.cpp +++ b/libp2p/Capability.cpp @@ -45,7 +45,7 @@ void Capability::disable(std::string const& _problem) RLPStream& Capability::prep(RLPStream& _s, unsigned _id, unsigned _args) { - return Session::prep(_s).appendList(_args + 1).append(_id + m_idOffset); + return _s.appendRaw(bytes(1, _id + m_idOffset)).appendList(_args); } void Capability::sealAndSend(RLPStream& _s) @@ -53,16 +53,6 @@ void Capability::sealAndSend(RLPStream& _s) m_session->sealAndSend(_s); } -void Capability::send(bytesConstRef _msg) -{ - m_session->send(_msg); -} - -void Capability::send(bytes&& _msg) -{ - m_session->send(move(_msg)); -} - void Capability::addRating(unsigned _r) { m_session->addRating(_r); diff --git a/libp2p/Capability.h b/libp2p/Capability.h index 04b116aa8..ad8127bb5 100644 --- a/libp2p/Capability.h +++ b/libp2p/Capability.h @@ -52,9 +52,6 @@ protected: RLPStream& prep(RLPStream& _s, unsigned _id, unsigned _args = 0); void sealAndSend(RLPStream& _s); - void send(bytes&& _msg); - void send(bytesConstRef _msg); - void addRating(unsigned _r); private: diff --git a/libp2p/Host.cpp b/libp2p/Host.cpp index ed0ee653e..8f4f8a69d 100644 --- a/libp2p/Host.cpp +++ b/libp2p/Host.cpp @@ -24,9 +24,8 @@ #include #include #include - +#include #include - #include #include #include @@ -36,6 +35,7 @@ #include "Common.h" #include "Capability.h" #include "UPnP.h" +#include "RLPxHandshake.h" #include "Host.h" using namespace std; using namespace dev; @@ -121,6 +121,23 @@ void Host::doneWorking() for (auto const& h: m_capabilities) h.second->onStopping(); + // disconnect pending handshake, before peers, as a handshake may create a peer + for (unsigned n = 0;; n = 0) + { + { + Guard l(x_connecting); + for (auto i: m_connecting) + if (auto h = i.lock()) + { + h->cancel(); + n++; + } + } + if (!n) + break; + m_ioService.poll(); + } + // disconnect peers for (unsigned n = 0;; n = 0) { @@ -157,32 +174,65 @@ unsigned Host::protocolVersion() const return 3; } -void Host::registerPeer(std::shared_ptr _s, CapDescs const& _caps) +void Host::startPeerSession(Public const& _id, RLP const& _rlp, RLPXFrameIO* _io, bi::tcp::endpoint _endpoint) { + /// Get or create Peer + shared_ptr 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; + p->endpoint.tcp.address(_endpoint.address()); + + auto protocolVersion = _rlp[0].toInt(); + auto clientVersion = _rlp[1].toString(); + auto caps = _rlp[2].toVector(); + auto listenPort = _rlp[3].toInt(); + + // 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(this, _io, p, PeerSessionInfo({_id, clientVersion, _endpoint.address().to_string(), listenPort, chrono::steady_clock::duration(), _rlp[2].toSet(), 0, map()})); + if (protocolVersion != this->protocolVersion()) + { + ps->disconnect(IncompatibleProtocol); + return; + } + { - clog(NetNote) << "p2p.host.peer.register" << _s->m_peer->id.abridged(); - StructuredLogger::p2pConnected( - _s->m_peer->id.abridged(), - _s->m_peer->peerEndpoint(), - _s->m_peer->m_lastConnected, - _s->m_info.clientVersion, - peerCount() - ); 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()) + if (auto s = m_sessions[_id].lock()) + if(s->isConnected()) + { + // Already connected. + clog(NetWarn) << "Session already exists for peer with id" << _id.abridged(); + ps->disconnect(DuplicatePeer); + return; + } + 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(m_capabilities[i]->newPeerCapability(_s.get(), o)); + ps->m_capabilities[i] = shared_ptr(m_capabilities[i]->newPeerCapability(ps.get(), o)); o += m_capabilities[i]->messageCount(); } + clog(NetNote) << "p2p.host.peer.register" << _id.abridged(); + StructuredLogger::p2pConnected(_id.abridged(), ps->m_peer->peerEndpoint(), ps->m_peer->m_lastConnected, clientVersion, peerCount()); } void Host::onNodeTableEvent(NodeId const& _n, NodeTableEventType const& _e) @@ -235,19 +285,6 @@ void Host::onNodeTableEvent(NodeId const& _n, NodeTableEventType const& _e) } } -void Host::seal(bytes& _b) -{ - _b[0] = 0x22; - _b[1] = 0x40; - _b[2] = 0x08; - _b[3] = 0x91; - uint32_t len = (uint32_t)_b.size() - 8; - _b[4] = (len >> 24) & 0xff; - _b[5] = (len >> 16) & 0xff; - _b[6] = (len >> 8) & 0xff; - _b[7] = len & 0xff; -} - void Host::determinePublic(string const& _publicAddress, bool _upnp) { m_peerAddresses.clear(); @@ -320,34 +357,19 @@ void Host::runAcceptor() clog(NetConnect) << "Listening on local port " << m_listenPort << " (public: " << m_tcpPublic << ")"; m_accepting = true; - // socket is created outside of acceptor-callback - // An allocated socket is necessary as asio can use the socket - // until the callback succeeds or fails. - // - // Until callback succeeds or fails, we can't dealloc it. - // - // Callback is guaranteed to be called via asio or when - // m_tcp4Acceptor->stop() is called by Host. - // - // All exceptions are caught so they don't halt asio and so the - // socket is deleted. - // - // It's possible for an accepted connection to return an error in which - // case the socket may be open and must be closed to prevent asio from - // processing socket events after socket is deallocated. - - bi::tcp::socket *s = new bi::tcp::socket(m_ioService); - m_tcp4Acceptor.async_accept(*s, [=](boost::system::error_code ec) + auto socket = make_shared(new bi::tcp::socket(m_ioService)); + m_tcp4Acceptor.async_accept(socket->ref(), [=](boost::system::error_code ec) { - // if no error code, doHandshake takes ownership + // if no error code bool success = false; if (!ec) { try { - // doHandshake takes ownersihp of *s via std::move // incoming connection; we don't yet know nodeid - doHandshake(s, NodeId()); + auto handshake = make_shared(this, socket); + m_connecting.push_back(handshake); + handshake->start(); success = true; } catch (Exception const& _e) @@ -360,41 +382,16 @@ void Host::runAcceptor() } } - // asio doesn't close socket on error - if (!success && s->is_open()) - { - boost::system::error_code ec; - s->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); - s->close(); - } - + if (!success) + socket->ref().close(); + m_accepting = false; - delete s; - if (ec.value() < 1) runAcceptor(); }); } } -void Host::doHandshake(bi::tcp::socket* _socket, NodeId _nodeId) -{ - try { - clog(NetConnect) << "Accepting connection for " << _socket->remote_endpoint(); - } catch (...){} - - shared_ptr p; - if (_nodeId) - p = m_peers[_nodeId]; - - if (!p) - p.reset(new Peer()); - p->endpoint.tcp.address(_socket->remote_endpoint().address()); - - auto ps = std::make_shared(this, std::move(*_socket), p); - ps->start(); -} - string Host::pocHost() { vector strs; @@ -440,15 +437,14 @@ void Host::addNode(NodeId const& _node, std::string const& _addr, unsigned short void Host::connect(std::shared_ptr const& _p) { - for (unsigned i = 0; i < 200; i++) - if (isWorking() && !m_run) - this_thread::sleep_for(chrono::milliseconds(50)); if (!m_run) return; + _p->m_lastAttempted = std::chrono::system_clock::now(); + if (havePeerSession(_p->id)) { - clog(NetWarn) << "Aborted connect. Node already connected."; + clog(NetConnect) << "Aborted connect. Node already connected."; return; } @@ -469,8 +465,8 @@ void Host::connect(std::shared_ptr const& _p) } clog(NetConnect) << "Attempting connection to node" << _p->id.abridged() << "@" << _p->peerEndpoint() << "from" << id().abridged(); - bi::tcp::socket* s = new bi::tcp::socket(m_ioService); - s->async_connect(_p->peerEndpoint(), [=](boost::system::error_code const& ec) + auto socket = make_shared(new bi::tcp::socket(m_ioService)); + socket->ref().async_connect(_p->peerEndpoint(), [=](boost::system::error_code const& ec) { if (ec) { @@ -480,16 +476,15 @@ void Host::connect(std::shared_ptr const& _p) } else { - clog(NetConnect) << "Connected to" << _p->id.abridged() << "@" << _p->peerEndpoint(); - _p->m_lastDisconnect = NoDisconnect; - _p->m_lastConnected = std::chrono::system_clock::now(); - _p->m_failedAttempts = 0; - - auto ps = make_shared(this, std::move(*s), _p); - ps->start(); - + clog(NetConnect) << "Connecting to" << _p->id.abridged() << "@" << _p->peerEndpoint(); + auto handshake = make_shared(this, socket, _p->id); + { + Guard l(x_connecting); + m_connecting.push_back(handshake); + } + handshake->start(); } - delete s; + Guard l(x_pendingNodeConns); m_pendingPeerConns.erase(nptr); }); @@ -538,26 +533,34 @@ void Host::run(boost::system::error_code const&) m_nodeTable->processEvents(); + // cleanup zombies + { + Guard l(x_connecting); + m_connecting.remove_if([](std::weak_ptr h){ return h.lock(); }); + } + for (auto p: m_sessions) if (auto pp = p.second.lock()) pp->serviceNodesRequest(); keepAlivePeers(); - disconnectLatePeers(); + + // At this time peers will be disconnected based on natural TCP timeout. + // disconnectLatePeers needs to be updated for the assumption that Session + // is always live and to ensure reputation and fallback timers are properly + // updated. // disconnectLatePeers(); - auto c = peerCount(); - if (m_idealPeerCount && !c) + if (peerCount() < m_idealPeerCount) + { for (auto p: m_peers) if (p.second->shouldReconnect()) { - // TODO p2p: fixme - p.second->m_lastAttempted = std::chrono::system_clock::now(); connect(p.second); break; } - - if (c < m_idealPeerCount) + m_nodeTable->discover(); + } auto runcb = [this](boost::system::error_code const& error) { run(error); }; m_timer->expires_from_now(boost::posix_time::milliseconds(c_timerInterval)); diff --git a/libp2p/Host.h b/libp2p/Host.h index ce4dfa50c..219316c58 100644 --- a/libp2p/Host.h +++ b/libp2p/Host.h @@ -35,10 +35,12 @@ #include #include #include +#include #include "NodeTable.h" #include "HostCapability.h" #include "Network.h" #include "Peer.h" +#include "RLPxFrameIO.h" #include "Common.h" namespace ba = boost::asio; namespace bi = ba::ip; @@ -68,19 +70,16 @@ private: * @brief The Host class * Capabilities should be registered prior to startNetwork, since m_capabilities is not thread-safe. * - * @todo exceptions when nodeTable not set (prior to start) - * @todo onNodeTableEvent: move peer-connection logic into ensurePeers - * @todo handshake: gracefully disconnect peer if peer already connected - * @todo abstract socket -> IPConnection + * @todo cleanup startPeerSession * @todo determinePublic: ipv6, udp * @todo handle conflict if addNode/requireNode called and Node already exists w/conflicting tcp or udp port - * @todo write host identifier to disk w/nodes * @todo per-session keepalive/ping instead of broadcast; set ping-timeout via median-latency - * @todo configuration-management (NetworkPrefs+Keys+Topology) */ class Host: public Worker { friend class HostNodeTableHandler; + friend class RLPXHandshake; + friend class Session; friend class HostCapabilityFace; @@ -114,8 +113,6 @@ public: CapDescs caps() const { CapDescs ret; for (auto const& i: m_capabilities) ret.push_back(i.first); return ret; } template std::shared_ptr cap() const { try { return std::static_pointer_cast(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. @@ -153,7 +150,8 @@ public: NodeId id() const { return m_alias.pub(); } - void registerPeer(std::shared_ptr _s, CapDescs const& _caps); + /// Validates and starts peer session, taking ownership of _io. Disconnects and returns false upon error. + void startPeerSession(Public const& _id, RLP const& _hello, RLPXFrameIO* _io, bi::tcp::endpoint _endpoint); protected: void onNodeTableEvent(NodeId const& _n, NodeTableEventType const& _e); @@ -162,6 +160,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); @@ -176,11 +176,6 @@ private: /// Called only from startedWorking(). void runAcceptor(); - /// Handler for verifying handshake siganture before creating session. _nodeId is passed for outbound connections. If successful, socket is moved to Session via std::move. - void doHandshake(bi::tcp::socket* _socket, NodeId _nodeId = NodeId()); - - void seal(bytes& _b); - /// Called by Worker. Not thread-safe; to be called only by worker. virtual void startedWorking(); /// Called by startedWorking. Not thread-safe; to be called only be Worker. @@ -229,6 +224,9 @@ private: /// Mutable because we flush zombie entries (null-weakptrs) as regular maintenance from a const method. mutable std::map> m_sessions; mutable RecursiveMutex x_sessions; + + std::list> m_connecting; ///< Pending connections. + Mutex x_connecting; ///< Mutex for m_connecting. unsigned m_idealPeerCount = 5; ///< Ideal number of peers to be connected to. diff --git a/libp2p/HostCapability.cpp b/libp2p/HostCapability.cpp index 9437cd45c..b2acdcd1b 100644 --- a/libp2p/HostCapability.cpp +++ b/libp2p/HostCapability.cpp @@ -27,11 +27,6 @@ using namespace std; using namespace dev; using namespace dev::p2p; -void HostCapabilityFace::seal(bytes& _b) -{ - m_host->seal(_b); -} - std::vector,std::shared_ptr>> HostCapabilityFace::peerSessions() const { RecursiveGuard l(m_host->x_sessions); diff --git a/libp2p/HostCapability.h b/libp2p/HostCapability.h index 9122ca1fa..93086b1c9 100644 --- a/libp2p/HostCapability.h +++ b/libp2p/HostCapability.h @@ -57,8 +57,6 @@ protected: virtual void onStarting() {} virtual void onStopping() {} - void seal(bytes& _b); - private: Host* m_host = nullptr; }; diff --git a/libp2p/Peer.h b/libp2p/Peer.h index 704e5c2b4..8774b6578 100644 --- a/libp2p/Peer.h +++ b/libp2p/Peer.h @@ -55,6 +55,8 @@ class Peer: public Node friend class Session; /// Allows Session to update score and rating. friend class Host; /// For Host: saveNetwork(), restoreNetwork() + friend class RLPXHandshake; + public: bool isOffline() const { return !m_session.lock(); } diff --git a/libp2p/RLPxFrameIO.cpp b/libp2p/RLPxFrameIO.cpp new file mode 100644 index 000000000..cb768bfeb --- /dev/null +++ b/libp2p/RLPxFrameIO.cpp @@ -0,0 +1,209 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . + */ +/** @file RLPXFrameIO.cpp + * @author Alex Leverington + * @date 2015 + */ + +#include "Host.h" +#include "Session.h" +#include "Peer.h" +#include "RLPxHandshake.h" +#include "RLPxFrameIO.h" +using namespace std; +using namespace dev; +using namespace dev::p2p; +using namespace CryptoPP; + +RLPXFrameIO::RLPXFrameIO(RLPXHandshake const& _init): m_socket(_init.m_socket) +{ + // we need: + // originated? + // Secret == output of ecdhe agreement + // authCipher + // ackCipher + + bytes keyMaterialBytes(64); + bytesRef keyMaterial(&keyMaterialBytes); + + // shared-secret = sha3(ecdhe-shared-secret || sha3(nonce || initiator-nonce)) + Secret ephemeralShared; + _init.m_ecdhe.agree(_init.m_remoteEphemeral, ephemeralShared); + ephemeralShared.ref().copyTo(keyMaterial.cropped(0, h256::size)); + h512 nonceMaterial; + h256 const& leftNonce = _init.m_originated ? _init.m_remoteNonce : _init.m_nonce; + h256 const& rightNonce = _init.m_originated ? _init.m_nonce : _init.m_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, bytesRef(&token)); -> m_host (to be saved to disk) + + // aes-secret = sha3(ecdhe-shared-secret || shared-secret) + sha3(keyMaterial, outRef); // output aes-secret + m_frameEncKey.resize(h256::size); + memcpy(m_frameEncKey.data(), outRef.data(), h256::size); + m_frameDecKey.resize(h256::size); + memcpy(m_frameDecKey.data(), outRef.data(), h256::size); + h128 iv; + m_frameEnc.SetKeyWithIV(m_frameEncKey, h256::size, iv.data()); + m_frameDec.SetKeyWithIV(m_frameDecKey, h256::size, iv.data()); + + // mac-secret = sha3(ecdhe-shared-secret || aes-secret) + sha3(keyMaterial, outRef); // output mac-secret + m_macEncKey.resize(h256::size); + memcpy(m_macEncKey.data(), outRef.data(), h256::size); + m_macEnc.SetKey(m_macEncKey, h256::size); + + // 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) + + (*(h256*)outRef.data() ^ _init.m_remoteNonce).ref().copyTo(keyMaterial); + bytes const& egressCipher = _init.m_originated ? _init.m_authCipher : _init.m_ackCipher; + keyMaterialBytes.resize(h256::size + egressCipher.size()); + keyMaterial.retarget(keyMaterialBytes.data(), keyMaterialBytes.size()); + bytesConstRef(&egressCipher).copyTo(keyMaterial.cropped(h256::size, egressCipher.size())); + m_egressMac.Update(keyMaterial.data(), keyMaterial.size()); + + // recover mac-secret by re-xoring remoteNonce + (*(h256*)keyMaterial.data() ^ _init.m_remoteNonce ^ _init.m_nonce).ref().copyTo(keyMaterial); + bytes const& ingressCipher = _init.m_originated ? _init.m_ackCipher : _init.m_authCipher; + keyMaterialBytes.resize(h256::size + ingressCipher.size()); + keyMaterial.retarget(keyMaterialBytes.data(), keyMaterialBytes.size()); + bytesConstRef(&ingressCipher).copyTo(keyMaterial.cropped(h256::size, ingressCipher.size())); + m_ingressMac.Update(keyMaterial.data(), keyMaterial.size()); +} + +void RLPXFrameIO::writeSingleFramePacket(bytesConstRef _packet, bytes& o_bytes) +{ + // _packet = type || rlpList() + + RLPStream header; + uint32_t len = (uint32_t)_packet.size(); + header.appendRaw(bytes({byte((len >> 16) & 0xff), byte((len >> 8) & 0xff), byte(len & 0xff)})); + // 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(h256::size); + bytesConstRef(&header.out()).copyTo(bytesRef(&headerWithMac)); + 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); + updateEgressMACWithFrame(packetWithPaddingRef); + bytesRef macRef(o_bytes.data() + 32 + _packet.size() + padding, h128::size); + egressDigest().ref().copyTo(macRef); +} + +bool RLPXFrameIO::authAndDecryptHeader(bytesRef io) +{ + asserts(io.size() == h256::size); + updateIngressMACWithHeader(io); + bytesConstRef macRef = io.cropped(h128::size, h128::size); + h128 expected = ingressDigest(); + if (*(h128*)macRef.data() != expected) + return false; + m_frameDec.ProcessData(io.data(), io.data(), h128::size); + return true; +} + +bool RLPXFrameIO::authAndDecryptFrame(bytesRef io) +{ + bytesRef cipherText(io.cropped(0, io.size() - h128::size)); + updateIngressMACWithFrame(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() +{ + SHA3_256 h(m_egressMac); + h128 digest; + h.TruncatedFinal(digest.data(), h128::size); + return move(digest); +} + +h128 RLPXFrameIO::ingressDigest() +{ + SHA3_256 h(m_ingressMac); + h128 digest; + h.TruncatedFinal(digest.data(), h128::size); + return move(digest); +} + +void RLPXFrameIO::updateEgressMACWithHeader(bytesConstRef _headerCipher) +{ + updateMAC(m_egressMac, _headerCipher.cropped(0, 16)); +} + +void RLPXFrameIO::updateEgressMACWithFrame(bytesConstRef _cipher) +{ + m_egressMac.Update(_cipher.data(), _cipher.size()); + updateMAC(m_egressMac); +} + +void RLPXFrameIO::updateIngressMACWithHeader(bytesConstRef _headerCipher) +{ + updateMAC(m_ingressMac, _headerCipher.cropped(0, 16)); +} + +void RLPXFrameIO::updateIngressMACWithFrame(bytesConstRef _cipher) +{ + m_ingressMac.Update(_cipher.data(), _cipher.size()); + updateMAC(m_ingressMac); +} + +void RLPXFrameIO::updateMAC(SHA3_256& _mac, bytesConstRef _seed) +{ + if (_seed.size() && _seed.size() != h128::size) + asserts(false); + + SHA3_256 prevDigest(_mac); + h128 encDigest(h128::size); + prevDigest.TruncatedFinal(encDigest.data(), h128::size); + h128 prevDigestOut = encDigest; + + { + Guard l(x_macEnc); + m_macEnc.ProcessData(encDigest.data(), encDigest.data(), 16); + } + if (_seed.size()) + encDigest ^= *(h128*)_seed.data(); + else + encDigest ^= *(h128*)prevDigestOut.data(); + + // update mac for final digest + _mac.Update(encDigest.data(), h128::size); +} diff --git a/libp2p/RLPxFrameIO.h b/libp2p/RLPxFrameIO.h new file mode 100644 index 000000000..0f0504e48 --- /dev/null +++ b/libp2p/RLPxFrameIO.h @@ -0,0 +1,133 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . + */ +/** @file RLPXFrameIO.h + * @author Alex Leverington + * @date 2015 + */ + + +#pragma once + +#include +#include +#include +#include +#include +#include "Common.h" +namespace ba = boost::asio; +namespace bi = boost::asio::ip; + +namespace dev +{ +namespace p2p +{ + +class RLPXHandshake; + +/** + * @brief Encoder/decoder transport for RLPx connections established by RLPXHandshake. + * Managed (via shared_ptr) socket for use by RLPXHandshake and RLPXFrameIO. + * + * Thread Safety + * Distinct Objects: Safe. + * Shared objects: Unsafe. + * * an instance method must not be called concurrently + * * a writeSingleFramePacket can be called concurrent to authAndDecryptHeader OR authAndDecryptFrame + */ +class RLPXSocket: public std::enable_shared_from_this +{ +public: + RLPXSocket(bi::tcp::socket* _socket): m_socket(std::move(*_socket)) {} + ~RLPXSocket() { close(); } + + bool isConnected() const { return m_socket.is_open(); } + void close() { try { boost::system::error_code ec; m_socket.shutdown(bi::tcp::socket::shutdown_both, ec); if (m_socket.is_open()) m_socket.close(); } catch (...){} } + bi::tcp::endpoint remoteEndpoint() { try { return m_socket.remote_endpoint(); } catch (...){ return bi::tcp::endpoint(); } } + bi::tcp::socket& ref() { return m_socket; } + +protected: + bi::tcp::socket m_socket; +}; + +/** + * @brief Encoder/decoder transport for RLPx connections established by RLPXHandshake. + * + * Thread Safety + * Distinct Objects: Safe. + * Shared objects: Unsafe. + */ +class RLPXFrameIO +{ + friend class Session; +public: + /// Constructor. + /// Requires instance of RLPXHandshake which has completed first two phases of handshake. + RLPXFrameIO(RLPXHandshake const& _init); + ~RLPXFrameIO() {} + + /// Encrypt _packet as RLPx frame. + void writeSingleFramePacket(bytesConstRef _packet, bytes& o_bytes); + + /// Authenticate and decrypt header in-place. + bool authAndDecryptHeader(bytesRef io_cipherWithMac); + + /// Authenticate and decrypt frame in-place. + bool authAndDecryptFrame(bytesRef io_cipherWithMac); + + /// Return first 16 bytes of current digest from egress mac. + h128 egressDigest(); + + /// Return first 16 bytes of current digest from ingress mac. + h128 ingressDigest(); + +protected: + /// Update state of egress MAC with frame header. + void updateEgressMACWithHeader(bytesConstRef _headerCipher); + + /// Update state of egress MAC with frame. + void updateEgressMACWithFrame(bytesConstRef _cipher); + + /// Update state of ingress MAC with frame header. + void updateIngressMACWithHeader(bytesConstRef _headerCipher); + + /// Update state of ingress MAC with frame. + void updateIngressMACWithFrame(bytesConstRef _cipher); + + bi::tcp::socket& socket() { return m_socket->ref(); } + +private: + /// Update state of _mac. + void updateMAC(CryptoPP::SHA3_256& _mac, bytesConstRef _seed = bytesConstRef()); + + CryptoPP::SecByteBlock m_frameEncKey; ///< Key for m_frameEnc + CryptoPP::CTR_Mode::Encryption m_frameEnc; ///< Encoder for egress plaintext. + + CryptoPP::SecByteBlock m_frameDecKey; ///< Key for m_frameDec + CryptoPP::CTR_Mode::Encryption m_frameDec; ///< Decoder for egress plaintext. + + CryptoPP::SecByteBlock m_macEncKey; /// Key for m_macEnd + CryptoPP::ECB_Mode::Encryption m_macEnc; /// One-way coder used by updateMAC for ingress and egress MAC updates. + Mutex x_macEnc; /// Mutex + + CryptoPP::SHA3_256 m_egressMac; ///< State of MAC for egress ciphertext. + CryptoPP::SHA3_256 m_ingressMac; ///< State of MAC for ingress ciphertext. + + std::shared_ptr m_socket; +}; + +} +} \ No newline at end of file diff --git a/libp2p/RLPxHandshake.cpp b/libp2p/RLPxHandshake.cpp new file mode 100644 index 000000000..4f5c70802 --- /dev/null +++ b/libp2p/RLPxHandshake.cpp @@ -0,0 +1,266 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . + */ +/** @file RLPXHandshake.cpp + * @author Alex Leverington + * @date 2015 + */ + +#include "Host.h" +#include "Session.h" +#include "Peer.h" +#include "RLPxHandshake.h" +using namespace std; +using namespace dev; +using namespace dev::p2p; +using namespace CryptoPP; + +void RLPXHandshake::writeAuth() +{ + clog(NetConnect) << "p2p.connect.egress sending auth to " << m_socket->remoteEndpoint(); + m_auth.resize(Signature::size + h256::size + Public::size + h256::size + 1); + bytesRef sig(&m_auth[0], Signature::size); + bytesRef hepubk(&m_auth[Signature::size], h256::size); + bytesRef pubk(&m_auth[Signature::size + h256::size], Public::size); + bytesRef nonce(&m_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) + Secret staticShared; + crypto::ecdh::agree(m_host->m_alias.sec(), m_remote, staticShared); + sign(m_ecdhe.seckey(), staticShared ^ m_nonce).ref().copyTo(sig); + sha3(m_ecdhe.pubkey().ref(), hepubk); + m_host->m_alias.pub().ref().copyTo(pubk); + m_nonce.ref().copyTo(nonce); + m_auth[m_auth.size() - 1] = 0x0; + encryptECIES(m_remote, &m_auth, m_authCipher); + + auto self(shared_from_this()); + ba::async_write(m_socket->ref(), ba::buffer(m_authCipher), [this, self](boost::system::error_code ec, std::size_t) + { + transition(ec); + }); +} + +void RLPXHandshake::writeAck() +{ + clog(NetConnect) << "p2p.connect.ingress sending ack to " << m_socket->remoteEndpoint(); + m_ack.resize(Public::size + h256::size + 1); + bytesRef epubk(&m_ack[0], Public::size); + bytesRef nonce(&m_ack[Public::size], h256::size); + m_ecdhe.pubkey().ref().copyTo(epubk); + m_nonce.ref().copyTo(nonce); + m_ack[m_ack.size() - 1] = 0x0; + encryptECIES(m_remote, &m_ack, m_ackCipher); + + auto self(shared_from_this()); + ba::async_write(m_socket->ref(), ba::buffer(m_ackCipher), [this, self](boost::system::error_code ec, std::size_t) + { + transition(ec); + }); +} + +void RLPXHandshake::readAuth() +{ + clog(NetConnect) << "p2p.connect.ingress recving auth from " << m_socket->remoteEndpoint(); + m_authCipher.resize(307); + auto self(shared_from_this()); + ba::async_read(m_socket->ref(), ba::buffer(m_authCipher, 307), [this, self](boost::system::error_code ec, std::size_t) + { + if (ec) + transition(ec); + else if (decryptECIES(m_host->m_alias.sec(), bytesConstRef(&m_authCipher), m_auth)) + { + bytesConstRef sig(&m_auth[0], Signature::size); + bytesConstRef hepubk(&m_auth[Signature::size], h256::size); + bytesConstRef pubk(&m_auth[Signature::size + h256::size], Public::size); + bytesConstRef nonce(&m_auth[Signature::size + h256::size + Public::size], h256::size); + pubk.copyTo(m_remote.ref()); + nonce.copyTo(m_remoteNonce.ref()); + + Secret sharedSecret; + crypto::ecdh::agree(m_host->m_alias.sec(), m_remote, sharedSecret); + m_remoteEphemeral = recover(*(Signature*)sig.data(), sharedSecret ^ m_remoteNonce); + assert(sha3(m_remoteEphemeral) == *(h256*)hepubk.data()); + transition(); + } + else + { + clog(NetWarn) << "p2p.connect.egress recving auth decrypt failed for" << m_socket->remoteEndpoint(); + m_nextState = Error; + transition(); + } + }); +} + +void RLPXHandshake::readAck() +{ + clog(NetConnect) << "p2p.connect.egress recving ack from " << m_socket->remoteEndpoint(); + m_ackCipher.resize(210); + auto self(shared_from_this()); + ba::async_read(m_socket->ref(), ba::buffer(m_ackCipher, 210), [this, self](boost::system::error_code ec, std::size_t) + { + if (ec) + transition(ec); + else if (decryptECIES(m_host->m_alias.sec(), bytesConstRef(&m_ackCipher), m_ack)) + { + bytesConstRef(&m_ack).cropped(0, Public::size).copyTo(m_remoteEphemeral.ref()); + bytesConstRef(&m_ack).cropped(Public::size, h256::size).copyTo(m_remoteNonce.ref()); + transition(); + } + else + { + clog(NetWarn) << "p2p.connect.egress recving ack decrypt failed for " << m_socket->remoteEndpoint(); + m_nextState = Error; + transition(); + } + }); +} + +void RLPXHandshake::error() +{ + clog(NetConnect) << "Disconnecting " << m_socket->remoteEndpoint() << " (Handshake Failed)"; + m_socket->close(); + if (m_io != nullptr) + delete m_io; +} + +void RLPXHandshake::transition(boost::system::error_code _ech) +{ + if (_ech || m_nextState == Error) + return error(); + + auto self(shared_from_this()); + if (m_nextState == New) + { + m_nextState = AckAuth; + if (m_originated) + writeAuth(); + else + readAuth(); + } + else if (m_nextState == AckAuth) + { + m_nextState = WriteHello; + if (m_originated) + readAck(); + else + writeAck(); + } + else if (m_nextState == WriteHello) + { + m_nextState = ReadHello; + + if (m_originated) + clog(NetConnect) << "p2p.connect.egress sending capabilities handshake"; + else + clog(NetConnect) << "p2p.connect.ingress sending capabilities handshake"; + + /// This pointer will be freed if there is an error otherwise + /// it will be passed to Host which will take ownership. + m_io = new RLPXFrameIO(*this); + + // old packet format + // 5 arguments, HelloPacket + RLPStream s; + s.append((unsigned)0).appendList(5) + << m_host->protocolVersion() + << m_host->m_clientVersion + << m_host->caps() + << m_host->listenPort() + << m_host->id(); + bytes packet; + s.swapOut(packet); + m_io->writeSingleFramePacket(&packet, m_handshakeOutBuffer); + ba::async_write(m_socket->ref(), ba::buffer(m_handshakeOutBuffer), [this, self](boost::system::error_code ec, std::size_t) + { + transition(ec); + }); + } + else if (m_nextState == ReadHello) + { + // Authenticate and decrypt initial hello frame with initial RLPXFrameIO + // and request m_host to start session. + m_nextState = StartSession; + + // read frame header + m_handshakeInBuffer.resize(h256::size); + ba::async_read(m_socket->ref(), boost::asio::buffer(m_handshakeInBuffer, h256::size), [this, self](boost::system::error_code ec, std::size_t) + { + if (ec) + transition(ec); + else + { + /// authenticate and decrypt header + if (!m_io->authAndDecryptHeader(bytesRef(m_handshakeInBuffer.data(), h256::size))) + { + m_nextState = Error; + transition(); + return; + } + + clog(NetNote) << (m_originated ? "p2p.connect.egress" : "p2p.connect.ingress") << "recvd hello header"; + + /// check frame size + bytes& header = m_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) << (m_originated ? "p2p.connect.egress" : "p2p.connect.ingress") << "hello frame is too large" << frameSize; + m_nextState = Error; + transition(); + return; + } + + /// 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 + m_handshakeInBuffer.resize(frameSize + ((16 - (frameSize % 16)) % 16) + h128::size); + ba::async_read(m_socket->ref(), boost::asio::buffer(m_handshakeInBuffer, m_handshakeInBuffer.size()), [this, self, headerRLP](boost::system::error_code ec, std::size_t) + { + if (ec) + transition(ec); + else + { + bytesRef frame(&m_handshakeInBuffer); + if (!m_io->authAndDecryptFrame(frame)) + { + clog(NetWarn) << (m_originated ? "p2p.connect.egress" : "p2p.connect.ingress") << "hello frame: decrypt failed"; + m_nextState = Error; + transition(); + return; + } + + PacketType packetType = (PacketType)(frame[0] == 0x80 ? 0x0 : frame[0]); + if (packetType != 0) + { + clog(NetWarn) << (m_originated ? "p2p.connect.egress" : "p2p.connect.ingress") << "hello frame: invalid packet type"; + m_nextState = Error; + transition(); + return; + } + + clog(NetNote) << (m_originated ? "p2p.connect.egress" : "p2p.connect.ingress") << "hello frame: success. starting session."; + RLP rlp(frame.cropped(1)); + m_host->startPeerSession(m_remote, rlp, m_io, m_socket->remoteEndpoint()); + } + }); + } + }); + } +} diff --git a/libp2p/RLPxHandshake.h b/libp2p/RLPxHandshake.h new file mode 100644 index 000000000..aac8f4b5a --- /dev/null +++ b/libp2p/RLPxHandshake.h @@ -0,0 +1,126 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . + */ +/** @file RLPXHandshake.h + * @author Alex Leverington + * @date 2015 + */ + + +#pragma once + +#include +#include +#include +#include "RLPxFrameIO.h" +#include "Common.h" +namespace ba = boost::asio; +namespace bi = boost::asio::ip; + +namespace dev +{ +namespace p2p +{ + +/** + * @brief Setup inbound or outbound connection for communication over RLPXFrameIO. + * RLPx Spec: https://github.com/ethereum/devp2p/blob/master/rlpx.md#encrypted-handshake + * + * @todo Implement StartSession transition via lambda which is passed to constructor. + * + * Thread Safety + * Distinct Objects: Safe. + * Shared objects: Unsafe. + */ +class RLPXHandshake: public std::enable_shared_from_this +{ + friend class RLPXFrameIO; + + /// Sequential states of handshake + enum State + { + Error = -1, + New, + AckAuth, + WriteHello, + ReadHello, + StartSession + }; + +public: + /// Setup incoming connection. + RLPXHandshake(Host* _host, std::shared_ptr const& _socket): m_host(_host), m_originated(false), m_socket(_socket) { crypto::Nonce::get().ref().copyTo(m_nonce.ref()); } + + /// Setup outbound connection. + RLPXHandshake(Host* _host, std::shared_ptr const& _socket, NodeId _remote): m_host(_host), m_remote(_remote), m_originated(true), m_socket(_socket) { crypto::Nonce::get().ref().copyTo(m_nonce.ref()); } + + ~RLPXHandshake() {} + + /// Start handshake. + void start() { transition(); } + + void cancel() { m_nextState = Error; } + +protected: + /// Write Auth message to socket and transitions to AckAuth. + void writeAuth(); + + /// Reads Auth message from socket and transitions to AckAuth. + void readAuth(); + + /// Write Ack message to socket and transitions to WriteHello. + void writeAck(); + + /// Reads Auth message from socket and transitions to WriteHello. + void readAck(); + + /// Closes connection and ends transitions. + void error(); + + /// Performs transition for m_nextState. + void transition(boost::system::error_code _ech = boost::system::error_code()); + + State m_nextState = New; ///< Current or expected state of transition. + + Host* m_host; ///< Host which provides m_alias, protocolVersion(), m_clientVersion, caps(), and TCP listenPort(). + + /// Node id of remote host for socket. + NodeId m_remote; ///< Public address of remote host. + bool m_originated = false; ///< True if connection is outbound. + + /// Buffers for encoded and decoded handshake phases + bytes m_auth; ///< Plaintext of egress or ingress Auth message. + bytes m_authCipher; ///< Ciphertext of egress or ingress Auth message. + bytes m_ack; ///< Plaintext of egress or ingress Ack message. + bytes m_ackCipher; ///< Ciphertext of egress or ingress Ack message. + bytes m_handshakeOutBuffer; ///< Frame buffer for egress Hello packet. + bytes m_handshakeInBuffer; ///< Frame buffer for ingress Hello packet. + + crypto::ECDHE m_ecdhe; ///< Ephemeral ECDH secret and agreement. + h256 m_nonce; ///< Nonce generated by this host for handshake. + + Public m_remoteEphemeral; ///< Remote ephemeral public key. + h256 m_remoteNonce; ///< Nonce generated by remote host for handshake. + + /// Used to read and write RLPx encrypted frames for last step of handshake authentication. + /// Passed onto Host which will take ownership. + RLPXFrameIO* m_io = nullptr; + + std::shared_ptr m_socket; ///< Socket. +}; + +} +} \ No newline at end of file diff --git a/libp2p/Session.cpp b/libp2p/Session.cpp index ec0a2d0d7..2c6530a2d 100644 --- a/libp2p/Session.cpp +++ b/libp2p/Session.cpp @@ -16,6 +16,7 @@ */ /** @file Session.cpp * @author Gav Wood + * @author Alex Leverington * @date 2014 */ @@ -37,11 +38,12 @@ using namespace dev::p2p; #endif #define clogS(X) dev::LogOutputStream(false) << "| " << std::setw(2) << m_socket.native_handle() << "] " -Session::Session(Host* _s, bi::tcp::socket _socket, std::shared_ptr const& _n): +Session::Session(Host* _s, RLPXFrameIO* _io, std::shared_ptr const& _n, PeerSessionInfo _info): m_server(_s), - m_socket(std::move(_socket)), + m_io(_io), + m_socket(m_io->socket()), m_peer(_n), - m_info({NodeId(), "?", m_socket.remote_endpoint().address().to_string(), 0, chrono::steady_clock::duration(0), CapDescSet(), 0, map()}), + m_info(_info), m_ping(chrono::steady_clock::time_point::max()) { m_lastReceived = m_connect = chrono::steady_clock::now(); @@ -65,6 +67,7 @@ Session::~Session() } } catch (...){} + delete m_io; } NodeId Session::id() const @@ -142,101 +145,21 @@ void Session::serviceNodesRequest() addNote("peers", "done"); } -bool Session::interpret(RLP const& _r) +bool Session::interpret(PacketType _t, RLP const& _r) { m_lastReceived = chrono::steady_clock::now(); - clogS(NetRight) << _r; + clogS(NetRight) << _t << _r; try // Generic try-catch block designed to capture RLP format errors - TODO: give decent diagnostics, make a bit more specific over what is caught. { - switch ((PacketType)_r[0].toInt()) + switch (_t) { - 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(); - auto clientVersion = _r[2].toString(); - auto caps = _r[3].toVector(); - auto listenPort = _r[4].toInt(); - auto id = _r[5].toHash(); - - // 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(); - m_info.socket = (unsigned)m_socket.native_handle(); - m_info.notes = map(); - - m_server->registerPeer(shared_from_this(), caps); - break; - } case DisconnectPacket: { string reason = "Unspecified"; - auto r = (DisconnectReason)_r[1].toInt(); - if (!_r[1].isInt()) + auto r = (DisconnectReason)_r[0].toInt(); + if (!_r[0].isInt()) drop(BadProtocol); else { @@ -275,7 +198,7 @@ bool Session::interpret(RLP const& _r) clogS(NetTriviaSummary) << "Peers (" << dec << (_r.itemCount() - 1) << " entries)"; m_weRequestedNodes = false; - for (unsigned i = 1; i < _r.itemCount(); ++i) + for (unsigned i = 0; i < _r.itemCount(); ++i) { bi::address peerAddress; if (_r[i][0].size() == 16) @@ -325,12 +248,11 @@ bool Session::interpret(RLP const& _r) break; default: { - auto id = _r[0].toInt(); for (auto const& i: m_capabilities) - if (id >= i.second->m_idOffset && id - i.second->m_idOffset < i.second->hostCapability()->messageCount()) + if (_t >= i.second->m_idOffset && _t - i.second->m_idOffset < i.second->hostCapability()->messageCount()) { if (i.second->m_enabled) - return i.second->interpret(id - i.second->m_idOffset, _r); + return i.second->interpret(_t - i.second->m_idOffset, _r); else return true; } @@ -356,47 +278,34 @@ void Session::ping() RLPStream& Session::prep(RLPStream& _s, PacketType _id, unsigned _args) { - return prep(_s).appendList(_args + 1).append((unsigned)_id); -} - -RLPStream& Session::prep(RLPStream& _s) -{ - return _s.appendRaw(bytes(8, 0)); + return _s.append((unsigned)_id).appendList(_args); } void Session::sealAndSend(RLPStream& _s) { bytes b; _s.swapOut(b); - m_server->seal(b); send(move(b)); } bool Session::checkPacket(bytesConstRef _msg) { - if (_msg.size() < 8) + if (_msg.size() < 2) return false; - if (!(_msg[0] == 0x22 && _msg[1] == 0x40 && _msg[2] == 0x08 && _msg[3] == 0x91)) + if (_msg[0] > 0x7f) return false; - uint32_t len = ((_msg[4] * 256 + _msg[5]) * 256 + _msg[6]) * 256 + _msg[7]; - if (_msg.size() != len + 8) - return false; - RLP r(_msg.cropped(8)); - if (r.actualSize() != len) + RLP r(_msg.cropped(1)); + if (r.actualSize() + 1 != _msg.size()) return false; return true; } -void Session::send(bytesConstRef _msg) -{ - send(_msg.toBytes()); -} - void Session::send(bytes&& _msg) { - clogS(NetLeft) << RLP(bytesConstRef(&_msg).cropped(8)); + clogS(NetLeft) << RLP(bytesConstRef(&_msg).cropped(1)); - if (!checkPacket(bytesConstRef(&_msg))) + bytesConstRef msg(&_msg); + if (!checkPacket(msg)) clogS(NetWarn) << "INVALID PACKET CONSTRUCTED!"; if (!m_socket.is_open()) @@ -416,6 +325,7 @@ void Session::send(bytes&& _msg) void Session::write() { const bytes& bytes = m_writeQueue[0]; + m_io->writeSingleFramePacket(&bytes, m_writeQueue[0]); auto self(shared_from_this()); ba::async_write(m_socket, ba::buffer(bytes), [this, self](boost::system::error_code ec, std::size_t /*length*/) { @@ -484,95 +394,88 @@ 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(); } void Session::doRead() { - // ignore packets received while waiting to disconnect + // ignore packets received while waiting to disconnect. if (m_dropped) return; - + auto self(shared_from_this()); - m_socket.async_read_some(boost::asio::buffer(m_data), [this,self](boost::system::error_code ec, std::size_t length) + ba::async_read(m_socket, boost::asio::buffer(m_data, h256::size), [this,self](boost::system::error_code ec, std::size_t length) { - // If error is end of file, ignore if (ec && ec.category() != boost::asio::error::get_misc_category() && ec.value() != boost::asio::error::eof) { - // got here with length of 1241... clogS(NetWarn) << "Error reading: " << ec.message(); drop(TCPError); } else if (ec && length == 0) - { return; - } else { - try + /// authenticate and decrypt header + bytesRef header(m_data.data(), h256::size); + if (!m_io->authAndDecryptHeader(header)) { - m_incoming.resize(m_incoming.size() + length); - memcpy(m_incoming.data() + m_incoming.size() - length, m_data.data(), length); - while (m_incoming.size() > 8) + clog(NetWarn) << "header decrypt failed"; + drop(BadProtocol); // todo: better error + return; + } + + /// check frame size + uint32_t frameSize = (m_data[0] * 256 + m_data[1]) * 256 + m_data[2]; + if (frameSize >= (uint32_t)1 << 24) + { + clog(NetWarn) << "frame size too large"; + drop(BadProtocol); + return; + } + + /// rlp of header has protocol-type, sequence-id[, total-packet-size] + bytes headerRLP(13); + bytesConstRef(m_data.data(), h128::size).cropped(3).copyTo(&headerRLP); + + /// read padded frame and mac + auto tlen = frameSize + ((16 - (frameSize % 16)) % 16) + h128::size; + ba::async_read(m_socket, boost::asio::buffer(m_data, tlen), [this, self, headerRLP, frameSize, tlen](boost::system::error_code ec, std::size_t length) + { + if (ec && ec.category() != boost::asio::error::get_misc_category() && ec.value() != boost::asio::error::eof) + { + clogS(NetWarn) << "Error reading: " << ec.message(); + drop(TCPError); + } + else if (ec && length == 0) + return; + else { - if (m_incoming[0] != 0x22 || m_incoming[1] != 0x40 || m_incoming[2] != 0x08 || m_incoming[3] != 0x91) + if (!m_io->authAndDecryptFrame(bytesRef(m_data.data(), tlen))) + { + clog(NetWarn) << "frame decrypt failed"; + drop(BadProtocol); // todo: better error + return; + } + + bytesConstRef frame(m_data.data(), frameSize); + if (!checkPacket(frame)) { - clogS(NetWarn) << "INVALID SYNCHRONISATION TOKEN; expected = 22400891; received = " << toHex(bytesConstRef(m_incoming.data(), 4)); + cerr << "Received " << frame.size() << ": " << toHex(frame) << endl; + clogS(NetWarn) << "INVALID MESSAGE RECEIVED"; disconnect(BadProtocol); return; } else { - uint32_t len = fromBigEndian(bytesConstRef(m_incoming.data() + 4, 4)); - uint32_t tlen = len + 8; - if (m_incoming.size() < tlen) - break; - - // enough has come in. - auto data = bytesConstRef(m_incoming.data(), tlen); - if (!checkPacket(data)) - { - cerr << "Received " << len << ": " << toHex(bytesConstRef(m_incoming.data() + 8, len)) << endl; - clogS(NetWarn) << "INVALID MESSAGE RECEIVED"; - disconnect(BadProtocol); - return; - } - else - { - RLP r(data.cropped(8)); - if (!interpret(r)) - { - // error - bad protocol - clogS(NetWarn) << "Couldn't interpret packet." << RLP(r); - // Just wasting our bandwidth - perhaps reduce rating? - //return; - } - } - memmove(m_incoming.data(), m_incoming.data() + tlen, m_incoming.size() - tlen); - m_incoming.resize(m_incoming.size() - tlen); + auto packetType = (PacketType)RLP(frame.cropped(0, 1)).toInt(); + RLP r(frame.cropped(1)); + if (!interpret(packetType, r)) + clogS(NetWarn) << "Couldn't interpret packet." << RLP(r); } + doRead(); } - doRead(); - } - catch (Exception const& _e) - { - clogS(NetWarn) << "ERROR: " << diagnostic_information(_e); - drop(BadProtocol); - } - catch (std::exception const& _e) - { - clogS(NetWarn) << "ERROR: " << _e.what(); - drop(BadProtocol); - } + }); } }); } diff --git a/libp2p/Session.h b/libp2p/Session.h index a5347cddb..51db5adc3 100644 --- a/libp2p/Session.h +++ b/libp2p/Session.h @@ -16,6 +16,7 @@ */ /** @file Session.h * @author Gav Wood + * @author Alex Leverington * @date 2014 */ @@ -32,6 +33,7 @@ #include #include #include +#include "RLPxHandshake.h" #include "Common.h" namespace dev @@ -52,7 +54,7 @@ class Session: public std::enable_shared_from_this friend class HostCapabilityFace; public: - Session(Host* _server, bi::tcp::socket _socket, std::shared_ptr const& _n); + Session(Host* _server, RLPXFrameIO* _io, std::shared_ptr const& _n, PeerSessionInfo _info); virtual ~Session(); void start(); @@ -63,16 +65,13 @@ public: bool isConnected() const { return m_socket.is_open(); } NodeId id() const; - unsigned socketId() const { return m_socket.native_handle(); } + unsigned socketId() const { return m_info.socket; } template std::shared_ptr cap() const { try { return std::static_pointer_cast(m_capabilities.at(std::make_pair(PeerCap::name(), PeerCap::version()))); } catch (...) { return nullptr; } } static RLPStream& prep(RLPStream& _s, PacketType _t, unsigned _args = 0); - static RLPStream& prep(RLPStream& _s); void sealAndSend(RLPStream& _s); - void send(bytes&& _msg); - void send(bytesConstRef _msg); int rating() const; void addRating(unsigned _r); @@ -85,6 +84,8 @@ public: void serviceNodesRequest(); private: + void send(bytes&& _msg); + /// Drop the connection for the reason @a _r. void drop(DisconnectReason _r); @@ -95,17 +96,18 @@ private: void write(); /// Interpret an incoming message. - bool interpret(RLP const& _r); + bool interpret(PacketType _t, RLP const& _r); /// @returns true iff the _msg forms a valid message for sending or receiving on the network. static bool checkPacket(bytesConstRef _msg); Host* m_server; ///< The host that owns us. Never null. - mutable bi::tcp::socket m_socket; ///< Socket for the peer's connection. Mutable to ask for native_handle(). + RLPXFrameIO* m_io; ///< Transport over which packets are sent. + bi::tcp::socket& m_socket; ///< Socket for the peer's connection. Mutex x_writeQueue; ///< Mutex for the write queue. std::deque m_writeQueue; ///< The write queue. - std::array m_data; ///< Buffer for ingress packet data. + std::array m_data; ///< Buffer for ingress packet data. bytes m_incoming; ///< Read buffer for ingress bytes. unsigned m_protocolVersion = 0; ///< The protocol version of the peer. diff --git a/libwhisper/WhisperPeer.cpp b/libwhisper/WhisperPeer.cpp index 7480a104e..53ea91a9e 100644 --- a/libwhisper/WhisperPeer.cpp +++ b/libwhisper/WhisperPeer.cpp @@ -55,7 +55,7 @@ bool WhisperPeer::interpret(unsigned _id, RLP const& _r) { case StatusPacket: { - auto protocolVersion = _r[1].toInt(); + auto protocolVersion = _r[0].toInt(); clogS(NetMessageSummary) << "Status: " << protocolVersion; diff --git a/test/crypto.cpp b/test/crypto.cpp index 291893f59..dbbc2dfa0 100644 --- a/test/crypto.cpp +++ b/test/crypto.cpp @@ -15,6 +15,7 @@ along with cpp-ethereum. If not, see . */ /** @file crypto.cpp + * @author Alex Leverington * @author Gav Wood * @date 2014 * Crypto test functions. @@ -228,6 +229,82 @@ BOOST_AUTO_TEST_CASE(cryptopp_ecdsa_sipaseckp256k1) } } +BOOST_AUTO_TEST_CASE(sha3_norestart) +{ + CryptoPP::SHA3_256 ctx; + bytes input(asBytes("test")); + ctx.Update(input.data(), 4); + CryptoPP::SHA3_256 ctxCopy(ctx); + bytes interimDigest(32); + ctx.Final(interimDigest.data()); + ctx.Update(input.data(), 4); + bytes firstDigest(32); + ctx.Final(firstDigest.data()); + BOOST_REQUIRE(interimDigest == firstDigest); + + ctxCopy.Update(input.data(), 4); + bytes finalDigest(32); + ctxCopy.Final(interimDigest.data()); + BOOST_REQUIRE(interimDigest != finalDigest); + + // we can do this another way -- copy the context for final + ctxCopy.Update(input.data(), 4); + ctxCopy.Update(input.data(), 4); + CryptoPP::SHA3_256 finalCtx(ctxCopy); + bytes finalDigest2(32); + finalCtx.Final(finalDigest2.data()); + BOOST_REQUIRE(finalDigest2 == interimDigest); + ctxCopy.Update(input.data(), 4); + bytes finalDigest3(32); + finalCtx.Final(finalDigest3.data()); + BOOST_REQUIRE(finalDigest2 != finalDigest3); +} + +BOOST_AUTO_TEST_CASE(ecies_kdf) +{ + KeyPair local = KeyPair::create(); + KeyPair remote = KeyPair::create(); + // nonce + Secret z1; + ecdh::agree(local.sec(), remote.pub(), z1); + auto key1 = s_secp256k1.eciesKDF(z1, bytes(), 64); + bytesConstRef eKey1 = bytesConstRef(&key1).cropped(0, 32); + bytesRef mKey1 = bytesRef(&key1).cropped(32, 32); + sha3(mKey1, mKey1); + + Secret z2; + ecdh::agree(remote.sec(), local.pub(), z2); + auto key2 = s_secp256k1.eciesKDF(z2, bytes(), 64); + bytesConstRef eKey2 = bytesConstRef(&key2).cropped(0, 32); + bytesRef mKey2 = bytesRef(&key2).cropped(32, 32); + sha3(mKey2, mKey2); + + BOOST_REQUIRE(eKey1.toBytes() == eKey2.toBytes()); + BOOST_REQUIRE(mKey1.toBytes() == mKey2.toBytes()); + + BOOST_REQUIRE((u256)h256(z1) > 0); + BOOST_REQUIRE(z1 == z2); + + BOOST_REQUIRE(key1.size() > 0 && ((u512)h512(key1)) > 0); + BOOST_REQUIRE(key1 == key2); +} + +BOOST_AUTO_TEST_CASE(ecies_standard) +{ + KeyPair k = KeyPair::create(); + + string message("Now is the time for all good persons to come to the aid of humanity."); + string original = message; + bytes b = asBytes(message); + + s_secp256k1.encryptECIES(k.pub(), b); + BOOST_REQUIRE(b != asBytes(original)); + BOOST_REQUIRE(b.size() > 0 && b[0] == 0x04); + + s_secp256k1.decryptECIES(k.sec(), b); + BOOST_REQUIRE(bytesConstRef(&b).cropped(0, original.size()).toBytes() == asBytes(original)); +} + BOOST_AUTO_TEST_CASE(ecies_eckeypair) { KeyPair k = KeyPair::create(); @@ -316,14 +393,233 @@ BOOST_AUTO_TEST_CASE(ecdhe) BOOST_REQUIRE_EQUAL(sremote, slocal); } -BOOST_AUTO_TEST_CASE(ecdhe_aes128_ctr_sha3mac) +BOOST_AUTO_TEST_CASE(handshakeNew) { - // New connections require new ECDH keypairs - // Every new connection requires a new EC keypair - // Every new trust requires a new EC keypair - // All connections should share seed for PRF (or PRNG) for nonces + // authInitiator -> E(remote-pubk, S(ecdhe-random, ecdh-shared-secret^nonce) || H(ecdhe-random-pubk) || pubk || nonce || 0x0) + // authRecipient -> E(remote-pubk, ecdhe-random-pubk || nonce || 0x0) + + h256 base(sha3("privacy")); + sha3(base.ref(), base.ref()); + Secret nodeAsecret(base); + KeyPair nodeA(nodeAsecret); + BOOST_REQUIRE(nodeA.pub()); + + sha3(base.ref(), base.ref()); + Secret nodeBsecret(base); + KeyPair nodeB(nodeBsecret); + BOOST_REQUIRE(nodeB.pub()); + + BOOST_REQUIRE_NE(nodeA.sec(), nodeB.sec()); + + // Initiator is Alice (nodeA) + ECDHE eA; + bytes nAbytes(fromHex("0xAAAA")); + h256 nonceA(sha3(nAbytes)); + bytes auth(Signature::size + h256::size + Public::size + h256::size + 1); + Secret ssA; + { + 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); + + crypto::ecdh::agree(nodeA.sec(), nodeB.pub(), ssA); + sign(eA.seckey(), ssA ^ nonceA).ref().copyTo(sig); + sha3(eA.pubkey().ref(), hepubk); + nodeA.pub().ref().copyTo(pubk); + nonceA.ref().copyTo(nonce); + auth[auth.size() - 1] = 0x0; + } + bytes authcipher; + encrypt(nodeB.pub(), &auth, authcipher); + BOOST_REQUIRE_EQUAL(authcipher.size(), 279); + + // Receipient is Bob (nodeB) + ECDHE eB; + bytes nBbytes(fromHex("0xBBBB")); + h256 nonceB(sha3(nAbytes)); + bytes ack(Public::size + h256::size + 1); + { + // todo: replace nodeA.pub() in encrypt() + // decrypt public key from auth + bytes authdecrypted; + decrypt(nodeB.sec(), &authcipher, authdecrypted); + Public node; + bytesConstRef pubk(&authdecrypted[Signature::size + h256::size], Public::size); + pubk.copyTo(node.ref()); + + bytesRef epubk(&ack[0], Public::size); + bytesRef nonce(&ack[Public::size], h256::size); + + eB.pubkey().ref().copyTo(epubk); + nonceB.ref().copyTo(nonce); + auth[auth.size() - 1] = 0x0; + } + bytes ackcipher; + encrypt(nodeA.pub(), &ack, ackcipher); + BOOST_REQUIRE_EQUAL(ackcipher.size(), 182); + + BOOST_REQUIRE(eA.pubkey()); + BOOST_REQUIRE(eB.pubkey()); + BOOST_REQUIRE_NE(eA.seckey(), eB.seckey()); + + /// Alice (after receiving ack) + Secret aEncryptK; + Secret aMacK; + Secret aEgressMac; + Secret aIngressMac; + { + bytes ackdecrypted; + decrypt(nodeA.sec(), &ackcipher, ackdecrypted); + BOOST_REQUIRE(ackdecrypted.size()); + bytesConstRef ackRef(&ackdecrypted); + Public eBAck; + h256 nonceBAck; + ackRef.cropped(0, Public::size).copyTo(bytesRef(eBAck.data(), Public::size)); + ackRef.cropped(Public::size, h256::size).copyTo(nonceBAck.ref()); + BOOST_REQUIRE_EQUAL(eBAck, eB.pubkey()); + BOOST_REQUIRE_EQUAL(nonceBAck, nonceB); + + // TODO: export ess and require equal to b + + bytes keyMaterialBytes(512); + bytesRef keyMaterial(&keyMaterialBytes); + + h256 ess; + // todo: ecdh-agree should be able to output bytes + eA.agree(eBAck, ess); + ess.ref().copyTo(keyMaterial.cropped(0, h256::size)); + ssA.ref().copyTo(keyMaterial.cropped(h256::size, h256::size)); +// auto token = sha3(ssA); + aEncryptK = sha3(keyMaterial); + aEncryptK.ref().copyTo(keyMaterial.cropped(h256::size, h256::size)); + aMacK = sha3(keyMaterial); + + keyMaterialBytes.resize(h256::size + authcipher.size()); + keyMaterial.retarget(keyMaterialBytes.data(), keyMaterialBytes.size()); + (aMacK ^ nonceBAck).ref().copyTo(keyMaterial); + bytesConstRef(&authcipher).copyTo(keyMaterial.cropped(h256::size, authcipher.size())); + aEgressMac = sha3(keyMaterial); + + keyMaterialBytes.resize(h256::size + ackcipher.size()); + keyMaterial.retarget(keyMaterialBytes.data(), keyMaterialBytes.size()); + (aMacK ^ nonceA).ref().copyTo(keyMaterial); + bytesConstRef(&ackcipher).copyTo(keyMaterial.cropped(h256::size, ackcipher.size())); + aIngressMac = sha3(keyMaterial); + } + + + /// Bob (after sending ack) + Secret ssB; + crypto::ecdh::agree(nodeB.sec(), nodeA.pub(), ssB); + BOOST_REQUIRE_EQUAL(ssA, ssB); + Secret bEncryptK; + Secret bMacK; + Secret bEgressMac; + Secret bIngressMac; + { + bytes authdecrypted; + decrypt(nodeB.sec(), &authcipher, authdecrypted); + BOOST_REQUIRE(authdecrypted.size()); + bytesConstRef ackRef(&authdecrypted); + Signature sigAuth; + h256 heA; + Public eAAuth; + Public nodeAAuth; + h256 nonceAAuth; + bytesConstRef sig(&authdecrypted[0], Signature::size); + bytesConstRef hepubk(&authdecrypted[Signature::size], h256::size); + bytesConstRef pubk(&authdecrypted[Signature::size + h256::size], Public::size); + bytesConstRef nonce(&authdecrypted[Signature::size + h256::size + Public::size], h256::size); + + nonce.copyTo(nonceAAuth.ref()); + pubk.copyTo(nodeAAuth.ref()); + BOOST_REQUIRE(nonceAAuth); + BOOST_REQUIRE_EQUAL(nonceA, nonceAAuth); + BOOST_REQUIRE(nodeAAuth); + BOOST_REQUIRE_EQUAL(nodeA.pub(), nodeAAuth); // bad test, bad!!! + hepubk.copyTo(heA.ref()); + sig.copyTo(sigAuth.ref()); + + Secret ss; + s_secp256k1.agree(nodeB.sec(), nodeAAuth, ss); + eAAuth = recover(sigAuth, ss ^ nonceAAuth); + // todo: test when this fails; means remote is bad or packet bits were flipped + BOOST_REQUIRE_EQUAL(heA, sha3(eAAuth)); + BOOST_REQUIRE_EQUAL(eAAuth, eA.pubkey()); + + bytes keyMaterialBytes(512); + bytesRef keyMaterial(&keyMaterialBytes); + + h256 ess; + // todo: ecdh-agree should be able to output bytes + eB.agree(eAAuth, ess); +// s_secp256k1.agree(eB.seckey(), eAAuth, ess); + ess.ref().copyTo(keyMaterial.cropped(0, h256::size)); + ssB.ref().copyTo(keyMaterial.cropped(h256::size, h256::size)); +// auto token = sha3(ssA); + bEncryptK = sha3(keyMaterial); + bEncryptK.ref().copyTo(keyMaterial.cropped(h256::size, h256::size)); + bMacK = sha3(keyMaterial); + + // todo: replace nonceB with decrypted nonceB + keyMaterialBytes.resize(h256::size + ackcipher.size()); + keyMaterial.retarget(keyMaterialBytes.data(), keyMaterialBytes.size()); + (bMacK ^ nonceAAuth).ref().copyTo(keyMaterial); + bytesConstRef(&ackcipher).copyTo(keyMaterial.cropped(h256::size, ackcipher.size())); + bEgressMac = sha3(keyMaterial); + + keyMaterialBytes.resize(h256::size + authcipher.size()); + keyMaterial.retarget(keyMaterialBytes.data(), keyMaterialBytes.size()); + (bMacK ^ nonceB).ref().copyTo(keyMaterial); + bytesConstRef(&authcipher).copyTo(keyMaterial.cropped(h256::size, authcipher.size())); + bIngressMac = sha3(keyMaterial); + } + + BOOST_REQUIRE_EQUAL(aEncryptK, bEncryptK); + BOOST_REQUIRE_EQUAL(aMacK, bMacK); + BOOST_REQUIRE_EQUAL(aEgressMac, bIngressMac); + BOOST_REQUIRE_EQUAL(bEgressMac, aIngressMac); + + + +} + +BOOST_AUTO_TEST_CASE(ecies_aes128_ctr_unaligned) +{ + Secret encryptK(sha3("...")); + h256 egressMac(sha3("+++")); + // TESTING: send encrypt magic sequence + bytes magic {0x22,0x40,0x08,0x91}; + bytes magicCipherAndMac; + encryptSymNoAuth(encryptK, &magic, magicCipherAndMac, h128()); + + magicCipherAndMac.resize(magicCipherAndMac.size() + 32); + sha3mac(egressMac.ref(), &magic, egressMac.ref()); + egressMac.ref().copyTo(bytesRef(&magicCipherAndMac).cropped(magicCipherAndMac.size() - 32, 32)); + + bytes plaintext; + bytesConstRef cipher(&magicCipherAndMac[0], magicCipherAndMac.size() - 32); + decryptSymNoAuth(encryptK, h128(), cipher, plaintext); + + plaintext.resize(magic.size()); + BOOST_REQUIRE(plaintext.size() > 0); + BOOST_REQUIRE(magic == plaintext); +} + +BOOST_AUTO_TEST_CASE(ecies_aes128_ctr) +{ + Secret k(sha3("0xAAAA")); + string m = "AAAAAAAAAAAAAAAA"; + bytesConstRef msg((byte*)m.data(), m.size()); + + bytes ciphertext; + auto iv = encryptSymNoAuth(k, msg, ciphertext); + bytes plaintext; + decryptSymNoAuth(k, iv, &ciphertext, plaintext); + BOOST_REQUIRE_EQUAL(asString(plaintext), m); } BOOST_AUTO_TEST_CASE(cryptopp_aes128_ctr) @@ -357,14 +653,14 @@ BOOST_AUTO_TEST_CASE(cryptopp_aes128_ctr) // 68 % 255 should be difference of counter e.ProcessData(out, in, text.size()); - ctr = h128(u128(ctr) + text.size() % 16); + ctr = h128(u128(ctr) + text.size() / 16); BOOST_REQUIRE(text != original); cipherCopy = text; } - catch(CryptoPP::Exception& e) + catch (CryptoPP::Exception& _e) { - cerr << e.what() << endl; + cerr << _e.what() << endl; } try @@ -374,9 +670,9 @@ BOOST_AUTO_TEST_CASE(cryptopp_aes128_ctr) d.ProcessData(out, in, text.size()); BOOST_REQUIRE(text == original); } - catch(CryptoPP::Exception& e) + catch (CryptoPP::Exception& _e) { - cerr << e.what() << endl; + cerr << _e.what() << endl; } @@ -394,9 +690,9 @@ BOOST_AUTO_TEST_CASE(cryptopp_aes128_ctr) // yep, ctr mode. BOOST_REQUIRE(cipherCopy == original); } - catch(CryptoPP::Exception& e) + catch (CryptoPP::Exception& _e) { - cerr << e.what() << endl; + cerr << _e.what() << endl; } } diff --git a/test/peer.cpp b/test/peer.cpp index 7f3c19e1e..0fe3fd1ed 100644 --- a/test/peer.cpp +++ b/test/peer.cpp @@ -32,6 +32,9 @@ BOOST_AUTO_TEST_SUITE(p2p) BOOST_AUTO_TEST_CASE(host) { + auto oldLogVerbosity = g_logVerbosity; + g_logVerbosity = 10; + NetworkPreferences host1prefs(30301, "127.0.0.1", true, true); NetworkPreferences host2prefs(30302, "127.0.0.1", true, true); @@ -44,10 +47,14 @@ BOOST_AUTO_TEST_CASE(host) host1.addNode(node2, "127.0.0.1", host2prefs.listenPort, host2prefs.listenPort); - this_thread::sleep_for(chrono::seconds(1)); + this_thread::sleep_for(chrono::seconds(3)); + + auto host1peerCount = host1.peerCount(); + auto host2peerCount = host2.peerCount(); + BOOST_REQUIRE_EQUAL(host1peerCount, 1); + BOOST_REQUIRE_EQUAL(host2peerCount, 1); - BOOST_REQUIRE_EQUAL(host1.peerCount(), 1); - BOOST_REQUIRE_EQUAL(host2.peerCount(), host1.peerCount()); + g_logVerbosity = oldLogVerbosity; } BOOST_AUTO_TEST_CASE(save_nodes) @@ -71,7 +78,7 @@ BOOST_AUTO_TEST_CASE(save_nodes) for (auto const& h: hosts) host2.addNode(h->id(), "127.0.0.1", h->listenPort(), h->listenPort()); - this_thread::sleep_for(chrono::milliseconds(1000)); + this_thread::sleep_for(chrono::milliseconds(2000)); bytes firstHostNetwork(host.saveNetwork()); bytes secondHostNetwork(host.saveNetwork()); diff --git a/test/rlpx.cpp b/test/rlpx.cpp new file mode 100644 index 000000000..6a86652fb --- /dev/null +++ b/test/rlpx.cpp @@ -0,0 +1,455 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file crypto.cpp + * @author Alex Leverington + * @date 2015 + * RLPx test functions. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace dev; +using namespace dev::crypto; +using namespace CryptoPP; + +BOOST_AUTO_TEST_SUITE(rlpx) + +static Secp256k1 s_secp256k1; +static CryptoPP::AutoSeededRandomPool s_rng; +static CryptoPP::OID s_curveOID(CryptoPP::ASN1::secp256k1()); +static CryptoPP::DL_GroupParameters_EC s_params(s_curveOID); +static CryptoPP::DL_GroupParameters_EC::EllipticCurve s_curve(s_params.GetCurve()); + +BOOST_AUTO_TEST_CASE(test_secrets_cpp_vectors) +{ + KeyPair init(Secret(sha3("initiator"))); + KeyPair initR(Secret(sha3("initiator-random"))); + h256 initNonce(sha3("initiator-nonce")); + + KeyPair recv(Secret(sha3("remote-recv"))); + KeyPair recvR(Secret(sha3("remote-recv-random"))); + h256 recvNonce(sha3("remote-recv-nonce")); + + bytes authCipher(fromHex("")); + bytes ackCipher(fromHex("")); + + CryptoPP::CTR_Mode::Encryption m_frameEnc; + CryptoPP::CTR_Mode::Encryption m_frameDec; + CryptoPP::ECB_Mode::Encryption m_macEnc; + CryptoPP::SHA3_256 m_egressMac; + CryptoPP::SHA3_256 m_ingressMac; + + // when originated is true, agreement is with init secrets + // when originated is true, remoteNonce = recvNonce + // when originated is true, nonce = initNonce + bool originated = true; + auto remoteNonce = recvNonce; + auto nonce = initNonce; + bytes keyMaterialBytes(64); + bytesRef keyMaterial(&keyMaterialBytes); + + // shared-secret = sha3(ecdhe-shared-secret || sha3(nonce || initiator-nonce)) + Secret ephemeralShared; + s_secp256k1.agree(initR.sec(), recvR.pub(), ephemeralShared); + Secret expected(fromHex("20d82c1092f351dc217bd66fa183e801234af14ead40423b6ee25112201c6e5a")); + BOOST_REQUIRE(expected == ephemeralShared); + + ephemeralShared.ref().copyTo(keyMaterial.cropped(0, h256::size)); + h512 nonceMaterial; + h256 const& leftNonce = originated ? remoteNonce : nonce; + h256 const& rightNonce = originated ? nonce : 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) + + // test that keyMaterial = ecdhe-shared-secret || sha3(nonce || initiator-nonce) + { + BOOST_REQUIRE(ephemeralShared == *(Secret*)keyMaterialBytes.data()); + + SHA3_256 ctx; + ctx.Update(leftNonce.data(), h256::size); + ctx.Update(rightNonce.data(), h256::size); + bytes expected(32); + ctx.Final(expected.data()); + bytes given(32); + outRef.copyTo(&given); + BOOST_REQUIRE(expected == given); + } + bytes preImage(keyMaterialBytes); + + // shared-secret <- sha3(ecdhe-shared-secret || sha3(nonce || initiator-nonce)) + // keyMaterial = ecdhe-shared-secret || shared-secret + sha3(keyMaterial, outRef); + bytes sharedSecret(32); + outRef.copyTo(&sharedSecret); + BOOST_REQUIRE(sharedSecret == fromHex("b65319ce56e00f3be75c4d0da92b5957d5583ca25eeeedac8e29b6dfc8b1ddf7")); + + // test that keyMaterial = ecdhe-shared-secret || shared-secret + { + BOOST_REQUIRE(ephemeralShared == *(Secret*)keyMaterialBytes.data()); + + SHA3_256 ctx; + ctx.Update(preImage.data(), preImage.size()); + bytes expected(32); + ctx.Final(expected.data()); + bytes test(32); + outRef.copyTo(&test); + BOOST_REQUIRE(expected == test); + } + + // token: sha3(outRef) + bytes token(32); + sha3(outRef, bytesRef(&token)); + BOOST_REQUIRE(token == fromHex("db41fe0180f372983cf19fca7ee890f7fb5481079d44683d2c027be9e71bbca2")); + + // aes-secret = sha3(ecdhe-shared-secret || shared-secret) + sha3(keyMaterial, outRef); // output aes-secret + bytes aesSecret(32); + outRef.copyTo(&aesSecret); + BOOST_REQUIRE(aesSecret == fromHex("12347b4784bcb4e74b84637940482852fe25d78e328cf5c6f7a396bf96cc20bb")); + 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 + bytes macSecret(32); + outRef.copyTo(&macSecret); + BOOST_REQUIRE(macSecret == fromHex("2ec149072353d54437422837c886b0538a9206e6c559f6b4a55f65a866867723")); + 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) + // Recipient egress-mac: sha3(mac-secret^initiator-nonce || auth-sent-ack) + // ingress-mac: sha3(mac-secret^recipient-nonce || auth-recvd-init) + + (*(h256*)outRef.data() ^ remoteNonce).ref().copyTo(keyMaterial); + bytes const& egressCipher = originated ? authCipher : ackCipher; + keyMaterialBytes.resize(h256::size + egressCipher.size()); + keyMaterial.retarget(keyMaterialBytes.data(), keyMaterialBytes.size()); + bytesConstRef(&egressCipher).copyTo(keyMaterial.cropped(h256::size, egressCipher.size())); + m_egressMac.Update(keyMaterial.data(), keyMaterial.size()); + + { + bytes egressMac; + SHA3_256 h(m_egressMac); + bytes digest(16); + h.TruncatedFinal(digest.data(), 16); + BOOST_REQUIRE(digest == fromHex("23e5e8efb6e3765ecae1fca9160b18df")); + } + + // recover mac-secret by re-xoring remoteNonce + (*(h256*)keyMaterial.data() ^ remoteNonce ^ nonce).ref().copyTo(keyMaterial); + bytes const& ingressCipher = originated ? ackCipher : authCipher; + keyMaterialBytes.resize(h256::size + ingressCipher.size()); + keyMaterial.retarget(keyMaterialBytes.data(), keyMaterialBytes.size()); + bytesConstRef(&ingressCipher).copyTo(keyMaterial.cropped(h256::size, ingressCipher.size())); + m_ingressMac.Update(keyMaterial.data(), keyMaterial.size()); + + { + bytes ingressMac; + SHA3_256 h(m_ingressMac); + bytes digest(16); + h.TruncatedFinal(digest.data(), 16); + BOOST_REQUIRE(digest == fromHex("ceed64135852064cbdde86e7ea05e8f5")); + } +} + +BOOST_AUTO_TEST_CASE(test_secrets_from_go) +{ + KeyPair init(Secret(fromHex("0x5e173f6ac3c669587538e7727cf19b782a4f2fda07c1eaa662c593e5e85e3051"))); + KeyPair initR(Secret(fromHex("0x19c2185f4f40634926ebed3af09070ca9e029f2edd5fae6253074896205f5f6c"))); + h256 initNonce(fromHex("0xcd26fecb93657d1cd9e9eaf4f8be720b56dd1d39f190c4e1c6b7ec66f077bb11")); + + KeyPair recv(Secret(fromHex("0xc45f950382d542169ea207959ee0220ec1491755abe405cd7498d6b16adb6df8"))); + KeyPair recvR(Secret(fromHex("0xd25688cf0ab10afa1a0e2dba7853ed5f1e5bf1c631757ed4e103b593ff3f5620"))); + h256 recvNonce(fromHex("0xf37ec61d84cea03dcc5e8385db93248584e8af4b4d1c832d8c7453c0089687a7")); + + bytes authCipher(fromHex("0x04a0274c5951e32132e7f088c9bdfdc76c9d91f0dc6078e848f8e3361193dbdc43b94351ea3d89e4ff33ddcefbc80070498824857f499656c4f79bbd97b6c51a514251d69fd1785ef8764bd1d262a883f780964cce6a14ff206daf1206aa073a2d35ce2697ebf3514225bef186631b2fd2316a4b7bcdefec8d75a1025ba2c5404a34e7795e1dd4bc01c6113ece07b0df13b69d3ba654a36e35e69ff9d482d88d2f0228e7d96fe11dccbb465a1831c7d4ad3a026924b182fc2bdfe016a6944312021da5cc459713b13b86a686cf34d6fe6615020e4acf26bf0d5b7579ba813e7723eb95b3cef9942f01a58bd61baee7c9bdd438956b426a4ffe238e61746a8c93d5e10680617c82e48d706ac4953f5e1c4c4f7d013c87d34a06626f498f34576dc017fdd3d581e83cfd26cf125b6d2bda1f1d56")); + bytes ackCipher(fromHex("0x049934a7b2d7f9af8fd9db941d9da281ac9381b5740e1f64f7092f3588d4f87f5ce55191a6653e5e80c1c5dd538169aa123e70dc6ffc5af1827e546c0e958e42dad355bcc1fcb9cdf2cf47ff524d2ad98cbf275e661bf4cf00960e74b5956b799771334f426df007350b46049adb21a6e78ab1408d5e6ccde6fb5e69f0f4c92bb9c725c02f99fa72b9cdc8dd53cff089e0e73317f61cc5abf6152513cb7d833f09d2851603919bf0fbe44d79a09245c6e8338eb502083dc84b846f2fee1cc310d2cc8b1b9334728f97220bb799376233e113")); + + bytes authPlainExpected(fromHex("0x884c36f7ae6b406637c1f61b2f57e1d2cab813d24c6559aaf843c3f48962f32f46662c066d39669b7b2e3ba14781477417600e7728399278b1b5d801a519aa570034fdb5419558137e0d44cd13d319afe5629eeccb47fd9dfe55cc6089426e46cc762dd8a0636e07a54b31169eba0c7a20a1ac1ef68596f1f283b5c676bae4064abfcce24799d09f67e392632d3ffdc12e3d6430dcb0ea19c318343ffa7aae74d4cd26fecb93657d1cd9e9eaf4f8be720b56dd1d39f190c4e1c6b7ec66f077bb1100")); + bytes ackPlainExpected(fromHex("0x802b052f8b066640bba94a4fc39d63815c377fced6fcb84d27f791c9921ddf3e9bf0108e298f490812847109cbd778fae393e80323fd643209841a3b7f110397f37ec61d84cea03dcc5e8385db93248584e8af4b4d1c832d8c7453c0089687a700")); + + bytes authPlain = authCipher; + BOOST_REQUIRE(s_secp256k1.decryptECIES(recv.sec(), authPlain)); + bytes ackPlain = ackCipher; + BOOST_REQUIRE(s_secp256k1.decryptECIES(init.sec(), ackPlain)); + + CryptoPP::CTR_Mode::Encryption m_frameEnc; + CryptoPP::CTR_Mode::Encryption m_frameDec; + CryptoPP::ECB_Mode::Encryption m_macEnc; + CryptoPP::SHA3_256 m_egressMac; + CryptoPP::SHA3_256 m_ingressMac; + + // when originated is true, agreement is with init secrets + // when originated is true, remoteNonce = recvNonce + // when originated is true, nonce = initNonce + bool originated = true; + auto remoteNonce = recvNonce; + auto nonce = initNonce; + bytes keyMaterialBytes(64); + bytesRef keyMaterial(&keyMaterialBytes); + + // shared-secret = sha3(ecdhe-shared-secret || sha3(nonce || initiator-nonce)) + Secret ephemeralShared; + s_secp256k1.agree(initR.sec(), recvR.pub(), ephemeralShared); + Secret expected(fromHex("0xe3f407f83fc012470c26a93fdff534100f2c6f736439ce0ca90e9914f7d1c381")); + BOOST_REQUIRE(expected == ephemeralShared); + + ephemeralShared.ref().copyTo(keyMaterial.cropped(0, h256::size)); + h512 nonceMaterial; + h256 const& leftNonce = originated ? remoteNonce : nonce; + h256 const& rightNonce = originated ? nonce : 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) + + // test that keyMaterial = ecdhe-shared-secret || sha3(nonce || initiator-nonce) + { + BOOST_REQUIRE(ephemeralShared == *(Secret*)keyMaterialBytes.data()); + + SHA3_256 ctx; + ctx.Update(leftNonce.data(), h256::size); + ctx.Update(rightNonce.data(), h256::size); + bytes expected(32); + ctx.Final(expected.data()); + bytes given(32); + outRef.copyTo(&given); + BOOST_REQUIRE(expected == given); + } + bytes preImage(keyMaterialBytes); + + // shared-secret <- sha3(ecdhe-shared-secret || sha3(nonce || initiator-nonce)) + // keyMaterial = ecdhe-shared-secret || shared-secret + sha3(keyMaterial, outRef); + + // test that keyMaterial = ecdhe-shared-secret || shared-secret + { + BOOST_REQUIRE(ephemeralShared == *(Secret*)keyMaterialBytes.data()); + + SHA3_256 ctx; + ctx.Update(preImage.data(), preImage.size()); + bytes expected(32); + ctx.Final(expected.data()); + bytes test(32); + outRef.copyTo(&test); + BOOST_REQUIRE(expected == test); + } + + // token: sha3(outRef) + bytes token(32); + sha3(outRef, bytesRef(&token)); + BOOST_REQUIRE(token == fromHex("0x3f9ec2592d1554852b1f54d228f042ed0a9310ea86d038dc2b401ba8cd7fdac4")); + + // aes-secret = sha3(ecdhe-shared-secret || shared-secret) + sha3(keyMaterial, outRef); // output aes-secret + bytes aesSecret(32); + outRef.copyTo(&aesSecret); + BOOST_REQUIRE(aesSecret == fromHex("0xc0458fa97a5230830e05f4f20b7c755c1d4e54b1ce5cf43260bb191eef4e418d")); + 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 + bytes macSecret(32); + outRef.copyTo(&macSecret); + BOOST_REQUIRE(macSecret == fromHex("0x48c938884d5067a1598272fcddaa4b833cd5e7d92e8228c0ecdfabbe68aef7f1")); + m_macEnc.SetKey(outRef.data(), h256::size); + + // 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) + + (*(h256*)outRef.data() ^ remoteNonce).ref().copyTo(keyMaterial); + bytes const& egressCipher = originated ? authCipher : ackCipher; + keyMaterialBytes.resize(h256::size + egressCipher.size()); + keyMaterial.retarget(keyMaterialBytes.data(), keyMaterialBytes.size()); + bytesConstRef(&egressCipher).copyTo(keyMaterial.cropped(h256::size, egressCipher.size())); + m_egressMac.Update(keyMaterialBytes.data(), keyMaterialBytes.size()); + + { + bytes egressMac; + SHA3_256 h(m_egressMac); + bytes digest(32); + h.Final(digest.data()); + BOOST_REQUIRE(digest == fromHex("0x09771e93b1a6109e97074cbe2d2b0cf3d3878efafe68f53c41bb60c0ec49097e")); + } + + // recover mac-secret by re-xoring remoteNonce + bytes recoverMacSecretTest(32); + (*(h256*)keyMaterial.data() ^ remoteNonce).ref().copyTo(&recoverMacSecretTest); + BOOST_REQUIRE(recoverMacSecretTest == macSecret); + + (*(h256*)keyMaterial.data() ^ remoteNonce ^ nonce).ref().copyTo(keyMaterial); + bytes const& ingressCipher = originated ? ackCipher : authCipher; + keyMaterialBytes.resize(h256::size + ingressCipher.size()); + keyMaterial.retarget(keyMaterialBytes.data(), keyMaterialBytes.size()); + bytesConstRef(&ingressCipher).copyTo(keyMaterial.cropped(h256::size, ingressCipher.size())); + m_ingressMac.Update(keyMaterial.data(), keyMaterial.size()); + + { + bytes ingressMac; + SHA3_256 h(m_ingressMac); + bytes digest(32); + h.Final(digest.data()); + BOOST_CHECK(digest == fromHex("0x75823d96e23136c89666ee025fb21a432be906512b3dd4a3049e898adb433847")); + } + + bytes initHello(fromHex("6ef23fcf1cec7312df623f9ae701e63b550cdb8517fefd8dd398fc2acd1d935e6e0434a2b96769078477637347b7b01924fff9ff1c06df2f804df3b0402bbb9f87365b3c6856b45e1e2b6470986813c3816a71bff9d69dd297a5dbd935ab578f6e5d7e93e4506a44f307c332d95e8a4b102585fd8ef9fc9e3e055537a5cec2e9")); + + bytes recvHello(fromHex("6ef23fcf1cec7312df623f9ae701e63be36a1cdd1b19179146019984f3625d4a6e0434a2b96769050577657247b7b02bc6c314470eca7e3ef650b98c83e9d7dd4830b3f718ff562349aead2530a8d28a8484604f92e5fced2c6183f304344ab0e7c301a0c05559f4c25db65e36820b4b909a226171a60ac6cb7beea09376d6d8")); + + /// test macs of frame headers + { + SHA3_256 egressmac(m_egressMac); + SHA3_256 prevDigest(egressmac); + h128 prevDigestOut; + prevDigest.TruncatedFinal(prevDigestOut.data(), h128::size); + h128 encDigest; + m_macEnc.ProcessData(encDigest.data(), prevDigestOut.data(), h128::size); + encDigest ^= *(h128*)initHello.data(); + egressmac.Update(encDigest.data(), h128::size); + egressmac.TruncatedFinal(encDigest.data(), h128::size); + + bytes provided(16); + bytesConstRef(&initHello).cropped(16, 16).copyTo(bytesRef(&provided)); + BOOST_REQUIRE(*(h128*)encDigest.data() == *(h128*)provided.data()); + } + + { + SHA3_256 ingressmac(m_ingressMac); + SHA3_256 prevDigest(ingressmac); + h128 prevDigestOut; + prevDigest.TruncatedFinal(prevDigestOut.data(), h128::size); + h128 encDigest; + m_macEnc.ProcessData(encDigest.data(), prevDigestOut.data(), h128::size); + encDigest ^= *(h128*)recvHello.data(); + ingressmac.Update(encDigest.data(), h128::size); + ingressmac.TruncatedFinal(encDigest.data(), h128::size); + + bytes provided(16); + bytesConstRef(&recvHello).cropped(16, 16).copyTo(bytesRef(&provided)); + BOOST_REQUIRE(*(h128*)encDigest.data() == *(h128*)provided.data()); + } + + // test decrypt of frame headers for recvHello + bytes plaintext(16); + m_frameDec.ProcessData(plaintext.data(), recvHello.data(), h128::size); + +} + +BOOST_AUTO_TEST_CASE(ecies_interop_test_primitives) +{ + CryptoPP::SHA256 sha256ctx; + bytes emptyExpected(fromHex("0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")); + bytes empty; + sha256ctx.Update(empty.data(), 0); + bytes emptyTestOut(32); + sha256ctx.Final(emptyTestOut.data()); + BOOST_REQUIRE(emptyExpected == emptyTestOut); + + bytes hash1Expected(fromHex("0x8949b278bbafb8da1aaa18cb724175c5952280f74be5d29ab4b37d1b45c84b08")); + bytes hash1input(fromHex("0x55a53b55afb12affff3c")); + sha256ctx.Update(hash1input.data(), hash1input.size()); + bytes hash1Out(32); + sha256ctx.Final(hash1Out.data()); + BOOST_REQUIRE(hash1Out == hash1Expected); + + h128 hmack(fromHex("0x07a4b6dfa06369a570f2dcba2f11a18f")); + CryptoPP::HMAC hmacctx(hmack.data(), h128::size); + bytes input(fromHex("0x4dcb92ed4fc67fe86832")); + hmacctx.Update(input.data(), input.size()); + bytes hmacExpected(fromHex("0xc90b62b1a673b47df8e395e671a68bfa68070d6e2ef039598bb829398b89b9a9")); + bytes hmacOut(hmacExpected.size()); + hmacctx.Final(hmacOut.data()); + BOOST_REQUIRE(hmacExpected == hmacOut); + + // go messageTag + bytes tagSecret(fromHex("0xaf6623e52208c596e17c72cea6f1cb09")); + bytes tagInput(fromHex("0x3461282bcedace970df2")); + bytes tagExpected(fromHex("0xb3ce623bce08d5793677ba9441b22bb34d3e8a7de964206d26589df3e8eb5183")); + CryptoPP::HMAC hmactagctx(tagSecret.data(), tagSecret.size()); + hmactagctx.Update(tagInput.data(), tagInput.size()); + h256 mac; + hmactagctx.Final(mac.data()); + BOOST_REQUIRE(mac.asBytes() == tagExpected); + + Secret input1(fromHex("0x0de72f1223915fa8b8bf45dffef67aef8d89792d116eb61c9a1eb02c422a4663")); + bytes expect1(fromHex("0x1d0c446f9899a3426f2b89a8cb75c14b")); + bytes test1; + test1 = s_secp256k1.eciesKDF(input1, bytes(), 16); + BOOST_REQUIRE(test1 == expect1); + + Secret kdfInput2(fromHex("0x961c065873443014e0371f1ed656c586c6730bf927415757f389d92acf8268df")); + bytes kdfExpect2(fromHex("0x4050c52e6d9c08755e5a818ac66fabe478b825b1836fd5efc4d44e40d04dabcc")); + bytes kdfTest2; + kdfTest2 = s_secp256k1.eciesKDF(kdfInput2, bytes(), 32); + BOOST_REQUIRE(kdfTest2 == kdfExpect2); + + KeyPair k(Secret(fromHex("0x332143e9629eedff7d142d741f896258f5a1bfab54dab2121d3ec5000093d74b"))); + Public p(fromHex("0xf0d2b97981bd0d415a843b5dfe8ab77a30300daab3658c578f2340308a2da1a07f0821367332598b6aa4e180a41e92f4ebbae3518da847f0b1c0bbfe20bcf4e1")); + Secret agreeExpected(fromHex("0xee1418607c2fcfb57fda40380e885a707f49000a5dda056d828b7d9bd1f29a08")); + Secret agreeTest; + s_secp256k1.agree(k.sec(), p, agreeTest); + BOOST_REQUIRE(agreeExpected == agreeTest); + + KeyPair kmK(Secret(fromHex("0x57baf2c62005ddec64c357d96183ebc90bf9100583280e848aa31d683cad73cb"))); + bytes kmCipher(fromHex("0x04ff2c874d0a47917c84eea0b2a4141ca95233720b5c70f81a8415bae1dc7b746b61df7558811c1d6054333907333ef9bb0cc2fbf8b34abb9730d14e0140f4553f4b15d705120af46cf653a1dc5b95b312cf8444714f95a4f7a0425b67fc064d18f4d0a528761565ca02d97faffdac23de10")); + bytes kmPlain = kmCipher; + bytes kmExpected(asBytes("a")); + BOOST_REQUIRE(s_secp256k1.decryptECIES(kmK.sec(), kmPlain)); + BOOST_REQUIRE(kmExpected == kmPlain); + + KeyPair kenc(Secret(fromHex("0x472413e97f1fd58d84e28a559479e6b6902d2e8a0cee672ef38a3a35d263886b"))); + Public penc(Public(fromHex("0x7a2aa2951282279dc1171549a7112b07c38c0d97c0fe2c0ae6c4588ba15be74a04efc4f7da443f6d61f68a9279bc82b73e0cc8d090048e9f87e838ae65dd8d4c"))); + BOOST_REQUIRE(penc == kenc.pub()); + + bytes cipher1(fromHex("0x046f647e1bd8a5cd1446d31513bac233e18bdc28ec0e59d46de453137a72599533f1e97c98154343420d5f16e171e5107999a7c7f1a6e26f57bcb0d2280655d08fb148d36f1d4b28642d3bb4a136f0e33e3dd2e3cffe4b45a03fb7c5b5ea5e65617250fdc89e1a315563c20504b9d3a72555")); + bytes plainTest1 = cipher1; + bytes expectedPlain1 = asBytes("a"); + BOOST_REQUIRE(s_secp256k1.decryptECIES(kenc.sec(), plainTest1)); + BOOST_REQUIRE(plainTest1 == expectedPlain1); + + bytes cipher2(fromHex("0x0443c24d6ccef3ad095140760bb143078b3880557a06392f17c5e368502d79532bc18903d59ced4bbe858e870610ab0d5f8b7963dd5c9c4cf81128d10efd7c7aa80091563c273e996578403694673581829e25a865191bdc9954db14285b56eb0043b6288172e0d003c10f42fe413222e273d1d4340c38a2d8344d7aadcbc846ee")); + bytes plainTest2 = cipher2; + bytes expectedPlain2 = asBytes("aaaaaaaaaaaaaaaa"); + BOOST_REQUIRE(s_secp256k1.decryptECIES(kenc.sec(), plainTest2)); + BOOST_REQUIRE(plainTest2 == expectedPlain2); + + bytes cipher3(fromHex("0x04c4e40c86bb5324e017e598c6d48c19362ae527af8ab21b077284a4656c8735e62d73fb3d740acefbec30ca4c024739a1fcdff69ecaf03301eebf156eb5f17cca6f9d7a7e214a1f3f6e34d1ee0ec00ce0ef7d2b242fbfec0f276e17941f9f1bfbe26de10a15a6fac3cda039904ddd1d7e06e7b96b4878f61860e47f0b84c8ceb64f6a900ff23844f4359ae49b44154980a626d3c73226c19e")); + bytes plainTest3 = cipher3; + bytes expectedPlain3 = asBytes("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + BOOST_REQUIRE(s_secp256k1.decryptECIES(kenc.sec(), plainTest3)); + BOOST_REQUIRE(plainTest3 == expectedPlain3); +} + +BOOST_AUTO_TEST_SUITE_END() +