Marek Kotewicz
10 years ago
28 changed files with 2227 additions and 411 deletions
@ -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 <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file RLPXFrameIO.cpp
|
|||
* @author Alex Leverington <nessence@gmail.com> |
|||
* @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); |
|||
} |
@ -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 <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file RLPXFrameIO.h
|
|||
* @author Alex Leverington <nessence@gmail.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
|
|||
#pragma once |
|||
|
|||
#include <memory> |
|||
#include <libdevcrypto/Common.h> |
|||
#include <libdevcrypto/ECDHE.h> |
|||
#include <libdevcrypto/CryptoPP.h> |
|||
#include <libdevcore/Guards.h> |
|||
#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<RLPXSocket> |
|||
{ |
|||
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<CryptoPP::AES>::Encryption m_frameEnc; ///< Encoder for egress plaintext.
|
|||
|
|||
CryptoPP::SecByteBlock m_frameDecKey; ///< Key for m_frameDec
|
|||
CryptoPP::CTR_Mode<CryptoPP::AES>::Encryption m_frameDec; ///< Decoder for egress plaintext.
|
|||
|
|||
CryptoPP::SecByteBlock m_macEncKey; /// Key for m_macEnd
|
|||
CryptoPP::ECB_Mode<CryptoPP::AES>::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<RLPXSocket> m_socket; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -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 <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file RLPXHandshake.cpp
|
|||
* @author Alex Leverington <nessence@gmail.com> |
|||
* @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()); |
|||
} |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
} |
@ -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 <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file RLPXHandshake.h
|
|||
* @author Alex Leverington <nessence@gmail.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
|
|||
#pragma once |
|||
|
|||
#include <memory> |
|||
#include <libdevcrypto/Common.h> |
|||
#include <libdevcrypto/ECDHE.h> |
|||
#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<RLPXHandshake> |
|||
{ |
|||
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<RLPXSocket> 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<RLPXSocket> 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<RLPXSocket> m_socket; ///< Socket.
|
|||
}; |
|||
|
|||
} |
|||
} |
@ -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 <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file crypto.cpp
|
|||
* @author Alex Leverington <nessence@gmail.com> |
|||
* @date 2015 |
|||
* RLPx test functions. |
|||
*/ |
|||
|
|||
#include <random> |
|||
#include <secp256k1/secp256k1.h> |
|||
#include <libdevcore/Common.h> |
|||
#include <libdevcore/RLP.h> |
|||
#include <libdevcore/Log.h> |
|||
#include <libethereum/Transaction.h> |
|||
#include <boost/test/unit_test.hpp> |
|||
#include <libdevcrypto/SHA3.h> |
|||
#include <libdevcrypto/ECDHE.h> |
|||
#include <libdevcrypto/CryptoPP.h> |
|||
#include <libp2p/RLPxHandshake.h> |
|||
|
|||
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<CryptoPP::ECP> s_params(s_curveOID); |
|||
static CryptoPP::DL_GroupParameters_EC<CryptoPP::ECP>::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<CryptoPP::AES>::Encryption m_frameEnc; |
|||
CryptoPP::CTR_Mode<CryptoPP::AES>::Encryption m_frameDec; |
|||
CryptoPP::ECB_Mode<CryptoPP::AES>::Encryption m_macEnc; |
|||
CryptoPP::SHA3_256 m_egressMac; |
|||
CryptoPP::SHA3_256 m_ingressMac; |
|||
|
|||
// 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<CryptoPP::AES>::Encryption m_frameEnc; |
|||
CryptoPP::CTR_Mode<CryptoPP::AES>::Encryption m_frameDec; |
|||
CryptoPP::ECB_Mode<CryptoPP::AES>::Encryption m_macEnc; |
|||
CryptoPP::SHA3_256 m_egressMac; |
|||
CryptoPP::SHA3_256 m_ingressMac; |
|||
|
|||
// 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<SHA256> 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<SHA256> 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() |
|||
|
Loading…
Reference in new issue